use std::io::Read;
use image::error::{DecodingError, ImageFormatHint};
use image::{ColorType, ImageDecoder, ImageError, ImageResult};
use crate::error::WebpError;
use crate::ffi;
use crate::sys;
#[derive(Default)]
pub struct DecoderConfig {
pub threads: bool,
}
pub struct WebpDecoder<R: Read> {
data: Vec<u8>,
config: DecoderConfig,
width: u32,
height: u32,
alpha_present: bool,
_reader: std::marker::PhantomData<R>,
}
impl<R: Read> WebpDecoder<R> {
pub fn new(mut r: R) -> ImageResult<Self> {
let mut data = Vec::new();
r.read_to_end(&mut data).map_err(ImageError::IoError)?;
unsafe {
let mut features: sys::WebPBitstreamFeatures = std::mem::zeroed();
let status = sys::WebPGetFeaturesInternal(
data.as_ptr(),
data.len(),
&mut features,
sys::WEBP_DECODER_ABI_VERSION as i32,
);
if !ffi::is_ok(status) {
return Err(to_image_error(WebpError::DecoderInit(ffi::status_message(status))));
}
Ok(Self {
width: features.width as u32,
height: features.height as u32,
alpha_present: features.has_alpha != 0,
data,
config: DecoderConfig::default(),
_reader: std::marker::PhantomData,
})
}
}
pub fn with_threads(mut self, threads: bool) -> Self {
self.config.threads = threads;
self
}
fn channels(&self) -> usize {
if self.alpha_present { 4 } else { 3 }
}
}
impl<R: Read> ImageDecoder for WebpDecoder<R> {
fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
fn color_type(&self) -> ColorType {
if self.alpha_present {
ColorType::Rgba8
} else {
ColorType::Rgb8
}
}
fn read_image(self, buf: &mut [u8]) -> ImageResult<()> {
let expected = self.width as usize * self.height as usize * self.channels();
if buf.len() != expected {
return Err(to_image_error(WebpError::Decode(format!(
"output buffer length {} does not match expected {expected}",
buf.len()
))));
}
unsafe {
let mut config: sys::WebPDecoderConfig = std::mem::zeroed();
if sys::WebPInitDecoderConfigInternal(&mut config, sys::WEBP_DECODER_ABI_VERSION as i32) == 0 {
return Err(to_image_error(WebpError::DecoderInit(
"WebPInitDecoderConfig failed (version mismatch)".into(),
)));
}
config.options.use_threads = self.config.threads as i32;
config.output.colorspace = if self.alpha_present {
sys::WEBP_CSP_MODE_MODE_RGBA
} else {
sys::WEBP_CSP_MODE_MODE_RGB
};
config.output.is_external_memory = 1;
config.output.u.RGBA.rgba = buf.as_mut_ptr();
config.output.u.RGBA.stride = (self.width as usize * self.channels()) as i32;
config.output.u.RGBA.size = buf.len();
let status = sys::WebPDecode(self.data.as_ptr(), self.data.len(), &mut config);
sys::WebPFreeDecBuffer(&mut config.output);
if !ffi::is_ok(status) {
return Err(to_image_error(WebpError::Decode(ffi::status_message(status))));
}
}
Ok(())
}
fn read_image_boxed(self: Box<Self>, buf: &mut [u8]) -> ImageResult<()> {
self.read_image(buf)
}
}
fn to_image_error(err: WebpError) -> ImageError {
ImageError::Decoding(DecodingError::new(ImageFormatHint::Name("WebP".into()), err))
}