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::lossy::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
274#[derive(Clone)]
276#[non_exhaustive]
277pub struct WebPDecodeOptions {
278 pub lossy_upsampling: UpsamplingMethod,
282}
283
284impl Default for WebPDecodeOptions {
285 fn default() -> Self {
286 Self {
287 lossy_upsampling: UpsamplingMethod::Bilinear,
288 }
289 }
290}
291
292#[derive(Clone, Copy, Default)]
297pub enum UpsamplingMethod {
298 #[default]
303 Bilinear,
304 Simple,
309}
310
311pub 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
333impl<R: BufRead + Seek> WebPDecoder<R> {
334 pub fn new(r: R) -> Result<Self, DecodingError> {
337 Self::new_with_options(r, WebPDecodeOptions::default())
338 }
339
340 pub fn new_with_options(
343 r: R,
344 webp_decode_options: WebPDecodeOptions,
345 ) -> Result<Self, DecodingError> {
346 let mut decoder = Self {
347 r,
348 width: 0,
349 height: 0,
350 num_frames: 0,
351 kind: ImageKind::Lossy,
352 chunks: HashMap::new(),
353 animation: Default::default(),
354 memory_limit: usize::MAX,
355 is_lossy: false,
356 has_alpha: false,
357 loop_count: LoopCount::Times(NonZeroU16::new(1).unwrap()),
358 loop_duration: 0,
359 webp_decode_options,
360 };
361 decoder.read_data()?;
362 Ok(decoder)
363 }
364
365 fn read_data(&mut self) -> Result<(), DecodingError> {
366 let (WebPRiffChunk::RIFF, riff_size, _) = read_chunk_header(&mut self.r)? else {
367 return Err(DecodingError::ChunkHeaderInvalid(*b"RIFF"));
368 };
369
370 match &read_fourcc(&mut self.r)? {
371 WebPRiffChunk::WEBP => {}
372 fourcc => return Err(DecodingError::WebpSignatureInvalid(fourcc.to_fourcc())),
373 }
374
375 let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
376 let start = self.r.stream_position()?;
377
378 match chunk {
379 WebPRiffChunk::VP8 => {
380 let tag = self.r.read_u24::<LittleEndian>()?;
381
382 let keyframe = tag & 1 == 0;
383 if !keyframe {
384 return Err(DecodingError::UnsupportedFeature(
385 "Non-keyframe frames".to_owned(),
386 ));
387 }
388
389 let mut tag = [0u8; 3];
390 self.r.read_exact(&mut tag)?;
391 if tag != [0x9d, 0x01, 0x2a] {
392 return Err(DecodingError::Vp8MagicInvalid(tag));
393 }
394
395 let w = self.r.read_u16::<LittleEndian>()?;
396 let h = self.r.read_u16::<LittleEndian>()?;
397
398 self.width = u32::from(w & 0x3FFF);
399 self.height = u32::from(h & 0x3FFF);
400 if self.width == 0 || self.height == 0 {
401 return Err(DecodingError::InconsistentImageSizes);
402 }
403
404 self.chunks
405 .insert(WebPRiffChunk::VP8, start..start + chunk_size);
406 self.kind = ImageKind::Lossy;
407 self.is_lossy = true;
408 }
409 WebPRiffChunk::VP8L => {
410 let signature = self.r.read_u8()?;
411 if signature != 0x2f {
412 return Err(DecodingError::LosslessSignatureInvalid(signature));
413 }
414
415 let header = self.r.read_u32::<LittleEndian>()?;
416 let version = header >> 29;
417 if version != 0 {
418 return Err(DecodingError::VersionNumberInvalid(version as u8));
419 }
420
421 self.width = (header & 0x3FFF) + 1;
422 self.height = ((header >> 14) & 0x3FFF) + 1;
423 self.chunks
424 .insert(WebPRiffChunk::VP8L, start..start + chunk_size);
425 self.kind = ImageKind::Lossless;
426 self.has_alpha = (header >> 28) & 1 != 0;
427 }
428 WebPRiffChunk::VP8X => {
429 let mut info = extended::read_extended_header(&mut self.r)?;
430 self.width = info.canvas_width;
431 self.height = info.canvas_height;
432
433 let mut position = start + chunk_size_rounded;
434 let max_position = position + riff_size.saturating_sub(12);
435 self.r.seek(io::SeekFrom::Start(position))?;
436
437 while position < max_position {
438 match read_chunk_header(&mut self.r) {
439 Ok((chunk, chunk_size, chunk_size_rounded)) => {
440 let range = position + 8..position + 8 + chunk_size;
441 position += 8 + chunk_size_rounded;
442
443 if !chunk.is_unknown() {
444 self.chunks.entry(chunk).or_insert(range);
445 }
446
447 if chunk == WebPRiffChunk::ANMF {
448 self.num_frames += 1;
449 if chunk_size < 24 {
450 return Err(DecodingError::InvalidChunkSize);
451 }
452
453 self.r.seek_relative(12)?;
454 let duration = self.r.read_u32::<LittleEndian>()? & 0xffffff;
455 self.loop_duration =
456 self.loop_duration.wrapping_add(u64::from(duration));
457
458 if !self.is_lossy {
464 let (subchunk, ..) = read_chunk_header(&mut self.r)?;
465 if let WebPRiffChunk::VP8 | WebPRiffChunk::ALPH = subchunk {
466 self.is_lossy = true;
467 }
468 self.r.seek_relative(chunk_size_rounded as i64 - 24)?;
469 } else {
470 self.r.seek_relative(chunk_size_rounded as i64 - 16)?;
471 }
472
473 continue;
474 }
475
476 self.r.seek_relative(chunk_size_rounded as i64)?;
477 }
478 Err(DecodingError::IoError(e))
479 if e.kind() == io::ErrorKind::UnexpectedEof =>
480 {
481 break;
482 }
483 Err(e) => return Err(e),
484 }
485 }
486 self.is_lossy = self.is_lossy || self.chunks.contains_key(&WebPRiffChunk::VP8);
487
488 if info.animation
491 && (!self.chunks.contains_key(&WebPRiffChunk::ANIM)
492 || !self.chunks.contains_key(&WebPRiffChunk::ANMF))
493 || info.exif_metadata && !self.chunks.contains_key(&WebPRiffChunk::EXIF)
494 || info.xmp_metadata && !self.chunks.contains_key(&WebPRiffChunk::XMP)
495 || !info.animation
496 && self.chunks.contains_key(&WebPRiffChunk::VP8)
497 == self.chunks.contains_key(&WebPRiffChunk::VP8L)
498 {
499 return Err(DecodingError::ChunkMissing);
500 }
501
502 if info.animation {
504 match self.read_chunk(WebPRiffChunk::ANIM, 6) {
505 Ok(Some(chunk)) => {
506 let mut cursor = Cursor::new(chunk);
507 cursor.read_exact(&mut info.background_color_hint)?;
508 self.loop_count = match cursor.read_u16::<LittleEndian>()? {
509 0 => LoopCount::Forever,
510 n => LoopCount::Times(NonZeroU16::new(n).unwrap()),
511 };
512 self.animation.next_frame_start =
513 self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
514 }
515 Ok(None) => return Err(DecodingError::ChunkMissing),
516 Err(DecodingError::MemoryLimitExceeded) => {
517 return Err(DecodingError::InvalidChunkSize)
518 }
519 Err(e) => return Err(e),
520 }
521 }
522
523 if let Some(range) = self.chunks.get(&WebPRiffChunk::ANMF).cloned() {
527 let mut position = range.start + 16;
528 self.r.seek(io::SeekFrom::Start(position))?;
529 for _ in 0..2 {
530 let (subchunk, subchunk_size, subchunk_size_rounded) =
531 read_chunk_header(&mut self.r)?;
532 let subrange = position + 8..position + 8 + subchunk_size;
533 self.chunks.entry(subchunk).or_insert(subrange.clone());
534
535 position += 8 + subchunk_size_rounded;
536 if position + 8 > range.end {
537 break;
538 }
539 }
540 }
541
542 self.has_alpha = info.alpha;
543 self.kind = ImageKind::Extended(info);
544 }
545 _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
546 };
547
548 Ok(())
549 }
550
551 pub fn set_memory_limit(&mut self, limit: usize) {
555 self.memory_limit = limit;
556 }
557
558 pub fn background_color_hint(&self) -> Option<[u8; 4]> {
560 if let ImageKind::Extended(info) = &self.kind {
561 Some(info.background_color_hint)
562 } else {
563 None
564 }
565 }
566
567 pub fn set_background_color(&mut self, color: [u8; 4]) -> Result<(), DecodingError> {
569 if let ImageKind::Extended(info) = &mut self.kind {
570 info.background_color = Some(color);
571 Ok(())
572 } else {
573 Err(DecodingError::InvalidParameter(
574 "Background color can only be set on animated webp".to_owned(),
575 ))
576 }
577 }
578
579 pub fn dimensions(&self) -> (u32, u32) {
581 (self.width, self.height)
582 }
583
584 pub fn has_alpha(&self) -> bool {
587 self.has_alpha
588 }
589
590 pub fn is_animated(&self) -> bool {
592 match &self.kind {
593 ImageKind::Lossy | ImageKind::Lossless => false,
594 ImageKind::Extended(extended) => extended.animation,
595 }
596 }
597
598 pub fn is_lossy(&mut self) -> bool {
600 self.is_lossy
601 }
602
603 pub fn num_frames(&self) -> u32 {
606 self.num_frames
607 }
608
609 pub fn loop_count(&self) -> LoopCount {
611 self.loop_count
612 }
613
614 pub fn loop_duration(&self) -> u64 {
619 self.loop_duration
620 }
621
622 fn read_chunk(
623 &mut self,
624 chunk: WebPRiffChunk,
625 max_size: usize,
626 ) -> Result<Option<Vec<u8>>, DecodingError> {
627 match self.chunks.get(&chunk) {
628 Some(range) => {
629 if range.end - range.start > max_size as u64 {
630 return Err(DecodingError::MemoryLimitExceeded);
631 }
632
633 self.r.seek(io::SeekFrom::Start(range.start))?;
634 let mut data = vec![0; (range.end - range.start) as usize];
635 self.r.read_exact(&mut data)?;
636 Ok(Some(data))
637 }
638 None => Ok(None),
639 }
640 }
641
642 pub fn icc_profile(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
644 self.read_chunk(WebPRiffChunk::ICCP, self.memory_limit)
645 }
646
647 pub fn exif_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
649 self.read_chunk(WebPRiffChunk::EXIF, self.memory_limit)
650 }
651
652 pub fn xmp_metadata(&mut self) -> Result<Option<Vec<u8>>, DecodingError> {
654 self.read_chunk(WebPRiffChunk::XMP, self.memory_limit)
655 }
656
657 pub fn output_buffer_size(&self) -> Option<usize> {
660 let bytes_per_pixel = if self.has_alpha() { 4 } else { 3 };
661 (self.width as usize)
662 .checked_mul(self.height as usize)?
663 .checked_mul(bytes_per_pixel)
664 }
665
666 pub fn read_image(&mut self, buf: &mut [u8]) -> Result<(), DecodingError> {
670 if Some(buf.len()) != self.output_buffer_size() {
671 return Err(DecodingError::ImageTooLarge);
672 }
673
674 if self.is_animated() {
675 let saved = std::mem::take(&mut self.animation);
676 self.animation.next_frame_start =
677 self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
678 let result = self.read_frame(buf);
679 self.animation = saved;
680 result?;
681 } else if let Some(range) = self.chunks.get(&WebPRiffChunk::VP8L) {
682 let mut decoder = LosslessDecoder::new(range_reader(&mut self.r, range.clone())?);
683
684 if self.has_alpha {
685 decoder.decode_frame(self.width, self.height, false, buf)?;
686 } else {
687 let mut data = vec![0; self.width as usize * self.height as usize * 4];
688 decoder.decode_frame(self.width, self.height, false, &mut data)?;
689 for (rgba_val, chunk) in data.chunks_exact(4).zip(buf.chunks_exact_mut(3)) {
690 chunk.copy_from_slice(&rgba_val[..3]);
691 }
692 }
693 } else {
694 let range = self
695 .chunks
696 .get(&WebPRiffChunk::VP8)
697 .ok_or(DecodingError::ChunkMissing)?;
698 let reader = range_reader(&mut self.r, range.start..range.end)?;
699 let frame = Vp8Decoder::decode_frame(reader)?;
700 if u32::from(frame.width) != self.width || u32::from(frame.height) != self.height {
701 return Err(DecodingError::InconsistentImageSizes);
702 }
703
704 if self.has_alpha() {
705 frame.fill_rgba(buf, self.webp_decode_options.lossy_upsampling);
706
707 let range = self
708 .chunks
709 .get(&WebPRiffChunk::ALPH)
710 .ok_or(DecodingError::ChunkMissing)?
711 .clone();
712 let alpha_chunk = read_alpha_chunk(
713 &mut range_reader(&mut self.r, range)?,
714 self.width as u16,
715 self.height as u16,
716 )?;
717
718 for y in 0..frame.height {
719 for x in 0..frame.width {
720 let predictor: u8 = get_alpha_predictor(
721 x.into(),
722 y.into(),
723 frame.width.into(),
724 alpha_chunk.filtering_method,
725 buf,
726 );
727
728 let alpha_index =
729 usize::from(y) * usize::from(frame.width) + usize::from(x);
730 let buffer_index = alpha_index * 4 + 3;
731
732 buf[buffer_index] = predictor.wrapping_add(alpha_chunk.data[alpha_index]);
733 }
734 }
735 } else {
736 frame.fill_rgb(buf, self.webp_decode_options.lossy_upsampling);
737 }
738 }
739
740 Ok(())
741 }
742
743 pub fn read_frame(&mut self, buf: &mut [u8]) -> Result<u32, DecodingError> {
753 assert!(self.is_animated());
754 assert_eq!(Some(buf.len()), self.output_buffer_size());
755
756 if self.animation.next_frame == self.num_frames {
757 return Err(DecodingError::NoMoreFrames);
758 }
759
760 let ImageKind::Extended(info) = &self.kind else {
761 unreachable!()
762 };
763
764 self.r
765 .seek(io::SeekFrom::Start(self.animation.next_frame_start))?;
766
767 let anmf_size = match read_chunk_header(&mut self.r)? {
768 (WebPRiffChunk::ANMF, size, _) if size >= 32 => size,
769 _ => return Err(DecodingError::ChunkHeaderInvalid(*b"ANMF")),
770 };
771
772 let frame_x = extended::read_3_bytes(&mut self.r)? * 2;
774 let frame_y = extended::read_3_bytes(&mut self.r)? * 2;
775 let frame_width = extended::read_3_bytes(&mut self.r)? + 1;
776 let frame_height = extended::read_3_bytes(&mut self.r)? + 1;
777 if frame_width > 16384 || frame_height > 16384 {
778 return Err(DecodingError::ImageTooLarge);
779 }
780 if frame_x + frame_width > self.width || frame_y + frame_height > self.height {
781 return Err(DecodingError::FrameOutsideImage);
782 }
783 let duration = extended::read_3_bytes(&mut self.r)?;
784 let frame_info = self.r.read_u8()?;
785 let use_alpha_blending = frame_info & 0b00000010 == 0;
786 let dispose = frame_info & 0b00000001 != 0;
787
788 let (chunk, chunk_size, chunk_size_rounded) = read_chunk_header(&mut self.r)?;
790 if chunk_size_rounded + 24 > anmf_size {
791 return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
792 }
793
794 let (frame, frame_has_alpha): (Vec<u8>, bool) = match chunk {
795 WebPRiffChunk::VP8 => {
796 let reader = (&mut self.r).take(chunk_size);
797 let raw_frame = Vp8Decoder::decode_frame(reader)?;
798 if u32::from(raw_frame.width) != frame_width
799 || u32::from(raw_frame.height) != frame_height
800 {
801 return Err(DecodingError::InconsistentImageSizes);
802 }
803 let mut rgb_frame = vec![0; frame_width as usize * frame_height as usize * 3];
804 raw_frame.fill_rgb(&mut rgb_frame, self.webp_decode_options.lossy_upsampling);
805 (rgb_frame, false)
806 }
807 WebPRiffChunk::VP8L => {
808 let reader = (&mut self.r).take(chunk_size);
809 let mut lossless_decoder = LosslessDecoder::new(reader);
810 let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
811 lossless_decoder.decode_frame(frame_width, frame_height, false, &mut rgba_frame)?;
812 (rgba_frame, true)
813 }
814 WebPRiffChunk::ALPH => {
815 if chunk_size_rounded + 32 > anmf_size {
816 return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc()));
817 }
818
819 let next_chunk_start = self.r.stream_position()? + chunk_size_rounded;
821 let mut reader = (&mut self.r).take(chunk_size);
822 let alpha_chunk =
823 read_alpha_chunk(&mut reader, frame_width as u16, frame_height as u16)?;
824
825 self.r.seek(io::SeekFrom::Start(next_chunk_start))?;
827 let (next_chunk, next_chunk_size, _) = read_chunk_header(&mut self.r)?;
828 if chunk_size + next_chunk_size + 32 > anmf_size {
829 return Err(DecodingError::ChunkHeaderInvalid(next_chunk.to_fourcc()));
830 }
831
832 let frame = Vp8Decoder::decode_frame((&mut self.r).take(next_chunk_size))?;
833
834 let mut rgba_frame = vec![0; frame_width as usize * frame_height as usize * 4];
835 frame.fill_rgba(&mut rgba_frame, self.webp_decode_options.lossy_upsampling);
836
837 for y in 0..frame.height {
838 for x in 0..frame.width {
839 let predictor: u8 = get_alpha_predictor(
840 x.into(),
841 y.into(),
842 frame.width.into(),
843 alpha_chunk.filtering_method,
844 &rgba_frame,
845 );
846
847 let alpha_index =
848 usize::from(y) * usize::from(frame.width) + usize::from(x);
849 let buffer_index = alpha_index * 4 + 3;
850
851 rgba_frame[buffer_index] =
852 predictor.wrapping_add(alpha_chunk.data[alpha_index]);
853 }
854 }
855
856 (rgba_frame, true)
857 }
858 _ => return Err(DecodingError::ChunkHeaderInvalid(chunk.to_fourcc())),
859 };
860
861 let clear_color = if self.animation.dispose_next_frame {
862 match (info.background_color, frame_has_alpha) {
863 (color @ Some(_), _) => color,
864 (_, true) => Some([0, 0, 0, 0]),
865 _ => None,
866 }
867 } else {
868 None
869 };
870
871 if self.animation.canvas.is_none() {
873 self.animation.canvas = {
874 let mut canvas = vec![0; (self.width * self.height * 4) as usize];
875 if let Some(color) = info.background_color.as_ref() {
876 canvas
877 .chunks_exact_mut(4)
878 .for_each(|c| c.copy_from_slice(color))
879 }
880 Some(canvas)
881 }
882 }
883 extended::composite_frame(
884 self.animation.canvas.as_mut().unwrap(),
885 self.width,
886 self.height,
887 clear_color,
888 &frame,
889 frame_x,
890 frame_y,
891 frame_width,
892 frame_height,
893 frame_has_alpha,
894 use_alpha_blending,
895 self.animation.previous_frame_width,
896 self.animation.previous_frame_height,
897 self.animation.previous_frame_x_offset,
898 self.animation.previous_frame_y_offset,
899 );
900
901 self.animation.previous_frame_width = frame_width;
902 self.animation.previous_frame_height = frame_height;
903 self.animation.previous_frame_x_offset = frame_x;
904 self.animation.previous_frame_y_offset = frame_y;
905
906 self.animation.dispose_next_frame = dispose;
907 self.animation.next_frame_start += anmf_size + 8;
908 self.animation.next_frame += 1;
909
910 if self.has_alpha() {
911 buf.copy_from_slice(self.animation.canvas.as_ref().unwrap());
912 } else {
913 for (b, c) in buf
914 .chunks_exact_mut(3)
915 .zip(self.animation.canvas.as_ref().unwrap().chunks_exact(4))
916 {
917 b.copy_from_slice(&c[..3]);
918 }
919 }
920
921 Ok(duration)
922 }
923
924 pub fn reset_animation(&mut self) {
930 assert!(self.is_animated());
931
932 self.animation.next_frame = 0;
933 self.animation.next_frame_start = self.chunks.get(&WebPRiffChunk::ANMF).unwrap().start - 8;
934 self.animation.dispose_next_frame = true;
935 }
936
937 pub fn set_lossy_upsampling(&mut self, upsampling_method: UpsamplingMethod) {
939 self.webp_decode_options.lossy_upsampling = upsampling_method;
940 }
941}
942
943pub(crate) fn range_reader<R: BufRead + Seek>(
944 mut r: R,
945 range: Range<u64>,
946) -> Result<impl BufRead, DecodingError> {
947 r.seek(io::SeekFrom::Start(range.start))?;
948 Ok(r.take(range.end - range.start))
949}
950
951pub(crate) fn read_fourcc<R: BufRead>(mut r: R) -> Result<WebPRiffChunk, DecodingError> {
952 let mut chunk_fourcc = [0; 4];
953 r.read_exact(&mut chunk_fourcc)?;
954 Ok(WebPRiffChunk::from_fourcc(chunk_fourcc))
955}
956
957pub(crate) fn read_chunk_header<R: BufRead>(
958 mut r: R,
959) -> Result<(WebPRiffChunk, u64, u64), DecodingError> {
960 let chunk = read_fourcc(&mut r)?;
961 let chunk_size = r.read_u32::<LittleEndian>()?;
962 let chunk_size_rounded = chunk_size.saturating_add(chunk_size & 1);
963 Ok((chunk, chunk_size.into(), chunk_size_rounded.into()))
964}
965
966#[cfg(test)]
967mod tests {
968 use super::*;
969 const RGB_BPP: usize = 3;
970
971 #[test]
972 fn add_with_overflow_size() {
973 let bytes = vec![
974 0x52, 0x49, 0x46, 0x46, 0xaf, 0x37, 0x80, 0x47, 0x57, 0x45, 0x42, 0x50, 0x6c, 0x64,
975 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xfb, 0x7e, 0x73, 0x00, 0x06, 0x00, 0x00, 0x00,
976 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
977 0x40, 0xfb, 0xff, 0xff, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
978 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49,
979 0x49, 0x54, 0x55, 0x50, 0x4c, 0x54, 0x59, 0x50, 0x45, 0x33, 0x37, 0x44, 0x4d, 0x46,
980 ];
981
982 let data = std::io::Cursor::new(bytes);
983
984 let _ = WebPDecoder::new(data);
985 }
986
987 #[test]
988 fn decode_2x2_single_color_image() {
989 const NUM_PIXELS: usize = 2 * 2 * RGB_BPP;
994 let bytes = [
996 0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
997 0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x02, 0x00,
998 0x02, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
999 0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
1000 0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
1001 ];
1002
1003 let mut data = [0; NUM_PIXELS];
1004 let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
1005 decoder.read_image(&mut data).unwrap();
1006
1007 let first_pixel = &data[..RGB_BPP];
1009 assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
1010 }
1011
1012 #[test]
1013 fn decode_3x3_single_color_image() {
1014 const NUM_PIXELS: usize = 3 * 3 * RGB_BPP;
1017 let bytes = [
1019 0x52, 0x49, 0x46, 0x46, 0x3c, 0x00, 0x00, 0x00, 0x57, 0x45, 0x42, 0x50, 0x56, 0x50,
1020 0x38, 0x20, 0x30, 0x00, 0x00, 0x00, 0xd0, 0x01, 0x00, 0x9d, 0x01, 0x2a, 0x03, 0x00,
1021 0x03, 0x00, 0x02, 0x00, 0x34, 0x25, 0xa0, 0x02, 0x74, 0xba, 0x01, 0xf8, 0x00, 0x03,
1022 0xb0, 0x00, 0xfe, 0xf0, 0xc4, 0x0b, 0xff, 0x20, 0xb9, 0x61, 0x75, 0xc8, 0xd7, 0xff,
1023 0x20, 0x3f, 0xe4, 0x07, 0xfc, 0x80, 0xff, 0xf8, 0xf2, 0x00, 0x00, 0x00,
1024 ];
1025
1026 let mut data = [0; NUM_PIXELS];
1027 let mut decoder = WebPDecoder::new(std::io::Cursor::new(bytes)).unwrap();
1028 decoder.read_image(&mut data).unwrap();
1029
1030 let first_pixel = &data[..RGB_BPP];
1032 assert!(data.chunks_exact(3).all(|ch| ch.iter().eq(first_pixel)));
1033 }
1034}