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