Coverage Report

Created: 2026-06-07 07:04

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
43.1k
    pub(crate) const fn from_fourcc(chunk_fourcc: [u8; 4]) -> Self {
190
43.1k
        match &chunk_fourcc {
191
8.29k
            b"RIFF" => Self::RIFF,
192
8.28k
            b"WEBP" => Self::WEBP,
193
2.79k
            b"VP8 " => Self::VP8,
194
4.14k
            b"VP8L" => Self::VP8L,
195
2.42k
            b"VP8X" => Self::VP8X,
196
1.60k
            b"ANIM" => Self::ANIM,
197
4.40k
            b"ANMF" => Self::ANMF,
198
3.68k
            b"ALPH" => Self::ALPH,
199
1.12k
            b"ICCP" => Self::ICCP,
200
777
            b"EXIF" => Self::EXIF,
201
325
            b"XMP " => Self::XMP,
202
5.30k
            _ => Self::Unknown(chunk_fourcc),
203
        }
204
43.1k
    }
205
206
96
    pub(crate) const fn to_fourcc(self) -> [u8; 4] {
207
96
        match self {
208
3
            Self::RIFF => *b"RIFF",
209
1
            Self::WEBP => *b"WEBP",
210
2
            Self::VP8 => *b"VP8 ",
211
2
            Self::VP8L => *b"VP8L",
212
2
            Self::VP8X => *b"VP8X",
213
3
            Self::ANIM => *b"ANIM",
214
4
            Self::ANMF => *b"ANMF",
215
5
            Self::ALPH => *b"ALPH",
216
2
            Self::ICCP => *b"ICCP",
217
2
            Self::EXIF => *b"EXIF",
218
2
            Self::XMP => *b"XMP ",
219
68
            Self::Unknown(fourcc) => fourcc,
220
        }
221
96
    }
222
223
10.0k
    pub(crate) const fn is_unknown(self) -> bool {
224
10.0k
        matches!(self, Self::Unknown(_))
225
10.0k
    }
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
8.50k
    fn default() -> Self {
252
8.50k
        Self {
253
8.50k
            next_frame: 0,
254
8.50k
            next_frame_start: 0,
255
8.50k
            dispose_next_frame: true,
256
8.50k
            previous_frame_width: 0,
257
8.50k
            previous_frame_height: 0,
258
8.50k
            previous_frame_x_offset: 0,
259
8.50k
            previous_frame_y_offset: 0,
260
8.50k
            canvas: None,
261
8.50k
        }
262
8.50k
    }
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
8.50k
    fn default() -> Self {
286
8.50k
        Self {
287
8.50k
            lossy_upsampling: UpsamplingMethod::Bilinear,
288
8.50k
        }
289
8.50k
    }
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
8.50k
    pub fn new(r: R) -> Result<Self, DecodingError> {
337
8.50k
        Self::new_with_options(r, WebPDecodeOptions::default())
338
8.50k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::new
Line
Count
Source
336
8.50k
    pub fn new(r: R) -> Result<Self, DecodingError> {
337
8.50k
        Self::new_with_options(r, WebPDecodeOptions::default())
338
8.50k
    }
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
8.50k
    pub fn new_with_options(
343
8.50k
        r: R,
344
8.50k
        webp_decode_options: WebPDecodeOptions,
345
8.50k
    ) -> Result<Self, DecodingError> {
346
8.50k
        let mut decoder = Self {
347
8.50k
            r,
348
8.50k
            width: 0,
349
8.50k
            height: 0,
350
8.50k
            num_frames: 0,
351
8.50k
            kind: ImageKind::Lossy,
352
8.50k
            chunks: HashMap::new(),
353
8.50k
            animation: Default::default(),
354
8.50k
            memory_limit: usize::MAX,
355
8.50k
            is_lossy: false,
356
8.50k
            has_alpha: false,
357
8.50k
            loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
358
8.50k
            loop_duration: 0,
359
8.50k
            webp_decode_options,
360
8.50k
        };
361
8.50k
        decoder.read_data()?;
362
7.85k
        Ok(decoder)
363
8.50k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::new_with_options
Line
Count
Source
342
8.50k
    pub fn new_with_options(
343
8.50k
        r: R,
344
8.50k
        webp_decode_options: WebPDecodeOptions,
345
8.50k
    ) -> Result<Self, DecodingError> {
346
8.50k
        let mut decoder = Self {
347
8.50k
            r,
348
8.50k
            width: 0,
349
8.50k
            height: 0,
350
8.50k
            num_frames: 0,
351
8.50k
            kind: ImageKind::Lossy,
352
8.50k
            chunks: HashMap::new(),
353
8.50k
            animation: Default::default(),
354
8.50k
            memory_limit: usize::MAX,
355
8.50k
            is_lossy: false,
356
8.50k
            has_alpha: false,
357
8.50k
            loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
358
8.50k
            loop_duration: 0,
359
8.50k
            webp_decode_options,
360
8.50k
        };
361
8.50k
        decoder.read_data()?;
362
7.85k
        Ok(decoder)
363
8.50k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::new_with_options
364
365
8.50k
    fn read_data(&mut self) -> Result<(), DecodingError> {
366
8.50k
        let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
367
204
            return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
368
        };
369
370
8.28k
        match &read_fourcc(&mut self.r)? {
371
8.25k
            WebPRiffChunk::WEBP => {}
372
19
            fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
373
        }
374
375
8.25k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
376
8.25k
        let start = self.r.stream_position()?;
377
378
8.25k
        match chunk {
379
            WebPRiffChunk::VP8 => {
380
2.11k
                let tag = self.r.read_u24::<LittleEndian>()?;
381
382
2.11k
                let keyframe = tag & 1 == 0;
383
2.11k
                if !keyframe {
384
1
                    return Err(DecodingError::UnsupportedFeature(
385
1
                        "Non-keyframe frames".to_owned(),
386
1
                    ));
387
2.11k
                }
388
389
2.11k
                let mut tag = [0u8; 3];
390
2.11k
                self.r.read_exact(&mut tag)?;
391
2.11k
                if tag != [0x9d, 0x01, 0x2a] {
392
1
                    return Err(DecodingError::Vp8MagicInvalid(tag));
393
2.11k
                }
394
395
2.11k
                let w = self.r.read_u16::<LittleEndian>()?;
396
2.11k
                let h = self.r.read_u16::<LittleEndian>()?;
397
398
2.10k
                self.width = u32::from(w & 0x3FFF);
399
2.10k
                self.height = u32::from(h & 0x3FFF);
400
2.10k
                if self.width == 0 || self.height == 0 {
401
2
                    return Err(DecodingError::InconsistentImageSizes);
402
2.10k
                }
403
404
2.10k
                self.chunks
405
2.10k
                    .insert(WebPRiffChunk::VP8, start..start + chunk_size);
406
2.10k
                self.kind = ImageKind::Lossy;
407
2.10k
                self.is_lossy = true;
408
            }
409
            WebPRiffChunk::VP8L => {
410
3.99k
                let signature = self.r.read_u8()?;
411
3.99k
                if signature != 0x2f {
412
12
                    return Err(DecodingError::LosslessSignatureInvalid(signature));
413
3.98k
                }
414
415
3.98k
                let header = self.r.read_u32::<LittleEndian>()?;
416
3.98k
                let version = header >> 29;
417
3.98k
                if version != 0 {
418
5
                    return Err(DecodingError::VersionNumberInvalid(version as u8));
419
3.97k
                }
420
421
3.97k
                self.width = (1 + header) & 0x3FFF;
422
3.97k
                self.height = (1 + (header >> 14)) & 0x3FFF;
423
3.97k
                self.chunks
424
3.97k
                    .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
425
3.97k
                self.kind = ImageKind::Lossless;
426
3.97k
                self.has_alpha = (header >> 28) & 1 != 0;
427
            }
428
            WebPRiffChunk::VP8X => {
429
2.12k
                let mut info = extended::read_extended_header(&mut self.r)?;
430
2.11k
                self.width = info.canvas_width;
431
2.11k
                self.height = info.canvas_height;
432
433
2.11k
                let mut position = start + chunk_size_rounded;
434
2.11k
                let max_position = position + riff_size.saturating_sub(12);
435
2.11k
                self.r.seek(io::SeekFrom::Start(position))?;
436
437
12.1k
                while position < max_position {
438
10.9k
                    match read_chunk_header(&mut self.r) {
439
10.0k
                        Ok((chunk, chunk_size, chunk_size_rounded)) => {
440
10.0k
                            let range = position + 8..position + 8 + chunk_size;
441
10.0k
                            position += 8 + chunk_size_rounded;
442
443
10.0k
                            if !chunk.is_unknown() {
444
7.95k
                                self.chunks.entry(chunk).or_insert(range);
445
7.95k
                            }
446
447
10.0k
                            if chunk == WebPRiffChunk::ANMF {
448
3.11k
                                self.num_frames += 1;
449
3.11k
                                if chunk_size < 24 {
450
1
                                    return Err(DecodingError::InvalidChunkSize);
451
3.11k
                                }
452
453
3.11k
                                self.r.seek_relative(12)?;
454
3.11k
                                let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
455
3.09k
                                self.loop_duration =
456
3.09k
                                    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.09k
                                if !self.is_lossy {
464
2.89k
                                    let (subchunk, ..) = read_chunk_header(&mut self.r)?;
465
2.88k
                                    if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
466
1.23k
                                        self.is_lossy = true;
467
1.64k
                                    }
468
2.88k
                                    self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
469
                                } else {
470
204
                                    self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
471
                                }
472
473
3.08k
                                continue;
474
6.91k
                            }
475
476
6.91k
                            self.r.seek_relative(chunk_size_rounded as i64)?;
477
                        }
478
935
                        Err(DecodingError::IoError(e))
479
935
                            if e.kind() == io::ErrorKind::UnexpectedEof =>
480
                        {
481
935
                            break;
482
                        }
483
0
                        Err(e) => return Err(e),
484
                    }
485
                }
486
2.09k
                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.09k
                if info.animation
491
1.51k
                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
492
1.39k
                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
493
1.96k
                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
494
1.93k
                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
495
1.91k
                    || !info.animation
496
535
                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
497
535
                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
498
                {
499
280
                    return Err(DecodingError::ChunkMissing);
500
1.81k
                }
501
502
                // Decode ANIM chunk.
503
1.81k
                if info.animation {
504
1.38k
                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
505
1.35k
                        Ok(Some(chunk)) => {
506
1.35k
                            let mut cursor = Cursor::new(chunk);
507
1.35k
                            cursor.read_exact(&mut info.background_color_hint)?;
508
1.34k
                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
509
605
                                0 => LoopCount::Forever,
510
743
                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
511
                            };
512
1.34k
                            self.animation.next_frame_start =
513
1.34k
                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
514
                        }
515
0
                        Ok(None) => return Err(DecodingError::ChunkMissing),
516
                        Err(DecodingError::MemoryLimitExceeded) => {
517
25
                            return Err(DecodingError::InvalidChunkSize)
518
                        }
519
1
                        Err(e) => return Err(e),
520
                    }
521
431
                }
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
1.77k
                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
527
1.35k
                    let mut position = range.start + 16;
528
1.35k
                    self.r.seek(io::SeekFrom::Start(position))?;
529
2.82k
                    for _ in 0..2 {
530
2.56k
                        let (subchunk, subchunk_size, subchunk_size_rounded) =
531
2.56k
                            read_chunk_header(&mut self.r)?;
532
2.56k
                        let subrange = position + 8..position + 8 + subchunk_size;
533
2.56k
                        self.chunks.entry(subchunk).or_insert(subrange.clone());
534
535
2.56k
                        position += 8 + subchunk_size_rounded;
536
2.56k
                        if position + 8 > range.end {
537
1.08k
                            break;
538
1.47k
                        }
539
                    }
540
428
                }
541
542
1.77k
                self.has_alpha = info.alpha;
543
1.77k
                self.kind = ImageKind::Extended(info);
544
            }
545
10
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
546
        };
547
548
7.85k
        Ok(())
549
8.50k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_data
Line
Count
Source
365
8.50k
    fn read_data(&mut self) -> Result<(), DecodingError> {
366
8.50k
        let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
367
204
            return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
368
        };
369
370
8.28k
        match &read_fourcc(&mut self.r)? {
371
8.25k
            WebPRiffChunk::WEBP => {}
372
19
            fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
373
        }
374
375
8.25k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
376
8.25k
        let start = self.r.stream_position()?;
377
378
8.25k
        match chunk {
379
            WebPRiffChunk::VP8 => {
380
2.11k
                let tag = self.r.read_u24::<LittleEndian>()?;
381
382
2.11k
                let keyframe = tag & 1 == 0;
383
2.11k
                if !keyframe {
384
1
                    return Err(DecodingError::UnsupportedFeature(
385
1
                        "Non-keyframe frames".to_owned(),
386
1
                    ));
387
2.11k
                }
388
389
2.11k
                let mut tag = [0u8; 3];
390
2.11k
                self.r.read_exact(&mut tag)?;
391
2.11k
                if tag != [0x9d, 0x01, 0x2a] {
392
1
                    return Err(DecodingError::Vp8MagicInvalid(tag));
393
2.11k
                }
394
395
2.11k
                let w = self.r.read_u16::<LittleEndian>()?;
396
2.11k
                let h = self.r.read_u16::<LittleEndian>()?;
397
398
2.10k
                self.width = u32::from(w & 0x3FFF);
399
2.10k
                self.height = u32::from(h & 0x3FFF);
400
2.10k
                if self.width == 0 || self.height == 0 {
401
2
                    return Err(DecodingError::InconsistentImageSizes);
402
2.10k
                }
403
404
2.10k
                self.chunks
405
2.10k
                    .insert(WebPRiffChunk::VP8, start..start + chunk_size);
406
2.10k
                self.kind = ImageKind::Lossy;
407
2.10k
                self.is_lossy = true;
408
            }
409
            WebPRiffChunk::VP8L => {
410
3.99k
                let signature = self.r.read_u8()?;
411
3.99k
                if signature != 0x2f {
412
12
                    return Err(DecodingError::LosslessSignatureInvalid(signature));
413
3.98k
                }
414
415
3.98k
                let header = self.r.read_u32::<LittleEndian>()?;
416
3.98k
                let version = header >> 29;
417
3.98k
                if version != 0 {
418
5
                    return Err(DecodingError::VersionNumberInvalid(version as u8));
419
3.97k
                }
420
421
3.97k
                self.width = (1 + header) & 0x3FFF;
422
3.97k
                self.height = (1 + (header >> 14)) & 0x3FFF;
423
3.97k
                self.chunks
424
3.97k
                    .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
425
3.97k
                self.kind = ImageKind::Lossless;
426
3.97k
                self.has_alpha = (header >> 28) & 1 != 0;
427
            }
428
            WebPRiffChunk::VP8X => {
429
2.12k
                let mut info = extended::read_extended_header(&mut self.r)?;
430
2.11k
                self.width = info.canvas_width;
431
2.11k
                self.height = info.canvas_height;
432
433
2.11k
                let mut position = start + chunk_size_rounded;
434
2.11k
                let max_position = position + riff_size.saturating_sub(12);
435
2.11k
                self.r.seek(io::SeekFrom::Start(position))?;
436
437
12.1k
                while position < max_position {
438
10.9k
                    match read_chunk_header(&mut self.r) {
439
10.0k
                        Ok((chunk, chunk_size, chunk_size_rounded)) => {
440
10.0k
                            let range = position + 8..position + 8 + chunk_size;
441
10.0k
                            position += 8 + chunk_size_rounded;
442
443
10.0k
                            if !chunk.is_unknown() {
444
7.95k
                                self.chunks.entry(chunk).or_insert(range);
445
7.95k
                            }
446
447
10.0k
                            if chunk == WebPRiffChunk::ANMF {
448
3.11k
                                self.num_frames += 1;
449
3.11k
                                if chunk_size < 24 {
450
1
                                    return Err(DecodingError::InvalidChunkSize);
451
3.11k
                                }
452
453
3.11k
                                self.r.seek_relative(12)?;
454
3.11k
                                let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
455
3.09k
                                self.loop_duration =
456
3.09k
                                    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.09k
                                if !self.is_lossy {
464
2.89k
                                    let (subchunk, ..) = read_chunk_header(&mut self.r)?;
465
2.88k
                                    if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
466
1.23k
                                        self.is_lossy = true;
467
1.64k
                                    }
468
2.88k
                                    self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
469
                                } else {
470
204
                                    self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
471
                                }
472
473
3.08k
                                continue;
474
6.91k
                            }
475
476
6.91k
                            self.r.seek_relative(chunk_size_rounded as i64)?;
477
                        }
478
935
                        Err(DecodingError::IoError(e))
479
935
                            if e.kind() == io::ErrorKind::UnexpectedEof =>
480
                        {
481
935
                            break;
482
                        }
483
0
                        Err(e) => return Err(e),
484
                    }
485
                }
486
2.09k
                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.09k
                if info.animation
491
1.51k
                    && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
492
1.39k
                        || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
493
1.96k
                    || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
494
1.93k
                    || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
495
1.91k
                    || !info.animation
496
535
                        && self.chunks.contains_key(&WebPRiffChunk::VP8)
497
535
                            == self.chunks.contains_key(&WebPRiffChunk::VP8L)
498
                {
499
280
                    return Err(DecodingError::ChunkMissing);
500
1.81k
                }
501
502
                // Decode ANIM chunk.
503
1.81k
                if info.animation {
504
1.38k
                    match self.read_chunk(WebPRiffChunk::ANIM, 6) {
505
1.35k
                        Ok(Some(chunk)) => {
506
1.35k
                            let mut cursor = Cursor::new(chunk);
507
1.35k
                            cursor.read_exact(&mut info.background_color_hint)?;
508
1.34k
                            self.loop_count = match cursor.read_u16::<LittleEndian>()? {
509
605
                                0 => LoopCount::Forever,
510
743
                                n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
511
                            };
512
1.34k
                            self.animation.next_frame_start =
513
1.34k
                                self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
514
                        }
515
0
                        Ok(None) => return Err(DecodingError::ChunkMissing),
516
                        Err(DecodingError::MemoryLimitExceeded) => {
517
25
                            return Err(DecodingError::InvalidChunkSize)
518
                        }
519
1
                        Err(e) => return Err(e),
520
                    }
521
431
                }
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
1.77k
                if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
527
1.35k
                    let mut position = range.start + 16;
528
1.35k
                    self.r.seek(io::SeekFrom::Start(position))?;
529
2.82k
                    for _ in 0..2 {
530
2.56k
                        let (subchunk, subchunk_size, subchunk_size_rounded) =
531
2.56k
                            read_chunk_header(&mut self.r)?;
532
2.56k
                        let subrange = position + 8..position + 8 + subchunk_size;
533
2.56k
                        self.chunks.entry(subchunk).or_insert(subrange.clone());
534
535
2.56k
                        position += 8 + subchunk_size_rounded;
536
2.56k
                        if position + 8 > range.end {
537
1.08k
                            break;
538
1.47k
                        }
539
                    }
540
428
                }
541
542
1.77k
                self.has_alpha = info.alpha;
543
1.77k
                self.kind = ImageKind::Extended(info);
544
            }
545
10
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
546
        };
547
548
7.85k
        Ok(())
549
8.50k
    }
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
30.3k
    pub fn dimensions(&self) -> (u32, u32) {
581
30.3k
        (self.width, self.height)
582
30.3k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::dimensions
Line
Count
Source
580
30.3k
    pub fn dimensions(&self) -> (u32, u32) {
581
30.3k
        (self.width, self.height)
582
30.3k
    }
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
38.7k
    pub fn has_alpha(&self) -> bool {
587
38.7k
        self.has_alpha
588
38.7k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::has_alpha
Line
Count
Source
586
38.7k
    pub fn has_alpha(&self) -> bool {
587
38.7k
        self.has_alpha
588
38.7k
    }
Unexecuted instantiation: <image_webp::decoder::WebPDecoder<_>>::has_alpha
589
590
    /// Returns true if the image is animated.
591
14.6k
    pub fn is_animated(&self) -> bool {
592
14.6k
        match &self.kind {
593
11.3k
            ImageKind::Lossy | ImageKind::Lossless => false,
594
3.29k
            ImageKind::Extended(extended) => extended.animation,
595
        }
596
14.6k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::is_animated
Line
Count
Source
591
14.6k
    pub fn is_animated(&self) -> bool {
592
14.6k
        match &self.kind {
593
11.3k
            ImageKind::Lossy | ImageKind::Lossless => false,
594
3.29k
            ImageKind::Extended(extended) => extended.animation,
595
        }
596
14.6k
    }
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.28k
    pub fn num_frames(&self) -> u32 {
606
1.28k
        self.num_frames
607
1.28k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::num_frames
Line
Count
Source
605
1.28k
    pub fn num_frames(&self) -> u32 {
606
1.28k
        self.num_frames
607
1.28k
    }
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
24.9k
    fn read_chunk(
623
24.9k
        &mut self,
624
24.9k
        chunk: WebPRiffChunk,
625
24.9k
        max_size: usize,
626
24.9k
    ) -> Result<Option<Vec<u8>>, DecodingError> {
627
24.9k
        match self.chunks.get(&chunk) {
628
1.38k
            Some(range) => {
629
1.38k
                if range.end - range.start > max_size as u64 {
630
25
                    return Err(DecodingError::MemoryLimitExceeded);
631
1.35k
                }
632
633
1.35k
                self.r.seek(io::SeekFrom::Start(range.start))?;
634
1.35k
                let mut data = vec![0; (range.end - range.start) as usize];
635
1.35k
                self.r.read_exact(&mut data)?;
636
1.35k
                Ok(Some(data))
637
            }
638
23.5k
            None => Ok(None),
639
        }
640
24.9k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_chunk
Line
Count
Source
622
24.9k
    fn read_chunk(
623
24.9k
        &mut self,
624
24.9k
        chunk: WebPRiffChunk,
625
24.9k
        max_size: usize,
626
24.9k
    ) -> Result<Option<Vec<u8>>, DecodingError> {
627
24.9k
        match self.chunks.get(&chunk) {
628
1.38k
            Some(range) => {
629
1.38k
                if range.end - range.start > max_size as u64 {
630
25
                    return Err(DecodingError::MemoryLimitExceeded);
631
1.35k
                }
632
633
1.35k
                self.r.seek(io::SeekFrom::Start(range.start))?;
634
1.35k
                let mut data = vec![0; (range.end - range.start) as usize];
635
1.35k
                self.r.read_exact(&mut data)?;
636
1.35k
                Ok(Some(data))
637
            }
638
23.5k
            None => Ok(None),
639
        }
640
24.9k
    }
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
7.85k
    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
644
7.85k
        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
645
7.85k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::icc_profile
Line
Count
Source
643
7.85k
    pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
644
7.85k
        self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
645
7.85k
    }
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
7.85k
    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
649
7.85k
        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
650
7.85k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::exif_metadata
Line
Count
Source
648
7.85k
    pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
649
7.85k
        self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
650
7.85k
    }
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
7.85k
    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
654
7.85k
        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
655
7.85k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::xmp_metadata
Line
Count
Source
653
7.85k
    pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
654
7.85k
        self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
655
7.85k
    }
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
7.33k
    pub fn output_buffer_size(&self) -> Option<usize> {
660
7.33k
        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
661
7.33k
        (self.width as usize)
662
7.33k
            .checked_mul(self.height as usize)?
663
7.33k
            .checked_mul(bytes_per_pixel)
664
7.33k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::output_buffer_size
Line
Count
Source
659
7.33k
    pub fn output_buffer_size(&self) -> Option<usize> {
660
7.33k
        let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
661
7.33k
        (self.width as usize)
662
7.33k
            .checked_mul(self.height as usize)?
663
7.33k
            .checked_mul(bytes_per_pixel)
664
7.33k
    }
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
6.05k
    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
670
6.05k
        if Some(buf.len()) != self.output_buffer_size() {
671
0
            return Err(DecodingError::ImageTooLarge);
672
6.05k
        }
673
674
6.05k
        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
6.05k
        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
682
3.75k
            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
683
684
3.75k
            if self.has_alpha {
685
2.18k
                decoder.decode_frame(self.width, self.height, false, buf)?;
686
            } else {
687
1.57k
                let mut data = vec![0; self.width as usize * self.height as usize * 4];
688
1.57k
                decoder.decode_frame(self.width, self.height, false, &mut data)?;
689
4.08G
                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
690
4.08G
                    chunk.copy_from_slice(&rgba_val[..3]);
691
4.08G
                }
692
            }
693
        } else {
694
2.30k
            let range = self
695
2.30k
                .chunks
696
2.30k
                .get(&WebPRiffChunk::VP8)
697
2.30k
                .ok_or(DecodingError::ChunkMissing)?;
698
2.30k
            let reader = range_reader(&mut self.r, range.start..range.end)?;
699
2.30k
            let frame = Vp8Decoder::decode_frame(reader)?;
700
891
            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
701
52
                return Err(DecodingError::InconsistentImageSizes);
702
839
            }
703
704
839
            if self.has_alpha() {
705
180
                frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling);
706
707
180
                let range = self
708
180
                    .chunks
709
180
                    .get(&WebPRiffChunk::ALPH)
710
180
                    .ok_or(DecodingError::ChunkMissing)?
711
165
                    .clone();
712
165
                let alpha_chunk = read_alpha_chunk(
713
165
                    &mut range_reader(&mut self.r, range)?,
714
165
                    self.width as u16,
715
165
                    self.height as u16,
716
139
                )?;
717
718
2.96k
                for y in 0..frame.height {
719
614k
                    for x in 0..frame.width {
720
614k
                        let predictor: u8 = get_alpha_predictor(
721
614k
                            x.into(),
722
614k
                            y.into(),
723
614k
                            frame.width.into(),
724
614k
                            alpha_chunk.filtering_method,
725
614k
                            buf,
726
614k
                        );
727
614k
728
614k
                        let alpha_index =
729
614k
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
730
614k
                        let buffer_index = alpha_index * 4 + 3;
731
614k
732
614k
                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
733
614k
                    }
734
                }
735
659
            } else {
736
659
                frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling);
737
659
            }
738
        }
739
740
3.31k
        Ok(())
741
6.05k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_image
Line
Count
Source
669
6.05k
    pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
670
6.05k
        if Some(buf.len()) != self.output_buffer_size() {
671
0
            return Err(DecodingError::ImageTooLarge);
672
6.05k
        }
673
674
6.05k
        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
6.05k
        } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
682
3.75k
            let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
683
684
3.75k
            if self.has_alpha {
685
2.18k
                decoder.decode_frame(self.width, self.height, false, buf)?;
686
            } else {
687
1.57k
                let mut data = vec![0; self.width as usize * self.height as usize * 4];
688
1.57k
                decoder.decode_frame(self.width, self.height, false, &mut data)?;
689
4.08G
                for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
690
4.08G
                    chunk.copy_from_slice(&rgba_val[..3]);
691
4.08G
                }
692
            }
693
        } else {
694
2.30k
            let range = self
695
2.30k
                .chunks
696
2.30k
                .get(&WebPRiffChunk::VP8)
697
2.30k
                .ok_or(DecodingError::ChunkMissing)?;
698
2.30k
            let reader = range_reader(&mut self.r, range.start..range.end)?;
699
2.30k
            let frame = Vp8Decoder::decode_frame(reader)?;
700
891
            if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
701
52
                return Err(DecodingError::InconsistentImageSizes);
702
839
            }
703
704
839
            if self.has_alpha() {
705
180
                frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling);
706
707
180
                let range = self
708
180
                    .chunks
709
180
                    .get(&WebPRiffChunk::ALPH)
710
180
                    .ok_or(DecodingError::ChunkMissing)?
711
165
                    .clone();
712
165
                let alpha_chunk = read_alpha_chunk(
713
165
                    &mut range_reader(&mut self.r, range)?,
714
165
                    self.width as u16,
715
165
                    self.height as u16,
716
139
                )?;
717
718
2.96k
                for y in 0..frame.height {
719
614k
                    for x in 0..frame.width {
720
614k
                        let predictor: u8 = get_alpha_predictor(
721
614k
                            x.into(),
722
614k
                            y.into(),
723
614k
                            frame.width.into(),
724
614k
                            alpha_chunk.filtering_method,
725
614k
                            buf,
726
614k
                        );
727
614k
728
614k
                        let alpha_index =
729
614k
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
730
614k
                        let buffer_index = alpha_index * 4 + 3;
731
614k
732
614k
                        buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
733
614k
                    }
734
                }
735
659
            } else {
736
659
                frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling);
737
659
            }
738
        }
739
740
3.31k
        Ok(())
741
6.05k
    }
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.28k
    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
753
1.28k
        assert!(self.is_animated());
754
1.28k
        assert_eq!(Some(buf.len()), self.output_buffer_size());
755
756
1.28k
        if self.animation.next_frame == self.num_frames {
757
0
            return Err(DecodingError::NoMoreFrames);
758
1.28k
        }
759
760
1.28k
        let ImageKind::Extended(info) = &self.kind else {
761
0
            unreachable!()
762
        };
763
764
1.28k
        self.r
765
1.28k
            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
766
767
1.28k
        let anmf_size = match read_chunk_header(&mut self.r)? {
768
1.28k
            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
769
2
            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
770
        };
771
772
        // Read ANMF chunk
773
1.28k
        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
774
1.28k
        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
775
1.28k
        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
776
1.28k
        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
777
1.28k
        if frame_width > 16384 || frame_height > 16384 {
778
26
            return Err(DecodingError::ImageTooLarge);
779
1.25k
        }
780
1.25k
        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
781
55
            return Err(DecodingError::FrameOutsideImage);
782
1.19k
        }
783
1.19k
        let duration = extended::read_3_bytes(&mut self.r)?;
784
1.19k
        let frame_info = self.r.read_u8()?;
785
1.19k
        let use_alpha_blending = frame_info & 0b00000010 == 0;
786
1.19k
        let dispose = frame_info & 0b00000001 != 0;
787
788
1.19k
        let clear_color = if self.animation.dispose_next_frame {
789
1.19k
            info.background_color
790
        } else {
791
0
            None
792
        };
793
794
        // Read normal bitstream now
795
1.19k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
796
1.19k
        if chunk_size_rounded + 24 > anmf_size {
797
21
            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
798
1.17k
        }
799
800
1.17k
        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
801
            WebPRiffChunk::VP8 => {
802
38
                let reader = (&mut self.r).take(chunk_size);
803
38
                let raw_frame = Vp8Decoder::decode_frame(reader)?;
804
36
                if u32::from(raw_frame.width) != frame_width
805
32
                    || u32::from(raw_frame.height) != frame_height
806
                {
807
6
                    return Err(DecodingError::InconsistentImageSizes);
808
30
                }
809
30
                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
810
30
                raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling);
811
30
                (rgb_frame, false)
812
            }
813
            WebPRiffChunk::VP8L => {
814
5
                let reader = (&mut self.r).take(chunk_size);
815
5
                let mut lossless_decoder = LosslessDecoder::new(reader);
816
5
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
817
5
                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
818
0
                (rgba_frame, true)
819
            }
820
            WebPRiffChunk::ALPH => {
821
1.12k
                if chunk_size_rounded + 32 > anmf_size {
822
1
                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
823
1.12k
                }
824
825
                // read alpha
826
1.12k
                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
827
1.12k
                let mut reader = (&mut self.r).take(chunk_size);
828
245
                let alpha_chunk =
829
1.12k
                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
830
831
                // read opaque
832
245
                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
833
245
                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
834
157
                if chunk_size + next_chunk_size + 32 > anmf_size {
835
39
                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
836
118
                }
837
838
118
                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
839
840
107
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
841
107
                frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling);
842
843
10.9k
                for y in 0..frame.height {
844
1.85M
                    for x in 0..frame.width {
845
1.85M
                        let predictor: u8 = get_alpha_predictor(
846
1.85M
                            x.into(),
847
1.85M
                            y.into(),
848
1.85M
                            frame.width.into(),
849
1.85M
                            alpha_chunk.filtering_method,
850
1.85M
                            &rgba_frame,
851
1.85M
                        );
852
1.85M
853
1.85M
                        let alpha_index =
854
1.85M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
855
1.85M
                        let buffer_index = alpha_index * 4 + 3;
856
1.85M
857
1.85M
                        rgba_frame[buffer_index] =
858
1.85M
                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
859
1.85M
                    }
860
                }
861
862
107
                (rgba_frame, true)
863
            }
864
6
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
865
        };
866
867
        // fill starting canvas with clear color
868
137
        if self.animation.canvas.is_none() {
869
137
            self.animation.canvas = {
870
137
                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
871
137
                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
137
                }
876
137
                Some(canvas)
877
            }
878
0
        }
879
137
        extended::composite_frame(
880
137
            self.animation.canvas.as_mut().unwrap(),
881
137
            self.width,
882
137
            self.height,
883
137
            clear_color,
884
137
            &frame,
885
137
            frame_x,
886
137
            frame_y,
887
137
            frame_width,
888
137
            frame_height,
889
137
            frame_has_alpha,
890
137
            use_alpha_blending,
891
137
            self.animation.previous_frame_width,
892
137
            self.animation.previous_frame_height,
893
137
            self.animation.previous_frame_x_offset,
894
137
            self.animation.previous_frame_y_offset,
895
        );
896
897
137
        self.animation.previous_frame_width = frame_width;
898
137
        self.animation.previous_frame_height = frame_height;
899
137
        self.animation.previous_frame_x_offset = frame_x;
900
137
        self.animation.previous_frame_y_offset = frame_y;
901
902
137
        self.animation.dispose_next_frame = dispose;
903
137
        self.animation.next_frame_start += anmf_size + 8;
904
137
        self.animation.next_frame += 1;
905
906
137
        if self.has_alpha() {
907
112
            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
908
112
        } else {
909
306M
            for (b, c) in buf
910
25
                .chunks_exact_mut(3)
911
25
                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
912
306M
            {
913
306M
                b.copy_from_slice(&c[..3]);
914
306M
            }
915
        }
916
917
137
        Ok(duration)
918
1.28k
    }
<image_webp::decoder::WebPDecoder<std::io::cursor::Cursor<&[u8]>>>::read_frame
Line
Count
Source
752
1.28k
    pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
753
1.28k
        assert!(self.is_animated());
754
1.28k
        assert_eq!(Some(buf.len()), self.output_buffer_size());
755
756
1.28k
        if self.animation.next_frame == self.num_frames {
757
0
            return Err(DecodingError::NoMoreFrames);
758
1.28k
        }
759
760
1.28k
        let ImageKind::Extended(info) = &self.kind else {
761
0
            unreachable!()
762
        };
763
764
1.28k
        self.r
765
1.28k
            .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
766
767
1.28k
        let anmf_size = match read_chunk_header(&mut self.r)? {
768
1.28k
            (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
769
2
            _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
770
        };
771
772
        // Read ANMF chunk
773
1.28k
        let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
774
1.28k
        let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
775
1.28k
        let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
776
1.28k
        let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
777
1.28k
        if frame_width > 16384 || frame_height > 16384 {
778
26
            return Err(DecodingError::ImageTooLarge);
779
1.25k
        }
780
1.25k
        if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
781
55
            return Err(DecodingError::FrameOutsideImage);
782
1.19k
        }
783
1.19k
        let duration = extended::read_3_bytes(&mut self.r)?;
784
1.19k
        let frame_info = self.r.read_u8()?;
785
1.19k
        let use_alpha_blending = frame_info & 0b00000010 == 0;
786
1.19k
        let dispose = frame_info & 0b00000001 != 0;
787
788
1.19k
        let clear_color = if self.animation.dispose_next_frame {
789
1.19k
            info.background_color
790
        } else {
791
0
            None
792
        };
793
794
        // Read normal bitstream now
795
1.19k
        let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
796
1.19k
        if chunk_size_rounded + 24 > anmf_size {
797
21
            return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
798
1.17k
        }
799
800
1.17k
        let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
801
            WebPRiffChunk::VP8 => {
802
38
                let reader = (&mut self.r).take(chunk_size);
803
38
                let raw_frame = Vp8Decoder::decode_frame(reader)?;
804
36
                if u32::from(raw_frame.width) != frame_width
805
32
                    || u32::from(raw_frame.height) != frame_height
806
                {
807
6
                    return Err(DecodingError::InconsistentImageSizes);
808
30
                }
809
30
                let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
810
30
                raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling);
811
30
                (rgb_frame, false)
812
            }
813
            WebPRiffChunk::VP8L => {
814
5
                let reader = (&mut self.r).take(chunk_size);
815
5
                let mut lossless_decoder = LosslessDecoder::new(reader);
816
5
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
817
5
                lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
818
0
                (rgba_frame, true)
819
            }
820
            WebPRiffChunk::ALPH => {
821
1.12k
                if chunk_size_rounded + 32 > anmf_size {
822
1
                    return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
823
1.12k
                }
824
825
                // read alpha
826
1.12k
                let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
827
1.12k
                let mut reader = (&mut self.r).take(chunk_size);
828
245
                let alpha_chunk =
829
1.12k
                    read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
830
831
                // read opaque
832
245
                self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
833
245
                let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
834
157
                if chunk_size + next_chunk_size + 32 > anmf_size {
835
39
                    return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
836
118
                }
837
838
118
                let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
839
840
107
                let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
841
107
                frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling);
842
843
10.9k
                for y in 0..frame.height {
844
1.85M
                    for x in 0..frame.width {
845
1.85M
                        let predictor: u8 = get_alpha_predictor(
846
1.85M
                            x.into(),
847
1.85M
                            y.into(),
848
1.85M
                            frame.width.into(),
849
1.85M
                            alpha_chunk.filtering_method,
850
1.85M
                            &rgba_frame,
851
1.85M
                        );
852
1.85M
853
1.85M
                        let alpha_index =
854
1.85M
                            usize::from(y) * usize::from(frame.width) + usize::from(x);
855
1.85M
                        let buffer_index = alpha_index * 4 + 3;
856
1.85M
857
1.85M
                        rgba_frame[buffer_index] =
858
1.85M
                            predictor.wrapping_add(alpha_chunk.data[alpha_index]);
859
1.85M
                    }
860
                }
861
862
107
                (rgba_frame, true)
863
            }
864
6
            _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
865
        };
866
867
        // fill starting canvas with clear color
868
137
        if self.animation.canvas.is_none() {
869
137
            self.animation.canvas = {
870
137
                let mut canvas = vec![0; (self.width * self.height * 4) as usize];
871
137
                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
137
                }
876
137
                Some(canvas)
877
            }
878
0
        }
879
137
        extended::composite_frame(
880
137
            self.animation.canvas.as_mut().unwrap(),
881
137
            self.width,
882
137
            self.height,
883
137
            clear_color,
884
137
            &frame,
885
137
            frame_x,
886
137
            frame_y,
887
137
            frame_width,
888
137
            frame_height,
889
137
            frame_has_alpha,
890
137
            use_alpha_blending,
891
137
            self.animation.previous_frame_width,
892
137
            self.animation.previous_frame_height,
893
137
            self.animation.previous_frame_x_offset,
894
137
            self.animation.previous_frame_y_offset,
895
        );
896
897
137
        self.animation.previous_frame_width = frame_width;
898
137
        self.animation.previous_frame_height = frame_height;
899
137
        self.animation.previous_frame_x_offset = frame_x;
900
137
        self.animation.previous_frame_y_offset = frame_y;
901
902
137
        self.animation.dispose_next_frame = dispose;
903
137
        self.animation.next_frame_start += anmf_size + 8;
904
137
        self.animation.next_frame += 1;
905
906
137
        if self.has_alpha() {
907
112
            buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
908
112
        } else {
909
306M
            for (b, c) in buf
910
25
                .chunks_exact_mut(3)
911
25
                .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
912
306M
            {
913
306M
                b.copy_from_slice(&c[..3]);
914
306M
            }
915
        }
916
917
137
        Ok(duration)
918
1.28k
    }
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
6.22k
pub(crate) fn range_reader<R: BufRead + Seek>(
940
6.22k
    mut r: R,
941
6.22k
    range: Range<u64>,
942
6.22k
) -> Result<impl BufRead, DecodingError> {
943
6.22k
    r.seek(io::SeekFrom::Start(range.start))?;
944
6.22k
    Ok(r.take(range.end - range.start))
945
6.22k
}
image_webp::decoder::range_reader::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
939
6.22k
pub(crate) fn range_reader<R: BufRead + Seek>(
940
6.22k
    mut r: R,
941
6.22k
    range: Range<u64>,
942
6.22k
) -> Result<impl BufRead, DecodingError> {
943
6.22k
    r.seek(io::SeekFrom::Start(range.start))?;
944
6.22k
    Ok(r.take(range.end - range.start))
945
6.22k
}
Unexecuted instantiation: image_webp::decoder::range_reader::<_>
946
947
44.1k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
44.1k
    let mut chunk_fourcc = [0; 4];
949
44.1k
    r.read_exact(&mut chunk_fourcc)?;
950
43.1k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
44.1k
}
image_webp::decoder::read_fourcc::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
947
8.28k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
8.28k
    let mut chunk_fourcc = [0; 4];
949
8.28k
    r.read_exact(&mut chunk_fourcc)?;
950
8.27k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
8.28k
}
image_webp::decoder::read_fourcc::<&mut &mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
947
35.9k
pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
948
35.9k
    let mut chunk_fourcc = [0; 4];
949
35.9k
    r.read_exact(&mut chunk_fourcc)?;
950
34.8k
    Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
951
35.9k
}
Unexecuted instantiation: image_webp::decoder::read_fourcc::<_>
952
953
35.9k
pub(crate) fn read_chunk_header<R: BufRead>(
954
35.9k
    mut r: R,
955
35.9k
) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
956
35.9k
    let chunk = read_fourcc(&mut r)?;
957
34.8k
    let chunk_size = r.read_u32::<LittleEndian>()?;
958
34.8k
    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
959
34.8k
    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
960
35.9k
}
image_webp::decoder::read_chunk_header::<&mut std::io::cursor::Cursor<&[u8]>>
Line
Count
Source
953
35.9k
pub(crate) fn read_chunk_header<R: BufRead>(
954
35.9k
    mut r: R,
955
35.9k
) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
956
35.9k
    let chunk = read_fourcc(&mut r)?;
957
34.8k
    let chunk_size = r.read_u32::<LittleEndian>()?;
958
34.8k
    let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
959
34.8k
    Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
960
35.9k
}
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
}