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