Coverage Report

Created: 2026-05-16 07:04

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/yuv.rs
Line
Count
Source
1
//! Utilities for doing the YUV -> RGB conversion
2
//! The images are encoded in the Y'CbCr format as detailed here: <https://en.wikipedia.org/wiki/YCbCr>
3
//! so need to be converted to RGB to be displayed
4
//! To do the YUV -> RGB conversion we need to first decide how to map the yuv values to the pixels
5
//! The y buffer is the same size as the pixel buffer so that maps 1-1 but the
6
//! u and v buffers are half the size of the pixel buffer so we need to scale it up
7
//! The simple way to upscale is just to take each u/v value and associate it with the 4
8
//! pixels around it e.g. for a 4x4 image:
9
//!
10
//! ||||||
11
//! |yyyy|
12
//! |yyyy|
13
//! |yyyy|
14
//! |yyyy|
15
//! ||||||
16
//!
17
//! |||||||
18
//! |uu|vv|
19
//! |uu|vv|
20
//! |||||||
21
//!
22
//! Then each of the 2x2 pixels would match the u/v from the same quadrant
23
//!
24
//! However fancy upsampling is the default for libwebp which does a little more work to make the values smoother
25
//! It interpolates u and v so that for e.g. the pixel 1 down and 1 from the left the u value
26
//! would be (9*u0 + 3*u1 + 3*u2 + u3 + 8) / 16 and similar for the other pixels
27
//! The edges are mirrored, so for the pixel 1 down and 0 from the left it uses (9*u0 + 3*u2 + 3*u0 + u2 + 8) / 16
28
29
/// `_mm_mulhi_epu16` emulation
30
2.54G
fn mulhi(v: u8, coeff: u16) -> i32 {
31
2.54G
    ((u32::from(v) * u32::from(coeff)) >> 8) as i32
32
2.54G
}
33
34
/// This function has been rewritten to encourage auto-vectorization.
35
///
36
/// Based on [src/dsp/yuv.h](https://github.com/webmproject/libwebp/blob/8534f53960befac04c9631e6e50d21dcb42dfeaf/src/dsp/yuv.h#L79)
37
/// from the libwebp source.
38
/// ```text
39
/// const YUV_FIX2: i32 = 6;
40
/// const YUV_MASK2: i32 = (256 << YUV_FIX2) - 1;
41
/// fn clip(v: i32) -> u8 {
42
///     if (v & !YUV_MASK2) == 0 {
43
///         (v >> YUV_FIX2) as u8
44
///     } else if v < 0 {
45
///         0
46
///     } else {
47
///         255
48
///     }
49
/// }
50
/// ```
51
// Clippy suggests the clamp method, but it seems to optimize worse as of rustc 1.82.0 nightly.
52
#[allow(clippy::manual_clamp)]
53
1.09G
fn clip(v: i32) -> u8 {
54
    const YUV_FIX2: i32 = 6;
55
1.09G
    (v >> YUV_FIX2).max(0).min(255) as u8
56
1.09G
}
57
58
#[inline(always)]
59
363M
fn yuv_to_r(y: u8, v: u8) -> u8 {
60
363M
    clip(mulhi(y, 19077) + mulhi(v, 26149) - 14234)
61
363M
}
62
63
#[inline(always)]
64
363M
fn yuv_to_g(y: u8, u: u8, v: u8) -> u8 {
65
363M
    clip(mulhi(y, 19077) - mulhi(u, 6419) - mulhi(v, 13320) + 8708)
66
363M
}
67
68
#[inline(always)]
69
363M
fn yuv_to_b(y: u8, u: u8) -> u8 {
70
363M
    clip(mulhi(y, 19077) + mulhi(u, 33050) - 17685)
71
363M
}
72
73
/// Fills an rgb buffer with the image from the yuv buffers
74
/// Size of the buffer is assumed to be correct
75
/// BPP is short for bytes per pixel, allows both rgb and rgba to be decoded
76
997
pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>(
77
997
    buffer: &mut [u8],
78
997
    y_buffer: &[u8],
79
997
    u_buffer: &[u8],
80
997
    v_buffer: &[u8],
81
997
    width: usize,
82
997
    height: usize,
83
997
    buffer_width: usize,
84
997
) {
85
    // buffer width is always even so don't need to do div_ceil
86
997
    let chroma_buffer_width = buffer_width / 2;
87
997
    let chroma_width = width.div_ceil(2);
88
89
    // fill top row first since it only uses the top u/v row
90
997
    let top_row_y = &y_buffer[..width];
91
997
    let top_row_u = &u_buffer[..chroma_width];
92
997
    let top_row_v = &v_buffer[..chroma_width];
93
997
    let top_row_buffer = &mut buffer[..width * BPP];
94
997
    fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v);
95
96
997
    let mut main_row_chunks = buffer[width * BPP..].chunks_exact_mut(width * BPP * 2);
97
    // the y buffer iterator limits the end of the row iterator so we need this end index
98
997
    let end_y_index = height * buffer_width;
99
997
    let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2);
100
997
    let mut main_u_windows = u_buffer
101
997
        .windows(chroma_buffer_width * 2)
102
997
        .step_by(chroma_buffer_width);
103
997
    let mut main_v_windows = v_buffer
104
997
        .windows(chroma_buffer_width * 2)
105
997
        .step_by(chroma_buffer_width);
106
107
206k
    for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks)
108
997
        .zip(&mut main_y_chunks)
109
997
        .zip(&mut main_u_windows)
110
997
        .zip(&mut main_v_windows)
111
206k
    {
112
206k
        let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width);
113
206k
        let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width);
114
206k
        let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP);
115
206k
        let (y_row_1, y_row_2) = y_rows.split_at(buffer_width);
116
206k
        fill_row_fancy_with_2_uv_rows::<BPP>(
117
206k
            row_buf_1,
118
206k
            &y_row_1[..width],
119
206k
            &u_row_1[..chroma_width],
120
206k
            &u_row_2[..chroma_width],
121
206k
            &v_row_1[..chroma_width],
122
206k
            &v_row_2[..chroma_width],
123
206k
        );
124
206k
        fill_row_fancy_with_2_uv_rows::<BPP>(
125
206k
            row_buf_2,
126
206k
            &y_row_2[..width],
127
206k
            &u_row_2[..chroma_width],
128
206k
            &u_row_1[..chroma_width],
129
206k
            &v_row_2[..chroma_width],
130
206k
            &v_row_1[..chroma_width],
131
206k
        );
132
206k
    }
133
134
997
    let final_row_buffer = main_row_chunks.into_remainder();
135
136
    // if the image has even height there will be one final row with only one u/v row matching it
137
997
    if !final_row_buffer.is_empty() {
138
620
        let final_y_row = main_y_chunks.remainder();
139
620
140
620
        let chroma_height = height.div_ceil(2);
141
620
        let start_chroma_index = (chroma_height - 1) * chroma_buffer_width;
142
620
143
620
        let final_u_row = &u_buffer[start_chroma_index..];
144
620
        let final_v_row = &v_buffer[start_chroma_index..];
145
620
        fill_row_fancy_with_1_uv_row::<BPP>(
146
620
            final_row_buffer,
147
620
            &final_y_row[..width],
148
620
            &final_u_row[..chroma_width],
149
620
            &final_v_row[..chroma_width],
150
620
        );
151
620
    }
152
997
}
image_webp::yuv::fill_rgb_buffer_fancy::<3>
Line
Count
Source
76
700
pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>(
77
700
    buffer: &mut [u8],
78
700
    y_buffer: &[u8],
79
700
    u_buffer: &[u8],
80
700
    v_buffer: &[u8],
81
700
    width: usize,
82
700
    height: usize,
83
700
    buffer_width: usize,
84
700
) {
85
    // buffer width is always even so don't need to do div_ceil
86
700
    let chroma_buffer_width = buffer_width / 2;
87
700
    let chroma_width = width.div_ceil(2);
88
89
    // fill top row first since it only uses the top u/v row
90
700
    let top_row_y = &y_buffer[..width];
91
700
    let top_row_u = &u_buffer[..chroma_width];
92
700
    let top_row_v = &v_buffer[..chroma_width];
93
700
    let top_row_buffer = &mut buffer[..width * BPP];
94
700
    fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v);
95
96
700
    let mut main_row_chunks = buffer[width * BPP..].chunks_exact_mut(width * BPP * 2);
97
    // the y buffer iterator limits the end of the row iterator so we need this end index
98
700
    let end_y_index = height * buffer_width;
99
700
    let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2);
100
700
    let mut main_u_windows = u_buffer
101
700
        .windows(chroma_buffer_width * 2)
102
700
        .step_by(chroma_buffer_width);
103
700
    let mut main_v_windows = v_buffer
104
700
        .windows(chroma_buffer_width * 2)
105
700
        .step_by(chroma_buffer_width);
106
107
195k
    for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks)
108
700
        .zip(&mut main_y_chunks)
109
700
        .zip(&mut main_u_windows)
110
700
        .zip(&mut main_v_windows)
111
195k
    {
112
195k
        let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width);
113
195k
        let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width);
114
195k
        let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP);
115
195k
        let (y_row_1, y_row_2) = y_rows.split_at(buffer_width);
116
195k
        fill_row_fancy_with_2_uv_rows::<BPP>(
117
195k
            row_buf_1,
118
195k
            &y_row_1[..width],
119
195k
            &u_row_1[..chroma_width],
120
195k
            &u_row_2[..chroma_width],
121
195k
            &v_row_1[..chroma_width],
122
195k
            &v_row_2[..chroma_width],
123
195k
        );
124
195k
        fill_row_fancy_with_2_uv_rows::<BPP>(
125
195k
            row_buf_2,
126
195k
            &y_row_2[..width],
127
195k
            &u_row_2[..chroma_width],
128
195k
            &u_row_1[..chroma_width],
129
195k
            &v_row_2[..chroma_width],
130
195k
            &v_row_1[..chroma_width],
131
195k
        );
132
195k
    }
133
134
700
    let final_row_buffer = main_row_chunks.into_remainder();
135
136
    // if the image has even height there will be one final row with only one u/v row matching it
137
700
    if !final_row_buffer.is_empty() {
138
374
        let final_y_row = main_y_chunks.remainder();
139
374
140
374
        let chroma_height = height.div_ceil(2);
141
374
        let start_chroma_index = (chroma_height - 1) * chroma_buffer_width;
142
374
143
374
        let final_u_row = &u_buffer[start_chroma_index..];
144
374
        let final_v_row = &v_buffer[start_chroma_index..];
145
374
        fill_row_fancy_with_1_uv_row::<BPP>(
146
374
            final_row_buffer,
147
374
            &final_y_row[..width],
148
374
            &final_u_row[..chroma_width],
149
374
            &final_v_row[..chroma_width],
150
374
        );
151
374
    }
152
700
}
image_webp::yuv::fill_rgb_buffer_fancy::<4>
Line
Count
Source
76
297
pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>(
77
297
    buffer: &mut [u8],
78
297
    y_buffer: &[u8],
79
297
    u_buffer: &[u8],
80
297
    v_buffer: &[u8],
81
297
    width: usize,
82
297
    height: usize,
83
297
    buffer_width: usize,
84
297
) {
85
    // buffer width is always even so don't need to do div_ceil
86
297
    let chroma_buffer_width = buffer_width / 2;
87
297
    let chroma_width = width.div_ceil(2);
88
89
    // fill top row first since it only uses the top u/v row
90
297
    let top_row_y = &y_buffer[..width];
91
297
    let top_row_u = &u_buffer[..chroma_width];
92
297
    let top_row_v = &v_buffer[..chroma_width];
93
297
    let top_row_buffer = &mut buffer[..width * BPP];
94
297
    fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v);
95
96
297
    let mut main_row_chunks = buffer[width * BPP..].chunks_exact_mut(width * BPP * 2);
97
    // the y buffer iterator limits the end of the row iterator so we need this end index
98
297
    let end_y_index = height * buffer_width;
99
297
    let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2);
100
297
    let mut main_u_windows = u_buffer
101
297
        .windows(chroma_buffer_width * 2)
102
297
        .step_by(chroma_buffer_width);
103
297
    let mut main_v_windows = v_buffer
104
297
        .windows(chroma_buffer_width * 2)
105
297
        .step_by(chroma_buffer_width);
106
107
11.3k
    for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks)
108
297
        .zip(&mut main_y_chunks)
109
297
        .zip(&mut main_u_windows)
110
297
        .zip(&mut main_v_windows)
111
11.3k
    {
112
11.3k
        let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width);
113
11.3k
        let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width);
114
11.3k
        let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP);
115
11.3k
        let (y_row_1, y_row_2) = y_rows.split_at(buffer_width);
116
11.3k
        fill_row_fancy_with_2_uv_rows::<BPP>(
117
11.3k
            row_buf_1,
118
11.3k
            &y_row_1[..width],
119
11.3k
            &u_row_1[..chroma_width],
120
11.3k
            &u_row_2[..chroma_width],
121
11.3k
            &v_row_1[..chroma_width],
122
11.3k
            &v_row_2[..chroma_width],
123
11.3k
        );
124
11.3k
        fill_row_fancy_with_2_uv_rows::<BPP>(
125
11.3k
            row_buf_2,
126
11.3k
            &y_row_2[..width],
127
11.3k
            &u_row_2[..chroma_width],
128
11.3k
            &u_row_1[..chroma_width],
129
11.3k
            &v_row_2[..chroma_width],
130
11.3k
            &v_row_1[..chroma_width],
131
11.3k
        );
132
11.3k
    }
133
134
297
    let final_row_buffer = main_row_chunks.into_remainder();
135
136
    // if the image has even height there will be one final row with only one u/v row matching it
137
297
    if !final_row_buffer.is_empty() {
138
246
        let final_y_row = main_y_chunks.remainder();
139
246
140
246
        let chroma_height = height.div_ceil(2);
141
246
        let start_chroma_index = (chroma_height - 1) * chroma_buffer_width;
142
246
143
246
        let final_u_row = &u_buffer[start_chroma_index..];
144
246
        let final_v_row = &v_buffer[start_chroma_index..];
145
246
        fill_row_fancy_with_1_uv_row::<BPP>(
146
246
            final_row_buffer,
147
246
            &final_y_row[..width],
148
246
            &final_u_row[..chroma_width],
149
246
            &final_v_row[..chroma_width],
150
246
        );
151
246
    }
152
297
}
153
154
/// Fills a row with the fancy interpolation as detailed
155
413k
fn fill_row_fancy_with_2_uv_rows<const BPP: usize>(
156
413k
    row_buffer: &mut [u8],
157
413k
    y_row: &[u8],
158
413k
    u_row_1: &[u8],
159
413k
    u_row_2: &[u8],
160
413k
    v_row_1: &[u8],
161
413k
    v_row_2: &[u8],
162
413k
) {
163
    // need to do left pixel separately since it will only have one u/v value
164
413k
    {
165
413k
        let rgb1 = &mut row_buffer[0..3];
166
413k
        let y_value = y_row[0];
167
413k
        // first pixel uses the first u/v as the main one
168
413k
        let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]);
169
413k
        let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]);
170
413k
        set_pixel(rgb1, y_value, u_value, v_value);
171
413k
    }
172
173
413k
    let rest_row_buffer = &mut row_buffer[BPP..];
174
413k
    let rest_y_row = &y_row[1..];
175
176
    // we do two pixels at a time since they share the same u/v values
177
413k
    let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2);
178
413k
    let mut main_y_chunks = rest_y_row.chunks_exact(2);
179
180
181M
    for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks)
181
413k
        .zip(&mut main_y_chunks)
182
413k
        .zip(u_row_1.windows(2))
183
413k
        .zip(u_row_2.windows(2))
184
413k
        .zip(v_row_1.windows(2))
185
413k
        .zip(v_row_2.windows(2))
186
    {
187
181M
        {
188
181M
            let rgb1 = &mut rgb[0..3];
189
181M
            let y_value = y_val[0];
190
181M
            // first pixel uses the first u/v as the main one
191
181M
            let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]);
192
181M
            let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]);
193
181M
            set_pixel(rgb1, y_value, u_value, v_value);
194
181M
        }
195
181M
        {
196
181M
            let rgb2 = &mut rgb[BPP..];
197
181M
            let y_value = y_val[1];
198
181M
            let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]);
199
181M
            let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]);
200
181M
            set_pixel(rgb2, y_value, u_value, v_value);
201
181M
        }
202
    }
203
204
413k
    let final_pixel = main_row_chunks.into_remainder();
205
413k
    let final_y = main_y_chunks.remainder();
206
207
413k
    if let (rgb, [y_value]) = (final_pixel, final_y) {
208
147k
        let final_u_1 = *u_row_1.last().unwrap();
209
147k
        let final_u_2 = *u_row_2.last().unwrap();
210
147k
211
147k
        let final_v_1 = *v_row_1.last().unwrap();
212
147k
        let final_v_2 = *v_row_2.last().unwrap();
213
147k
214
147k
        let rgb1 = &mut rgb[0..3];
215
147k
        // first pixel uses the first u/v as the main one
216
147k
        let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2);
217
147k
        let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2);
218
147k
        set_pixel(rgb1, *y_value, u_value, v_value);
219
265k
    }
220
413k
}
image_webp::yuv::fill_row_fancy_with_2_uv_rows::<3>
Line
Count
Source
155
390k
fn fill_row_fancy_with_2_uv_rows<const BPP: usize>(
156
390k
    row_buffer: &mut [u8],
157
390k
    y_row: &[u8],
158
390k
    u_row_1: &[u8],
159
390k
    u_row_2: &[u8],
160
390k
    v_row_1: &[u8],
161
390k
    v_row_2: &[u8],
162
390k
) {
163
    // need to do left pixel separately since it will only have one u/v value
164
390k
    {
165
390k
        let rgb1 = &mut row_buffer[0..3];
166
390k
        let y_value = y_row[0];
167
390k
        // first pixel uses the first u/v as the main one
168
390k
        let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]);
169
390k
        let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]);
170
390k
        set_pixel(rgb1, y_value, u_value, v_value);
171
390k
    }
172
173
390k
    let rest_row_buffer = &mut row_buffer[BPP..];
174
390k
    let rest_y_row = &y_row[1..];
175
176
    // we do two pixels at a time since they share the same u/v values
177
390k
    let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2);
178
390k
    let mut main_y_chunks = rest_y_row.chunks_exact(2);
179
180
177M
    for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks)
181
390k
        .zip(&mut main_y_chunks)
182
390k
        .zip(u_row_1.windows(2))
183
390k
        .zip(u_row_2.windows(2))
184
390k
        .zip(v_row_1.windows(2))
185
390k
        .zip(v_row_2.windows(2))
186
    {
187
177M
        {
188
177M
            let rgb1 = &mut rgb[0..3];
189
177M
            let y_value = y_val[0];
190
177M
            // first pixel uses the first u/v as the main one
191
177M
            let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]);
192
177M
            let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]);
193
177M
            set_pixel(rgb1, y_value, u_value, v_value);
194
177M
        }
195
177M
        {
196
177M
            let rgb2 = &mut rgb[BPP..];
197
177M
            let y_value = y_val[1];
198
177M
            let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]);
199
177M
            let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]);
200
177M
            set_pixel(rgb2, y_value, u_value, v_value);
201
177M
        }
202
    }
203
204
390k
    let final_pixel = main_row_chunks.into_remainder();
205
390k
    let final_y = main_y_chunks.remainder();
206
207
390k
    if let (rgb, [y_value]) = (final_pixel, final_y) {
208
127k
        let final_u_1 = *u_row_1.last().unwrap();
209
127k
        let final_u_2 = *u_row_2.last().unwrap();
210
127k
211
127k
        let final_v_1 = *v_row_1.last().unwrap();
212
127k
        let final_v_2 = *v_row_2.last().unwrap();
213
127k
214
127k
        let rgb1 = &mut rgb[0..3];
215
127k
        // first pixel uses the first u/v as the main one
216
127k
        let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2);
217
127k
        let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2);
218
127k
        set_pixel(rgb1, *y_value, u_value, v_value);
219
263k
    }
220
390k
}
image_webp::yuv::fill_row_fancy_with_2_uv_rows::<4>
Line
Count
Source
155
22.7k
fn fill_row_fancy_with_2_uv_rows<const BPP: usize>(
156
22.7k
    row_buffer: &mut [u8],
157
22.7k
    y_row: &[u8],
158
22.7k
    u_row_1: &[u8],
159
22.7k
    u_row_2: &[u8],
160
22.7k
    v_row_1: &[u8],
161
22.7k
    v_row_2: &[u8],
162
22.7k
) {
163
    // need to do left pixel separately since it will only have one u/v value
164
22.7k
    {
165
22.7k
        let rgb1 = &mut row_buffer[0..3];
166
22.7k
        let y_value = y_row[0];
167
22.7k
        // first pixel uses the first u/v as the main one
168
22.7k
        let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]);
169
22.7k
        let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]);
170
22.7k
        set_pixel(rgb1, y_value, u_value, v_value);
171
22.7k
    }
172
173
22.7k
    let rest_row_buffer = &mut row_buffer[BPP..];
174
22.7k
    let rest_y_row = &y_row[1..];
175
176
    // we do two pixels at a time since they share the same u/v values
177
22.7k
    let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2);
178
22.7k
    let mut main_y_chunks = rest_y_row.chunks_exact(2);
179
180
3.47M
    for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks)
181
22.7k
        .zip(&mut main_y_chunks)
182
22.7k
        .zip(u_row_1.windows(2))
183
22.7k
        .zip(u_row_2.windows(2))
184
22.7k
        .zip(v_row_1.windows(2))
185
22.7k
        .zip(v_row_2.windows(2))
186
    {
187
3.47M
        {
188
3.47M
            let rgb1 = &mut rgb[0..3];
189
3.47M
            let y_value = y_val[0];
190
3.47M
            // first pixel uses the first u/v as the main one
191
3.47M
            let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]);
192
3.47M
            let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]);
193
3.47M
            set_pixel(rgb1, y_value, u_value, v_value);
194
3.47M
        }
195
3.47M
        {
196
3.47M
            let rgb2 = &mut rgb[BPP..];
197
3.47M
            let y_value = y_val[1];
198
3.47M
            let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]);
199
3.47M
            let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]);
200
3.47M
            set_pixel(rgb2, y_value, u_value, v_value);
201
3.47M
        }
202
    }
203
204
22.7k
    let final_pixel = main_row_chunks.into_remainder();
205
22.7k
    let final_y = main_y_chunks.remainder();
206
207
22.7k
    if let (rgb, [y_value]) = (final_pixel, final_y) {
208
20.2k
        let final_u_1 = *u_row_1.last().unwrap();
209
20.2k
        let final_u_2 = *u_row_2.last().unwrap();
210
20.2k
211
20.2k
        let final_v_1 = *v_row_1.last().unwrap();
212
20.2k
        let final_v_2 = *v_row_2.last().unwrap();
213
20.2k
214
20.2k
        let rgb1 = &mut rgb[0..3];
215
20.2k
        // first pixel uses the first u/v as the main one
216
20.2k
        let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2);
217
20.2k
        let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2);
218
20.2k
        set_pixel(rgb1, *y_value, u_value, v_value);
219
20.2k
    }
220
22.7k
}
221
222
1.61k
fn fill_row_fancy_with_1_uv_row<const BPP: usize>(
223
1.61k
    row_buffer: &mut [u8],
224
1.61k
    y_row: &[u8],
225
1.61k
    u_row: &[u8],
226
1.61k
    v_row: &[u8],
227
1.61k
) {
228
    // doing left pixel first
229
1.61k
    {
230
1.61k
        let rgb1 = &mut row_buffer[0..3];
231
1.61k
        let y_value = y_row[0];
232
1.61k
233
1.61k
        let u_value = u_row[0];
234
1.61k
        let v_value = v_row[0];
235
1.61k
        set_pixel(rgb1, y_value, u_value, v_value);
236
1.61k
    }
237
238
    // two pixels at a time since they share the same u/v value
239
1.61k
    let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2);
240
1.61k
    let mut main_y_row_chunks = y_row[1..].chunks_exact(2);
241
242
473k
    for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks)
243
1.61k
        .zip(&mut main_y_row_chunks)
244
1.61k
        .zip(u_row.windows(2))
245
1.61k
        .zip(v_row.windows(2))
246
    {
247
473k
        {
248
473k
            let rgb1 = &mut rgb[0..3];
249
473k
            let y_value = y_val[0];
250
473k
            // first pixel uses the first u/v as the main one
251
473k
            let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]);
252
473k
            let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]);
253
473k
            set_pixel(rgb1, y_value, u_value, v_value);
254
473k
        }
255
473k
        {
256
473k
            let rgb2 = &mut rgb[BPP..];
257
473k
            let y_value = y_val[1];
258
473k
            let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]);
259
473k
            let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]);
260
473k
            set_pixel(rgb2, y_value, u_value, v_value);
261
473k
        }
262
    }
263
264
1.61k
    let final_pixel = main_row_chunks.into_remainder();
265
1.61k
    let final_y = main_y_row_chunks.remainder();
266
267
1.61k
    if let (rgb, [final_y]) = (final_pixel, final_y) {
268
965
        let final_u = *u_row.last().unwrap();
269
965
        let final_v = *v_row.last().unwrap();
270
965
271
965
        set_pixel(rgb, *final_y, final_u, final_v);
272
965
    }
273
1.61k
}
image_webp::yuv::fill_row_fancy_with_1_uv_row::<3>
Line
Count
Source
222
1.07k
fn fill_row_fancy_with_1_uv_row<const BPP: usize>(
223
1.07k
    row_buffer: &mut [u8],
224
1.07k
    y_row: &[u8],
225
1.07k
    u_row: &[u8],
226
1.07k
    v_row: &[u8],
227
1.07k
) {
228
    // doing left pixel first
229
1.07k
    {
230
1.07k
        let rgb1 = &mut row_buffer[0..3];
231
1.07k
        let y_value = y_row[0];
232
1.07k
233
1.07k
        let u_value = u_row[0];
234
1.07k
        let v_value = v_row[0];
235
1.07k
        set_pixel(rgb1, y_value, u_value, v_value);
236
1.07k
    }
237
238
    // two pixels at a time since they share the same u/v value
239
1.07k
    let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2);
240
1.07k
    let mut main_y_row_chunks = y_row[1..].chunks_exact(2);
241
242
449k
    for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks)
243
1.07k
        .zip(&mut main_y_row_chunks)
244
1.07k
        .zip(u_row.windows(2))
245
1.07k
        .zip(v_row.windows(2))
246
    {
247
449k
        {
248
449k
            let rgb1 = &mut rgb[0..3];
249
449k
            let y_value = y_val[0];
250
449k
            // first pixel uses the first u/v as the main one
251
449k
            let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]);
252
449k
            let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]);
253
449k
            set_pixel(rgb1, y_value, u_value, v_value);
254
449k
        }
255
449k
        {
256
449k
            let rgb2 = &mut rgb[BPP..];
257
449k
            let y_value = y_val[1];
258
449k
            let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]);
259
449k
            let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]);
260
449k
            set_pixel(rgb2, y_value, u_value, v_value);
261
449k
        }
262
    }
263
264
1.07k
    let final_pixel = main_row_chunks.into_remainder();
265
1.07k
    let final_y = main_y_row_chunks.remainder();
266
267
1.07k
    if let (rgb, [final_y]) = (final_pixel, final_y) {
268
495
        let final_u = *u_row.last().unwrap();
269
495
        let final_v = *v_row.last().unwrap();
270
495
271
495
        set_pixel(rgb, *final_y, final_u, final_v);
272
579
    }
273
1.07k
}
image_webp::yuv::fill_row_fancy_with_1_uv_row::<4>
Line
Count
Source
222
543
fn fill_row_fancy_with_1_uv_row<const BPP: usize>(
223
543
    row_buffer: &mut [u8],
224
543
    y_row: &[u8],
225
543
    u_row: &[u8],
226
543
    v_row: &[u8],
227
543
) {
228
    // doing left pixel first
229
543
    {
230
543
        let rgb1 = &mut row_buffer[0..3];
231
543
        let y_value = y_row[0];
232
543
233
543
        let u_value = u_row[0];
234
543
        let v_value = v_row[0];
235
543
        set_pixel(rgb1, y_value, u_value, v_value);
236
543
    }
237
238
    // two pixels at a time since they share the same u/v value
239
543
    let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2);
240
543
    let mut main_y_row_chunks = y_row[1..].chunks_exact(2);
241
242
23.5k
    for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks)
243
543
        .zip(&mut main_y_row_chunks)
244
543
        .zip(u_row.windows(2))
245
543
        .zip(v_row.windows(2))
246
    {
247
23.5k
        {
248
23.5k
            let rgb1 = &mut rgb[0..3];
249
23.5k
            let y_value = y_val[0];
250
23.5k
            // first pixel uses the first u/v as the main one
251
23.5k
            let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]);
252
23.5k
            let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]);
253
23.5k
            set_pixel(rgb1, y_value, u_value, v_value);
254
23.5k
        }
255
23.5k
        {
256
23.5k
            let rgb2 = &mut rgb[BPP..];
257
23.5k
            let y_value = y_val[1];
258
23.5k
            let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]);
259
23.5k
            let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]);
260
23.5k
            set_pixel(rgb2, y_value, u_value, v_value);
261
23.5k
        }
262
    }
263
264
543
    let final_pixel = main_row_chunks.into_remainder();
265
543
    let final_y = main_y_row_chunks.remainder();
266
267
543
    if let (rgb, [final_y]) = (final_pixel, final_y) {
268
470
        let final_u = *u_row.last().unwrap();
269
470
        let final_v = *v_row.last().unwrap();
270
470
271
470
        set_pixel(rgb, *final_y, final_u, final_v);
272
470
    }
273
543
}
274
275
#[inline]
276
727M
fn get_fancy_chroma_value(main: u8, secondary1: u8, secondary2: u8, tertiary: u8) -> u8 {
277
727M
    let val0 = u16::from(main);
278
727M
    let val1 = u16::from(secondary1);
279
727M
    let val2 = u16::from(secondary2);
280
727M
    let val3 = u16::from(tertiary);
281
727M
    ((9 * val0 + 3 * val1 + 3 * val2 + val3 + 8) / 16) as u8
282
727M
}
283
284
#[inline]
285
363M
fn set_pixel(rgb: &mut [u8], y: u8, u: u8, v: u8) {
286
363M
    rgb[0] = yuv_to_r(y, v);
287
363M
    rgb[1] = yuv_to_g(y, u, v);
288
363M
    rgb[2] = yuv_to_b(y, u);
289
363M
}
290
291
/// Simple conversion, not currently used but could add a config to allow for using the simple
292
#[allow(unused)]
293
0
pub(crate) fn fill_rgb_buffer_simple<const BPP: usize>(
294
0
    buffer: &mut [u8],
295
0
    y_buffer: &[u8],
296
0
    u_buffer: &[u8],
297
0
    v_buffer: &[u8],
298
0
    width: usize,
299
0
    chroma_width: usize,
300
0
    buffer_width: usize,
301
0
) {
302
0
    let u_row_twice_iter = u_buffer
303
0
        .chunks_exact(buffer_width / 2)
304
0
        .flat_map(|n| std::iter::repeat(n).take(2));
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<3>::{closure#0}
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<4>::{closure#0}
305
0
    let v_row_twice_iter = v_buffer
306
0
        .chunks_exact(buffer_width / 2)
307
0
        .flat_map(|n| std::iter::repeat(n).take(2));
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<3>::{closure#1}
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<4>::{closure#1}
308
309
0
    for (((row, y_row), u_row), v_row) in buffer
310
0
        .chunks_exact_mut(width * BPP)
311
0
        .zip(y_buffer.chunks_exact(buffer_width))
312
0
        .zip(u_row_twice_iter)
313
0
        .zip(v_row_twice_iter)
314
0
    {
315
0
        fill_rgba_row_simple::<BPP>(
316
0
            &y_row[..width],
317
0
            &u_row[..chroma_width],
318
0
            &v_row[..chroma_width],
319
0
            row,
320
0
        );
321
0
    }
322
0
}
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<3>
Unexecuted instantiation: image_webp::yuv::fill_rgb_buffer_simple::<4>
323
324
0
fn fill_rgba_row_simple<const BPP: usize>(
325
0
    y_vec: &[u8],
326
0
    u_vec: &[u8],
327
0
    v_vec: &[u8],
328
0
    rgba: &mut [u8],
329
0
) {
330
    // Fill 2 pixels per iteration: these pixels share `u` and `v` components
331
0
    let mut rgb_chunks = rgba.chunks_exact_mut(BPP * 2);
332
0
    let mut y_chunks = y_vec.chunks_exact(2);
333
0
    let mut u_iter = u_vec.iter();
334
0
    let mut v_iter = v_vec.iter();
335
336
0
    for (((rgb, y), &u), &v) in (&mut rgb_chunks)
337
0
        .zip(&mut y_chunks)
338
0
        .zip(&mut u_iter)
339
0
        .zip(&mut v_iter)
340
    {
341
0
        let coeffs = [
342
0
            mulhi(v, 26149),
343
0
            mulhi(u, 6419),
344
0
            mulhi(v, 13320),
345
0
            mulhi(u, 33050),
346
0
        ];
347
348
0
        let get_r = |y: u8| clip(mulhi(y, 19077) + coeffs[0] - 14234);
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<3>::{closure#0}
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<4>::{closure#0}
349
0
        let get_g = |y: u8| clip(mulhi(y, 19077) - coeffs[1] - coeffs[2] + 8708);
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<3>::{closure#1}
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<4>::{closure#1}
350
0
        let get_b = |y: u8| clip(mulhi(y, 19077) + coeffs[3] - 17685);
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<3>::{closure#2}
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<4>::{closure#2}
351
352
0
        let rgb1 = &mut rgb[0..3];
353
0
        rgb1[0] = get_r(y[0]);
354
0
        rgb1[1] = get_g(y[0]);
355
0
        rgb1[2] = get_b(y[0]);
356
357
0
        let rgb2 = &mut rgb[BPP..];
358
0
        rgb2[0] = get_r(y[1]);
359
0
        rgb2[1] = get_g(y[1]);
360
0
        rgb2[2] = get_b(y[1]);
361
    }
362
363
0
    let remainder = rgb_chunks.into_remainder();
364
0
    if remainder.len() >= 3 {
365
0
        if let (Some(&y), Some(&u), Some(&v)) = (
366
0
            y_chunks.remainder().iter().next(),
367
0
            u_iter.next(),
368
0
            v_iter.next(),
369
0
        ) {
370
0
            let coeffs = [
371
0
                mulhi(v, 26149),
372
0
                mulhi(u, 6419),
373
0
                mulhi(v, 13320),
374
0
                mulhi(u, 33050),
375
0
            ];
376
0
377
0
            remainder[0] = clip(mulhi(y, 19077) + coeffs[0] - 14234);
378
0
            remainder[1] = clip(mulhi(y, 19077) - coeffs[1] - coeffs[2] + 8708);
379
0
            remainder[2] = clip(mulhi(y, 19077) + coeffs[3] - 17685);
380
0
        }
381
0
    }
382
0
}
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<3>
Unexecuted instantiation: image_webp::yuv::fill_rgba_row_simple::<4>
383
384
#[cfg(test)]
385
mod tests {
386
    use super::*;
387
388
    #[test]
389
    fn test_fancy_grid() {
390
        #[rustfmt::skip]
391
        let y_buffer = [
392
            77, 162, 202, 185,
393
            28, 13, 199, 182,
394
            135, 147, 164, 135, 
395
            66, 27, 171, 130,
396
        ];
397
398
        #[rustfmt::skip]
399
        let u_buffer = [
400
            34, 101, 
401
            123, 163
402
        ];
403
404
        #[rustfmt::skip]
405
        let v_buffer = [
406
            97, 167,
407
            149, 23,
408
        ];
409
410
        let mut rgb_buffer = [0u8; 16 * 3];
411
        fill_rgb_buffer_fancy::<3>(&mut rgb_buffer, &y_buffer, &u_buffer, &v_buffer, 4, 4, 4);
412
413
        #[rustfmt::skip]
414
        let upsampled_u_buffer = [
415
            34, 51, 84, 101,
416
            56, 71, 101, 117,
417
            101, 112, 136, 148,
418
            123, 133, 153, 163,
419
        ];
420
421
        #[rustfmt::skip]
422
        let upsampled_v_buffer = [
423
            97, 115, 150, 167,
424
            110, 115, 126, 131,
425
            136, 117, 78, 59,
426
            149, 118, 55, 23,
427
        ];
428
429
        let mut upsampled_rgb_buffer = [0u8; 16 * 3];
430
        for (((rgb_val, y), u), v) in upsampled_rgb_buffer
431
            .chunks_exact_mut(3)
432
            .zip(y_buffer)
433
            .zip(upsampled_u_buffer)
434
            .zip(upsampled_v_buffer)
435
        {
436
            rgb_val[0] = yuv_to_r(y, v);
437
            rgb_val[1] = yuv_to_g(y, u, v);
438
            rgb_val[2] = yuv_to_b(y, u);
439
        }
440
441
        assert_eq!(rgb_buffer, upsampled_rgb_buffer);
442
    }
443
444
    #[test]
445
    fn test_yuv_conversions() {
446
        let (y, u, v) = (203, 40, 42);
447
448
        assert_eq!(yuv_to_r(y, v), 80);
449
        assert_eq!(yuv_to_g(y, u, v), 255);
450
        assert_eq!(yuv_to_b(y, u), 40);
451
    }
452
}