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