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