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                if info.animation
440                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
441                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
442                    || info.icc_profile && !self.chunks.contains_key(&WebPRiffChunk::ICCP)
443                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
444                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
445                    || !info.animation
446                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
447                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
448                {
449                    return Err(DecodingError::ChunkMissing);
450                }
451
452                // Decode ANIM chunk.
453                if info.animation {
454                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
455                        Ok(Some(chunk)) => {
456                            let mut cursor = Cursor::new(chunk);
457                            cursor.read_exact(&mut info.background_color)?;
458                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
459                                0 => LoopCount::Forever,
460                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
461                            };
462                            self.animation.next_frame_start =
463                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
464                        }
465                        Ok(None) => return Err(DecodingError::ChunkMissing),
466                        Err(DecodingError::MemoryLimitExceeded) => {
467                            return Err(DecodingError::InvalidChunkSize)
468                        }
469                        Err(e) => return Err(e),
470                    }
471                }
472
473                // If the image is animated, the image data chunk will be inside the ANMF chunks. We
474                // store the ALPH, VP8, and VP8L chunks (as applicable) of the first frame in the
475                // hashmap so that we can read them later.
476                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
477                    let mut position = range.start + 16;
478                    self.r.seek(io::SeekFrom::Start(position))?;
479                    for _ in 0..2 {
480                        let (subchunk, subchunk_size, subchunk_size_rounded) =
481                            read_chunk_header(&mut self.r)?;
482                        let subrange = position + 8..position + 8 + subchunk_size;
483                        self.chunks.entry(subchunk).or_insert(subrange.clone());
484
485                        position += 8 + subchunk_size_rounded;
486                        if position + 8 > range.end {
487                            break;
488                        }
489                    }
490                }
491
492                self.has_alpha = info.alpha;
493                self.kind = ImageKind::Extended(info);
494            }
495            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
496        };
497
498        Ok(())
499    }
500
501    /// Sets the maximum amount of memory that the decoder is allowed to allocate at once.
502    ///
503    /// TODO: Some allocations currently ignore this limit.
504    pub fn set_memory_limit(&mut self, limit: usize) {
505        self.memory_limit = limit;
506    }
507
508    /// Sets the background color if the image is an extended and animated webp.
509    pub fn set_background_color(&mut self, color: [u8; 4]) -> Result<(), DecodingError> {
510        if let ImageKind::Extended(info) = &mut self.kind {
511            info.background_color = color;
512            Ok(())
513        } else {
514            Err(DecodingError::InvalidParameter(
515                "Background color can only be set on animated webp".to_owned(),
516            ))
517        }
518    }
519
520    /// Returns the (width, height) of the image in pixels.
521    pub fn dimensions(&self) -> (u32, u32) {
522        (self.width, self.height)
523    }
524
525    /// Returns whether the image has an alpha channel. If so, the pixel format is Rgba8 and
526    /// otherwise Rgb8.
527    pub fn has_alpha(&self) -> bool {
528        self.has_alpha
529    }
530
531    /// Returns true if the image is animated.
532    pub fn is_animated(&self) -> bool {
533        match &self.kind {
534            ImageKind::Lossy | ImageKind::Lossless => false,
535            ImageKind::Extended(extended) => extended.animation,
536        }
537    }
538
539    /// Returns whether the image is lossy. For animated images, this is true if any frame is lossy.
540    pub fn is_lossy(&mut self) -> bool {
541        self.is_lossy
542    }
543
544    /// Returns the number of frames of a single loop of the animation, or zero if the image is not
545    /// animated.
546    pub fn num_frames(&self) -> u32 {
547        self.num_frames
548    }
549
550    /// Returns the number of times the animation should loop.
551    pub fn loop_count(&self) -> LoopCount {
552        self.loop_count
553    }
554
555    /// Returns the total duration of one loop through the animation in milliseconds, or zero if the
556    /// image is not animated.
557    ///
558    /// This is the sum of the durations of all individual frames of the image.
559    pub fn loop_duration(&self) -> u64 {
560        self.loop_duration
561    }
562
563    fn read_chunk(
564        &mut self,
565        chunk: WebPRiffChunk,
566        max_size: usize,
567    ) -> Result<Option<Vec<u8>>, DecodingError> {
568        match self.chunks.get(&chunk) {
569            Some(range) => {
570                if range.end - range.start > max_size as u64 {
571                    return Err(DecodingError::MemoryLimitExceeded);
572                }
573
574                self.r.seek(io::SeekFrom::Start(range.start))?;
575                let mut data = vec![0; (range.end - range.start) as usize];
576                self.r.read_exact(&mut data)?;
577                Ok(Some(data))
578            }
579            None => Ok(None),
580        }
581    }
582
583    /// Returns the raw bytes of the ICC profile, or None if there is no ICC profile.
584    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
585        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
586    }
587
588    /// Returns the raw bytes of the EXIF metadata, or None if there is no EXIF metadata.
589    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
590        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
591    }
592
593    /// Returns the raw bytes of the XMP metadata, or None if there is no XMP metadata.
594    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
595        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
596    }
597
598    /// Returns the number of bytes required to store the image or a single frame, or None if that
599    /// would take more than `usize::MAX` bytes.
600    pub fn output_buffer_size(&self) -> Option<usize> {
601        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
602        (self.width as usize)
603            .checked_mul(self.height as usize)?
604            .checked_mul(bytes_per_pixel)
605    }
606
607    /// Returns the raw bytes of the image. For animated images, this is the first frame.
608    ///
609    /// Fails with `ImageTooLarge` if `buf` has length different than `output_buffer_size()`
610    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
611        if Some(buf.len()) != self.output_buffer_size() {
612            return Err(DecodingError::ImageTooLarge);
613        }
614
615        if self.is_animated() {
616            let saved = std::mem::take(&mut self.animation);
617            self.animation.next_frame_start =
618                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
619            let result = self.read_frame(buf);
620            self.animation = saved;
621            result?;
622        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
623            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
624
625            if self.has_alpha {
626                decoder.decode_frame(self.width, self.height, false, buf)?;
627            } else {
628                let mut data = vec![0; self.width as usize * self.height as usize * 4];
629                decoder.decode_frame(self.width, self.height, false, &mut data)?;
630                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
631                    chunk.copy_from_slice(&rgba_val[..3]);
632                }
633            }
634        } else {
635            let range = self
636                .chunks
637                .get(&WebPRiffChunk::VP8)
638                .ok_or(DecodingError::ChunkMissing)?;
639            let reader = range_reader(&mut self.r, range.start..range.end)?;
640            let frame = Vp8Decoder::decode_frame(reader)?;
641            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
642                return Err(DecodingError::InconsistentImageSizes);
643            }
644
645            if self.has_alpha() {
646                frame.fill_rgba(buf);
647
648                let range = self
649                    .chunks
650                    .get(&WebPRiffChunk::ALPH)
651                    .ok_or(DecodingError::ChunkMissing)?
652                    .clone();
653                let alpha_chunk = read_alpha_chunk(
654                    &mut range_reader(&mut self.r, range)?,
655                    self.width as u16,
656                    self.height as u16,
657                )?;
658
659                for y in 0..frame.height {
660                    for x in 0..frame.width {
661                        let predictor: u8 = get_alpha_predictor(
662                            x.into(),
663                            y.into(),
664                            frame.width.into(),
665                            alpha_chunk.filtering_method,
666                            buf,
667                        );
668
669                        let alpha_index =
670                            usize::from(y) * usize::from(frame.width) + usize::from(x);
671                        let buffer_index = alpha_index * 4 + 3;
672
673                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
674                    }
675                }
676            } else {
677                frame.fill_rgb(buf);
678            }
679        }
680
681        Ok(())
682    }
683
684    /// Reads the next frame of the animation.
685    ///
686    /// The frame contents are written into `buf` and the method returns the duration of the frame
687    /// in milliseconds. If there are no more frames, the method returns
688    /// `DecodingError::NoMoreFrames` and `buf` is left unchanged.
689    ///
690    /// # Panics
691    ///
692    /// Panics if the image is not animated.
693    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
694        assert!(self.is_animated());
695        assert_eq!(Some(buf.len()), self.output_buffer_size());
696
697        if self.animation.next_frame == self.num_frames {
698            return Err(DecodingError::NoMoreFrames);
699        }
700
701        let ImageKind::Extended(info) = &self.kind else {
702            unreachable!()
703        };
704
705        self.r
706            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
707
708        let anmf_size = match read_chunk_header(&mut self.r)? {
709            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
710            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
711        };
712
713        // Read ANMF chunk
714        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
715        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
716        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
717        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
718        if frame_width > 16384 || frame_height > 16384 {
719            return Err(DecodingError::ImageTooLarge);
720        }
721        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
722            return Err(DecodingError::FrameOutsideImage);
723        }
724        let duration = extended::read_3_bytes(&mut self.r)?;
725        let frame_info = self.r.read_u8()?;
726        let use_alpha_blending = frame_info & 0b00000010 == 0;
727        let dispose = frame_info & 0b00000001 != 0;
728
729        let clear_color = if self.animation.dispose_next_frame {
730            Some(info.background_color)
731        } else {
732            None
733        };
734
735        // Read normal bitstream now
736        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
737        if chunk_size_rounded + 24 > anmf_size {
738            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
739        }
740
741        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
742            WebPRiffChunk::VP8 => {
743                let reader = (&mut self.r).take(chunk_size);
744                let raw_frame = Vp8Decoder::decode_frame(reader)?;
745                if u32::from(raw_frame.width) != frame_width
746                    || u32::from(raw_frame.height) != frame_height
747                {
748                    return Err(DecodingError::InconsistentImageSizes);
749                }
750                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
751                raw_frame.fill_rgb(&mut rgb_frame);
752                (rgb_frame, false)
753            }
754            WebPRiffChunk::VP8L => {
755                let reader = (&mut self.r).take(chunk_size);
756                let mut lossless_decoder = LosslessDecoder::new(reader);
757                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
758                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
759                (rgba_frame, true)
760            }
761            WebPRiffChunk::ALPH => {
762                if chunk_size_rounded + 32 > anmf_size {
763                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
764                }
765
766                // read alpha
767                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
768                let mut reader = (&mut self.r).take(chunk_size);
769                let alpha_chunk =
770                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
771
772                // read opaque
773                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
774                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
775                if chunk_size + next_chunk_size + 32 > anmf_size {
776                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
777                }
778
779                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
780
781                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
782                frame.fill_rgba(&mut rgba_frame);
783
784                for y in 0..frame.height {
785                    for x in 0..frame.width {
786                        let predictor: u8 = get_alpha_predictor(
787                            x.into(),
788                            y.into(),
789                            frame.width.into(),
790                            alpha_chunk.filtering_method,
791                            &rgba_frame,
792                        );
793
794                        let alpha_index =
795                            usize::from(y) * usize::from(frame.width) + usize::from(x);
796                        let buffer_index = alpha_index * 4 + 3;
797
798                        rgba_frame[buffer_index] =
799                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
800                    }
801                }
802
803                (rgba_frame, true)
804            }
805            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
806        };
807
808        // fill starting canvas with clear color
809        if self.animation.canvas.is_none() {
810            self.animation.canvas = {
811                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
812                canvas
813                    .chunks_exact_mut(4)
814                    .for_each(|c| c.copy_from_slice(&info.background_color));
815                Some(canvas)
816            }
817        }
818        extended::composite_frame(
819            self.animation.canvas.as_mut().unwrap(),
820            self.width,
821            self.height,
822            clear_color,
823            &frame,
824            frame_x,
825            frame_y,
826            frame_width,
827            frame_height,
828            frame_has_alpha,
829            use_alpha_blending,
830            self.animation.previous_frame_width,
831            self.animation.previous_frame_height,
832            self.animation.previous_frame_x_offset,
833            self.animation.previous_frame_y_offset,
834        );
835
836        self.animation.previous_frame_width = frame_width;
837        self.animation.previous_frame_height = frame_height;
838        self.animation.previous_frame_x_offset = frame_x;
839        self.animation.previous_frame_y_offset = frame_y;
840
841        self.animation.dispose_next_frame = dispose;
842        self.animation.next_frame_start += anmf_size + 8;
843        self.animation.next_frame += 1;
844
845        if self.has_alpha() {
846            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
847        } else {
848            for (b, c) in buf
849                .chunks_exact_mut(3)
850                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
851            {
852                b.copy_from_slice(&c[..3]);
853            }
854        }
855
856        Ok(duration)
857    }
858
859    /// Resets the animation to the first frame.
860    ///
861    /// # Panics
862    ///
863    /// Panics if the image is not animated.
864    pub fn reset_animation(&mut self) {
865        assert!(self.is_animated());
866
867        self.animation.next_frame = 0;
868        self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
869        self.animation.dispose_next_frame = true;
870    }
871}
872
873pub(crate) fn range_reader<R: BufRead + Seek>(
874    mut r: R,
875    range: Range<u64>,
876) -> Result<impl BufRead, DecodingError> {
877    r.seek(io::SeekFrom::Start(range.start))?;
878    Ok(r.take(range.end - range.start))
879}
880
881pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
882    let mut chunk_fourcc = [0; 4];
883    r.read_exact(&mut chunk_fourcc)?;
884    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
885}
886
887pub(crate) fn read_chunk_header<R: BufRead>(
888    mut r: R,
889) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
890    let chunk = read_fourcc(&mut r)?;
891    let chunk_size = r.read_u32::<LittleEndian>()?;
892    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
893    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
894}
895
896#[cfg(test)]
897mod tests {
898    use super::*;
899    const RGB_BPP: usize = 3;
900
901    #[test]
902    fn add_with_overflow_size() {
903        let bytes = vec![
904            0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64,
905            0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00,
906            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
907            0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
908            0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49,
909            0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46,
910        ];
911
912        let data = std::io::Cursor::new(bytes);
913
914        let _ = WebPDecoder::new(data);
915    }
916
917    #[test]
918    fn decode_2x2_single_color_image() {
919        // Image data created from imagemagick and output of xxd:
920        // $ convert -size 2x2 xc:#f00 red.webp
921        // $ xxd -g 1 red.webp | head
922
923        const NUM_PIXELS: usize = 2 * 2 * RGB_BPP;
924        // 2x2 red pixel image
925        let bytes = [
926            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
927            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x02, 0x00,
928            0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
929            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
930            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
931        ];
932
933        let mut data = [0; NUM_PIXELS];
934        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
935        decoder.read_image(&mut data).unwrap();
936
937        // All pixels are the same value
938        let first_pixel = &data[..RGB_BPP];
939        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
940    }
941
942    #[test]
943    fn decode_3x3_single_color_image() {
944        // Test that any odd pixel "tail" is decoded properly
945
946        const NUM_PIXELS: usize = 3 * 3 * RGB_BPP;
947        // 3x3 red pixel image
948        let bytes = [
949            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
950            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x03, 0x00,
951            0x03, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
952            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
953            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
954        ];
955
956        let mut data = [0; NUM_PIXELS];
957        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
958        decoder.read_image(&mut data).unwrap();
959
960        // All pixels are the same value
961        let first_pixel = &data[..RGB_BPP];
962        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
963    }
964}