1 // Copyright 2024 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 use super::coeffs::*;
16 use super::rgb;
17 use super::rgb::*;
18
19 use crate::image::Plane;
20 use crate::image::PlaneRow;
21 use crate::image::YuvRange;
22 use crate::internal_utils::*;
23 use crate::*;
24
25 use std::cmp::min;
26
27 #[derive(Clone, Copy, PartialEq)]
28 enum Mode {
29 YuvCoefficients(f32, f32, f32),
30 Identity,
31 Ycgco,
32 YcgcoRe,
33 YcgcoRo,
34 }
35
36 impl From<&image::Image> for Mode {
from(image: &image::Image) -> Self37 fn from(image: &image::Image) -> Self {
38 match image.matrix_coefficients {
39 MatrixCoefficients::Identity => Mode::Identity,
40 MatrixCoefficients::Ycgco => Mode::Ycgco,
41 MatrixCoefficients::YcgcoRe => Mode::YcgcoRe,
42 MatrixCoefficients::YcgcoRo => Mode::YcgcoRo,
43 _ => {
44 let coeffs =
45 calculate_yuv_coefficients(image.color_primaries, image.matrix_coefficients);
46 Mode::YuvCoefficients(coeffs[0], coeffs[1], coeffs[2])
47 }
48 }
49 }
50 }
51
identity_yuv8_to_rgb8_full_range(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<()>52 fn identity_yuv8_to_rgb8_full_range(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<()> {
53 if image.yuv_format != PixelFormat::Yuv444 || rgb.format == Format::Rgb565 {
54 return Err(AvifError::NotImplemented);
55 }
56
57 let r_offset = rgb.format.r_offset();
58 let g_offset = rgb.format.g_offset();
59 let b_offset = rgb.format.b_offset();
60 let channel_count = rgb.channel_count() as usize;
61 for i in 0..image.height {
62 let y = image.row(Plane::Y, i)?;
63 let u = image.row(Plane::U, i)?;
64 let v = image.row(Plane::V, i)?;
65 let rgb_pixels = rgb.row_mut(i)?;
66 for j in 0..image.width as usize {
67 rgb_pixels[(j * channel_count) + r_offset] = v[j];
68 rgb_pixels[(j * channel_count) + g_offset] = y[j];
69 rgb_pixels[(j * channel_count) + b_offset] = u[j];
70 }
71 }
72 Ok(())
73 }
74
75 // This is a macro and not a function because this is invoked per-pixel and there is a non-trivial
76 // performance impact if this is made into a function call.
77 macro_rules! store_rgb_pixel8 {
78 ($dst:ident, $rgb_565: ident, $index: ident, $r: ident, $g: ident, $b: ident, $r_offset: ident,
79 $g_offset: ident, $b_offset: ident, $rgb_channel_count: ident, $rgb_max_channel_f: ident) => {
80 let r8 = (0.5 + ($r * $rgb_max_channel_f)) as u8;
81 let g8 = (0.5 + ($g * $rgb_max_channel_f)) as u8;
82 let b8 = (0.5 + ($b * $rgb_max_channel_f)) as u8;
83 if $rgb_565 {
84 // References for RGB565 color conversion:
85 // * https://docs.microsoft.com/en-us/windows/win32/directshow/working-with-16-bit-rgb
86 // * https://chromium.googlesource.com/libyuv/libyuv/+/9892d70c965678381d2a70a1c9002d1cf136ee78/source/row_common.cc#2362
87 let r16 = ((r8 >> 3) as u16) << 11;
88 let g16 = ((g8 >> 2) as u16) << 5;
89 let b16 = (b8 >> 3) as u16;
90 let rgb565 = (r16 | g16 | b16).to_le_bytes();
91 $dst[($index * $rgb_channel_count) + $r_offset] = rgb565[0];
92 $dst[($index * $rgb_channel_count) + $r_offset + 1] = rgb565[1];
93 } else {
94 $dst[($index * $rgb_channel_count) + $r_offset] = r8;
95 $dst[($index * $rgb_channel_count) + $g_offset] = g8;
96 $dst[($index * $rgb_channel_count) + $b_offset] = b8;
97 }
98 };
99 }
100
yuv8_to_rgb8_color( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>101 fn yuv8_to_rgb8_color(
102 image: &image::Image,
103 rgb: &mut rgb::Image,
104 kr: f32,
105 kg: f32,
106 kb: f32,
107 ) -> AvifResult<()> {
108 let (table_y, table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
109 let table_uv = match &table_uv {
110 Some(table_uv) => table_uv,
111 None => &table_y,
112 };
113 let rgb_max_channel_f = rgb.max_channel_f();
114 let r_offset = rgb.format.r_offset();
115 let g_offset = rgb.format.g_offset();
116 let b_offset = rgb.format.b_offset();
117 let rgb_channel_count = rgb.channel_count() as usize;
118 let rgb_565 = rgb.format == rgb::Format::Rgb565;
119 let chroma_shift = image.yuv_format.chroma_shift_x();
120 for j in 0..image.height {
121 let uv_j = j >> image.yuv_format.chroma_shift_y();
122 let y_row = image.row(Plane::Y, j)?;
123 let u_row = image.row(Plane::U, uv_j)?;
124 let v_row = image.row(Plane::V, uv_j)?;
125 let dst = rgb.row_mut(j)?;
126 for i in 0..image.width as usize {
127 let uv_i = (i >> chroma_shift.0) << chroma_shift.1;
128 let y = table_y[y_row[i] as usize];
129 let cb = table_uv[u_row[uv_i] as usize];
130 let cr = table_uv[v_row[uv_i] as usize];
131 let r = y + (2.0 * (1.0 - kr)) * cr;
132 let b = y + (2.0 * (1.0 - kb)) * cb;
133 let g = y - ((2.0 * ((kr * (1.0 - kr) * cr) + (kb * (1.0 - kb) * cb))) / kg);
134 let r = clamp_f32(r, 0.0, 1.0);
135 let g = clamp_f32(g, 0.0, 1.0);
136 let b = clamp_f32(b, 0.0, 1.0);
137 store_rgb_pixel8!(
138 dst,
139 rgb_565,
140 i,
141 r,
142 g,
143 b,
144 r_offset,
145 g_offset,
146 b_offset,
147 rgb_channel_count,
148 rgb_max_channel_f
149 );
150 }
151 }
152 Ok(())
153 }
154
yuv16_to_rgb16_color( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>155 fn yuv16_to_rgb16_color(
156 image: &image::Image,
157 rgb: &mut rgb::Image,
158 kr: f32,
159 kg: f32,
160 kb: f32,
161 ) -> AvifResult<()> {
162 let (table_y, table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
163 let table_uv = match &table_uv {
164 Some(table_uv) => table_uv,
165 None => &table_y,
166 };
167 let yuv_max_channel = image.max_channel();
168 let rgb_max_channel_f = rgb.max_channel_f();
169 let r_offset = rgb.format.r_offset();
170 let g_offset = rgb.format.g_offset();
171 let b_offset = rgb.format.b_offset();
172 let rgb_channel_count = rgb.channel_count() as usize;
173 let chroma_shift = image.yuv_format.chroma_shift_x();
174 for j in 0..image.height {
175 let uv_j = j >> image.yuv_format.chroma_shift_y();
176 let y_row = image.row16(Plane::Y, j)?;
177 let u_row = image.row16(Plane::U, uv_j)?;
178 // If V plane is missing, then the format is P010. In that case, set V
179 // as U plane but starting at offset 1.
180 let v_row = image.row16(Plane::V, uv_j).unwrap_or(&u_row[1..]);
181 let dst = rgb.row16_mut(j)?;
182 for i in 0..image.width as usize {
183 let uv_i = (i >> chroma_shift.0) << chroma_shift.1;
184 let y = table_y[min(y_row[i], yuv_max_channel) as usize];
185 let cb = table_uv[min(u_row[uv_i], yuv_max_channel) as usize];
186 let cr = table_uv[min(v_row[uv_i], yuv_max_channel) as usize];
187 let r = y + (2.0 * (1.0 - kr)) * cr;
188 let b = y + (2.0 * (1.0 - kb)) * cb;
189 let g = y - ((2.0 * ((kr * (1.0 - kr) * cr) + (kb * (1.0 - kb) * cb))) / kg);
190 let r = clamp_f32(r, 0.0, 1.0);
191 let g = clamp_f32(g, 0.0, 1.0);
192 let b = clamp_f32(b, 0.0, 1.0);
193 dst[(i * rgb_channel_count) + r_offset] = (0.5 + (r * rgb_max_channel_f)) as u16;
194 dst[(i * rgb_channel_count) + g_offset] = (0.5 + (g * rgb_max_channel_f)) as u16;
195 dst[(i * rgb_channel_count) + b_offset] = (0.5 + (b * rgb_max_channel_f)) as u16;
196 }
197 }
198 Ok(())
199 }
200
yuv16_to_rgb8_color( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>201 fn yuv16_to_rgb8_color(
202 image: &image::Image,
203 rgb: &mut rgb::Image,
204 kr: f32,
205 kg: f32,
206 kb: f32,
207 ) -> AvifResult<()> {
208 let (table_y, table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
209 let table_uv = match &table_uv {
210 Some(table_uv) => table_uv,
211 None => &table_y,
212 };
213 let yuv_max_channel = image.max_channel();
214 let rgb_max_channel_f = rgb.max_channel_f();
215 let r_offset = rgb.format.r_offset();
216 let g_offset = rgb.format.g_offset();
217 let b_offset = rgb.format.b_offset();
218 let rgb_channel_count = rgb.channel_count() as usize;
219 let rgb_565 = rgb.format == rgb::Format::Rgb565;
220 let chroma_shift = image.yuv_format.chroma_shift_x();
221 for j in 0..image.height {
222 let uv_j = j >> image.yuv_format.chroma_shift_y();
223 let y_row = image.row16(Plane::Y, j)?;
224 let u_row = image.row16(Plane::U, uv_j)?;
225 // If V plane is missing, then the format is P010. In that case, set V
226 // as U plane but starting at offset 1.
227 let v_row = image.row16(Plane::V, uv_j).unwrap_or(&u_row[1..]);
228 let dst = rgb.row_mut(j)?;
229 for i in 0..image.width as usize {
230 let uv_i = (i >> chroma_shift.0) << chroma_shift.1;
231 let y = table_y[min(y_row[i], yuv_max_channel) as usize];
232 let cb = table_uv[min(u_row[uv_i], yuv_max_channel) as usize];
233 let cr = table_uv[min(v_row[uv_i], yuv_max_channel) as usize];
234 let r = y + (2.0 * (1.0 - kr)) * cr;
235 let b = y + (2.0 * (1.0 - kb)) * cb;
236 let g = y - ((2.0 * ((kr * (1.0 - kr) * cr) + (kb * (1.0 - kb) * cb))) / kg);
237 let r = clamp_f32(r, 0.0, 1.0);
238 let g = clamp_f32(g, 0.0, 1.0);
239 let b = clamp_f32(b, 0.0, 1.0);
240 store_rgb_pixel8!(
241 dst,
242 rgb_565,
243 i,
244 r,
245 g,
246 b,
247 r_offset,
248 g_offset,
249 b_offset,
250 rgb_channel_count,
251 rgb_max_channel_f
252 );
253 }
254 }
255 Ok(())
256 }
257
yuv8_to_rgb16_color( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>258 fn yuv8_to_rgb16_color(
259 image: &image::Image,
260 rgb: &mut rgb::Image,
261 kr: f32,
262 kg: f32,
263 kb: f32,
264 ) -> AvifResult<()> {
265 let (table_y, table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
266 let table_uv = match &table_uv {
267 Some(table_uv) => table_uv,
268 None => &table_y,
269 };
270 let rgb_max_channel_f = rgb.max_channel_f();
271 let r_offset = rgb.format.r_offset();
272 let g_offset = rgb.format.g_offset();
273 let b_offset = rgb.format.b_offset();
274 let rgb_channel_count = rgb.channel_count() as usize;
275 let chroma_shift = image.yuv_format.chroma_shift_x();
276 for j in 0..image.height {
277 let uv_j = j >> image.yuv_format.chroma_shift_y();
278 let y_row = image.row(Plane::Y, j)?;
279 let u_row = image.row(Plane::U, uv_j)?;
280 let v_row = image.row(Plane::V, uv_j)?;
281 let dst = rgb.row16_mut(j)?;
282 for i in 0..image.width as usize {
283 let uv_i = (i >> chroma_shift.0) << chroma_shift.1;
284 let y = table_y[y_row[i] as usize];
285 let cb = table_uv[u_row[uv_i] as usize];
286 let cr = table_uv[v_row[uv_i] as usize];
287 let r = y + (2.0 * (1.0 - kr)) * cr;
288 let b = y + (2.0 * (1.0 - kb)) * cb;
289 let g = y - ((2.0 * ((kr * (1.0 - kr) * cr) + (kb * (1.0 - kb) * cb))) / kg);
290 let r = clamp_f32(r, 0.0, 1.0);
291 let g = clamp_f32(g, 0.0, 1.0);
292 let b = clamp_f32(b, 0.0, 1.0);
293 dst[(i * rgb_channel_count) + r_offset] = (0.5 + (r * rgb_max_channel_f)) as u16;
294 dst[(i * rgb_channel_count) + g_offset] = (0.5 + (g * rgb_max_channel_f)) as u16;
295 dst[(i * rgb_channel_count) + b_offset] = (0.5 + (b * rgb_max_channel_f)) as u16;
296 }
297 }
298 Ok(())
299 }
300
yuv8_to_rgb8_monochrome( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>301 fn yuv8_to_rgb8_monochrome(
302 image: &image::Image,
303 rgb: &mut rgb::Image,
304 kr: f32,
305 kg: f32,
306 kb: f32,
307 ) -> AvifResult<()> {
308 let (table_y, _table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
309 let rgb_max_channel_f = rgb.max_channel_f();
310 let r_offset = rgb.format.r_offset();
311 let g_offset = rgb.format.g_offset();
312 let b_offset = rgb.format.b_offset();
313 let rgb_channel_count = rgb.channel_count() as usize;
314 let rgb_565 = rgb.format == rgb::Format::Rgb565;
315 for j in 0..image.height {
316 let y_row = image.row(Plane::Y, j)?;
317 let dst = rgb.row_mut(j)?;
318 for i in 0..image.width as usize {
319 let y = table_y[y_row[i] as usize];
320 store_rgb_pixel8!(
321 dst,
322 rgb_565,
323 i,
324 y,
325 y,
326 y,
327 r_offset,
328 g_offset,
329 b_offset,
330 rgb_channel_count,
331 rgb_max_channel_f
332 );
333 }
334 }
335 Ok(())
336 }
337
yuv16_to_rgb16_monochrome( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>338 fn yuv16_to_rgb16_monochrome(
339 image: &image::Image,
340 rgb: &mut rgb::Image,
341 kr: f32,
342 kg: f32,
343 kb: f32,
344 ) -> AvifResult<()> {
345 let (table_y, _table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
346 let yuv_max_channel = image.max_channel();
347 let rgb_max_channel_f = rgb.max_channel_f();
348 let r_offset = rgb.format.r_offset();
349 let g_offset = rgb.format.g_offset();
350 let b_offset = rgb.format.b_offset();
351 let rgb_channel_count = rgb.channel_count() as usize;
352 for j in 0..image.height {
353 let y_row = image.row16(Plane::Y, j)?;
354 let dst = rgb.row16_mut(j)?;
355 for i in 0..image.width as usize {
356 let y = table_y[min(y_row[i], yuv_max_channel) as usize];
357 let rgb_pixel = (0.5 + (y * rgb_max_channel_f)) as u16;
358 dst[(i * rgb_channel_count) + r_offset] = rgb_pixel;
359 dst[(i * rgb_channel_count) + g_offset] = rgb_pixel;
360 dst[(i * rgb_channel_count) + b_offset] = rgb_pixel;
361 }
362 }
363 Ok(())
364 }
365
yuv16_to_rgb8_monochrome( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>366 fn yuv16_to_rgb8_monochrome(
367 image: &image::Image,
368 rgb: &mut rgb::Image,
369 kr: f32,
370 kg: f32,
371 kb: f32,
372 ) -> AvifResult<()> {
373 let (table_y, _table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
374 let yuv_max_channel = image.max_channel();
375 let rgb_max_channel_f = rgb.max_channel_f();
376 let r_offset = rgb.format.r_offset();
377 let g_offset = rgb.format.g_offset();
378 let b_offset = rgb.format.b_offset();
379 let rgb_channel_count = rgb.channel_count() as usize;
380 let rgb_565 = rgb.format == rgb::Format::Rgb565;
381 for j in 0..image.height {
382 let y_row = image.row16(Plane::Y, j)?;
383 let dst = rgb.row_mut(j)?;
384 for i in 0..image.width as usize {
385 let y = table_y[min(y_row[i], yuv_max_channel) as usize];
386 store_rgb_pixel8!(
387 dst,
388 rgb_565,
389 i,
390 y,
391 y,
392 y,
393 r_offset,
394 g_offset,
395 b_offset,
396 rgb_channel_count,
397 rgb_max_channel_f
398 );
399 }
400 }
401 Ok(())
402 }
403
yuv8_to_rgb16_monochrome( image: &image::Image, rgb: &mut rgb::Image, kr: f32, kg: f32, kb: f32, ) -> AvifResult<()>404 fn yuv8_to_rgb16_monochrome(
405 image: &image::Image,
406 rgb: &mut rgb::Image,
407 kr: f32,
408 kg: f32,
409 kb: f32,
410 ) -> AvifResult<()> {
411 let (table_y, _table_uv) = unorm_lookup_tables(image, Mode::YuvCoefficients(kr, kg, kb))?;
412 let rgb_max_channel_f = rgb.max_channel_f();
413 let r_offset = rgb.format.r_offset();
414 let g_offset = rgb.format.g_offset();
415 let b_offset = rgb.format.b_offset();
416 let rgb_channel_count = rgb.channel_count() as usize;
417 for j in 0..image.height {
418 let y_row = image.row(Plane::Y, j)?;
419 let dst = rgb.row16_mut(j)?;
420 for i in 0..image.width as usize {
421 let y = table_y[y_row[i] as usize];
422 let rgb_pixel = (0.5 + (y * rgb_max_channel_f)) as u16;
423 dst[(i * rgb_channel_count) + r_offset] = rgb_pixel;
424 dst[(i * rgb_channel_count) + g_offset] = rgb_pixel;
425 dst[(i * rgb_channel_count) + b_offset] = rgb_pixel;
426 }
427 }
428 Ok(())
429 }
430
yuv_to_rgb_fast(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<()>431 pub fn yuv_to_rgb_fast(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<()> {
432 let mode: Mode = image.into();
433 match mode {
434 Mode::Identity => {
435 if image.depth == 8 && rgb.depth == 8 && image.yuv_range == YuvRange::Full {
436 identity_yuv8_to_rgb8_full_range(image, rgb)
437 } else {
438 // TODO: Add more fast paths for identity.
439 Err(AvifError::NotImplemented)
440 }
441 }
442 Mode::YuvCoefficients(kr, kg, kb) => {
443 let has_color = image.yuv_format != PixelFormat::Yuv400;
444 match (image.depth == 8, rgb.depth == 8, has_color) {
445 (true, true, true) => yuv8_to_rgb8_color(image, rgb, kr, kg, kb),
446 (false, false, true) => yuv16_to_rgb16_color(image, rgb, kr, kg, kb),
447 (false, true, true) => yuv16_to_rgb8_color(image, rgb, kr, kg, kb),
448 (true, false, true) => yuv8_to_rgb16_color(image, rgb, kr, kg, kb),
449 (true, true, false) => yuv8_to_rgb8_monochrome(image, rgb, kr, kg, kb),
450 (false, false, false) => yuv16_to_rgb16_monochrome(image, rgb, kr, kg, kb),
451 (false, true, false) => yuv16_to_rgb8_monochrome(image, rgb, kr, kg, kb),
452 (true, false, false) => yuv8_to_rgb16_monochrome(image, rgb, kr, kg, kb),
453 }
454 }
455 Mode::Ycgco | Mode::YcgcoRe | Mode::YcgcoRo => Err(AvifError::NotImplemented),
456 }
457 }
458
unorm_lookup_tables( image: &image::Image, mode: Mode, ) -> AvifResult<(Vec<f32>, Option<Vec<f32>>)>459 fn unorm_lookup_tables(
460 image: &image::Image,
461 mode: Mode,
462 ) -> AvifResult<(Vec<f32>, Option<Vec<f32>>)> {
463 let count = 1usize << image.depth;
464 let mut table_y: Vec<f32> = create_vec_exact(count)?;
465 let bias_y;
466 let range_y;
467 // Formula specified in ISO/IEC 23091-2.
468 if image.yuv_range == YuvRange::Limited {
469 bias_y = (16 << (image.depth - 8)) as f32;
470 range_y = (219 << (image.depth - 8)) as f32;
471 } else {
472 bias_y = 0.0;
473 range_y = image.max_channel_f();
474 }
475 for cp in 0..count {
476 table_y.push(((cp as f32) - bias_y) / range_y);
477 }
478 if mode == Mode::Identity {
479 Ok((table_y, None))
480 } else {
481 // Formula specified in ISO/IEC 23091-2.
482 let bias_uv = (1 << (image.depth - 1)) as f32;
483 let range_uv = if image.yuv_range == YuvRange::Limited {
484 (224 << (image.depth - 8)) as f32
485 } else {
486 image.max_channel_f()
487 };
488 let mut table_uv: Vec<f32> = create_vec_exact(count)?;
489 for cp in 0..count {
490 table_uv.push(((cp as f32) - bias_uv) / range_uv);
491 }
492 Ok((table_y, Some(table_uv)))
493 }
494 }
495
496 #[allow(clippy::too_many_arguments)]
compute_rgb( y: f32, cb: f32, cr: f32, has_color: bool, mode: Mode, clamped_y: u16, yuv_max_channel: u16, rgb_max_channel: u16, rgb_max_channel_f: f32, ) -> (f32, f32, f32)497 fn compute_rgb(
498 y: f32,
499 cb: f32,
500 cr: f32,
501 has_color: bool,
502 mode: Mode,
503 clamped_y: u16,
504 yuv_max_channel: u16,
505 rgb_max_channel: u16,
506 rgb_max_channel_f: f32,
507 ) -> (f32, f32, f32) {
508 let r: f32;
509 let g: f32;
510 let b: f32;
511 if has_color {
512 match mode {
513 Mode::Identity => {
514 g = y;
515 b = cb;
516 r = cr;
517 }
518 Mode::Ycgco => {
519 let t = y - cb;
520 g = y + cb;
521 b = t - cr;
522 r = t + cr;
523 }
524 Mode::YcgcoRe | Mode::YcgcoRo => {
525 // Equations (62) through (65) in https://www.itu.int/rec/T-REC-H.273
526 let cg = (0.5 + cb * yuv_max_channel as f32).floor() as i32;
527 let co = (0.5 + cr * yuv_max_channel as f32).floor() as i32;
528 let t = clamped_y as i32 - (cg >> 1);
529 let rgb_max_channel = rgb_max_channel as i32;
530 g = clamp_i32(t + cg, 0, rgb_max_channel) as f32 / rgb_max_channel_f;
531 let tmp_b = clamp_i32(t - (co >> 1), 0, rgb_max_channel) as f32;
532 b = tmp_b / rgb_max_channel_f;
533 r = clamp_i32(tmp_b as i32 + co, 0, rgb_max_channel) as f32 / rgb_max_channel_f;
534 }
535 Mode::YuvCoefficients(kr, kg, kb) => {
536 r = y + (2.0 * (1.0 - kr)) * cr;
537 b = y + (2.0 * (1.0 - kb)) * cb;
538 g = y - ((2.0 * ((kr * (1.0 - kr) * cr) + (kb * (1.0 - kb) * cb))) / kg);
539 }
540 }
541 } else {
542 r = y;
543 g = y;
544 b = y;
545 }
546 (
547 clamp_f32(r, 0.0, 1.0),
548 clamp_f32(g, 0.0, 1.0),
549 clamp_f32(b, 0.0, 1.0),
550 )
551 }
552
clamped_pixel(row: PlaneRow, index: usize, max_channel: u16) -> u16553 fn clamped_pixel(row: PlaneRow, index: usize, max_channel: u16) -> u16 {
554 match row {
555 PlaneRow::Depth8(row) => row[index] as u16,
556 PlaneRow::Depth16(row) => min(max_channel, row[index]),
557 }
558 }
559
unorm_value(row: PlaneRow, index: usize, max_channel: u16, table: &[f32]) -> f32560 fn unorm_value(row: PlaneRow, index: usize, max_channel: u16, table: &[f32]) -> f32 {
561 table[clamped_pixel(row, index, max_channel) as usize]
562 }
563
yuv_to_rgb_any( image: &image::Image, rgb: &mut rgb::Image, alpha_multiply_mode: AlphaMultiplyMode, ) -> AvifResult<()>564 pub fn yuv_to_rgb_any(
565 image: &image::Image,
566 rgb: &mut rgb::Image,
567 alpha_multiply_mode: AlphaMultiplyMode,
568 ) -> AvifResult<()> {
569 let mode: Mode = image.into();
570 let (table_y, table_uv) = unorm_lookup_tables(image, mode)?;
571 let table_uv = match &table_uv {
572 Some(table_uv) => table_uv,
573 None => &table_y,
574 };
575 let r_offset = rgb.format.r_offset();
576 let g_offset = rgb.format.g_offset();
577 let b_offset = rgb.format.b_offset();
578 let rgb_channel_count = rgb.channel_count() as usize;
579 let rgb_depth = rgb.depth;
580 let chroma_upsampling = rgb.chroma_upsampling;
581 let has_color = image.has_plane(Plane::U)
582 && image.has_plane(Plane::V)
583 && image.yuv_format != PixelFormat::Yuv400;
584 let yuv_max_channel = image.max_channel();
585 let rgb_max_channel = rgb.max_channel();
586 let rgb_max_channel_f = rgb.max_channel_f();
587 let chroma_shift = image.yuv_format.chroma_shift_x();
588 for j in 0..image.height {
589 let uv_j = j >> image.yuv_format.chroma_shift_y();
590 let y_row = image.row_generic(Plane::Y, j)?;
591 let u_row = image.row_generic(Plane::U, uv_j).ok();
592 let v_row = image.row_generic(Plane::V, uv_j).ok();
593 let a_row = image.row_generic(Plane::A, j).ok();
594 for i in 0..image.width as usize {
595 let clamped_y = clamped_pixel(y_row, i, yuv_max_channel);
596 let y = table_y[clamped_y as usize];
597 let mut cb = 0.5;
598 let mut cr = 0.5;
599 if has_color {
600 let u_row = u_row.unwrap();
601 let v_row = v_row.unwrap();
602 let uv_i = (i >> chroma_shift.0) << chroma_shift.1;
603 if image.yuv_format == PixelFormat::Yuv444
604 || matches!(
605 chroma_upsampling,
606 ChromaUpsampling::Fastest | ChromaUpsampling::Nearest
607 )
608 {
609 cb = unorm_value(u_row, uv_i, yuv_max_channel, table_uv);
610 cr = unorm_value(v_row, uv_i, yuv_max_channel, table_uv);
611 } else {
612 if image.chroma_sample_position != ChromaSamplePosition::CENTER {
613 return Err(AvifError::NotImplemented);
614 }
615
616 // Bilinear filtering with weights. See
617 // https://github.com/AOMediaCodec/libavif/blob/0580334466d57fedb889d5ed7ae9574d6f66e00c/src/reformat.c#L657-L685.
618 let image_width_minus_1 = (image.width - 1) as usize;
619 let uv_adj_i = if i == 0 || (i == image_width_minus_1 && (i % 2) != 0) {
620 uv_i
621 } else if (i % 2) != 0 {
622 uv_i + 1
623 } else {
624 uv_i - 1
625 };
626 let uv_adj_j = if j == 0
627 || (j == image.height - 1 && (j % 2) != 0)
628 || image.yuv_format == PixelFormat::Yuv422
629 {
630 uv_j
631 } else if (j % 2) != 0 {
632 uv_j + 1
633 } else {
634 uv_j - 1
635 };
636 let u_adj_row = image.row_generic(Plane::U, uv_adj_j)?;
637 let v_adj_row = image.row_generic(Plane::V, uv_adj_j)?;
638 let mut unorm_u: [[f32; 2]; 2] = [[0.0; 2]; 2];
639 let mut unorm_v: [[f32; 2]; 2] = [[0.0; 2]; 2];
640 unorm_u[0][0] = unorm_value(u_row, uv_i, yuv_max_channel, table_uv);
641 unorm_v[0][0] = unorm_value(v_row, uv_i, yuv_max_channel, table_uv);
642 unorm_u[1][0] = unorm_value(u_row, uv_adj_i, yuv_max_channel, table_uv);
643 unorm_v[1][0] = unorm_value(v_row, uv_adj_i, yuv_max_channel, table_uv);
644 unorm_u[0][1] = unorm_value(u_adj_row, uv_i, yuv_max_channel, table_uv);
645 unorm_v[0][1] = unorm_value(v_adj_row, uv_i, yuv_max_channel, table_uv);
646 unorm_u[1][1] = unorm_value(u_adj_row, uv_adj_i, yuv_max_channel, table_uv);
647 unorm_v[1][1] = unorm_value(v_adj_row, uv_adj_i, yuv_max_channel, table_uv);
648 cb = (unorm_u[0][0] * (9.0 / 16.0))
649 + (unorm_u[1][0] * (3.0 / 16.0))
650 + (unorm_u[0][1] * (3.0 / 16.0))
651 + (unorm_u[1][1] * (1.0 / 16.0));
652 cr = (unorm_v[0][0] * (9.0 / 16.0))
653 + (unorm_v[1][0] * (3.0 / 16.0))
654 + (unorm_v[0][1] * (3.0 / 16.0))
655 + (unorm_v[1][1] * (1.0 / 16.0));
656 }
657 }
658 let (mut rc, mut gc, mut bc) = compute_rgb(
659 y,
660 cb,
661 cr,
662 has_color,
663 mode,
664 clamped_y,
665 yuv_max_channel,
666 rgb_max_channel,
667 rgb_max_channel_f,
668 );
669 if alpha_multiply_mode != AlphaMultiplyMode::NoOp {
670 let unorm_a = clamped_pixel(a_row.unwrap(), i, yuv_max_channel);
671 let ac = clamp_f32((unorm_a as f32) / (yuv_max_channel as f32), 0.0, 1.0);
672 if ac == 0.0 {
673 rc = 0.0;
674 gc = 0.0;
675 bc = 0.0;
676 } else if ac < 1.0 {
677 match alpha_multiply_mode {
678 AlphaMultiplyMode::Multiply => {
679 rc *= ac;
680 gc *= ac;
681 bc *= ac;
682 }
683 AlphaMultiplyMode::UnMultiply => {
684 rc = f32::min(rc / ac, 1.0);
685 gc = f32::min(gc / ac, 1.0);
686 bc = f32::min(bc / ac, 1.0);
687 }
688 _ => {} // Not reached.
689 }
690 }
691 }
692 if rgb_depth == 8 {
693 let dst = rgb.row_mut(j)?;
694 dst[(i * rgb_channel_count) + r_offset] = (0.5 + (rc * rgb_max_channel_f)) as u8;
695 dst[(i * rgb_channel_count) + g_offset] = (0.5 + (gc * rgb_max_channel_f)) as u8;
696 dst[(i * rgb_channel_count) + b_offset] = (0.5 + (bc * rgb_max_channel_f)) as u8;
697 } else {
698 let dst16 = rgb.row16_mut(j)?;
699 dst16[(i * rgb_channel_count) + r_offset] = (0.5 + (rc * rgb_max_channel_f)) as u16;
700 dst16[(i * rgb_channel_count) + g_offset] = (0.5 + (gc * rgb_max_channel_f)) as u16;
701 dst16[(i * rgb_channel_count) + b_offset] = (0.5 + (bc * rgb_max_channel_f)) as u16;
702 }
703 }
704 }
705 Ok(())
706 }
707
708 #[cfg(test)]
709 mod tests {
710 use super::*;
711
712 #[test]
yuv_to_rgb()713 fn yuv_to_rgb() {
714 fn create_420(
715 matrix_coefficients: MatrixCoefficients,
716 y: &[&[u8]],
717 u: &[&[u8]],
718 v: &[&[u8]],
719 ) -> image::Image {
720 let mut yuv = image::Image {
721 width: y[0].len() as u32,
722 height: y.len() as u32,
723 depth: 8,
724 yuv_format: PixelFormat::Yuv420,
725 matrix_coefficients,
726 yuv_range: YuvRange::Limited,
727 ..Default::default()
728 };
729 assert!(yuv.allocate_planes(decoder::Category::Color).is_ok());
730 for plane in image::YUV_PLANES {
731 let samples = if plane == Plane::Y {
732 &y
733 } else if plane == Plane::U {
734 &u
735 } else {
736 &v
737 };
738 assert_eq!(yuv.height(plane), samples.len());
739 for y in 0..yuv.height(plane) {
740 assert_eq!(yuv.width(plane), samples[y].len());
741 for x in 0..yuv.width(plane) {
742 yuv.row_mut(plane, y as u32).unwrap()[x] = samples[y][x];
743 }
744 }
745 }
746 yuv
747 }
748 fn assert_near(yuv: &image::Image, r: &[&[u8]], g: &[&[u8]], b: &[&[u8]]) {
749 let mut dst = rgb::Image::create_from_yuv(yuv);
750 dst.format = rgb::Format::Rgb;
751 dst.chroma_upsampling = ChromaUpsampling::Bilinear;
752 assert!(dst.allocate().is_ok());
753 assert!(yuv_to_rgb_any(yuv, &mut dst, AlphaMultiplyMode::NoOp).is_ok());
754 assert_eq!(dst.height, r.len() as u32);
755 assert_eq!(dst.height, g.len() as u32);
756 assert_eq!(dst.height, b.len() as u32);
757 for y in 0..dst.height {
758 assert_eq!(dst.width, r[y as usize].len() as u32);
759 assert_eq!(dst.width, g[y as usize].len() as u32);
760 assert_eq!(dst.width, b[y as usize].len() as u32);
761 for x in 0..dst.width {
762 let i = (x * dst.pixel_size() + 0) as usize;
763 let pixel = &dst.row(y).unwrap()[i..i + 3];
764 assert_eq!(pixel[0], r[y as usize][x as usize]);
765 assert_eq!(pixel[1], g[y as usize][x as usize]);
766 assert_eq!(pixel[2], b[y as usize][x as usize]);
767 }
768 }
769 }
770
771 // Testing identity 4:2:0 -> RGB would be simpler to check upsampling
772 // but this is not allowed (not a real use case).
773 assert_near(
774 &create_420(
775 MatrixCoefficients::Bt601,
776 /*y=*/
777 &[
778 &[0, 100, 200], //
779 &[10, 110, 210], //
780 &[50, 150, 250],
781 ],
782 /*u=*/
783 &[
784 &[0, 100], //
785 &[10, 110],
786 ],
787 /*v=*/
788 &[
789 &[57, 57], //
790 &[57, 57],
791 ],
792 ),
793 /*r=*/
794 &[
795 &[0, 0, 101], //
796 &[0, 0, 113], //
797 &[0, 43, 159],
798 ],
799 /*g=*/
800 &[
801 &[89, 196, 255], //
802 &[100, 207, 255], //
803 &[145, 251, 255],
804 ],
805 /*b=*/
806 &[
807 &[0, 0, 107], //
808 &[0, 0, 124], //
809 &[0, 0, 181],
810 ],
811 );
812
813 // Extreme values.
814 assert_near(
815 &create_420(
816 MatrixCoefficients::Bt601,
817 /*y=*/ &[&[0]],
818 /*u=*/ &[&[0]],
819 /*v=*/ &[&[0]],
820 ),
821 /*r=*/ &[&[0]],
822 /*g=*/ &[&[136]],
823 /*b=*/ &[&[0]],
824 );
825 assert_near(
826 &create_420(
827 MatrixCoefficients::Bt601,
828 /*y=*/ &[&[255]],
829 /*u=*/ &[&[255]],
830 /*v=*/ &[&[255]],
831 ),
832 /*r=*/ &[&[255]],
833 /*g=*/ &[&[125]],
834 /*b=*/ &[&[255]],
835 );
836
837 // Top-right square "bleeds" into other samples during upsampling.
838 assert_near(
839 &create_420(
840 MatrixCoefficients::Bt601,
841 /*y=*/
842 &[
843 &[0, 0, 255, 255],
844 &[0, 0, 255, 255],
845 &[0, 0, 0, 0],
846 &[0, 0, 0, 0],
847 ],
848 /*u=*/
849 &[
850 &[0, 255], //
851 &[0, 0],
852 ],
853 /*v=*/
854 &[
855 &[0, 255], //
856 &[0, 0],
857 ],
858 ),
859 /*r=*/
860 &[
861 &[0, 0, 255, 255],
862 &[0, 0, 255, 255],
863 &[0, 0, 0, 0],
864 &[0, 0, 0, 0],
865 ],
866 /*g=*/
867 &[
868 &[136, 59, 202, 125],
869 &[136, 78, 255, 202],
870 &[136, 116, 78, 59],
871 &[136, 136, 136, 136],
872 ],
873 /*b=*/
874 &[
875 &[0, 0, 255, 255],
876 &[0, 0, 255, 255],
877 &[0, 0, 0, 0],
878 &[0, 0, 0, 0],
879 ],
880 );
881
882 // Middle square does not "bleed" into other samples during upsampling.
883 assert_near(
884 &create_420(
885 MatrixCoefficients::Bt601,
886 /*y=*/
887 &[
888 &[0, 0, 0, 0],
889 &[0, 255, 255, 0],
890 &[0, 255, 255, 0],
891 &[0, 0, 0, 0],
892 ],
893 /*u=*/
894 &[
895 &[0, 0], //
896 &[0, 0],
897 ],
898 /*v=*/
899 &[
900 &[0, 0], //
901 &[0, 0],
902 ],
903 ),
904 /*r=*/
905 &[
906 &[0, 0, 0, 0],
907 &[0, 74, 74, 0],
908 &[0, 74, 74, 0],
909 &[0, 0, 0, 0],
910 ],
911 /*g=*/
912 &[
913 &[136, 136, 136, 136],
914 &[136, 255, 255, 136],
915 &[136, 255, 255, 136],
916 &[136, 136, 136, 136],
917 ],
918 /*b=*/
919 &[
920 &[0, 0, 0, 0],
921 &[0, 20, 20, 0],
922 &[0, 20, 20, 0],
923 &[0, 0, 0, 0],
924 ],
925 );
926 }
927 }
928