1use super::lossless::LosslessDecoder;
2use crate::decoder::DecodingError;
3use byteorder_lite::ReadBytesExt;
4use std::io::{BufRead, Read};
5
6use crate::alpha_blending::do_alpha_blending;
7
8#[derive(Debug, Clone)]
9pub(crate) struct WebPExtendedInfo {
10 pub(crate) alpha: bool,
11
12 pub(crate) canvas_width: u32,
13 pub(crate) canvas_height: u32,
14
15 #[allow(unused)]
16 pub(crate) icc_profile: bool,
17 pub(crate) exif_metadata: bool,
18 pub(crate) xmp_metadata: bool,
19 pub(crate) animation: bool,
20
21 pub(crate) background_color: Option<[u8; 4]>,
22 pub(crate) background_color_hint: [u8; 4],
23}
24
25#[allow(clippy::too_many_arguments)]
30pub(crate) fn composite_frame(
31 canvas: &mut [u8],
32 canvas_width: u32,
33 canvas_height: u32,
34 clear_color: Option<[u8; 4]>,
35 frame: &[u8],
36 frame_offset_x: u32,
37 frame_offset_y: u32,
38 frame_width: u32,
39 frame_height: u32,
40 frame_has_alpha: bool,
41 frame_use_alpha_blending: bool,
42 previous_frame_width: u32,
43 previous_frame_height: u32,
44 previous_frame_offset_x: u32,
45 previous_frame_offset_y: u32,
46) {
47 let frame_is_full_size = frame_offset_x == 0
48 && frame_offset_y == 0
49 && frame_width == canvas_width
50 && frame_height == canvas_height;
51
52 if frame_is_full_size && !frame_use_alpha_blending {
53 if frame_has_alpha {
54 canvas.copy_from_slice(frame);
55 } else {
56 for (input, output) in frame.chunks_exact(3).zip(canvas.chunks_exact_mut(4)) {
57 output[..3].copy_from_slice(input);
58 output[3] = 255;
59 }
60 }
61 return;
62 }
63
64 if let Some(clear_color) = clear_color {
66 match (frame_is_full_size, frame_has_alpha) {
67 (true, true) => {
68 for pixel in canvas.chunks_exact_mut(4) {
69 pixel.copy_from_slice(&clear_color);
70 }
71 }
72 (true, false) => {
73 for pixel in canvas.chunks_exact_mut(3) {
74 pixel.copy_from_slice(&clear_color[..3]);
75 }
76 }
77 (false, true) => {
78 for y in 0..previous_frame_height as usize {
79 for x in 0..previous_frame_width as usize {
80 let canvas_index = ((x + previous_frame_offset_x as usize)
81 + (y + previous_frame_offset_y as usize) * canvas_width as usize)
82 * 4;
83
84 let output = &mut canvas[canvas_index..][..4];
85 output.copy_from_slice(&clear_color);
86 }
87 }
88 }
89 (false, false) => {
90 for y in 0..previous_frame_height as usize {
91 for x in 0..previous_frame_width as usize {
92 let canvas_index = ((x + previous_frame_offset_x as usize)
94 + (y + previous_frame_offset_y as usize) * canvas_width as usize)
95 * 3;
96
97 let output = &mut canvas[canvas_index..][..3];
98 output.copy_from_slice(&clear_color[..3]);
99 }
100 }
101 }
102 }
103 }
104
105 let width = frame_width.min(canvas_width.saturating_sub(frame_offset_x)) as usize;
106 let height = frame_height.min(canvas_height.saturating_sub(frame_offset_y)) as usize;
107
108 if frame_has_alpha && frame_use_alpha_blending {
109 for y in 0..height {
110 for x in 0..width {
111 let frame_index = (x + y * frame_width as usize) * 4;
112 let canvas_index = ((x + frame_offset_x as usize)
113 + (y + frame_offset_y as usize) * canvas_width as usize)
114 * 4;
115
116 let input = &frame[frame_index..][..4];
117 let output = &mut canvas[canvas_index..][..4];
118
119 let blended =
120 do_alpha_blending(input.try_into().unwrap(), output.try_into().unwrap());
121 output.copy_from_slice(&blended);
122 }
123 }
124 } else if frame_has_alpha {
125 for y in 0..height {
126 let frame_index = (y * frame_width as usize) * 4;
127 let canvas_index = (frame_offset_x as usize
128 + (y + frame_offset_y as usize) * canvas_width as usize)
129 * 4;
130
131 canvas[canvas_index..][..width * 4].copy_from_slice(&frame[frame_index..][..width * 4]);
132 }
133 } else {
134 for y in 0..height {
135 let index = (y * frame_width as usize) * 3;
136 let canvas_index = (frame_offset_x as usize
137 + (y + frame_offset_y as usize) * canvas_width as usize)
138 * 4;
139 let input = &frame[index..][..width * 3];
140 let output = &mut canvas[canvas_index..][..width * 4];
141
142 for (input, output) in input.chunks_exact(3).zip(output.chunks_exact_mut(4)) {
143 output[..3].copy_from_slice(input);
144 output[3] = 255;
145 }
146 }
147 }
148}
149
150pub(crate) fn get_alpha_predictor(
151 x: usize,
152 y: usize,
153 width: usize,
154 filtering_method: FilteringMethod,
155 image_slice: &[u8],
156) -> u8 {
157 match filtering_method {
158 FilteringMethod::None => 0,
159 FilteringMethod::Horizontal => {
160 if x == 0 && y == 0 {
161 0
162 } else if x == 0 {
163 let index = (y - 1) * width + x;
164 image_slice[index * 4 + 3]
165 } else {
166 let index = y * width + x - 1;
167 image_slice[index * 4 + 3]
168 }
169 }
170 FilteringMethod::Vertical => {
171 if x == 0 && y == 0 {
172 0
173 } else if y == 0 {
174 let index = y * width + x - 1;
175 image_slice[index * 4 + 3]
176 } else {
177 let index = (y - 1) * width + x;
178 image_slice[index * 4 + 3]
179 }
180 }
181 FilteringMethod::Gradient => {
182 let (left, top, top_left) = match (x, y) {
183 (0, 0) => (0, 0, 0),
184 (0, y) => {
185 let above_index = (y - 1) * width + x;
186 let val = image_slice[above_index * 4 + 3];
187 (val, val, val)
188 }
189 (x, 0) => {
190 let before_index = y * width + x - 1;
191 let val = image_slice[before_index * 4 + 3];
192 (val, val, val)
193 }
194 (x, y) => {
195 let left_index = y * width + x - 1;
196 let left = image_slice[left_index * 4 + 3];
197 let top_index = (y - 1) * width + x;
198 let top = image_slice[top_index * 4 + 3];
199 let top_left_index = (y - 1) * width + x - 1;
200 let top_left = image_slice[top_left_index * 4 + 3];
201
202 (left, top, top_left)
203 }
204 };
205
206 let combination = i16::from(left) + i16::from(top) - i16::from(top_left);
207 i16::clamp(combination, 0, 255).try_into().unwrap()
208 }
209 }
210}
211
212pub(crate) fn read_extended_header<R: Read>(
213 reader: &mut R,
214) -> Result<WebPExtendedInfo, DecodingError> {
215 let chunk_flags = reader.read_u8()?;
216
217 let icc_profile = chunk_flags & 0b00100000 != 0;
218 let alpha = chunk_flags & 0b00010000 != 0;
219 let exif_metadata = chunk_flags & 0b00001000 != 0;
220 let xmp_metadata = chunk_flags & 0b00000100 != 0;
221 let animation = chunk_flags & 0b00000010 != 0;
222
223 let _reserved_bytes = read_3_bytes(reader)?;
225
226 let canvas_width = read_3_bytes(reader)? + 1;
227 let canvas_height = read_3_bytes(reader)? + 1;
228
229 if u32::checked_mul(canvas_width, canvas_height).is_none() {
231 return Err(DecodingError::ImageTooLarge);
232 }
233
234 let info = WebPExtendedInfo {
235 icc_profile,
236 alpha,
237 exif_metadata,
238 xmp_metadata,
239 animation,
240 canvas_width,
241 canvas_height,
242 background_color_hint: [0; 4],
243 background_color: None,
244 };
245
246 Ok(info)
247}
248
249pub(crate) fn read_3_bytes<R: Read>(reader: &mut R) -> Result<u32, DecodingError> {
250 let mut buffer: [u8; 3] = [0; 3];
251 reader.read_exact(&mut buffer)?;
252 let value: u32 =
253 (u32::from(buffer[2]) << 16) | (u32::from(buffer[1]) << 8) | u32::from(buffer[0]);
254 Ok(value)
255}
256
257#[derive(Debug)]
258pub(crate) struct AlphaChunk {
259 _preprocessing: bool,
260 pub(crate) filtering_method: FilteringMethod,
261 pub(crate) data: Vec<u8>,
262}
263
264#[derive(Debug, Copy, Clone)]
265pub(crate) enum FilteringMethod {
266 None,
267 Horizontal,
268 Vertical,
269 Gradient,
270}
271
272pub(crate) fn read_alpha_chunk<R: BufRead>(
273 reader: &mut R,
274 width: u16,
275 height: u16,
276) -> Result<AlphaChunk, DecodingError> {
277 let info_byte = reader.read_u8()?;
278
279 let preprocessing = (info_byte & 0b00110000) >> 4;
280 let filtering = (info_byte & 0b00001100) >> 2;
281 let compression = info_byte & 0b00000011;
282
283 let preprocessing = match preprocessing {
284 0 => false,
285 1 => true,
286 _ => return Err(DecodingError::InvalidAlphaPreprocessing),
287 };
288
289 let filtering_method = match filtering {
290 0 => FilteringMethod::None,
291 1 => FilteringMethod::Horizontal,
292 2 => FilteringMethod::Vertical,
293 3 => FilteringMethod::Gradient,
294 _ => unreachable!(),
295 };
296
297 let lossless_compression = match compression {
298 0 => false,
299 1 => true,
300 _ => return Err(DecodingError::InvalidCompressionMethod),
301 };
302
303 let data = if lossless_compression {
304 let mut decoder = LosslessDecoder::new(reader);
305
306 let mut data = vec![0; usize::from(width) * usize::from(height) * 4];
307 decoder.decode_frame(u32::from(width), u32::from(height), true, &mut data)?;
308
309 let mut green = vec![0; usize::from(width) * usize::from(height)];
310 for (rgba_val, green_val) in data.chunks_exact(4).zip(green.iter_mut()) {
311 *green_val = rgba_val[1];
312 }
313 green
314 } else {
315 let mut framedata = vec![0; width as usize * height as usize];
316 reader.read_exact(&mut framedata)?;
317 framedata
318 };
319
320 let chunk = AlphaChunk {
321 _preprocessing: preprocessing,
322 filtering_method,
323 data,
324 };
325
326 Ok(chunk)
327}