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