/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 | 1.71G | fn mulhi(v: u8, coeff: u16) -> i32 { |
31 | 1.71G | ((u32::from(v) * u32::from(coeff)) >> 8) as i32 |
32 | 1.71G | } |
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 | 734M | fn clip(v: i32) -> u8 { |
54 | | const YUV_FIX2: i32 = 6; |
55 | 734M | (v >> YUV_FIX2).max(0).min(255) as u8 |
56 | 734M | } |
57 | | |
58 | | #[inline(always)] |
59 | 244M | fn yuv_to_r(y: u8, v: u8) -> u8 { |
60 | 244M | clip(mulhi(y, 19077) + mulhi(v, 26149) - 14234) |
61 | 244M | } |
62 | | |
63 | | #[inline(always)] |
64 | 244M | fn yuv_to_g(y: u8, u: u8, v: u8) -> u8 { |
65 | 244M | clip(mulhi(y, 19077) - mulhi(u, 6419) - mulhi(v, 13320) + 8708) |
66 | 244M | } |
67 | | |
68 | | #[inline(always)] |
69 | 244M | fn yuv_to_b(y: u8, u: u8) -> u8 { |
70 | 244M | clip(mulhi(y, 19077) + mulhi(u, 33050) - 17685) |
71 | 244M | } |
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 | 687 | pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>( |
77 | 687 | buffer: &mut [u8], |
78 | 687 | y_buffer: &[u8], |
79 | 687 | u_buffer: &[u8], |
80 | 687 | v_buffer: &[u8], |
81 | 687 | width: usize, |
82 | 687 | height: usize, |
83 | 687 | buffer_width: usize, |
84 | 687 | ) { |
85 | | // buffer width is always even so don't need to do div_ceil |
86 | 687 | let chroma_buffer_width = buffer_width / 2; |
87 | 687 | let chroma_width = width.div_ceil(2); |
88 | | |
89 | | // fill top row first since it only uses the top u/v row |
90 | 687 | let top_row_y = &y_buffer[..width]; |
91 | 687 | let top_row_u = &u_buffer[..chroma_width]; |
92 | 687 | let top_row_v = &v_buffer[..chroma_width]; |
93 | 687 | let top_row_buffer = &mut buffer[..width * BPP]; |
94 | 687 | fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v); |
95 | | |
96 | 687 | 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 | 687 | let end_y_index = height * buffer_width; |
99 | 687 | let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2); |
100 | 687 | let mut main_u_windows = u_buffer |
101 | 687 | .windows(chroma_buffer_width * 2) |
102 | 687 | .step_by(chroma_buffer_width); |
103 | 687 | let mut main_v_windows = v_buffer |
104 | 687 | .windows(chroma_buffer_width * 2) |
105 | 687 | .step_by(chroma_buffer_width); |
106 | | |
107 | 162k | for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks) |
108 | 687 | .zip(&mut main_y_chunks) |
109 | 687 | .zip(&mut main_u_windows) |
110 | 687 | .zip(&mut main_v_windows) |
111 | 162k | { |
112 | 162k | let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width); |
113 | 162k | let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width); |
114 | 162k | let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP); |
115 | 162k | let (y_row_1, y_row_2) = y_rows.split_at(buffer_width); |
116 | 162k | fill_row_fancy_with_2_uv_rows::<BPP>( |
117 | 162k | row_buf_1, |
118 | 162k | &y_row_1[..width], |
119 | 162k | &u_row_1[..chroma_width], |
120 | 162k | &u_row_2[..chroma_width], |
121 | 162k | &v_row_1[..chroma_width], |
122 | 162k | &v_row_2[..chroma_width], |
123 | 162k | ); |
124 | 162k | fill_row_fancy_with_2_uv_rows::<BPP>( |
125 | 162k | row_buf_2, |
126 | 162k | &y_row_2[..width], |
127 | 162k | &u_row_2[..chroma_width], |
128 | 162k | &u_row_1[..chroma_width], |
129 | 162k | &v_row_2[..chroma_width], |
130 | 162k | &v_row_1[..chroma_width], |
131 | 162k | ); |
132 | 162k | } |
133 | | |
134 | 687 | 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 | 687 | if !final_row_buffer.is_empty() { |
138 | 472 | let final_y_row = main_y_chunks.remainder(); |
139 | 472 | |
140 | 472 | let chroma_height = height.div_ceil(2); |
141 | 472 | let start_chroma_index = (chroma_height - 1) * chroma_buffer_width; |
142 | 472 | |
143 | 472 | let final_u_row = &u_buffer[start_chroma_index..]; |
144 | 472 | let final_v_row = &v_buffer[start_chroma_index..]; |
145 | 472 | fill_row_fancy_with_1_uv_row::<BPP>( |
146 | 472 | final_row_buffer, |
147 | 472 | &final_y_row[..width], |
148 | 472 | &final_u_row[..chroma_width], |
149 | 472 | &final_v_row[..chroma_width], |
150 | 472 | ); |
151 | 472 | } |
152 | 687 | } image_webp::yuv::fill_rgb_buffer_fancy::<3> Line | Count | Source | 76 | 288 | pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>( | 77 | 288 | buffer: &mut [u8], | 78 | 288 | y_buffer: &[u8], | 79 | 288 | u_buffer: &[u8], | 80 | 288 | v_buffer: &[u8], | 81 | 288 | width: usize, | 82 | 288 | height: usize, | 83 | 288 | buffer_width: usize, | 84 | 288 | ) { | 85 | | // buffer width is always even so don't need to do div_ceil | 86 | 288 | let chroma_buffer_width = buffer_width / 2; | 87 | 288 | let chroma_width = width.div_ceil(2); | 88 | | | 89 | | // fill top row first since it only uses the top u/v row | 90 | 288 | let top_row_y = &y_buffer[..width]; | 91 | 288 | let top_row_u = &u_buffer[..chroma_width]; | 92 | 288 | let top_row_v = &v_buffer[..chroma_width]; | 93 | 288 | let top_row_buffer = &mut buffer[..width * BPP]; | 94 | 288 | fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v); | 95 | | | 96 | 288 | 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 | 288 | let end_y_index = height * buffer_width; | 99 | 288 | let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2); | 100 | 288 | let mut main_u_windows = u_buffer | 101 | 288 | .windows(chroma_buffer_width * 2) | 102 | 288 | .step_by(chroma_buffer_width); | 103 | 288 | let mut main_v_windows = v_buffer | 104 | 288 | .windows(chroma_buffer_width * 2) | 105 | 288 | .step_by(chroma_buffer_width); | 106 | | | 107 | 139k | for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks) | 108 | 288 | .zip(&mut main_y_chunks) | 109 | 288 | .zip(&mut main_u_windows) | 110 | 288 | .zip(&mut main_v_windows) | 111 | 139k | { | 112 | 139k | let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width); | 113 | 139k | let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width); | 114 | 139k | let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP); | 115 | 139k | let (y_row_1, y_row_2) = y_rows.split_at(buffer_width); | 116 | 139k | fill_row_fancy_with_2_uv_rows::<BPP>( | 117 | 139k | row_buf_1, | 118 | 139k | &y_row_1[..width], | 119 | 139k | &u_row_1[..chroma_width], | 120 | 139k | &u_row_2[..chroma_width], | 121 | 139k | &v_row_1[..chroma_width], | 122 | 139k | &v_row_2[..chroma_width], | 123 | 139k | ); | 124 | 139k | fill_row_fancy_with_2_uv_rows::<BPP>( | 125 | 139k | row_buf_2, | 126 | 139k | &y_row_2[..width], | 127 | 139k | &u_row_2[..chroma_width], | 128 | 139k | &u_row_1[..chroma_width], | 129 | 139k | &v_row_2[..chroma_width], | 130 | 139k | &v_row_1[..chroma_width], | 131 | 139k | ); | 132 | 139k | } | 133 | | | 134 | 288 | 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 | 288 | if !final_row_buffer.is_empty() { | 138 | 145 | let final_y_row = main_y_chunks.remainder(); | 139 | 145 | | 140 | 145 | let chroma_height = height.div_ceil(2); | 141 | 145 | let start_chroma_index = (chroma_height - 1) * chroma_buffer_width; | 142 | 145 | | 143 | 145 | let final_u_row = &u_buffer[start_chroma_index..]; | 144 | 145 | let final_v_row = &v_buffer[start_chroma_index..]; | 145 | 145 | fill_row_fancy_with_1_uv_row::<BPP>( | 146 | 145 | final_row_buffer, | 147 | 145 | &final_y_row[..width], | 148 | 145 | &final_u_row[..chroma_width], | 149 | 145 | &final_v_row[..chroma_width], | 150 | 145 | ); | 151 | 145 | } | 152 | 288 | } |
image_webp::yuv::fill_rgb_buffer_fancy::<4> Line | Count | Source | 76 | 399 | pub(crate) fn fill_rgb_buffer_fancy<const BPP: usize>( | 77 | 399 | buffer: &mut [u8], | 78 | 399 | y_buffer: &[u8], | 79 | 399 | u_buffer: &[u8], | 80 | 399 | v_buffer: &[u8], | 81 | 399 | width: usize, | 82 | 399 | height: usize, | 83 | 399 | buffer_width: usize, | 84 | 399 | ) { | 85 | | // buffer width is always even so don't need to do div_ceil | 86 | 399 | let chroma_buffer_width = buffer_width / 2; | 87 | 399 | let chroma_width = width.div_ceil(2); | 88 | | | 89 | | // fill top row first since it only uses the top u/v row | 90 | 399 | let top_row_y = &y_buffer[..width]; | 91 | 399 | let top_row_u = &u_buffer[..chroma_width]; | 92 | 399 | let top_row_v = &v_buffer[..chroma_width]; | 93 | 399 | let top_row_buffer = &mut buffer[..width * BPP]; | 94 | 399 | fill_row_fancy_with_1_uv_row::<BPP>(top_row_buffer, top_row_y, top_row_u, top_row_v); | 95 | | | 96 | 399 | 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 | 399 | let end_y_index = height * buffer_width; | 99 | 399 | let mut main_y_chunks = y_buffer[buffer_width..end_y_index].chunks_exact(buffer_width * 2); | 100 | 399 | let mut main_u_windows = u_buffer | 101 | 399 | .windows(chroma_buffer_width * 2) | 102 | 399 | .step_by(chroma_buffer_width); | 103 | 399 | let mut main_v_windows = v_buffer | 104 | 399 | .windows(chroma_buffer_width * 2) | 105 | 399 | .step_by(chroma_buffer_width); | 106 | | | 107 | 22.7k | for (((row_buffer, y_rows), u_rows), v_rows) in (&mut main_row_chunks) | 108 | 399 | .zip(&mut main_y_chunks) | 109 | 399 | .zip(&mut main_u_windows) | 110 | 399 | .zip(&mut main_v_windows) | 111 | 22.7k | { | 112 | 22.7k | let (u_row_1, u_row_2) = u_rows.split_at(chroma_buffer_width); | 113 | 22.7k | let (v_row_1, v_row_2) = v_rows.split_at(chroma_buffer_width); | 114 | 22.7k | let (row_buf_1, row_buf_2) = row_buffer.split_at_mut(width * BPP); | 115 | 22.7k | let (y_row_1, y_row_2) = y_rows.split_at(buffer_width); | 116 | 22.7k | fill_row_fancy_with_2_uv_rows::<BPP>( | 117 | 22.7k | row_buf_1, | 118 | 22.7k | &y_row_1[..width], | 119 | 22.7k | &u_row_1[..chroma_width], | 120 | 22.7k | &u_row_2[..chroma_width], | 121 | 22.7k | &v_row_1[..chroma_width], | 122 | 22.7k | &v_row_2[..chroma_width], | 123 | 22.7k | ); | 124 | 22.7k | fill_row_fancy_with_2_uv_rows::<BPP>( | 125 | 22.7k | row_buf_2, | 126 | 22.7k | &y_row_2[..width], | 127 | 22.7k | &u_row_2[..chroma_width], | 128 | 22.7k | &u_row_1[..chroma_width], | 129 | 22.7k | &v_row_2[..chroma_width], | 130 | 22.7k | &v_row_1[..chroma_width], | 131 | 22.7k | ); | 132 | 22.7k | } | 133 | | | 134 | 399 | 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 | 399 | if !final_row_buffer.is_empty() { | 138 | 327 | let final_y_row = main_y_chunks.remainder(); | 139 | 327 | | 140 | 327 | let chroma_height = height.div_ceil(2); | 141 | 327 | let start_chroma_index = (chroma_height - 1) * chroma_buffer_width; | 142 | 327 | | 143 | 327 | let final_u_row = &u_buffer[start_chroma_index..]; | 144 | 327 | let final_v_row = &v_buffer[start_chroma_index..]; | 145 | 327 | fill_row_fancy_with_1_uv_row::<BPP>( | 146 | 327 | final_row_buffer, | 147 | 327 | &final_y_row[..width], | 148 | 327 | &final_u_row[..chroma_width], | 149 | 327 | &final_v_row[..chroma_width], | 150 | 327 | ); | 151 | 327 | } | 152 | 399 | } |
|
153 | | |
154 | | /// Fills a row with the fancy interpolation as detailed |
155 | 325k | fn fill_row_fancy_with_2_uv_rows<const BPP: usize>( |
156 | 325k | row_buffer: &mut [u8], |
157 | 325k | y_row: &[u8], |
158 | 325k | u_row_1: &[u8], |
159 | 325k | u_row_2: &[u8], |
160 | 325k | v_row_1: &[u8], |
161 | 325k | v_row_2: &[u8], |
162 | 325k | ) { |
163 | | // need to do left pixel separately since it will only have one u/v value |
164 | 325k | { |
165 | 325k | let rgb1 = &mut row_buffer[0..3]; |
166 | 325k | let y_value = y_row[0]; |
167 | 325k | // first pixel uses the first u/v as the main one |
168 | 325k | let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]); |
169 | 325k | let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]); |
170 | 325k | set_pixel(rgb1, y_value, u_value, v_value); |
171 | 325k | } |
172 | | |
173 | 325k | let rest_row_buffer = &mut row_buffer[BPP..]; |
174 | 325k | 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 | 325k | let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2); |
178 | 325k | let mut main_y_chunks = rest_y_row.chunks_exact(2); |
179 | | |
180 | 121M | for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks) |
181 | 325k | .zip(&mut main_y_chunks) |
182 | 325k | .zip(u_row_1.windows(2)) |
183 | 325k | .zip(u_row_2.windows(2)) |
184 | 325k | .zip(v_row_1.windows(2)) |
185 | 325k | .zip(v_row_2.windows(2)) |
186 | | { |
187 | 121M | { |
188 | 121M | let rgb1 = &mut rgb[0..3]; |
189 | 121M | let y_value = y_val[0]; |
190 | 121M | // first pixel uses the first u/v as the main one |
191 | 121M | let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]); |
192 | 121M | let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]); |
193 | 121M | set_pixel(rgb1, y_value, u_value, v_value); |
194 | 121M | } |
195 | 121M | { |
196 | 121M | let rgb2 = &mut rgb[BPP..]; |
197 | 121M | let y_value = y_val[1]; |
198 | 121M | let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]); |
199 | 121M | let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]); |
200 | 121M | set_pixel(rgb2, y_value, u_value, v_value); |
201 | 121M | } |
202 | | } |
203 | | |
204 | 325k | let final_pixel = main_row_chunks.into_remainder(); |
205 | 325k | let final_y = main_y_chunks.remainder(); |
206 | | |
207 | 325k | if let (rgb, [y_value]) = (final_pixel, final_y) { |
208 | 169k | let final_u_1 = *u_row_1.last().unwrap(); |
209 | 169k | let final_u_2 = *u_row_2.last().unwrap(); |
210 | 169k | |
211 | 169k | let final_v_1 = *v_row_1.last().unwrap(); |
212 | 169k | let final_v_2 = *v_row_2.last().unwrap(); |
213 | 169k | |
214 | 169k | let rgb1 = &mut rgb[0..3]; |
215 | 169k | // first pixel uses the first u/v as the main one |
216 | 169k | let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2); |
217 | 169k | let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2); |
218 | 169k | set_pixel(rgb1, *y_value, u_value, v_value); |
219 | 169k | } |
220 | 325k | } image_webp::yuv::fill_row_fancy_with_2_uv_rows::<3> Line | Count | Source | 155 | 279k | fn fill_row_fancy_with_2_uv_rows<const BPP: usize>( | 156 | 279k | row_buffer: &mut [u8], | 157 | 279k | y_row: &[u8], | 158 | 279k | u_row_1: &[u8], | 159 | 279k | u_row_2: &[u8], | 160 | 279k | v_row_1: &[u8], | 161 | 279k | v_row_2: &[u8], | 162 | 279k | ) { | 163 | | // need to do left pixel separately since it will only have one u/v value | 164 | 279k | { | 165 | 279k | let rgb1 = &mut row_buffer[0..3]; | 166 | 279k | let y_value = y_row[0]; | 167 | 279k | // first pixel uses the first u/v as the main one | 168 | 279k | let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]); | 169 | 279k | let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]); | 170 | 279k | set_pixel(rgb1, y_value, u_value, v_value); | 171 | 279k | } | 172 | | | 173 | 279k | let rest_row_buffer = &mut row_buffer[BPP..]; | 174 | 279k | 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 | 279k | let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2); | 178 | 279k | let mut main_y_chunks = rest_y_row.chunks_exact(2); | 179 | | | 180 | 115M | for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks) | 181 | 279k | .zip(&mut main_y_chunks) | 182 | 279k | .zip(u_row_1.windows(2)) | 183 | 279k | .zip(u_row_2.windows(2)) | 184 | 279k | .zip(v_row_1.windows(2)) | 185 | 279k | .zip(v_row_2.windows(2)) | 186 | | { | 187 | 115M | { | 188 | 115M | let rgb1 = &mut rgb[0..3]; | 189 | 115M | let y_value = y_val[0]; | 190 | 115M | // first pixel uses the first u/v as the main one | 191 | 115M | let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]); | 192 | 115M | let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]); | 193 | 115M | set_pixel(rgb1, y_value, u_value, v_value); | 194 | 115M | } | 195 | 115M | { | 196 | 115M | let rgb2 = &mut rgb[BPP..]; | 197 | 115M | let y_value = y_val[1]; | 198 | 115M | let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]); | 199 | 115M | let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]); | 200 | 115M | set_pixel(rgb2, y_value, u_value, v_value); | 201 | 115M | } | 202 | | } | 203 | | | 204 | 279k | let final_pixel = main_row_chunks.into_remainder(); | 205 | 279k | let final_y = main_y_chunks.remainder(); | 206 | | | 207 | 279k | if let (rgb, [y_value]) = (final_pixel, final_y) { | 208 | 125k | let final_u_1 = *u_row_1.last().unwrap(); | 209 | 125k | let final_u_2 = *u_row_2.last().unwrap(); | 210 | 125k | | 211 | 125k | let final_v_1 = *v_row_1.last().unwrap(); | 212 | 125k | let final_v_2 = *v_row_2.last().unwrap(); | 213 | 125k | | 214 | 125k | let rgb1 = &mut rgb[0..3]; | 215 | 125k | // first pixel uses the first u/v as the main one | 216 | 125k | let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2); | 217 | 125k | let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2); | 218 | 125k | set_pixel(rgb1, *y_value, u_value, v_value); | 219 | 154k | } | 220 | 279k | } |
image_webp::yuv::fill_row_fancy_with_2_uv_rows::<4> Line | Count | Source | 155 | 45.4k | fn fill_row_fancy_with_2_uv_rows<const BPP: usize>( | 156 | 45.4k | row_buffer: &mut [u8], | 157 | 45.4k | y_row: &[u8], | 158 | 45.4k | u_row_1: &[u8], | 159 | 45.4k | u_row_2: &[u8], | 160 | 45.4k | v_row_1: &[u8], | 161 | 45.4k | v_row_2: &[u8], | 162 | 45.4k | ) { | 163 | | // need to do left pixel separately since it will only have one u/v value | 164 | 45.4k | { | 165 | 45.4k | let rgb1 = &mut row_buffer[0..3]; | 166 | 45.4k | let y_value = y_row[0]; | 167 | 45.4k | // first pixel uses the first u/v as the main one | 168 | 45.4k | let u_value = get_fancy_chroma_value(u_row_1[0], u_row_1[0], u_row_2[0], u_row_2[0]); | 169 | 45.4k | let v_value = get_fancy_chroma_value(v_row_1[0], v_row_1[0], v_row_2[0], v_row_2[0]); | 170 | 45.4k | set_pixel(rgb1, y_value, u_value, v_value); | 171 | 45.4k | } | 172 | | | 173 | 45.4k | let rest_row_buffer = &mut row_buffer[BPP..]; | 174 | 45.4k | 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 | 45.4k | let mut main_row_chunks = rest_row_buffer.chunks_exact_mut(BPP * 2); | 178 | 45.4k | let mut main_y_chunks = rest_y_row.chunks_exact(2); | 179 | | | 180 | 5.99M | for (((((rgb, y_val), u_val_1), u_val_2), v_val_1), v_val_2) in (&mut main_row_chunks) | 181 | 45.4k | .zip(&mut main_y_chunks) | 182 | 45.4k | .zip(u_row_1.windows(2)) | 183 | 45.4k | .zip(u_row_2.windows(2)) | 184 | 45.4k | .zip(v_row_1.windows(2)) | 185 | 45.4k | .zip(v_row_2.windows(2)) | 186 | | { | 187 | 5.99M | { | 188 | 5.99M | let rgb1 = &mut rgb[0..3]; | 189 | 5.99M | let y_value = y_val[0]; | 190 | 5.99M | // first pixel uses the first u/v as the main one | 191 | 5.99M | let u_value = get_fancy_chroma_value(u_val_1[0], u_val_1[1], u_val_2[0], u_val_2[1]); | 192 | 5.99M | let v_value = get_fancy_chroma_value(v_val_1[0], v_val_1[1], v_val_2[0], v_val_2[1]); | 193 | 5.99M | set_pixel(rgb1, y_value, u_value, v_value); | 194 | 5.99M | } | 195 | 5.99M | { | 196 | 5.99M | let rgb2 = &mut rgb[BPP..]; | 197 | 5.99M | let y_value = y_val[1]; | 198 | 5.99M | let u_value = get_fancy_chroma_value(u_val_1[1], u_val_1[0], u_val_2[1], u_val_2[0]); | 199 | 5.99M | let v_value = get_fancy_chroma_value(v_val_1[1], v_val_1[0], v_val_2[1], v_val_2[0]); | 200 | 5.99M | set_pixel(rgb2, y_value, u_value, v_value); | 201 | 5.99M | } | 202 | | } | 203 | | | 204 | 45.4k | let final_pixel = main_row_chunks.into_remainder(); | 205 | 45.4k | let final_y = main_y_chunks.remainder(); | 206 | | | 207 | 45.4k | if let (rgb, [y_value]) = (final_pixel, final_y) { | 208 | 44.0k | let final_u_1 = *u_row_1.last().unwrap(); | 209 | 44.0k | let final_u_2 = *u_row_2.last().unwrap(); | 210 | 44.0k | | 211 | 44.0k | let final_v_1 = *v_row_1.last().unwrap(); | 212 | 44.0k | let final_v_2 = *v_row_2.last().unwrap(); | 213 | 44.0k | | 214 | 44.0k | let rgb1 = &mut rgb[0..3]; | 215 | 44.0k | // first pixel uses the first u/v as the main one | 216 | 44.0k | let u_value = get_fancy_chroma_value(final_u_1, final_u_1, final_u_2, final_u_2); | 217 | 44.0k | let v_value = get_fancy_chroma_value(final_v_1, final_v_1, final_v_2, final_v_2); | 218 | 44.0k | set_pixel(rgb1, *y_value, u_value, v_value); | 219 | 44.0k | } | 220 | 45.4k | } |
|
221 | | |
222 | 1.15k | fn fill_row_fancy_with_1_uv_row<const BPP: usize>( |
223 | 1.15k | row_buffer: &mut [u8], |
224 | 1.15k | y_row: &[u8], |
225 | 1.15k | u_row: &[u8], |
226 | 1.15k | v_row: &[u8], |
227 | 1.15k | ) { |
228 | | // doing left pixel first |
229 | 1.15k | { |
230 | 1.15k | let rgb1 = &mut row_buffer[0..3]; |
231 | 1.15k | let y_value = y_row[0]; |
232 | 1.15k | |
233 | 1.15k | let u_value = u_row[0]; |
234 | 1.15k | let v_value = v_row[0]; |
235 | 1.15k | set_pixel(rgb1, y_value, u_value, v_value); |
236 | 1.15k | } |
237 | | |
238 | | // two pixels at a time since they share the same u/v value |
239 | 1.15k | let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2); |
240 | 1.15k | let mut main_y_row_chunks = y_row[1..].chunks_exact(2); |
241 | | |
242 | 444k | for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks) |
243 | 1.15k | .zip(&mut main_y_row_chunks) |
244 | 1.15k | .zip(u_row.windows(2)) |
245 | 1.15k | .zip(v_row.windows(2)) |
246 | | { |
247 | 444k | { |
248 | 444k | let rgb1 = &mut rgb[0..3]; |
249 | 444k | let y_value = y_val[0]; |
250 | 444k | // first pixel uses the first u/v as the main one |
251 | 444k | let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]); |
252 | 444k | let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]); |
253 | 444k | set_pixel(rgb1, y_value, u_value, v_value); |
254 | 444k | } |
255 | 444k | { |
256 | 444k | let rgb2 = &mut rgb[BPP..]; |
257 | 444k | let y_value = y_val[1]; |
258 | 444k | let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]); |
259 | 444k | let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]); |
260 | 444k | set_pixel(rgb2, y_value, u_value, v_value); |
261 | 444k | } |
262 | | } |
263 | | |
264 | 1.15k | let final_pixel = main_row_chunks.into_remainder(); |
265 | 1.15k | let final_y = main_y_row_chunks.remainder(); |
266 | | |
267 | 1.15k | if let (rgb, [final_y]) = (final_pixel, final_y) { |
268 | 900 | let final_u = *u_row.last().unwrap(); |
269 | 900 | let final_v = *v_row.last().unwrap(); |
270 | 900 | |
271 | 900 | set_pixel(rgb, *final_y, final_u, final_v); |
272 | 900 | } |
273 | 1.15k | } image_webp::yuv::fill_row_fancy_with_1_uv_row::<3> Line | Count | Source | 222 | 433 | fn fill_row_fancy_with_1_uv_row<const BPP: usize>( | 223 | 433 | row_buffer: &mut [u8], | 224 | 433 | y_row: &[u8], | 225 | 433 | u_row: &[u8], | 226 | 433 | v_row: &[u8], | 227 | 433 | ) { | 228 | | // doing left pixel first | 229 | 433 | { | 230 | 433 | let rgb1 = &mut row_buffer[0..3]; | 231 | 433 | let y_value = y_row[0]; | 232 | 433 | | 233 | 433 | let u_value = u_row[0]; | 234 | 433 | let v_value = v_row[0]; | 235 | 433 | set_pixel(rgb1, y_value, u_value, v_value); | 236 | 433 | } | 237 | | | 238 | | // two pixels at a time since they share the same u/v value | 239 | 433 | let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2); | 240 | 433 | let mut main_y_row_chunks = y_row[1..].chunks_exact(2); | 241 | | | 242 | 393k | for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks) | 243 | 433 | .zip(&mut main_y_row_chunks) | 244 | 433 | .zip(u_row.windows(2)) | 245 | 433 | .zip(v_row.windows(2)) | 246 | | { | 247 | 393k | { | 248 | 393k | let rgb1 = &mut rgb[0..3]; | 249 | 393k | let y_value = y_val[0]; | 250 | 393k | // first pixel uses the first u/v as the main one | 251 | 393k | let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]); | 252 | 393k | let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]); | 253 | 393k | set_pixel(rgb1, y_value, u_value, v_value); | 254 | 393k | } | 255 | 393k | { | 256 | 393k | let rgb2 = &mut rgb[BPP..]; | 257 | 393k | let y_value = y_val[1]; | 258 | 393k | let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]); | 259 | 393k | let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]); | 260 | 393k | set_pixel(rgb2, y_value, u_value, v_value); | 261 | 393k | } | 262 | | } | 263 | | | 264 | 433 | let final_pixel = main_row_chunks.into_remainder(); | 265 | 433 | let final_y = main_y_row_chunks.remainder(); | 266 | | | 267 | 433 | if let (rgb, [final_y]) = (final_pixel, final_y) { | 268 | 196 | let final_u = *u_row.last().unwrap(); | 269 | 196 | let final_v = *v_row.last().unwrap(); | 270 | 196 | | 271 | 196 | set_pixel(rgb, *final_y, final_u, final_v); | 272 | 237 | } | 273 | 433 | } |
image_webp::yuv::fill_row_fancy_with_1_uv_row::<4> Line | Count | Source | 222 | 726 | fn fill_row_fancy_with_1_uv_row<const BPP: usize>( | 223 | 726 | row_buffer: &mut [u8], | 224 | 726 | y_row: &[u8], | 225 | 726 | u_row: &[u8], | 226 | 726 | v_row: &[u8], | 227 | 726 | ) { | 228 | | // doing left pixel first | 229 | 726 | { | 230 | 726 | let rgb1 = &mut row_buffer[0..3]; | 231 | 726 | let y_value = y_row[0]; | 232 | 726 | | 233 | 726 | let u_value = u_row[0]; | 234 | 726 | let v_value = v_row[0]; | 235 | 726 | set_pixel(rgb1, y_value, u_value, v_value); | 236 | 726 | } | 237 | | | 238 | | // two pixels at a time since they share the same u/v value | 239 | 726 | let mut main_row_chunks = row_buffer[BPP..].chunks_exact_mut(BPP * 2); | 240 | 726 | let mut main_y_row_chunks = y_row[1..].chunks_exact(2); | 241 | | | 242 | 51.0k | for (((rgb, y_val), u_val), v_val) in (&mut main_row_chunks) | 243 | 726 | .zip(&mut main_y_row_chunks) | 244 | 726 | .zip(u_row.windows(2)) | 245 | 726 | .zip(v_row.windows(2)) | 246 | | { | 247 | 51.0k | { | 248 | 51.0k | let rgb1 = &mut rgb[0..3]; | 249 | 51.0k | let y_value = y_val[0]; | 250 | 51.0k | // first pixel uses the first u/v as the main one | 251 | 51.0k | let u_value = get_fancy_chroma_value(u_val[0], u_val[1], u_val[0], u_val[1]); | 252 | 51.0k | let v_value = get_fancy_chroma_value(v_val[0], v_val[1], v_val[0], v_val[1]); | 253 | 51.0k | set_pixel(rgb1, y_value, u_value, v_value); | 254 | 51.0k | } | 255 | 51.0k | { | 256 | 51.0k | let rgb2 = &mut rgb[BPP..]; | 257 | 51.0k | let y_value = y_val[1]; | 258 | 51.0k | let u_value = get_fancy_chroma_value(u_val[1], u_val[0], u_val[1], u_val[0]); | 259 | 51.0k | let v_value = get_fancy_chroma_value(v_val[1], v_val[0], v_val[1], v_val[0]); | 260 | 51.0k | set_pixel(rgb2, y_value, u_value, v_value); | 261 | 51.0k | } | 262 | | } | 263 | | | 264 | 726 | let final_pixel = main_row_chunks.into_remainder(); | 265 | 726 | let final_y = main_y_row_chunks.remainder(); | 266 | | | 267 | 726 | if let (rgb, [final_y]) = (final_pixel, final_y) { | 268 | 704 | let final_u = *u_row.last().unwrap(); | 269 | 704 | let final_v = *v_row.last().unwrap(); | 270 | 704 | | 271 | 704 | set_pixel(rgb, *final_y, final_u, final_v); | 272 | 704 | } | 273 | 726 | } |
|
274 | | |
275 | | #[inline] |
276 | 489M | fn get_fancy_chroma_value(main: u8, secondary1: u8, secondary2: u8, tertiary: u8) -> u8 { |
277 | 489M | let val0 = u16::from(main); |
278 | 489M | let val1 = u16::from(secondary1); |
279 | 489M | let val2 = u16::from(secondary2); |
280 | 489M | let val3 = u16::from(tertiary); |
281 | 489M | ((9 * val0 + 3 * val1 + 3 * val2 + val3 + 8) / 16) as u8 |
282 | 489M | } |
283 | | |
284 | | #[inline] |
285 | 244M | fn set_pixel(rgb: &mut [u8], y: u8, u: u8, v: u8) { |
286 | 244M | rgb[0] = yuv_to_r(y, v); |
287 | 244M | rgb[1] = yuv_to_g(y, u, v); |
288 | 244M | rgb[2] = yuv_to_b(y, u); |
289 | 244M | } |
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 | | } |