image_webp/
extended.rs

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/// Composites a frame onto a canvas.
26///
27/// Starts by filling the rectangle occupied by the previous frame with the background
28/// color, if provided. Then copies or blends the frame onto the canvas.
29#[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    // clear rectangle occupied by previous frame
65    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 frame_index = (x + y * frame_width as usize) * 4;
93                        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    // reserved bytes are ignored
224    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    //product of canvas dimensions cannot be larger than u32 max
230    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}