Coverage Report

Created: 2026-07-16 07:16

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/image-webp-0.2.4/src/decoder.rs
Line
Count
Source
1
use byteorder_lite::{LittleEndian, ReadBytesExt};
2
use quick_error::quick_error;
3
4
use std::collections::HashMap;
5
use std::io::{self, BufRead, Cursor, Read, Seek};
6
use std::num::NonZeroU16;
7
use std::ops::Range;
8
9
use crate::extended::{self, get_alpha_predictor, read_alpha_chunk, WebPExtendedInfo};
10
11
use super::lossless::LosslessDecoder;
12
use super::vp8::Vp8Decoder;
13
14
quick_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)]
173
pub(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
188
impl WebPRiffChunk {
189
59.2k
    pub(crate) const fn from_fourcc(chunk_fourcc: [u8; 4]) -> Self {
190
59.2k
        match &chunk_fourcc {
191
9.24k
            b"RIFF" => Self::RIFF,
192
9.24k
            b"WEBP" => Self::WEBP,
193
3.49k
            b"VP8 " => Self::VP8,
194
4.47k
            b"VP8L" => Self::VP8L,
195
3.21k
            b"VP8X" => Self::VP8X,
196
2.02k
            b"ANIM" => Self::ANIM,
197
5.27k
            b"ANMF" => Self::ANMF,
198
4.05k
            b"ALPH" => Self::ALPH,
199
1.55k
            b"ICCP" => Self::ICCP,
200
1.02k
            b"EXIF" => Self::EXIF,
201
681
            b"XMP " => Self::XMP,
202
14.9k
            _ => Self::Unknown(chunk_fourcc),
203
        }
204
59.2k
    }
205
206
134
    pub(crate) const fn to_fourcc(self) -> [u8; 4] {
207
134
        match self {
208
3
            Self::RIFF => *b"RIFF",
209
3
            Self::WEBP => *b"WEBP",
210
2
            Self::VP8 => *b"VP8 ",
211
2
            Self::VP8L => *b"VP8L",
212
2
            Self::VP8X => *b"VP8X",
213
2
            Self::ANIM => *b"ANIM",
214
2
            Self::ANMF => *b"ANMF",
215
10
            Self::ALPH => *b"ALPH",
216
2
            Self::ICCP => *b"ICCP",
217
3
            Self::EXIF => *b"EXIF",
218
2
            Self::XMP => *b"XMP ",
219
101
            Self::Unknown(fourcc) => fourcc,
220
        }
221
134
    }
222
223
22.3k
    pub(crate) const fn is_unknown(self) -> bool {
224
22.3k
        matches!(self, Self::Unknown(_))
225
22.3k
    }
226
}
227
228
// enum WebPImage {
229
//     Lossy(VP8Frame),
230
//     Lossless(LosslessFrame),
231
//     Extended(ExtendedImage),
232
// }
233
234
enum ImageKind {
235
    Lossy,
236
    Lossless,
237
    Extended(WebPExtendedInfo),
238
}
239
240
struct 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
}
250
impl Default for AnimationState {
251
9.44k
    fn default() -> Self {
252
9.44k
        Self {
253
9.44k
            next_frame: 0,
254
9.44k
            next_frame_start: 0,
255
9.44k
            dispose_next_frame: true,
256
9.44k
            previous_frame_width: 0,
257
9.44k
            previous_frame_height: 0,
258
9.44k
            previous_frame_x_offset: 0,
259
9.44k
            previous_frame_y_offset: 0,
260
9.44k
            canvas: None,
261
9.44k
        }
262
9.44k
    }
263
}
264
265
/// Number of times that an animation loops.
266
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
267
pub 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 decoder configuration options
275
#[derive(Clone)]
276
#[non_exhaustive]
277
pub struct WebPDecodeOptions {
278
    /// The upsampling method used in conversion from lossy yuv to rgb
279
    ///
280
    /// Defaults to `Bilinear`.
281
    pub lossy_upsampling: UpsamplingMethod,
282
}
283
284
impl Default for WebPDecodeOptions {
285
9.44k
    fn default() -> Self {
286
9.44k
        Self {
287
9.44k
            lossy_upsampling: UpsamplingMethod::Bilinear,
288
9.44k
        }
289
9.44k
    }
290
}
291
292
/// Methods for upsampling the chroma values in lossy decoding
293
///
294
/// The chroma red and blue planes are encoded in VP8 as half the size of the luma plane
295
/// Therefore we need to upsample these values up to fit each pixel in the image.
296
#[derive(Clone, Copy, Default)]
297
pub enum UpsamplingMethod {
298
    /// Fancy upsampling
299
    ///
300
    /// Does bilinear interpolation using the 4 values nearest to the pixel, weighting based on the distance
301
    /// from the pixel.
302
    #[default]
303
    Bilinear,
304
    /// Simple upsampling, just uses the closest u/v value to the pixel when upsampling
305
    ///
306
    /// Matches the -nofancy option in dwebp.
307
    /// Should be faster but may lead to slightly jagged edges.
308
    Simple,
309
}
310
311
/// WebP image format decoder.
312
pub struct WebPDecoder<R> {
313
    r: R,
314
    memory_limit: usize,
315
316
    width: u32,
317
    height: u32,
318
319
    kind: ImageKind,
320
    animation: AnimationState,
321
322
    is_lossy: bool,
323
    has_alpha: bool,
324
    num_frames: u32,
325
    loop_count: LoopCount,
326
    loop_duration: u64,
327
328
    chunks: HashMap<WebPRiffChunk, Range<u64>>,
329
330
    webp_decode_options: WebPDecodeOptions,
331
}
332
333
impl<R: BufRead + Seek> WebPDecoder<R> {
334
    /// Create a new `WebPDecoder` from the reader `r`. The decoder performs many small reads, so the
335
    /// reader should be buffered.
336
9.44k
    pub fn new(r: R) -> Result<Self, DecodingError> {
337
9.44k
        Self::new_with_options(r, WebPDecodeOptions::default())
338
9.44k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::new
Line
Count
Source
336
9.44k
    pub fn new(r: R) -> Result<Self, DecodingError> {
337
9.44k
        Self::new_with_options(r, WebPDecodeOptions::default())
338
9.44k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::new
339
340
    /// Create a new `WebPDecoder` from the reader `r` with the options `WebPDecodeOptions`. The decoder
341
    /// performs many small reads, so the reader should be buffered.
342
9.44k
    pub fn new_with_options(
343
9.44k
        r: R,
344
9.44k
        webp_decode_options: WebPDecodeOptions,
345
9.44k
    ) -> Result<Self, DecodingError> {
346
9.44k
        let mut decoder = Self {
347
9.44k
            r,
348
9.44k
            width: 0,
349
9.44k
            height: 0,
350
9.44k
            num_frames: 0,
351
9.44k
            kind: ImageKind::Lossy,
352
9.44k
            chunks: HashMap::new(),
353
9.44k
            animation: Default::default(),
354
9.44k
            memory_limit: usize::MAX,
355
9.44k
            is_lossy: false,
356
9.44k
            has_alpha: false,
357
9.44k
            loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
358
9.44k
            loop_duration: 0,
359
9.44k
            webp_decode_options,
360
9.44k
        };
361
9.44k
        decoder.read_data()?;
362
8.71k
        Ok(decoder)
363
9.44k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::new_with_options
Line
Count
Source
342
9.44k
    pub fn new_with_options(
343
9.44k
        r: R,
344
9.44k
        webp_decode_options: WebPDecodeOptions,
345
9.44k
    ) -> Result<Self, DecodingError> {
346
9.44k
        let mut decoder = Self {
347
9.44k
            r,
348
9.44k
            width: 0,
349
9.44k
            height: 0,
350
9.44k
            num_frames: 0,
351
9.44k
            kind: ImageKind::Lossy,
352
9.44k
            chunks: HashMap::new(),
353
9.44k
            animation: Default::default(),
354
9.44k
            memory_limit: usize::MAX,
355
9.44k
            is_lossy: false,
356
9.44k
            has_alpha: false,
357
9.44k
            loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
358
9.44k
            loop_duration: 0,
359
9.44k
            webp_decode_options,
360
9.44k
        };
361
9.44k
        decoder.read_data()?;
362
8.71k
        Ok(decoder)
363
9.44k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::new_with_options
364
365
9.44k
    fn read_data(&mut self) -> Result<(), DecodingError> {
366
9.44k
        let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
367
201
            return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
368
        };
369
370
9.23k
        match &read_fourcc(&mut self.r)? {
371
9.21k
            WebPRiffChunk::WEBP => {}
372
13
            fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
373
        }
374
375
9.21k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
376
9.21k
        let start = self.r.stream_position()?;
377
378
9.21k
        match chunk {
379
            WebPRiffChunk::VP8 => {
380
2.40k
                let tag = self.r.read_u24::<LittleEndian>()?;
381
382
2.40k
                let keyframe = tag & 1 == 0;
383
2.40k
                if !keyframe {
384
1
                    return Err(DecodingError::UnsupportedFeature(
385
1
                        "Non-keyframe frames".to_owned(),
386
1
                    ));
387
2.40k
                }
388
389
2.40k
                let mut tag = [0u8; 3];
390
2.40k
                self.r.read_exact(&mut tag)?;
391
2.40k
                if tag != [0x9d, 0x01, 0x2a] {
392
2
                    return Err(DecodingError::Vp8MagicInvalid(tag));
393
2.39k
                }
394
395
2.39k
                let w = self.r.read_u16::<LittleEndian>()?;
396
2.39k
                let h = self.r.read_u16::<LittleEndian>()?;
397
398
2.39k
                self.width = u32::from(w & 0x3FFF);
399
2.39k
                self.height = u32::from(h & 0x3FFF);
400
2.39k
                if self.width == 0 || self.height == 0 {
401
2
                    return Err(DecodingError::InconsistentImageSizes);
402
2.39k
                }
403
404
2.39k
                self.chunks
405
2.39k
                    .insert(WebPRiffChunk::VP8, start..start + chunk_size);
406
2.39k
                self.kind = ImageKind::Lossy;
407
2.39k
                self.is_lossy = true;
408
            }
409
            WebPRiffChunk::VP8L => {
410
4.07k
                let signature = self.r.read_u8()?;
411
4.07k
                if signature != 0x2f {
412
13
                    return Err(DecodingError::LosslessSignatureInvalid(signature));
413
4.06k
                }
414
415
4.06k
                let header = self.r.read_u32::<LittleEndian>()?;
416
4.05k
                let version = header >> 29;
417
4.05k
                if version != 0 {
418
3
                    return Err(DecodingError::VersionNumberInvalid(version as u8));
419
4.05k
                }
420
421
4.05k
                self.width = (1 + header) & 0x3FFF;
422
4.05k
                self.height = (1 + (header >> 14)) & 0x3FFF;
423
4.05k
                self.chunks
424
4.05k
                    .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
425
4.05k
                self.kind = ImageKind::Lossless;
426
4.05k
                self.has_alpha = (header >> 28) & 1 != 0;
427
            }
428
            WebPRiffChunk::VP8X => {
429
2.71k
                let mut info = extended::read_extended_header(&mut self.r)?;
430
2.70k
                self.width = info.canvas_width;
431
2.70k
                self.height = info.canvas_height;
432
433
2.70k
                let mut position = start + chunk_size_rounded;
434
2.70k
                let max_position = position + riff_size.saturating_sub(12);
435
2.70k
                self.r.seek(io::SeekFrom::Start(position))?;
436
437
25.0k
                while position < max_position {
438
23.6k
                    match read_chunk_header(&mut self.r) {
439
22.3k
                        Ok((chunk, chunk_size, chunk_size_rounded)) => {
440
22.3k
                            let range = position + 8..position + 8 + chunk_size;
441
22.3k
                            position += 8 + chunk_size_rounded;
442
443
22.3k
                            if !chunk.is_unknown() {
444
10.7k
                                self.chunks.entry(chunk).or_insert(range);
445
11.5k
                            }
446
447
22.3k
                            if chunk == WebPRiffChunk::ANMF {
448
3.76k
                                self.num_frames += 1;
449
3.76k
                                if chunk_size < 24 {
450
3
                                    return Err(DecodingError::InvalidChunkSize);
451
3.76k
                                }
452
453
3.76k
                                self.r.seek_relative(12)?;
454
3.76k
                                let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
455
3.75k
                                self.loop_duration =
456
3.75k
                                    self.loop_duration.wrapping_add(u64::from(duration));
457
458
                                // If the image is animated, the image data chunk will be inside the
459
                                // ANMF chunks, so we must inspect them to determine whether the
460
                                // image contains any lossy image data. VP8 chunks store lossy data
461
                                // and the spec says that lossless images SHOULD NOT contain ALPH
462
                                // chunks, so we treat both as indicators of lossy images.
463
3.75k
                                if !self.is_lossy {
464
3.16k
                                    let (subchunk, ..) = read_chunk_header(&mut self.r)?;
465
3.15k
                                    if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
466
1.31k
                                        self.is_lossy = true;
467
1.83k
                                    }
468
3.15k
                                    self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
469
                                } else {
470
586
                                    self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
471
                                }
472
473
3.74k
                                continue;
474
18.5k
                            }
475
476
18.5k
                            self.r.seek_relative(chunk_size_rounded as i64)?;
477
                        }
478
1.28k
                        Err(DecodingError::IoError(e))
479
1.28k
                            if e.kind() == io::ErrorKind::UnexpectedEof =>
480
                        {
481
1.28k
                            break;
482
                        }
483
0
                        Err(e) => return Err(e),
484
                    }
485
                }
486
2.68k
                self.is_lossy = self.is_lossy || self.chunks.contains_key(&WebPRiffChunk::VP8);
487
488
                // NOTE: We allow malformed images that have `info.icc_profile` set without a ICCP chunk,
489
                // because this is relatively common.
490
2.68k
                if info.animation
491
1.76k
                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
492
1.60k
                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
493
2.50k
                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
494
2.48k
                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
495
2.45k
                    || !info.animation
496
875
                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
497
875
                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
498
                {
499
367
                    return Err(DecodingError::ChunkMissing);
500
2.31k
                }
501
502
                // Decode ANIM chunk.
503
2.31k
                if info.animation {
504
1.58k
                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
505
1.54k
                        Ok(Some(chunk)) => {
506
1.54k
                            let mut cursor = Cursor::new(chunk);
507
1.54k
                            cursor.read_exact(&mut info.background_color_hint)?;
508
1.54k
                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
509
604
                                0 => LoopCount::Forever,
510
937
                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
511
                            };
512
1.54k
                            self.animation.next_frame_start =
513
1.54k
                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
514
                        }
515
0
                        Ok(None) => return Err(DecodingError::ChunkMissing),
516
                        Err(DecodingError::MemoryLimitExceeded) => {
517
34
                            return Err(DecodingError::InvalidChunkSize)
518
                        }
519
2
                        Err(e) => return Err(e),
520
                    }
521
735
                }
522
523
                // If the image is animated, the image data chunk will be inside the ANMF chunks. We
524
                // store the ALPH, VP8, and VP8L chunks (as applicable) of the first frame in the
525
                // hashmap so that we can read them later.
526
2.27k
                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
527
1.54k
                    let mut position = range.start + 16;
528
1.54k
                    self.r.seek(io::SeekFrom::Start(position))?;
529
3.14k
                    for _ in 0..2 {
530
2.83k
                        let (subchunk, subchunk_size, subchunk_size_rounded) =
531
2.84k
                            read_chunk_header(&mut self.r)?;
532
2.83k
                        let subrange = position + 8..position + 8 + subchunk_size;
533
2.83k
                        self.chunks.entry(subchunk).or_insert(subrange.clone());
534
535
2.83k
                        position += 8 + subchunk_size_rounded;
536
2.83k
                        if position + 8 > range.end {
537
1.23k
                            break;
538
1.59k
                        }
539
                    }
540
733
                }
541
542
2.26k
                self.has_alpha = info.alpha;
543
2.26k
                self.kind = ImageKind::Extended(info);
544
            }
545
11
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
546
        };
547
548
8.71k
        Ok(())
549
9.44k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_data
Line
Count
Source
365
9.44k
    fn read_data(&mut self) -> Result<(), DecodingError> {
366
9.44k
        let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
367
201
            return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
368
        };
369
370
9.23k
        match &read_fourcc(&mut self.r)? {
371
9.21k
            WebPRiffChunk::WEBP => {}
372
13
            fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
373
        }
374
375
9.21k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
376
9.21k
        let start = self.r.stream_position()?;
377
378
9.21k
        match chunk {
379
            WebPRiffChunk::VP8 => {
380
2.40k
                let tag = self.r.read_u24::<LittleEndian>()?;
381
382
2.40k
                let keyframe = tag & 1 == 0;
383
2.40k
                if !keyframe {
384
1
                    return Err(DecodingError::UnsupportedFeature(
385
1
                        "Non-keyframe frames".to_owned(),
386
1
                    ));
387
2.40k
                }
388
389
2.40k
                let mut tag = [0u8; 3];
390
2.40k
                self.r.read_exact(&mut tag)?;
391
2.40k
                if tag != [0x9d, 0x01, 0x2a] {
392
2
                    return Err(DecodingError::Vp8MagicInvalid(tag));
393
2.39k
                }
394
395
2.39k
                let w = self.r.read_u16::<LittleEndian>()?;
396
2.39k
                let h = self.r.read_u16::<LittleEndian>()?;
397
398
2.39k
                self.width = u32::from(w & 0x3FFF);
399
2.39k
                self.height = u32::from(h & 0x3FFF);
400
2.39k
                if self.width == 0 || self.height == 0 {
401
2
                    return Err(DecodingError::InconsistentImageSizes);
402
2.39k
                }
403
404
2.39k
                self.chunks
405
2.39k
                    .insert(WebPRiffChunk::VP8, start..start + chunk_size);
406
2.39k
                self.kind = ImageKind::Lossy;
407
2.39k
                self.is_lossy = true;
408
            }
409
            WebPRiffChunk::VP8L => {
410
4.07k
                let signature = self.r.read_u8()?;
411
4.07k
                if signature != 0x2f {
412
13
                    return Err(DecodingError::LosslessSignatureInvalid(signature));
413
4.06k
                }
414
415
4.06k
                let header = self.r.read_u32::<LittleEndian>()?;
416
4.05k
                let version = header >> 29;
417
4.05k
                if version != 0 {
418
3
                    return Err(DecodingError::VersionNumberInvalid(version as u8));
419
4.05k
                }
420
421
4.05k
                self.width = (1 + header) & 0x3FFF;
422
4.05k
                self.height = (1 + (header >> 14)) & 0x3FFF;
423
4.05k
                self.chunks
424
4.05k
                    .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
425
4.05k
                self.kind = ImageKind::Lossless;
426
4.05k
                self.has_alpha = (header >> 28) & 1 != 0;
427
            }
428
            WebPRiffChunk::VP8X => {
429
2.71k
                let mut info = extended::read_extended_header(&mut self.r)?;
430
2.70k
                self.width = info.canvas_width;
431
2.70k
                self.height = info.canvas_height;
432
433
2.70k
                let mut position = start + chunk_size_rounded;
434
2.70k
                let max_position = position + riff_size.saturating_sub(12);
435
2.70k
                self.r.seek(io::SeekFrom::Start(position))?;
436
437
25.0k
                while position < max_position {
438
23.6k
                    match read_chunk_header(&mut self.r) {
439
22.3k
                        Ok((chunk, chunk_size, chunk_size_rounded)) => {
440
22.3k
                            let range = position + 8..position + 8 + chunk_size;
441
22.3k
                            position += 8 + chunk_size_rounded;
442
443
22.3k
                            if !chunk.is_unknown() {
444
10.7k
                                self.chunks.entry(chunk).or_insert(range);
445
11.5k
                            }
446
447
22.3k
                            if chunk == WebPRiffChunk::ANMF {
448
3.76k
                                self.num_frames += 1;
449
3.76k
                                if chunk_size < 24 {
450
3
                                    return Err(DecodingError::InvalidChunkSize);
451
3.76k
                                }
452
453
3.76k
                                self.r.seek_relative(12)?;
454
3.76k
                                let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
455
3.75k
                                self.loop_duration =
456
3.75k
                                    self.loop_duration.wrapping_add(u64::from(duration));
457
458
                                // If the image is animated, the image data chunk will be inside the
459
                                // ANMF chunks, so we must inspect them to determine whether the
460
                                // image contains any lossy image data. VP8 chunks store lossy data
461
                                // and the spec says that lossless images SHOULD NOT contain ALPH
462
                                // chunks, so we treat both as indicators of lossy images.
463
3.75k
                                if !self.is_lossy {
464
3.16k
                                    let (subchunk, ..) = read_chunk_header(&mut self.r)?;
465
3.15k
                                    if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
466
1.31k
                                        self.is_lossy = true;
467
1.83k
                                    }
468
3.15k
                                    self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
469
                                } else {
470
586
                                    self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
471
                                }
472
473
3.74k
                                continue;
474
18.5k
                            }
475
476
18.5k
                            self.r.seek_relative(chunk_size_rounded as i64)?;
477
                        }
478
1.28k
                        Err(DecodingError::IoError(e))
479
1.28k
                            if e.kind() == io::ErrorKind::UnexpectedEof =>
480
                        {
481
1.28k
                            break;
482
                        }
483
0
                        Err(e) => return Err(e),
484
                    }
485
                }
486
2.68k
                self.is_lossy = self.is_lossy || self.chunks.contains_key(&WebPRiffChunk::VP8);
487
488
                // NOTE: We allow malformed images that have `info.icc_profile` set without a ICCP chunk,
489
                // because this is relatively common.
490
2.68k
                if info.animation
491
1.76k
                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
492
1.60k
                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
493
2.50k
                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
494
2.48k
                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
495
2.45k
                    || !info.animation
496
875
                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
497
875
                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
498
                {
499
367
                    return Err(DecodingError::ChunkMissing);
500
2.31k
                }
501
502
                // Decode ANIM chunk.
503
2.31k
                if info.animation {
504
1.58k
                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
505
1.54k
                        Ok(Some(chunk)) => {
506
1.54k
                            let mut cursor = Cursor::new(chunk);
507
1.54k
                            cursor.read_exact(&mut info.background_color_hint)?;
508
1.54k
                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
509
604
                                0 => LoopCount::Forever,
510
937
                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
511
                            };
512
1.54k
                            self.animation.next_frame_start =
513
1.54k
                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
514
                        }
515
0
                        Ok(None) => return Err(DecodingError::ChunkMissing),
516
                        Err(DecodingError::MemoryLimitExceeded) => {
517
34
                            return Err(DecodingError::InvalidChunkSize)
518
                        }
519
2
                        Err(e) => return Err(e),
520
                    }
521
735
                }
522
523
                // If the image is animated, the image data chunk will be inside the ANMF chunks. We
524
                // store the ALPH, VP8, and VP8L chunks (as applicable) of the first frame in the
525
                // hashmap so that we can read them later.
526
2.27k
                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
527
1.54k
                    let mut position = range.start + 16;
528
1.54k
                    self.r.seek(io::SeekFrom::Start(position))?;
529
3.14k
                    for _ in 0..2 {
530
2.83k
                        let (subchunk, subchunk_size, subchunk_size_rounded) =
531
2.84k
                            read_chunk_header(&mut self.r)?;
532
2.83k
                        let subrange = position + 8..position + 8 + subchunk_size;
533
2.83k
                        self.chunks.entry(subchunk).or_insert(subrange.clone());
534
535
2.83k
                        position += 8 + subchunk_size_rounded;
536
2.83k
                        if position + 8 > range.end {
537
1.23k
                            break;
538
1.59k
                        }
539
                    }
540
733
                }
541
542
2.26k
                self.has_alpha = info.alpha;
543
2.26k
                self.kind = ImageKind::Extended(info);
544
            }
545
11
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
546
        };
547
548
8.71k
        Ok(())
549
9.44k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::read_data
550
551
    /// Sets the maximum amount of memory that the decoder is allowed to allocate at once.
552
    ///
553
    /// TODO: Some allocations currently ignore this limit.
554
0
    pub fn set_memory_limit(&mut self, limit: usize) {
555
0
        self.memory_limit = limit;
556
0
    }
557
558
    /// Get the background color specified in the image file if the image is extended and animated webp.
559
0
    pub fn background_color_hint(&self) -> Option<[u8; 4]> {
560
0
        if let ImageKind::Extended(info) = &self.kind {
561
0
            Some(info.background_color_hint)
562
        } else {
563
0
            None
564
        }
565
0
    }
566
567
    /// Sets the background color if the image is an extended and animated webp.
568
0
    pub fn set_background_color(&mut self, color: [u8; 4]) -> Result<(), DecodingError> {
569
0
        if let ImageKind::Extended(info) = &mut self.kind {
570
0
            info.background_color = Some(color);
571
0
            Ok(())
572
        } else {
573
0
            Err(DecodingError::InvalidParameter(
574
0
                "Background color can only be set on animated webp".to_owned(),
575
0
            ))
576
        }
577
0
    }
578
579
    /// Returns the (width, height) of the image in pixels.
580
34.7k
    pub fn dimensions(&self) -> (u32, u32) {
581
34.7k
        (self.width, self.height)
582
34.7k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::dimensions
Line
Count
Source
580
34.7k
    pub fn dimensions(&self) -> (u32, u32) {
581
34.7k
        (self.width, self.height)
582
34.7k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::dimensions
583
584
    /// Returns whether the image has an alpha channel. If so, the pixel format is Rgba8 and
585
    /// otherwise Rgb8.
586
44.7k
    pub fn has_alpha(&self) -> bool {
587
44.7k
        self.has_alpha
588
44.7k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::has_alpha
Line
Count
Source
586
44.7k
    pub fn has_alpha(&self) -> bool {
587
44.7k
        self.has_alpha
588
44.7k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::has_alpha
589
590
    /// Returns true if the image is animated.
591
17.3k
    pub fn is_animated(&self) -> bool {
592
17.3k
        match &self.kind {
593
12.8k
            ImageKind::Lossy | ImageKind::Lossless => false,
594
4.46k
            ImageKind::Extended(extended) => extended.animation,
595
        }
596
17.3k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::is_animated
Line
Count
Source
591
17.3k
    pub fn is_animated(&self) -> bool {
592
17.3k
        match &self.kind {
593
12.8k
            ImageKind::Lossy | ImageKind::Lossless => false,
594
4.46k
            ImageKind::Extended(extended) => extended.animation,
595
        }
596
17.3k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::is_animated
597
598
    /// Returns whether the image is lossy. For animated images, this is true if any frame is lossy.
599
0
    pub fn is_lossy(&mut self) -> bool {
600
0
        self.is_lossy
601
0
    }
602
603
    /// Returns the number of frames of a single loop of the animation, or zero if the image is not
604
    /// animated.
605
1.50k
    pub fn num_frames(&self) -> u32 {
606
1.50k
        self.num_frames
607
1.50k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::num_frames
Line
Count
Source
605
1.50k
    pub fn num_frames(&self) -> u32 {
606
1.50k
        self.num_frames
607
1.50k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::num_frames
608
609
    /// Returns the number of times the animation should loop.
610
0
    pub fn loop_count(&self) -> LoopCount {
611
0
        self.loop_count
612
0
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::loop_count
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::loop_count
613
614
    /// Returns the total duration of one loop through the animation in milliseconds, or zero if the
615
    /// image is not animated.
616
    ///
617
    /// This is the sum of the durations of all individual frames of the image.
618
0
    pub fn loop_duration(&self) -> u64 {
619
0
        self.loop_duration
620
0
    }
621
622
27.7k
    fn read_chunk(
623
27.7k
        &mut self,
624
27.7k
        chunk: WebPRiffChunk,
625
27.7k
        max_size: usize,
626
27.7k
    ) -> Result<Option<Vec<u8>>, DecodingError> {
627
27.7k
        match self.chunks.get(&chunk) {
628
1.81k
            Some(range) => {
629
1.81k
                if range.end - range.start > max_size as u64 {
630
34
                    return Err(DecodingError::MemoryLimitExceeded);
631
1.77k
                }
632
633
1.77k
                self.r.seek(io::SeekFrom::Start(range.start))?;
634
1.77k
                let mut data = vec![0; (range.end - range.start) as usize];
635
1.77k
                self.r.read_exact(&mut data)?;
636
1.71k
                Ok(Some(data))
637
            }
638
25.9k
            None => Ok(None),
639
        }
640
27.7k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_chunk
Line
Count
Source
622
27.7k
    fn read_chunk(
623
27.7k
        &mut self,
624
27.7k
        chunk: WebPRiffChunk,
625
27.7k
        max_size: usize,
626
27.7k
    ) -> Result<Option<Vec<u8>>, DecodingError> {
627
27.7k
        match self.chunks.get(&chunk) {
628
1.81k
            Some(range) => {
629
1.81k
                if range.end - range.start > max_size as u64 {
630
34
                    return Err(DecodingError::MemoryLimitExceeded);
631
1.77k
                }
632
633
1.77k
                self.r.seek(io::SeekFrom::Start(range.start))?;
634
1.77k
                let mut data = vec![0; (range.end - range.start) as usize];
635
1.77k
                self.r.read_exact(&mut data)?;
636
1.71k
                Ok(Some(data))
637
            }
638
25.9k
            None => Ok(None),
639
        }
640
27.7k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::read_chunk
641
642
    /// Returns the raw bytes of the ICC profile, or None if there is no ICC profile.
643
8.71k
    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
644
8.71k
        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
645
8.71k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::icc_profile
Line
Count
Source
643
8.71k
    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
644
8.71k
        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
645
8.71k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::icc_profile
646
647
    /// Returns the raw bytes of the EXIF metadata, or None if there is no EXIF metadata.
648
8.71k
    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
649
8.71k
        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
650
8.71k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::exif_metadata
Line
Count
Source
648
8.71k
    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
649
8.71k
        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
650
8.71k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::exif_metadata
651
652
    /// Returns the raw bytes of the XMP metadata, or None if there is no XMP metadata.
653
8.71k
    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
654
8.71k
        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
655
8.71k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::xmp_metadata
Line
Count
Source
653
8.71k
    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
654
8.71k
        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
655
8.71k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::xmp_metadata
656
657
    /// Returns the number of bytes required to store the image or a single frame, or None if that
658
    /// would take more than `usize::MAX` bytes.
659
8.66k
    pub fn output_buffer_size(&self) -> Option<usize> {
660
8.66k
        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
661
8.66k
        (self.width as usize)
662
8.66k
            .checked_mul(self.height as usize)?
663
8.66k
            .checked_mul(bytes_per_pixel)
664
8.66k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::output_buffer_size
Line
Count
Source
659
8.66k
    pub fn output_buffer_size(&self) -> Option<usize> {
660
8.66k
        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
661
8.66k
        (self.width as usize)
662
8.66k
            .checked_mul(self.height as usize)?
663
8.66k
            .checked_mul(bytes_per_pixel)
664
8.66k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::output_buffer_size
665
666
    /// Returns the raw bytes of the image. For animated images, this is the first frame.
667
    ///
668
    /// Fails with `ImageTooLarge` if `buf` has length different than `output_buffer_size()`
669
7.16k
    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
670
7.16k
        if Some(buf.len()) != self.output_buffer_size() {
671
0
            return Err(DecodingError::ImageTooLarge);
672
7.16k
        }
673
674
7.16k
        if self.is_animated() {
675
0
            let saved = std::mem::take(&mut self.animation);
676
0
            self.animation.next_frame_start =
677
0
                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
678
0
            let result = self.read_frame(buf);
679
0
            self.animation = saved;
680
0
            result?;
681
7.16k
        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
682
4.09k
            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
683
684
4.09k
            if self.has_alpha {
685
2.12k
                decoder.decode_frame(self.width, self.height, false, buf)?;
686
            } else {
687
1.96k
                let mut data = vec![0; self.width as usize * self.height as usize * 4];
688
1.96k
                decoder.decode_frame(self.width, self.height, false, &mut data)?;
689
5.11G
                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
690
5.11G
                    chunk.copy_from_slice(&rgba_val[..3]);
691
5.11G
                }
692
            }
693
        } else {
694
3.07k
            let range = self
695
3.07k
                .chunks
696
3.07k
                .get(&WebPRiffChunk::VP8)
697
3.07k
                .ok_or(DecodingError::ChunkMissing)?;
698
3.07k
            let reader = range_reader(&mut self.r, range.start..range.end)?;
699
3.07k
            let frame = Vp8Decoder::decode_frame(reader)?;
700
1.22k
            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
701
66
                return Err(DecodingError::InconsistentImageSizes);
702
1.16k
            }
703
704
1.16k
            if self.has_alpha() {
705
356
                frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling);
706
707
356
                let range = self
708
356
                    .chunks
709
356
                    .get(&WebPRiffChunk::ALPH)
710
356
                    .ok_or(DecodingError::ChunkMissing)?
711
337
                    .clone();
712
337
                let alpha_chunk = read_alpha_chunk(
713
337
                    &mut range_reader(&mut self.r, range)?,
714
337
                    self.width as u16,
715
337
                    self.height as u16,
716
277
                )?;
717
718
7.16k
                for y in 0..frame.height {
719
1.64M
                    for x in 0..frame.width {
720
1.64M
                        let predictor: u8 = get_alpha_predictor(
721
1.64M
                            x.into(),
722
1.64M
                            y.into(),
723
1.64M
                            frame.width.into(),
724
1.64M
                            alpha_chunk.filtering_method,
725
1.64M
                            buf,
726
1.64M
                        );
727
1.64M
728
1.64M
                        let alpha_index =
729
1.64M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
730
1.64M
                        let buffer_index = alpha_index * 4 + 3;
731
1.64M
732
1.64M
                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
733
1.64M
                    }
734
                }
735
806
            } else {
736
806
                frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling);
737
806
            }
738
        }
739
740
3.39k
        Ok(())
741
7.16k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_image
Line
Count
Source
669
7.16k
    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
670
7.16k
        if Some(buf.len()) != self.output_buffer_size() {
671
0
            return Err(DecodingError::ImageTooLarge);
672
7.16k
        }
673
674
7.16k
        if self.is_animated() {
675
0
            let saved = std::mem::take(&mut self.animation);
676
0
            self.animation.next_frame_start =
677
0
                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
678
0
            let result = self.read_frame(buf);
679
0
            self.animation = saved;
680
0
            result?;
681
7.16k
        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
682
4.09k
            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
683
684
4.09k
            if self.has_alpha {
685
2.12k
                decoder.decode_frame(self.width, self.height, false, buf)?;
686
            } else {
687
1.96k
                let mut data = vec![0; self.width as usize * self.height as usize * 4];
688
1.96k
                decoder.decode_frame(self.width, self.height, false, &mut data)?;
689
5.11G
                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
690
5.11G
                    chunk.copy_from_slice(&rgba_val[..3]);
691
5.11G
                }
692
            }
693
        } else {
694
3.07k
            let range = self
695
3.07k
                .chunks
696
3.07k
                .get(&WebPRiffChunk::VP8)
697
3.07k
                .ok_or(DecodingError::ChunkMissing)?;
698
3.07k
            let reader = range_reader(&mut self.r, range.start..range.end)?;
699
3.07k
            let frame = Vp8Decoder::decode_frame(reader)?;
700
1.22k
            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
701
66
                return Err(DecodingError::InconsistentImageSizes);
702
1.16k
            }
703
704
1.16k
            if self.has_alpha() {
705
356
                frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling);
706
707
356
                let range = self
708
356
                    .chunks
709
356
                    .get(&WebPRiffChunk::ALPH)
710
356
                    .ok_or(DecodingError::ChunkMissing)?
711
337
                    .clone();
712
337
                let alpha_chunk = read_alpha_chunk(
713
337
                    &mut range_reader(&mut self.r, range)?,
714
337
                    self.width as u16,
715
337
                    self.height as u16,
716
277
                )?;
717
718
7.16k
                for y in 0..frame.height {
719
1.64M
                    for x in 0..frame.width {
720
1.64M
                        let predictor: u8 = get_alpha_predictor(
721
1.64M
                            x.into(),
722
1.64M
                            y.into(),
723
1.64M
                            frame.width.into(),
724
1.64M
                            alpha_chunk.filtering_method,
725
1.64M
                            buf,
726
1.64M
                        );
727
1.64M
728
1.64M
                        let alpha_index =
729
1.64M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
730
1.64M
                        let buffer_index = alpha_index * 4 + 3;
731
1.64M
732
1.64M
                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
733
1.64M
                    }
734
                }
735
806
            } else {
736
806
                frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling);
737
806
            }
738
        }
739
740
3.39k
        Ok(())
741
7.16k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::read_image
742
743
    /// Reads the next frame of the animation.
744
    ///
745
    /// The frame contents are written into `buf` and the method returns the duration of the frame
746
    /// in milliseconds. If there are no more frames, the method returns
747
    /// `DecodingError::NoMoreFrames` and `buf` is left unchanged.
748
    ///
749
    /// # Panics
750
    ///
751
    /// Panics if the image is not animated.
752
1.50k
    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
753
1.50k
        assert!(self.is_animated());
754
1.50k
        assert_eq!(Some(buf.len()), self.output_buffer_size());
755
756
1.50k
        if self.animation.next_frame == self.num_frames {
757
0
            return Err(DecodingError::NoMoreFrames);
758
1.50k
        }
759
760
1.50k
        let ImageKind::Extended(info) = &self.kind else {
761
0
            unreachable!()
762
        };
763
764
1.50k
        self.r
765
1.50k
            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
766
767
1.50k
        let anmf_size = match read_chunk_header(&mut self.r)? {
768
1.50k
            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
769
3
            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
770
        };
771
772
        // Read ANMF chunk
773
1.49k
        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
774
1.49k
        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
775
1.49k
        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
776
1.49k
        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
777
1.49k
        if frame_width > 16384 || frame_height > 16384 {
778
67
            return Err(DecodingError::ImageTooLarge);
779
1.43k
        }
780
1.43k
        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
781
73
            return Err(DecodingError::FrameOutsideImage);
782
1.35k
        }
783
1.35k
        let duration = extended::read_3_bytes(&mut self.r)?;
784
1.35k
        let frame_info = self.r.read_u8()?;
785
1.35k
        let use_alpha_blending = frame_info & 0b00000010 == 0;
786
1.35k
        let dispose = frame_info & 0b00000001 != 0;
787
788
1.35k
        let clear_color = if self.animation.dispose_next_frame {
789
1.35k
            info.background_color
790
        } else {
791
0
            None
792
        };
793
794
        // Read normal bitstream now
795
1.35k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
796
1.35k
        if chunk_size_rounded + 24 > anmf_size {
797
53
            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
798
1.30k
        }
799
800
1.30k
        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
801
            WebPRiffChunk::VP8 => {
802
69
                let reader = (&mut self.r).take(chunk_size);
803
69
                let raw_frame = Vp8Decoder::decode_frame(reader)?;
804
64
                if u32::from(raw_frame.width) != frame_width
805
53
                    || u32::from(raw_frame.height) != frame_height
806
                {
807
25
                    return Err(DecodingError::InconsistentImageSizes);
808
39
                }
809
39
                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
810
39
                raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling);
811
39
                (rgb_frame, false)
812
            }
813
            WebPRiffChunk::VP8L => {
814
60
                let reader = (&mut self.r).take(chunk_size);
815
60
                let mut lossless_decoder = LosslessDecoder::new(reader);
816
60
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
817
60
                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
818
56
                (rgba_frame, true)
819
            }
820
            WebPRiffChunk::ALPH => {
821
1.16k
                if chunk_size_rounded + 32 > anmf_size {
822
2
                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
823
1.16k
                }
824
825
                // read alpha
826
1.16k
                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
827
1.16k
                let mut reader = (&mut self.r).take(chunk_size);
828
250
                let alpha_chunk =
829
1.16k
                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
830
831
                // read opaque
832
250
                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
833
250
                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
834
159
                if chunk_size + next_chunk_size + 32 > anmf_size {
835
42
                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
836
117
                }
837
838
117
                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
839
840
110
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
841
110
                frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling);
842
843
9.52k
                for y in 0..frame.height {
844
1.56M
                    for x in 0..frame.width {
845
1.56M
                        let predictor: u8 = get_alpha_predictor(
846
1.56M
                            x.into(),
847
1.56M
                            y.into(),
848
1.56M
                            frame.width.into(),
849
1.56M
                            alpha_chunk.filtering_method,
850
1.56M
                            &rgba_frame,
851
1.56M
                        );
852
1.56M
853
1.56M
                        let alpha_index =
854
1.56M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
855
1.56M
                        let buffer_index = alpha_index * 4 + 3;
856
1.56M
857
1.56M
                        rgba_frame[buffer_index] =
858
1.56M
                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
859
1.56M
                    }
860
                }
861
862
110
                (rgba_frame, true)
863
            }
864
13
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
865
        };
866
867
        // fill starting canvas with clear color
868
205
        if self.animation.canvas.is_none() {
869
205
            self.animation.canvas = {
870
205
                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
871
205
                if let Some(color) = info.background_color.as_ref() {
872
0
                    canvas
873
0
                        .chunks_exact_mut(4)
874
0
                        .for_each(|c| c.copy_from_slice(color))
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_frame::{closure#0}
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::read_frame::{closure#0}
875
205
                }
876
205
                Some(canvas)
877
            }
878
0
        }
879
205
        extended::composite_frame(
880
205
            self.animation.canvas.as_mut().unwrap(),
881
205
            self.width,
882
205
            self.height,
883
205
            clear_color,
884
205
            &frame,
885
205
            frame_x,
886
205
            frame_y,
887
205
            frame_width,
888
205
            frame_height,
889
205
            frame_has_alpha,
890
205
            use_alpha_blending,
891
205
            self.animation.previous_frame_width,
892
205
            self.animation.previous_frame_height,
893
205
            self.animation.previous_frame_x_offset,
894
205
            self.animation.previous_frame_y_offset,
895
        );
896
897
205
        self.animation.previous_frame_width = frame_width;
898
205
        self.animation.previous_frame_height = frame_height;
899
205
        self.animation.previous_frame_x_offset = frame_x;
900
205
        self.animation.previous_frame_y_offset = frame_y;
901
902
205
        self.animation.dispose_next_frame = dispose;
903
205
        self.animation.next_frame_start += anmf_size + 8;
904
205
        self.animation.next_frame += 1;
905
906
205
        if self.has_alpha() {
907
164
            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
908
164
        } else {
909
526M
            for (b, c) in buf
910
41
                .chunks_exact_mut(3)
911
41
                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
912
526M
            {
913
526M
                b.copy_from_slice(&c[..3]);
914
526M
            }
915
        }
916
917
205
        Ok(duration)
918
1.50k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_frame
Line
Count
Source
752
1.50k
    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
753
1.50k
        assert!(self.is_animated());
754
1.50k
        assert_eq!(Some(buf.len()), self.output_buffer_size());
755
756
1.50k
        if self.animation.next_frame == self.num_frames {
757
0
            return Err(DecodingError::NoMoreFrames);
758
1.50k
        }
759
760
1.50k
        let ImageKind::Extended(info) = &self.kind else {
761
0
            unreachable!()
762
        };
763
764
1.50k
        self.r
765
1.50k
            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
766
767
1.50k
        let anmf_size = match read_chunk_header(&mut self.r)? {
768
1.50k
            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
769
3
            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
770
        };
771
772
        // Read ANMF chunk
773
1.49k
        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
774
1.49k
        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
775
1.49k
        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
776
1.49k
        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
777
1.49k
        if frame_width > 16384 || frame_height > 16384 {
778
67
            return Err(DecodingError::ImageTooLarge);
779
1.43k
        }
780
1.43k
        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
781
73
            return Err(DecodingError::FrameOutsideImage);
782
1.35k
        }
783
1.35k
        let duration = extended::read_3_bytes(&mut self.r)?;
784
1.35k
        let frame_info = self.r.read_u8()?;
785
1.35k
        let use_alpha_blending = frame_info & 0b00000010 == 0;
786
1.35k
        let dispose = frame_info & 0b00000001 != 0;
787
788
1.35k
        let clear_color = if self.animation.dispose_next_frame {
789
1.35k
            info.background_color
790
        } else {
791
0
            None
792
        };
793
794
        // Read normal bitstream now
795
1.35k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
796
1.35k
        if chunk_size_rounded + 24 > anmf_size {
797
53
            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
798
1.30k
        }
799
800
1.30k
        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
801
            WebPRiffChunk::VP8 => {
802
69
                let reader = (&mut self.r).take(chunk_size);
803
69
                let raw_frame = Vp8Decoder::decode_frame(reader)?;
804
64
                if u32::from(raw_frame.width) != frame_width
805
53
                    || u32::from(raw_frame.height) != frame_height
806
                {
807
25
                    return Err(DecodingError::InconsistentImageSizes);
808
39
                }
809
39
                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
810
39
                raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling);
811
39
                (rgb_frame, false)
812
            }
813
            WebPRiffChunk::VP8L => {
814
60
                let reader = (&mut self.r).take(chunk_size);
815
60
                let mut lossless_decoder = LosslessDecoder::new(reader);
816
60
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
817
60
                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
818
56
                (rgba_frame, true)
819
            }
820
            WebPRiffChunk::ALPH => {
821
1.16k
                if chunk_size_rounded + 32 > anmf_size {
822
2
                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
823
1.16k
                }
824
825
                // read alpha
826
1.16k
                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
827
1.16k
                let mut reader = (&mut self.r).take(chunk_size);
828
250
                let alpha_chunk =
829
1.16k
                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
830
831
                // read opaque
832
250
                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
833
250
                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
834
159
                if chunk_size + next_chunk_size + 32 > anmf_size {
835
42
                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
836
117
                }
837
838
117
                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
839
840
110
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
841
110
                frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling);
842
843
9.52k
                for y in 0..frame.height {
844
1.56M
                    for x in 0..frame.width {
845
1.56M
                        let predictor: u8 = get_alpha_predictor(
846
1.56M
                            x.into(),
847
1.56M
                            y.into(),
848
1.56M
                            frame.width.into(),
849
1.56M
                            alpha_chunk.filtering_method,
850
1.56M
                            &rgba_frame,
851
1.56M
                        );
852
1.56M
853
1.56M
                        let alpha_index =
854
1.56M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
855
1.56M
                        let buffer_index = alpha_index * 4 + 3;
856
1.56M
857
1.56M
                        rgba_frame[buffer_index] =
858
1.56M
                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
859
1.56M
                    }
860
                }
861
862
110
                (rgba_frame, true)
863
            }
864
13
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
865
        };
866
867
        // fill starting canvas with clear color
868
205
        if self.animation.canvas.is_none() {
869
205
            self.animation.canvas = {
870
205
                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
871
205
                if let Some(color) = info.background_color.as_ref() {
872
0
                    canvas
873
0
                        .chunks_exact_mut(4)
874
0
                        .for_each(|c| c.copy_from_slice(color))
875
205
                }
876
205
                Some(canvas)
877
            }
878
0
        }
879
205
        extended::composite_frame(
880
205
            self.animation.canvas.as_mut().unwrap(),
881
205
            self.width,
882
205
            self.height,
883
205
            clear_color,
884
205
            &frame,
885
205
            frame_x,
886
205
            frame_y,
887
205
            frame_width,
888
205
            frame_height,
889
205
            frame_has_alpha,
890
205
            use_alpha_blending,
891
205
            self.animation.previous_frame_width,
892
205
            self.animation.previous_frame_height,
893
205
            self.animation.previous_frame_x_offset,
894
205
            self.animation.previous_frame_y_offset,
895
        );
896
897
205
        self.animation.previous_frame_width = frame_width;
898
205
        self.animation.previous_frame_height = frame_height;
899
205
        self.animation.previous_frame_x_offset = frame_x;
900
205
        self.animation.previous_frame_y_offset = frame_y;
901
902
205
        self.animation.dispose_next_frame = dispose;
903
205
        self.animation.next_frame_start += anmf_size + 8;
904
205
        self.animation.next_frame += 1;
905
906
205
        if self.has_alpha() {
907
164
            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
908
164
        } else {
909
526M
            for (b, c) in buf
910
41
                .chunks_exact_mut(3)
911
41
                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
912
526M
            {
913
526M
                b.copy_from_slice(&c[..3]);
914
526M
            }
915
        }
916
917
205
        Ok(duration)
918
1.50k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::read_frame
919
920
    /// Resets the animation to the first frame.
921
    ///
922
    /// # Panics
923
    ///
924
    /// Panics if the image is not animated.
925
0
    pub fn reset_animation(&mut self) {
926
0
        assert!(self.is_animated());
927
928
0
        self.animation.next_frame = 0;
929
0
        self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
930
0
        self.animation.dispose_next_frame = true;
931
0
    }
932
933
    /// Sets the upsampling method that is used in lossy decoding
934
0
    pub fn set_lossy_upsampling(&mut self, upsampling_method: UpsamplingMethod) {
935
0
        self.webp_decode_options.lossy_upsampling = upsampling_method;
936
0
    }
937
}
938
939
7.50k
pub(crate) fn range_reader<R: BufRead + Seek>(
940
7.50k
    mut r: R,
941
7.50k
    range: Range<u64>,
942
7.50k
) -> Result<impl BufRead, DecodingError> {
943
7.50k
    r.seek(io::SeekFrom::Start(range.start))?;
944
7.50k
    Ok(r.take(range.end - range.start))
945
7.50k
}
image_webp::decoder::range_reader::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
939
7.50k
pub(crate) fn range_reader<R: BufRead + Seek>(
940
7.50k
    mut r: R,
941
7.50k
    range: Range<u64>,
942
7.50k
) -> Result<impl BufRead, DecodingError> {
943
7.50k
    r.seek(io::SeekFrom::Start(range.start))?;
944
7.50k
    Ok(r.take(range.end - range.start))
945
7.50k
}
Unexecuted instantiation: image_webp::decoder::range_reader::<_>
946
947
60.6k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
60.6k
    let mut chunk_fourcc = [0; 4];
949
60.6k
    r.read_exact(&mut chunk_fourcc)?;
950
59.2k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
60.6k
}
image_webp::decoder::read_fourcc::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
947
9.23k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
9.23k
    let mut chunk_fourcc = [0; 4];
949
9.23k
    r.read_exact(&mut chunk_fourcc)?;
950
9.23k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
9.23k
}
image_webp::decoder::read_fourcc::<&mut &mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
947
51.3k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
51.3k
    let mut chunk_fourcc = [0; 4];
949
51.3k
    r.read_exact(&mut chunk_fourcc)?;
950
50.0k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
51.3k
}
Unexecuted instantiation: image_webp::decoder::read_fourcc::<_>
952
953
51.3k
pub(crate) fn read_chunk_header<R: BufRead>(
954
51.3k
    mut r: R,
955
51.3k
) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
956
51.3k
    let chunk = read_fourcc(&mut r)?;
957
50.0k
    let chunk_size = r.read_u32::<LittleEndian>()?;
958
49.9k
    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
959
49.9k
    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
960
51.3k
}
image_webp::decoder::read_chunk_header::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
953
51.3k
pub(crate) fn read_chunk_header<R: BufRead>(
954
51.3k
    mut r: R,
955
51.3k
) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
956
51.3k
    let chunk = read_fourcc(&mut r)?;
957
50.0k
    let chunk_size = r.read_u32::<LittleEndian>()?;
958
49.9k
    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
959
49.9k
    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
960
51.3k
}
Unexecuted instantiation: image_webp::decoder::read_chunk_header::<_>
961
962
#[cfg(test)]
963
mod tests {
964
    use super::*;
965
    const RGB_BPP: usize = 3;
966
967
    #[test]
968
    fn add_with_overflow_size() {
969
        let bytes = vec![
970
            0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64,
971
            0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00,
972
            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
973
            0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
974
            0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49,
975
            0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46,
976
        ];
977
978
        let data = std::io::Cursor::new(bytes);
979
980
        let _ = WebPDecoder::new(data);
981
    }
982
983
    #[test]
984
    fn decode_2x2_single_color_image() {
985
        // Image data created from imagemagick and output of xxd:
986
        // $ convert -size 2x2 xc:#f00 red.webp
987
        // $ xxd -g 1 red.webp | head
988
989
        const NUM_PIXELS: usize = 2 * 2 * RGB_BPP;
990
        // 2x2 red pixel image
991
        let bytes = [
992
            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
993
            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x02, 0x00,
994
            0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
995
            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
996
            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
997
        ];
998
999
        let mut data = [0; NUM_PIXELS];
1000
        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
1001
        decoder.read_image(&mut data).unwrap();
1002
1003
        // All pixels are the same value
1004
        let first_pixel = &data[..RGB_BPP];
1005
        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
1006
    }
1007
1008
    #[test]
1009
    fn decode_3x3_single_color_image() {
1010
        // Test that any odd pixel "tail" is decoded properly
1011
1012
        const NUM_PIXELS: usize = 3 * 3 * RGB_BPP;
1013
        // 3x3 red pixel image
1014
        let bytes = [
1015
            0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
1016
            0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x03, 0x00,
1017
            0x03, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
1018
            0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
1019
            0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
1020
        ];
1021
1022
        let mut data = [0; NUM_PIXELS];
1023
        let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
1024
        decoder.read_image(&mut data).unwrap();
1025
1026
        // All pixels are the same value
1027
        let first_pixel = &data[..RGB_BPP];
1028
        assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
1029
    }
1030
}