Skip to main content

atmos/os_lib/webp/
extended.rs

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