Coverage Report

Created: 2026-06-10 07:53

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