Skip to main content

atmos/os_lib/webp/
decoder.rs

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