webp_animation/
decoder.rs

1use std::{fmt::Debug, mem, pin::Pin};
2
3use libwebp_sys as webp;
4
5use crate::{ColorMode, Error, Frame};
6
7const MAX_CANVAS_SIZE: usize = 3840 * 2160; // 4k
8
9/// An options struct for [`Decoder`]
10///
11/// For usage, see [`Decoder::new_with_options`]
12pub struct DecoderOptions {
13    /// If true, use multi-threaded decoding. Enabled by default
14    pub use_threads: bool,
15    /// Output colorspace. [`ColorMode::Rgba`] by default. Affects [`Frame`] output
16    pub color_mode: ColorMode,
17}
18
19impl Default for DecoderOptions {
20    fn default() -> Self {
21        Self {
22            use_threads: true,
23            color_mode: ColorMode::Rgba,
24        }
25    }
26}
27
28/// A decoder for webp animation data
29///
30/// Will take a webp buffer, and try to decode it to frame(s)
31///
32/// ```rust
33/// use webp_animation::prelude::*;
34///
35/// let buffer = std::fs::read("./data/animated.webp").unwrap();
36/// let decoder = Decoder::new(&buffer).unwrap();
37///
38/// for frame in decoder.into_iter() {
39///   assert_eq!(frame.dimensions(), (400, 400));
40///   assert_eq!(frame.data().len(), 400 * 400 * 4); // w * h * rgba
41/// }
42/// ```
43///
44/// See also documentation for the item produced by iterator: [`Frame`]
45///
46/// If `image` feature is enabled, frames can be produced in [`image::ImageBuffer`]
47/// format:
48/// ```rust
49/// use webp_animation::prelude::*;
50/// #
51/// # let buffer = std::fs::read("./data/animated.webp").unwrap();
52/// # let decoder = Decoder::new(&buffer).unwrap();
53/// #
54/// for frame in decoder.into_iter() {
55///   ##[cfg(feature = "image")]
56///   assert_eq!(frame.into_image().unwrap().dimensions(), (400, 400));
57/// }
58/// ```
59pub struct Decoder<'a> {
60    buffer: &'a [u8],
61    decoder_wr: DecoderWrapper,
62    info: webp::WebPAnimInfo,
63    options: DecoderOptions,
64}
65
66impl<'a> Decoder<'a> {
67    /// Construct a new decoder from webp `buffer`
68    ///
69    /// Returns an [`Error`] in case of a decoding failure (e.g. malformed input)
70    ///
71    /// ```
72    /// # use webp_animation::prelude::*;
73    /// #
74    /// let buffer = std::fs::read("./data/animated.webp").unwrap();
75    /// let decoder = Decoder::new(&buffer).unwrap();
76    /// ```
77    pub fn new(buffer: &'a [u8]) -> Result<Self, Error> {
78        Decoder::new_with_options(buffer, Default::default())
79    }
80
81    /// Construct a new decoder from webp `buffer`
82    ///
83    /// Returns an [`Error`] in case of a decoding failure (e.g. malformed input)
84    ///
85    /// ```
86    /// # use webp_animation::prelude::*;
87    /// #
88    /// let buf = std::fs::read("./data/animated.webp").unwrap();
89    /// let decoder = Decoder::new_with_options(&buf, DecoderOptions {
90    ///   use_threads: false,
91    ///   color_mode: ColorMode::Bgra
92    /// }).unwrap();
93    /// ```
94    pub fn new_with_options(buffer: &'a [u8], options: DecoderOptions) -> Result<Self, Error> {
95        if buffer.is_empty() {
96            return Err(Error::ZeroSizeBuffer);
97        }
98
99        let mut decoder_options = Box::pin(unsafe {
100            let mut options = mem::zeroed();
101
102            if webp::WebPAnimDecoderOptionsInit(&mut options) != 1 {
103                return Err(Error::OptionsInitFailed);
104            }
105
106            options
107        });
108
109        decoder_options.use_threads = if options.use_threads { 1 } else { 0 };
110        decoder_options.color_mode = match options.color_mode {
111            ColorMode::Rgba => libwebp_sys::MODE_RGBA,
112            ColorMode::Bgra => libwebp_sys::MODE_BGRA,
113            ColorMode::Rgb => libwebp_sys::MODE_RGB,
114            ColorMode::Bgr => libwebp_sys::MODE_BGR,
115        };
116
117        // pin data (& options above) because decoder takes reference to them
118        let data = Box::pin(webp::WebPData {
119            bytes: buffer.as_ptr(),
120            size: buffer.len(),
121        });
122
123        let decoder_wr = DecoderWrapper::new(data, decoder_options)?;
124
125        let info = unsafe {
126            let mut info = mem::zeroed();
127            if webp::WebPAnimDecoderGetInfo(decoder_wr.decoder, &mut info) != 1 {
128                return Err(Error::DecoderGetInfoFailed);
129            }
130            info
131        };
132
133        // prevent too large allocations
134        if info.canvas_width * info.canvas_height > MAX_CANVAS_SIZE as u32 {
135            return Err(Error::TooLargeCanvas(
136                info.canvas_width,
137                info.canvas_height,
138                MAX_CANVAS_SIZE,
139            ));
140        }
141
142        log::trace!("Decoder initialized. {:?}", info);
143
144        Ok(Self {
145            buffer,
146            decoder_wr,
147            info,
148            options,
149        })
150    }
151
152    /// Returns dimensions for webp frames (`width`, `height`)
153    ///
154    /// ```
155    /// # use webp_animation::prelude::*;
156    /// #
157    /// let buffer = std::fs::read("./data/animated.webp").unwrap();
158    /// let decoder = Decoder::new(&buffer).unwrap();
159    /// assert_eq!(decoder.dimensions(), (400, 400));
160    /// ```
161    pub fn dimensions(&self) -> (u32, u32) {
162        (self.info.canvas_width, self.info.canvas_height)
163    }
164
165    fn has_more_frames(&self) -> bool {
166        let frames = unsafe { webp::WebPAnimDecoderHasMoreFrames(self.decoder_wr.decoder) };
167        frames > 0
168    }
169}
170
171impl<'a> Debug for Decoder<'a> {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        let info = &self.info;
174
175        write!(f, "Decoder {{ buffer: {}b, info: {{ w: {}, h: {}, loop_cnt: {}, bgcolor: 0x{:x}, frame_count: {} }} }}", self.buffer.len(), info.canvas_width, info.canvas_height, info.loop_count, info.bgcolor, info.frame_count)
176    }
177}
178
179struct DecoderWrapper {
180    decoder: *mut webp::WebPAnimDecoder,
181
182    #[allow(dead_code)]
183    data: Pin<Box<webp::WebPData>>,
184    #[allow(dead_code)]
185    options: Pin<Box<webp::WebPAnimDecoderOptions>>,
186}
187
188impl DecoderWrapper {
189    pub fn new(
190        data: Pin<Box<webp::WebPData>>,
191        options: Pin<Box<webp::WebPAnimDecoderOptions>>,
192    ) -> Result<Self, Error> {
193        let decoder = unsafe { webp::WebPAnimDecoderNew(&*data, &*options) };
194        if decoder.is_null() {
195            return Err(Error::DecodeFailed);
196        }
197
198        Ok(Self {
199            decoder,
200            data,
201            options,
202        })
203    }
204}
205
206impl Drop for DecoderWrapper {
207    fn drop(&mut self) {
208        unsafe { webp::WebPAnimDecoderDelete(self.decoder) };
209    }
210}
211
212impl<'a> IntoIterator for Decoder<'a> {
213    type Item = Frame;
214
215    type IntoIter = DecoderIterator<'a>;
216
217    fn into_iter(self) -> Self::IntoIter {
218        DecoderIterator::new(self)
219    }
220}
221
222/// An iterator that produces decoded [`Frame`]'s from webp data
223pub struct DecoderIterator<'a> {
224    animation_decoder: Decoder<'a>,
225}
226
227impl<'a> DecoderIterator<'a> {
228    fn new(animation_decoder: Decoder<'a>) -> Self {
229        Self { animation_decoder }
230    }
231}
232
233impl<'a> Iterator for DecoderIterator<'a> {
234    type Item = Frame;
235
236    fn next(&mut self) -> Option<Self::Item> {
237        if !self.animation_decoder.has_more_frames() {
238            return None;
239        }
240
241        let mut output_buffer = std::ptr::null_mut();
242        let mut timestamp: i32 = 0;
243
244        if unsafe {
245            webp::WebPAnimDecoderGetNext(
246                self.animation_decoder.decoder_wr.decoder,
247                &mut output_buffer,
248                &mut timestamp,
249            )
250        } != 1
251        {
252            // "False if any of the arguments are NULL, or if there is a parsing or decoding error, or if there are no more frames. Otherwise, returns true."
253            log::warn!("webp::WebPAnimDecoderGetNext did not return success - frame parsing failed, parsing/decoding error?");
254            return None;
255        }
256
257        if output_buffer.is_null() {
258            log::error!("webp::WebPAnimDecoderGetNext returned null output ptr, can not decode a frame. This should not happen");
259            return None;
260        }
261
262        let info = &self.animation_decoder.info;
263        let opts = &self.animation_decoder.options;
264        let data = unsafe {
265            std::slice::from_raw_parts(
266                output_buffer,
267                info.canvas_width as usize * info.canvas_height as usize * opts.color_mode.size(),
268            )
269        };
270
271        log::trace!(
272            "Decoded a frame, timestamp {}, {} bytes",
273            timestamp,
274            data.len()
275        );
276
277        Some(Frame::new_from_decoder(
278            timestamp,
279            self.animation_decoder.options.color_mode,
280            data.to_vec(),
281            self.animation_decoder.dimensions(),
282        ))
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use super::*;
289    use std::fs::File;
290    use std::io::prelude::*;
291
292    #[test]
293    fn test_decoder_failure() {
294        let decoder = Decoder::new(&[]);
295        assert_eq!(decoder.unwrap_err(), Error::ZeroSizeBuffer);
296
297        let decoder = Decoder::new(&[0x00, 0x01]);
298        assert_eq!(decoder.unwrap_err(), Error::DecodeFailed);
299
300        let mut buffer = Vec::new();
301        File::open("./data/animated.webp")
302            .unwrap()
303            .read_to_end(&mut buffer)
304            .unwrap();
305
306        let decoder = Decoder::new(&buffer[..1500]);
307        assert_eq!(decoder.unwrap_err(), Error::DecodeFailed);
308    }
309
310    fn get_animated_buffer() -> Vec<u8> {
311        let mut buffer = Vec::new();
312        File::open("./data/animated.webp")
313            .unwrap()
314            .read_to_end(&mut buffer)
315            .unwrap();
316        buffer
317    }
318
319    #[cfg(feature = "image")]
320    #[test]
321    fn test_decode_to_image() {
322        use std::io::Cursor;
323
324        use image::{codecs::png::PngDecoder, DynamicImage, ImageDecoder as _, ImageOutputFormat};
325
326        let buffer = get_animated_buffer();
327        let decoder = Decoder::new(&buffer).unwrap();
328        let mut iter = decoder.into_iter();
329        let frame = iter.next().unwrap();
330        let image = frame.into_image().unwrap();
331        assert_eq!(image.dimensions(), (400, 400));
332
333        let mut buf = Cursor::new(Vec::new());
334        DynamicImage::ImageRgba8(image)
335            .write_to(&mut buf, ImageOutputFormat::Png)
336            .unwrap();
337
338        let buf = buf.into_inner();
339
340        let png_decoder = PngDecoder::new(&buf[..]).unwrap();
341        assert_eq!(png_decoder.dimensions(), (400, 400));
342    }
343
344    #[test]
345    fn test_decoder_success() {
346        // read file
347        let buffer = get_animated_buffer();
348
349        // decode frames
350        let decoder = Decoder::new_with_options(
351            &buffer,
352            DecoderOptions {
353                color_mode: ColorMode::Rgba,
354                ..Default::default()
355            },
356        )
357        .unwrap();
358
359        assert_eq!(decoder.dimensions(), (400, 400));
360        let frames: Vec<_> = decoder.into_iter().collect();
361
362        // various asserts
363        let timestamps: Vec<_> = frames.iter().map(|f| f.timestamp()).collect();
364        assert_eq!(timestamps, [40, 80, 120, 160, 200, 240, 280, 320, 360, 400]);
365        assert_eq!(frames[0].data().len(), 400 * 400 * 4);
366        assert_eq!(
367            frames[2].data()[89394 * 4..89394 * 4 + 4],
368            [167, 166, 167, 255]
369        );
370
371        assert_eq!(
372            frames[2].data().iter().map(|x| *x as usize).sum::<usize>(),
373            41668527
374        )
375    }
376
377    #[test]
378    fn test_fuzz_case_1() {
379        // initially, this data caused 768MB allocation -> now an error is returned
380        let data = [
381            0x2f, 0xff, 0xff, 0xff, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
382            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
383            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
384        ];
385
386        let decoder = Decoder::new(&data);
387        assert_eq!(
388            decoder.unwrap_err(),
389            Error::TooLargeCanvas(16384, 12288, MAX_CANVAS_SIZE)
390        );
391    }
392}