image_webp/
decoder.rs

1use byteorder_lite::{LittleEndian, ReadBytesExt};
2use quick_error::quick_error;
3
4use std::collections::HashMap;
5use std::io::{self, BufRead, Cursor, Read, Seek};
6use std::num::NonZeroU16;
7use std::ops::Range;
8
9use crate::extended::{self, get_alpha_predictor, read_alpha_chunk, WebPExtendedInfo};
10
11use super::lossless::LosslessDecoder;
12use super::vp8::Vp8Decoder;
13
14quick_error! {
15    /// Errors that can occur when attempting to decode a WebP image
16    #[derive(Debug)]
17    #[non_exhaustive]
18    pub enum DecodingError {
19        /// An IO error occurred while reading the file
20        IoError(err: io::Error) {
21            from()
22            display("IO Error: {}", err)
23            source(err)
24        }
25
26        /// RIFF's "RIFF" signature not found or invalid
27        RiffSignatureInvalid(err: [u8; 4]) {
28            display("Invalid RIFF signature: {err:x?}")
29        }
30
31        /// WebP's "WEBP" signature not found or invalid
32        WebpSignatureInvalid(err: [u8; 4]) {
33            display("Invalid WebP signature: {err:x?}")
34        }
35
36        /// An expected chunk was missing
37        ChunkMissing {
38            display("An expected chunk was missing")
39        }
40
41        /// Chunk Header was incorrect or invalid in its usage
42        ChunkHeaderInvalid(err: [u8; 4]) {
43            display("Invalid Chunk header: {err:x?}")
44        }
45
46        #[allow(deprecated)]
47        #[deprecated]
48        /// Some bits were invalid
49        ReservedBitSet {
50            display("Reserved bits set")
51        }
52
53        /// The ALPH chunk preprocessing info flag was invalid
54        InvalidAlphaPreprocessing {
55            display("Alpha chunk preprocessing flag invalid")
56        }
57
58        /// Invalid compression method
59        InvalidCompressionMethod {
60            display("Invalid compression method")
61        }
62
63        /// Alpha chunk doesn't match the frame's size
64        AlphaChunkSizeMismatch {
65            display("Alpha chunk size mismatch")
66        }
67
68        /// Image is too large, either for the platform's pointer size or generally
69        ImageTooLarge {
70            display("Image too large")
71        }
72
73        /// Frame would go out of the canvas
74        FrameOutsideImage {
75            display("Frame outside image")
76        }
77
78        /// Signature of 0x2f not found
79        LosslessSignatureInvalid(err: u8) {
80            display("Invalid lossless signature: {err:x?}")
81        }
82
83        /// Version Number was not zero
84        VersionNumberInvalid(err: u8) {
85            display("Invalid lossless version number: {err}")
86        }
87
88        /// Invalid color cache bits
89        InvalidColorCacheBits(err: u8) {
90            display("Invalid color cache bits: {err}")
91        }
92
93        /// An invalid Huffman code was encountered
94        HuffmanError {
95            display("Invalid Huffman code")
96        }
97
98        /// The bitstream was somehow corrupt
99        BitStreamError {
100            display("Corrupt bitstream")
101        }
102
103        /// The transforms specified were invalid
104        TransformError {
105            display("Invalid transform")
106        }
107
108        /// VP8's `[0x9D, 0x01, 0x2A]` magic not found or invalid
109        Vp8MagicInvalid(err: [u8; 3]) {
110            display("Invalid VP8 magic: {err:x?}")
111        }
112
113        /// VP8 Decoder initialisation wasn't provided with enough data
114        NotEnoughInitData {
115            display("Not enough VP8 init data")
116        }
117
118        /// At time of writing, only the YUV colour-space encoded as `0` is specified
119        ColorSpaceInvalid(err: u8) {
120            display("Invalid VP8 color space: {err}")
121        }
122
123        /// LUMA prediction mode was not recognised
124        LumaPredictionModeInvalid(err: i8) {
125            display("Invalid VP8 luma prediction mode: {err}")
126        }
127
128        /// Intra-prediction mode was not recognised
129        IntraPredictionModeInvalid(err: i8) {
130            display("Invalid VP8 intra prediction mode: {err}")
131        }
132
133        /// Chroma prediction mode was not recognised
134        ChromaPredictionModeInvalid(err: i8) {
135            display("Invalid VP8 chroma prediction mode: {err}")
136        }
137
138        /// Inconsistent image sizes
139        InconsistentImageSizes {
140            display("Inconsistent image sizes")
141        }
142
143        /// The file may be valid, but this crate doesn't support decoding it.
144        UnsupportedFeature(err: String) {
145            display("Unsupported feature: {err}")
146        }
147
148        /// Invalid function call or parameter
149        InvalidParameter(err: String) {
150            display("Invalid parameter: {err}")
151        }
152
153        /// Memory limit exceeded
154        MemoryLimitExceeded {
155            display("Memory limit exceeded")
156        }
157
158        /// Invalid chunk size
159        InvalidChunkSize {
160            display("Invalid chunk size")
161        }
162
163        /// No more frames in image
164        NoMoreFrames {
165            display("No more frames")
166        }
167    }
168}
169
170/// All possible RIFF chunks in a WebP image file
171#[allow(clippy::upper_case_acronyms)]
172#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)]
173pub(crate) enum WebPRiffChunk {
174    RIFF,
175    WEBP,
176    VP8,
177    VP8L,
178    VP8X,
179    ANIM,
180    ANMF,
181    ALPH,
182    ICCP,
183    EXIF,
184    XMP,
185    Unknown([u8; 4]),
186}
187
188impl WebPRiffChunk {
189    pub(crate) const fn from_fourcc(chunk_fourcc: [u8; 4]) -> Self {
190        match &chunk_fourcc {
191            b"RIFF" => Self::RIFF,
192            b"WEBP" => Self::WEBP,
193            b"VP8 " => Self::VP8,
194            b"VP8L" => Self::VP8L,
195            b"VP8X" => Self::VP8X,
196            b"ANIM" => Self::ANIM,
197            b"ANMF" => Self::ANMF,
198            b"ALPH" => Self::ALPH,
199            b"ICCP" => Self::ICCP,
200            b"EXIF" => Self::EXIF,
201            b"XMP " => Self::XMP,
202            _ => Self::Unknown(chunk_fourcc),
203        }
204    }
205
206    pub(crate) const fn to_fourcc(self) -> [u8; 4] {
207        match self {
208            Self::RIFF => *b"RIFF",
209            Self::WEBP => *b"WEBP",
210            Self::VP8 => *b"VP8 ",
211            Self::VP8L => *b"VP8L",
212            Self::VP8X => *b"VP8X",
213            Self::ANIM => *b"ANIM",
214            Self::ANMF => *b"ANMF",
215            Self::ALPH => *b"ALPH",
216            Self::ICCP => *b"ICCP",
217            Self::EXIF => *b"EXIF",
218            Self::XMP => *b"XMP ",
219            Self::Unknown(fourcc) => fourcc,
220        }
221    }
222
223    pub(crate) const fn is_unknown(self) -> bool {
224        matches!(self, Self::Unknown(_))
225    }
226}
227
228// enum WebPImage {
229//     Lossy(VP8Frame),
230//     Lossless(LosslessFrame),
231//     Extended(ExtendedImage),
232// }
233
234enum ImageKind {
235    Lossy,
236    Lossless,
237    Extended(WebPExtendedInfo),
238}
239
240struct AnimationState {
241    next_frame: u32,
242    next_frame_start: u64,
243    dispose_next_frame: bool,
244    previous_frame_width: u32,
245    previous_frame_height: u32,
246    previous_frame_x_offset: u32,
247    previous_frame_y_offset: u32,
248    canvas: Option<Vec<u8>>,
249}
250impl Default for AnimationState {
251    fn default() -> Self {
252        Self {
253            next_frame: 0,
254            next_frame_start: 0,
255            dispose_next_frame: true,
256            previous_frame_width: 0,
257            previous_frame_height: 0,
258            previous_frame_x_offset: 0,
259            previous_frame_y_offset: 0,
260            canvas: None,
261        }
262    }
263}
264
265/// Number of times that an animation loops.
266#[derive(Copy, Clone, Debug, Eq, PartialEq)]
267pub enum LoopCount {
268    /// The animation loops forever.
269    Forever,
270    /// Each frame of the animation is displayed the specified number of times.
271    Times(NonZeroU16),
272}
273
274/// WebP image format decoder.
275pub struct WebPDecoder<R> {
276    r: R,
277    memory_limit: usize,
278
279    width: u32,
280    height: u32,
281
282    kind: ImageKind,
283    animation: AnimationState,
284
285    is_lossy: bool,
286    has_alpha: bool,
287    num_frames: u32,
288    loop_count: LoopCount,
289    loop_duration: u64,
290
291    chunks: HashMap<WebPRiffChunk, Range<u64>>,
292}
293
294impl<R: BufRead + Seek> WebPDecoder<R> {
295    /// Create a new `WebPDecoder` from the reader `r`. The decoder performs many small reads, so the
296    /// reader should be buffered.
297    pub fn new(r: R) -> Result<Self, DecodingError> {
298        let mut decoder = Self {
299            r,
300            width: 0,
301            height: 0,
302            num_frames: 0,
303            kind: ImageKind::Lossy,
304            chunks: HashMap::new(),
305            animation: Default::default(),
306            memory_limit: usize::MAX,
307            is_lossy: false,
308            has_alpha: false,
309            loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
310            loop_duration: 0,
311        };
312        decoder.read_data()?;
313        Ok(decoder)
314    }
315
316    fn read_data(&mut self) -> Result<(), DecodingError> {
317        let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
318            return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
319        };
320
321        match &read_fourcc(&mut self.r)? {
322            WebPRiffChunk::WEBP => {}
323            fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
324        }
325
326        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
327        let start = self.r.stream_position()?;
328
329        match chunk {
330            WebPRiffChunk::VP8 => {
331                let tag = self.r.read_u24::<LittleEndian>()?;
332
333                let keyframe = tag & 1 == 0;
334                if !keyframe {
335                    return Err(DecodingError::UnsupportedFeature(
336                        "Non-keyframe frames".to_owned(),
337                    ));
338                }
339
340                let mut tag = [0u8; 3];
341                self.r.read_exact(&mut tag)?;
342                if tag != [0x9d, 0x01, 0x2a] {
343                    return Err(DecodingError::Vp8MagicInvalid(tag));
344                }
345
346                let w = self.r.read_u16::<LittleEndian>()?;
347                let h = self.r.read_u16::<LittleEndian>()?;
348
349                self.width = u32::from(w & 0x3FFF);
350                self.height = u32::from(h & 0x3FFF);
351                if self.width == 0 || self.height == 0 {
352                    return Err(DecodingError::InconsistentImageSizes);
353                }
354
355                self.chunks
356                    .insert(WebPRiffChunk::VP8, start..start + chunk_size);
357                self.kind = ImageKind::Lossy;
358                self.is_lossy = true;
359            }
360            WebPRiffChunk::VP8L => {
361                let signature = self.r.read_u8()?;
362                if signature != 0x2f {
363                    return Err(DecodingError::LosslessSignatureInvalid(signature));
364                }
365
366                let header = self.r.read_u32::<LittleEndian>()?;
367                let version = header >> 29;
368                if version != 0 {
369                    return Err(DecodingError::VersionNumberInvalid(version as u8));
370                }
371
372                self.width = (1 + header) & 0x3FFF;
373                self.height = (1 + (header >> 14)) & 0x3FFF;
374                self.chunks
375                    .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
376                self.kind = ImageKind::Lossless;
377                self.has_alpha = (header >> 28) & 1 != 0;
378            }
379            WebPRiffChunk::VP8X => {
380                let mut info = extended::read_extended_header(&mut self.r)?;
381                self.width = info.canvas_width;
382                self.height = info.canvas_height;
383
384                let mut position = start + chunk_size_rounded;
385                let max_position = position + riff_size.saturating_sub(12);
386                self.r.seek(io::SeekFrom::Start(position))?;
387
388                while position < max_position {
389                    match read_chunk_header(&mut self.r) {
390                        Ok((chunk, chunk_size, chunk_size_rounded)) => {
391                            let range = position + 8..position + 8 + chunk_size;
392                            position += 8 + chunk_size_rounded;
393
394                            if !chunk.is_unknown() {
395                                self.chunks.entry(chunk).or_insert(range);
396                            }
397
398                            if chunk == WebPRiffChunk::ANMF {
399                                self.num_frames += 1;
400                                if chunk_size < 24 {
401                                    return Err(DecodingError::InvalidChunkSize);
402                                }
403
404                                self.r.seek_relative(12)?;
405                                let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
406                                self.loop_duration =
407                                    self.loop_duration.wrapping_add(u64::from(duration));
408
409                                // If the image is animated, the image data chunk will be inside the
410                                // ANMF chunks, so we must inspect them to determine whether the
411                                // image contains any lossy image data. VP8 chunks store lossy data
412                                // and the spec says that lossless images SHOULD NOT contain ALPH
413                                // chunks, so we treat both as indicators of lossy images.
414                                if !self.is_lossy {
415                                    let (subchunk, ..) = read_chunk_header(&mut self.r)?;
416                                    if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
417                                        self.is_lossy = true;
418                                    }
419                                    self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
420                                } else {
421                                    self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
422                                }
423
424                                continue;
425                            }
426
427                            self.r.seek_relative(chunk_size_rounded as i64)?;
428                        }
429                        Err(DecodingError::IoError(e))
430                            if e.kind() == io::ErrorKind::UnexpectedEof =>
431                        {
432                            break;
433                        }
434                        Err(e) => return Err(e),
435                    }
436                }
437                self.is_lossy = self.is_lossy || self.chunks.contains_key(&WebPRiffChunk::VP8);
438
439                // NOTE: We allow malformed images that have `info.icc_profile` set without a ICCP chunk,
440                // because this is relatively common.
441                if info.animation
442                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
443                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
444                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
445                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
446                    || !info.animation
447                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
448                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
449                {
450                    return Err(DecodingError::ChunkMissing);
451                }
452
453                // Decode ANIM chunk.
454                if info.animation {
455                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
456                        Ok(Some(chunk)) => {
457                            let mut cursor = Cursor::new(chunk);
458                            cursor.read_exact(&mut info.background_color_hint)?;
459                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
460                                0 => LoopCount::Forever,
461                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
462                            };
463                            self.animation.next_frame_start =
464                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
465                        }
466                        Ok(None) => return Err(DecodingError::ChunkMissing),
467                        Err(DecodingError::MemoryLimitExceeded) => {
468                            return Err(DecodingError::InvalidChunkSize)
469                        }
470                        Err(e) => return Err(e),
471                    }
472                }
473
474                // If the image is animated, the image data chunk will be inside the ANMF chunks. We
475                // store the ALPH, VP8, and VP8L chunks (as applicable) of the first frame in the
476                // hashmap so that we can read them later.
477                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
478                    let mut position = range.start + 16;
479                    self.r.seek(io::SeekFrom::Start(position))?;
480                    for _ in 0..2 {
481                        let (subchunk, subchunk_size, subchunk_size_rounded) =
482                            read_chunk_header(&mut self.r)?;
483                        let subrange = position + 8..position + 8 + subchunk_size;
484                        self.chunks.entry(subchunk).or_insert(subrange.clone());
485
486                        position += 8 + subchunk_size_rounded;
487                        if position + 8 > range.end {
488                            break;
489                        }
490                    }
491                }
492
493                self.has_alpha = info.alpha;
494                self.kind = ImageKind::Extended(info);
495            }
496            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
497        };
498
499        Ok(())
500    }
501
502    /// Sets the maximum amount of memory that the decoder is allowed to allocate at once.
503    ///
504    /// TODO: Some allocations currently ignore this limit.
505    pub fn set_memory_limit(&mut self, limit: usize) {
506        self.memory_limit = limit;
507    }
508
509    /// Get the background color specified in the image file if the image is extended and animated webp.
510    pub fn background_color_hint(&self) -> Option<[u8; 4]> {
511        if let ImageKind::Extended(info) = &self.kind {
512            Some(info.background_color_hint)
513        } else {
514            None
515        }
516    }
517
518    /// Sets the background color if the image is an extended and animated webp.
519    pub fn set_background_color(&mut self, color: [u8; 4]) -> Result<(), DecodingError> {
520        if let ImageKind::Extended(info) = &mut self.kind {
521            info.background_color = Some(color);
522            Ok(())
523        } else {
524            Err(DecodingError::InvalidParameter(
525                "Background color can only be set on animated webp".to_owned(),
526            ))
527        }
528    }
529
530    /// Returns the (width, height) of the image in pixels.
531    pub fn dimensions(&self) -> (u32, u32) {
532        (self.width, self.height)
533    }
534
535    /// Returns whether the image has an alpha channel. If so, the pixel format is Rgba8 and
536    /// otherwise Rgb8.
537    pub fn has_alpha(&self) -> bool {
538        self.has_alpha
539    }
540
541    /// Returns true if the image is animated.
542    pub fn is_animated(&self) -> bool {
543        match &self.kind {
544            ImageKind::Lossy | ImageKind::Lossless => false,
545            ImageKind::Extended(extended) => extended.animation,
546        }
547    }
548
549    /// Returns whether the image is lossy. For animated images, this is true if any frame is lossy.
550    pub fn is_lossy(&mut self) -> bool {
551        self.is_lossy
552    }
553
554    /// Returns the number of frames of a single loop of the animation, or zero if the image is not
555    /// animated.
556    pub fn num_frames(&self) -> u32 {
557        self.num_frames
558    }
559
560    /// Returns the number of times the animation should loop.
561    pub fn loop_count(&self) -> LoopCount {
562        self.loop_count
563    }
564
565    /// Returns the total duration of one loop through the animation in milliseconds, or zero if the
566    /// image is not animated.
567    ///
568    /// This is the sum of the durations of all individual frames of the image.
569    pub fn loop_duration(&self) -> u64 {
570        self.loop_duration
571    }
572
573    fn read_chunk(
574        &mut self,
575        chunk: WebPRiffChunk,
576        max_size: usize,
577    ) -> Result<Option<Vec<u8>>, DecodingError> {
578        match self.chunks.get(&chunk) {
579            Some(range) => {
580                if range.end - range.start > max_size as u64 {
581                    return Err(DecodingError::MemoryLimitExceeded);
582                }
583
584                self.r.seek(io::SeekFrom::Start(range.start))?;
585                let mut data = vec![0; (range.end - range.start) as usize];
586                self.r.read_exact(&mut data)?;
587                Ok(Some(data))
588            }
589            None => Ok(None),
590        }
591    }
592
593    /// Returns the raw bytes of the ICC profile, or None if there is no ICC profile.
594    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
595        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
596    }
597
598    /// Returns the raw bytes of the EXIF metadata, or None if there is no EXIF metadata.
599    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
600        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
601    }
602
603    /// Returns the raw bytes of the XMP metadata, or None if there is no XMP metadata.
604    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
605        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
606    }
607
608    /// Returns the number of bytes required to store the image or a single frame, or None if that
609    /// would take more than `usize::MAX` bytes.
610    pub fn output_buffer_size(&self) -> Option<usize> {
611        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
612        (self.width as usize)
613            .checked_mul(self.height as usize)?
614            .checked_mul(bytes_per_pixel)
615    }
616
617    /// Returns the raw bytes of the image. For animated images, this is the first frame.
618    ///
619    /// Fails with `ImageTooLarge` if `buf` has length different than `output_buffer_size()`
620    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
621        if Some(buf.len()) != self.output_buffer_size() {
622            return Err(DecodingError::ImageTooLarge);
623        }
624
625        if self.is_animated() {
626            let saved = std::mem::take(&mut self.animation);
627            self.animation.next_frame_start =
628                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
629            let result = self.read_frame(buf);
630            self.animation = saved;
631            result?;
632        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
633            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
634
635            if self.has_alpha {
636                decoder.decode_frame(self.width, self.height, false, buf)?;
637            } else {
638                let mut data = vec![0; self.width as usize * self.height as usize * 4];
639                decoder.decode_frame(self.width, self.height, false, &mut data)?;
640                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
641                    chunk.copy_from_slice(&rgba_val[..3]);
642                }
643            }
644        } else {
645            let range = self
646                .chunks
647                .get(&WebPRiffChunk::VP8)
648                .ok_or(DecodingError::ChunkMissing)?;
649            let reader = range_reader(&mut self.r, range.start..range.end)?;
650            let frame = Vp8Decoder::decode_frame(reader)?;
651            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
652                return Err(DecodingError::InconsistentImageSizes);
653            }
654
655            if self.has_alpha() {
656                frame.fill_rgba(buf);
657
658                let range = self
659                    .chunks
660                    .get(&WebPRiffChunk::ALPH)
661                    .ok_or(DecodingError::ChunkMissing)?
662                    .clone();
663                let alpha_chunk = read_alpha_chunk(
664                    &mut range_reader(&mut self.r, range)?,
665                    self.width as u16,
666                    self.height as u16,
667                )?;
668
669                for y in 0..frame.height {
670                    for x in 0..frame.width {
671                        let predictor: u8 = get_alpha_predictor(
672                            x.into(),
673                            y.into(),
674                            frame.width.into(),
675                            alpha_chunk.filtering_method,
676                            buf,
677                        );
678
679                        let alpha_index =
680                            usize::from(y) * usize::from(frame.width) + usize::from(x);
681                        let buffer_index = alpha_index * 4 + 3;
682
683                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
684                    }
685                }
686            } else {
687                frame.fill_rgb(buf);
688            }
689        }
690
691        Ok(())
692    }
693
694    /// Reads the next frame of the animation.
695    ///
696    /// The frame contents are written into `buf` and the method returns the duration of the frame
697    /// in milliseconds. If there are no more frames, the method returns
698    /// `DecodingError::NoMoreFrames` and `buf` is left unchanged.
699    ///
700    /// # Panics
701    ///
702    /// Panics if the image is not animated.
703    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
704        assert!(self.is_animated());
705        assert_eq!(Some(buf.len()), self.output_buffer_size());
706
707        if self.animation.next_frame == self.num_frames {
708            return Err(DecodingError::NoMoreFrames);
709        }
710
711        let ImageKind::Extended(info) = &self.kind else {
712            unreachable!()
713        };
714
715        self.r
716            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
717
718        let anmf_size = match read_chunk_header(&mut self.r)? {
719            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
720            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
721        };
722
723        // Read ANMF chunk
724        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
725        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
726        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
727        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
728        if frame_width > 16384 || frame_height > 16384 {
729            return Err(DecodingError::ImageTooLarge);
730        }
731        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
732            return Err(DecodingError::FrameOutsideImage);
733        }
734        let duration = extended::read_3_bytes(&mut self.r)?;
735        let frame_info = self.r.read_u8()?;
736        let use_alpha_blending = frame_info & 0b00000010 == 0;
737        let dispose = frame_info & 0b00000001 != 0;
738
739        let clear_color = if self.animation.dispose_next_frame {
740            info.background_color
741        } else {
742            None
743        };
744
745        // Read normal bitstream now
746        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
747        if chunk_size_rounded + 24 > anmf_size {
748            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
749        }
750
751        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
752            WebPRiffChunk::VP8 => {
753                let reader = (&mut self.r).take(chunk_size);
754                let raw_frame = Vp8Decoder::decode_frame(reader)?;
755                if u32::from(raw_frame.width) != frame_width
756                    || u32::from(raw_frame.height) != frame_height
757                {
758                    return Err(DecodingError::InconsistentImageSizes);
759                }
760                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
761                raw_frame.fill_rgb(&mut rgb_frame);
762                (rgb_frame, false)
763            }
764            WebPRiffChunk::VP8L => {
765                let reader = (&mut self.r).take(chunk_size);
766                let mut lossless_decoder = LosslessDecoder::new(reader);
767                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
768                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
769                (rgba_frame, true)
770            }
771            WebPRiffChunk::ALPH => {
772                if chunk_size_rounded + 32 > anmf_size {
773                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
774                }
775
776                // read alpha
777                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
778                let mut reader = (&mut self.r).take(chunk_size);
779                let alpha_chunk =
780                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
781
782                // read opaque
783                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
784                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
785                if chunk_size + next_chunk_size + 32 > anmf_size {
786                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
787                }
788
789                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
790
791                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
792                frame.fill_rgba(&mut rgba_frame);
793
794                for y in 0..frame.height {
795                    for x in 0..frame.width {
796                        let predictor: u8 = get_alpha_predictor(
797                            x.into(),
798                            y.into(),
799                            frame.width.into(),
800                            alpha_chunk.filtering_method,
801                            &rgba_frame,
802                        );
803
804                        let alpha_index =
805                            usize::from(y) * usize::from(frame.width) + usize::from(x);
806                        let buffer_index = alpha_index * 4 + 3;
807
808                        rgba_frame[buffer_index] =
809                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
810                    }
811                }
812
813                (rgba_frame, true)
814            }
815            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
816        };
817
818        // fill starting canvas with clear color
819        if self.animation.canvas.is_none() {
820            self.animation.canvas = {
821                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
822                if let Some(color) = info.background_color.as_ref() {
823                    canvas
824                        .chunks_exact_mut(4)
825                        .for_each(|c| c.copy_from_slice(color))
826                }
827                Some(canvas)
828            }
829        }
830        extended::composite_frame(
831            self.animation.canvas.as_mut().unwrap(),
832            self.width,
833            self.height,
834            clear_color,
835            &frame,
836            frame_x,
837            frame_y,
838            frame_width,
839            frame_height,
840            frame_has_alpha,
841            use_alpha_blending,
842            self.animation.previous_frame_width,
843            self.animation.previous_frame_height,
844            self.animation.previous_frame_x_offset,
845            self.animation.previous_frame_y_offset,
846        );
847
848        self.animation.previous_frame_width = frame_width;
849        self.animation.previous_frame_height = frame_height;
850        self.animation.previous_frame_x_offset = frame_x;
851        self.animation.previous_frame_y_offset = frame_y;
852
853        self.animation.dispose_next_frame = dispose;
854        self.animation.next_frame_start += anmf_size + 8;
855        self.animation.next_frame += 1;
856
857        if self.has_alpha() {
858            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
859        } else {
860            for (b, c) in buf
861                .chunks_exact_mut(3)
862                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
863            {
864                b.copy_from_slice(&c[..3]);
865            }
866        }
867
868        Ok(duration)
869    }
870
871    /// Resets the animation to the first frame.
872    ///
873    /// # Panics
874    ///
875    /// Panics if the image is not animated.
876    pub fn reset_animation(&mut self) {
877        assert!(self.is_animated());
878
879        self.animation.next_frame = 0;
880        self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
881        self.animation.dispose_next_frame = true;
882    }
883}
884
885pub(crate) fn range_reader<R: BufRead + Seek>(
886    mut r: R,
887    range: Range<u64>,
888) -> Result<impl BufRead, DecodingError> {
889    r.seek(io::SeekFrom::Start(range.start))?;
890    Ok(r.take(range.end - range.start))
891}
892
893pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
894    let mut chunk_fourcc = [0; 4];
895    r.read_exact(&mut chunk_fourcc)?;
896    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
897}
898
899pub(crate) fn read_chunk_header<R: BufRead>(
900    mut r: R,
901) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
902    let chunk = read_fourcc(&mut r)?;
903    let chunk_size = r.read_u32::<LittleEndian>()?;
904    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
905    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
906}
907
908#[cfg(test)]
909mod tests {
910    use super::*;
911    const RGB_BPP: usize = 3;
912
913    #[test]
914    fn add_with_overflow_size() {
915        let bytes = vec![
916            0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64,
917            0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00,
918            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
919            0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
920            0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49,
921            0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46,
922        ];
923
924        let data = std::io::Cursor::new(bytes);
925
926        let _ = WebPDecoder::new(data);
927    }
928
929    #[test]
930    fn decode_2x2_single_color_image() {
931        // Image data created from imagemagick and output of xxd:
932        // $ convert -size 2x2 xc:#f00 red.webp
933        // $ xxd -g 1 red.webp | head
934
935        const NUM_PIXELS: usize = 2 * 2 * RGB_BPP;
936        // 2x2 red pixel image
937        let bytes = [
938            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
939            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x02, 0x00,
940            0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
941            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
942            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
943        ];
944
945        let mut data = [0; NUM_PIXELS];
946        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
947        decoder.read_image(&mut data).unwrap();
948
949        // All pixels are the same value
950        let first_pixel = &data[..RGB_BPP];
951        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
952    }
953
954    #[test]
955    fn decode_3x3_single_color_image() {
956        // Test that any odd pixel "tail" is decoded properly
957
958        const NUM_PIXELS: usize = 3 * 3 * RGB_BPP;
959        // 3x3 red pixel image
960        let bytes = [
961            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
962            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x03, 0x00,
963            0x03, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
964            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
965            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
966        ];
967
968        let mut data = [0; NUM_PIXELS];
969        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
970        decoder.read_image(&mut data).unwrap();
971
972        // All pixels are the same value
973        let first_pixel = &data[..RGB_BPP];
974        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
975    }
976}