1use crate::decoder::header::{parse_animation_webp, ParsedAnimationFrame};
4use crate::decoder::lossless::decode_lossless_vp8l_to_rgba;
5use crate::decoder::lossy::{decode_lossy_vp8_frame_to_rgba, DecodedImage};
6use crate::decoder::DecoderError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct DecodedAnimationFrame {
11 pub duration: usize,
13 pub rgba: Vec<u8>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct DecodedAnimation {
20 pub width: usize,
22 pub height: usize,
24 pub background_color: u32,
26 pub loop_count: u16,
28 pub frames: Vec<DecodedAnimationFrame>,
30}
31
32fn argb_to_rgba(argb: u32) -> [u8; 4] {
33 [
34 ((argb >> 16) & 0xff) as u8,
35 ((argb >> 8) & 0xff) as u8,
36 (argb & 0xff) as u8,
37 (argb >> 24) as u8,
38 ]
39}
40
41fn fill_rect(
42 canvas: &mut [u8],
43 canvas_width: usize,
44 x_offset: usize,
45 y_offset: usize,
46 width: usize,
47 height: usize,
48 rgba: [u8; 4],
49) {
50 for y in 0..height {
51 let row = ((y_offset + y) * canvas_width + x_offset) * 4;
52 let row = &mut canvas[row..row + width * 4];
53 for pixel in row.chunks_exact_mut(4) {
54 pixel.copy_from_slice(&rgba);
55 }
56 }
57}
58
59fn blend_channel(src: u8, src_alpha: u32, dst: u8, dst_factor_alpha: u32, scale: u32) -> u8 {
60 let blended = (src as u32 * src_alpha + dst as u32 * dst_factor_alpha) * scale;
61 (blended >> 24) as u8
62}
63
64fn blend_pixel_non_premult(src: [u8; 4], dst: [u8; 4]) -> [u8; 4] {
65 let src_alpha = src[3] as u32;
66 if src_alpha == 0 {
67 return dst;
68 }
69 if src_alpha == 255 {
70 return src;
71 }
72
73 let dst_alpha = dst[3] as u32;
74 let dst_factor_alpha = (dst_alpha * (256 - src_alpha)) >> 8;
75 let blend_alpha = src_alpha + dst_factor_alpha;
76 let scale = (1u32 << 24) / blend_alpha;
77
78 [
79 blend_channel(src[0], src_alpha, dst[0], dst_factor_alpha, scale),
80 blend_channel(src[1], src_alpha, dst[1], dst_factor_alpha, scale),
81 blend_channel(src[2], src_alpha, dst[2], dst_factor_alpha, scale),
82 blend_alpha as u8,
83 ]
84}
85
86fn composite_frame(
87 canvas: &mut [u8],
88 canvas_width: usize,
89 frame_rgba: &[u8],
90 frame: &ParsedAnimationFrame<'_>,
91) {
92 if !frame.blend {
93 for y in 0..frame.height {
94 let src = y * frame.width * 4;
95 let dst = ((frame.y_offset + y) * canvas_width + frame.x_offset) * 4;
96 let len = frame.width * 4;
97 canvas[dst..dst + len].copy_from_slice(&frame_rgba[src..src + len]);
98 }
99 return;
100 }
101
102 for y in 0..frame.height {
103 let src_row = y * frame.width * 4;
104 let dst_row = ((frame.y_offset + y) * canvas_width + frame.x_offset) * 4;
105 for x in 0..frame.width {
106 let src = src_row + x * 4;
107 let dst = dst_row + x * 4;
108 let src_alpha = frame_rgba[src + 3];
109 if src_alpha == 0 {
110 continue;
111 }
112 if src_alpha == 255 {
113 canvas[dst..dst + 4].copy_from_slice(&frame_rgba[src..src + 4]);
114 continue;
115 }
116 let src_pixel = [
117 frame_rgba[src],
118 frame_rgba[src + 1],
119 frame_rgba[src + 2],
120 src_alpha,
121 ];
122 let dst_pixel = [
123 canvas[dst],
124 canvas[dst + 1],
125 canvas[dst + 2],
126 canvas[dst + 3],
127 ];
128 let out = blend_pixel_non_premult(src_pixel, dst_pixel);
129 canvas[dst..dst + 4].copy_from_slice(&out);
130 }
131 }
132}
133
134fn decode_frame_image(frame: &ParsedAnimationFrame<'_>) -> Result<DecodedImage, DecoderError> {
135 let image = match &frame.image_chunk.fourcc {
136 b"VP8L" => {
137 if frame.alpha_chunk.is_some() {
138 return Err(DecoderError::Bitstream(
139 "VP8L animation frame must not carry ALPH chunk",
140 ));
141 }
142 decode_lossless_vp8l_to_rgba(frame.image_data)?
143 }
144 b"VP8 " => decode_lossy_vp8_frame_to_rgba(frame.image_data, frame.alpha_data)?,
145 _ => return Err(DecoderError::Bitstream("unsupported animation frame chunk")),
146 };
147
148 if image.width != frame.width || image.height != frame.height {
149 return Err(DecoderError::Bitstream(
150 "animation frame dimensions do not match bitstream",
151 ));
152 }
153 Ok(image)
154}
155
156pub fn decode_animation_webp(data: &[u8]) -> Result<DecodedAnimation, DecoderError> {
158 let parsed = parse_animation_webp(data)?;
159 let background = argb_to_rgba(parsed.animation.background_color);
160 let mut canvas = vec![0u8; parsed.features.width * parsed.features.height * 4];
161 fill_rect(
162 &mut canvas,
163 parsed.features.width,
164 0,
165 0,
166 parsed.features.width,
167 parsed.features.height,
168 background,
169 );
170
171 let mut previous_rect = None;
172 let mut frames = Vec::with_capacity(parsed.frames.len());
173 for frame in &parsed.frames {
174 if let Some((x_offset, y_offset, width, height)) = previous_rect.take() {
175 fill_rect(
176 &mut canvas,
177 parsed.features.width,
178 x_offset,
179 y_offset,
180 width,
181 height,
182 background,
183 );
184 }
185
186 let decoded = decode_frame_image(frame)?;
187 composite_frame(&mut canvas, parsed.features.width, &decoded.rgba, frame);
188 frames.push(DecodedAnimationFrame {
189 duration: frame.duration,
190 rgba: canvas.clone(),
191 });
192
193 if frame.dispose_to_background {
194 previous_rect = Some((frame.x_offset, frame.y_offset, frame.width, frame.height));
195 }
196 }
197
198 Ok(DecodedAnimation {
199 width: parsed.features.width,
200 height: parsed.features.height,
201 background_color: parsed.animation.background_color,
202 loop_count: parsed.animation.loop_count,
203 frames,
204 })
205}