• 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::rgb;
16 use super::rgb::*;
17 
18 use crate::image::*;
19 use crate::internal_utils::*;
20 use crate::*;
21 
22 use libyuv_sys::bindings::*;
23 
24 use std::os::raw::c_int;
25 
find_constants(image: &image::Image) -> Option<(&YuvConstants, &YuvConstants)>26 fn find_constants(image: &image::Image) -> Option<(&YuvConstants, &YuvConstants)> {
27     let matrix_coefficients = if image.yuv_format == PixelFormat::Yuv400
28         && image.matrix_coefficients == MatrixCoefficients::Identity
29     {
30         MatrixCoefficients::Bt601
31     } else {
32         image.matrix_coefficients
33     };
34     // Android MediaCodec always uses Yuv420. So use Bt601 instead of Identity in that case.
35     #[cfg(feature = "android_mediacodec")]
36     let matrix_coefficients = if matrix_coefficients == MatrixCoefficients::Identity {
37         MatrixCoefficients::Bt601
38     } else {
39         matrix_coefficients
40     };
41     unsafe {
42         match image.yuv_range {
43             YuvRange::Full => match matrix_coefficients {
44                 MatrixCoefficients::Bt709 => Some((&kYuvF709Constants, &kYvuF709Constants)),
45                 MatrixCoefficients::Bt470bg
46                 | MatrixCoefficients::Bt601
47                 | MatrixCoefficients::Unspecified => Some((&kYuvJPEGConstants, &kYvuJPEGConstants)),
48                 MatrixCoefficients::Bt2020Ncl => Some((&kYuvV2020Constants, &kYvuV2020Constants)),
49                 MatrixCoefficients::ChromaDerivedNcl => match image.color_primaries {
50                     ColorPrimaries::Srgb | ColorPrimaries::Unspecified => {
51                         Some((&kYuvF709Constants, &kYvuF709Constants))
52                     }
53                     ColorPrimaries::Bt470bg | ColorPrimaries::Bt601 => {
54                         Some((&kYuvJPEGConstants, &kYvuJPEGConstants))
55                     }
56                     ColorPrimaries::Bt2020 => Some((&kYuvV2020Constants, &kYvuV2020Constants)),
57                     _ => None,
58                 },
59                 _ => None,
60             },
61             YuvRange::Limited => match matrix_coefficients {
62                 MatrixCoefficients::Bt709 => Some((&kYuvH709Constants, &kYvuH709Constants)),
63                 MatrixCoefficients::Bt470bg
64                 | MatrixCoefficients::Bt601
65                 | MatrixCoefficients::Unspecified => Some((&kYuvI601Constants, &kYvuI601Constants)),
66                 MatrixCoefficients::Bt2020Ncl => Some((&kYuv2020Constants, &kYvu2020Constants)),
67                 MatrixCoefficients::ChromaDerivedNcl => match image.color_primaries {
68                     ColorPrimaries::Srgb | ColorPrimaries::Unspecified => {
69                         Some((&kYuvH709Constants, &kYvuH709Constants))
70                     }
71                     ColorPrimaries::Bt470bg | ColorPrimaries::Bt601 => {
72                         Some((&kYuvI601Constants, &kYvuI601Constants))
73                     }
74                     ColorPrimaries::Bt2020 => Some((&kYuv2020Constants, &kYvu2020Constants)),
75                     _ => None,
76                 },
77                 _ => None,
78             },
79         }
80     }
81 }
82 
83 #[rustfmt::skip]
84 type YUV400ToRGBMatrix = unsafe extern "C" fn(
85     *const u8, c_int, *mut u8, c_int, *const YuvConstants, c_int, c_int) -> c_int;
86 #[rustfmt::skip]
87 type YUVToRGBMatrixFilter = unsafe extern "C" fn(
88     *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants,
89     c_int, c_int, FilterMode) -> c_int;
90 #[rustfmt::skip]
91 type YUVAToRGBMatrixFilter = unsafe extern "C" fn(
92     *const u8, c_int, *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int,
93     *const YuvConstants, c_int, c_int, c_int, FilterMode) -> c_int;
94 #[rustfmt::skip]
95 type YUVToRGBMatrix = unsafe extern "C" fn(
96     *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants,
97     c_int, c_int) -> c_int;
98 #[rustfmt::skip]
99 type YUVAToRGBMatrix = unsafe extern "C" fn(
100     *const u8, c_int, *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int,
101     *const YuvConstants, c_int, c_int, c_int) -> c_int;
102 #[rustfmt::skip]
103 type YUVToRGBMatrixFilterHighBitDepth = unsafe extern "C" fn(
104     *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants,
105     c_int, c_int, FilterMode) -> c_int;
106 #[rustfmt::skip]
107 type YUVAToRGBMatrixFilterHighBitDepth = unsafe extern "C" fn(
108     *const u16, c_int, *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int,
109     *const YuvConstants, c_int, c_int, c_int, FilterMode) -> c_int;
110 #[rustfmt::skip]
111 type YUVToRGBMatrixHighBitDepth = unsafe extern "C" fn(
112     *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants,
113     c_int, c_int) -> c_int;
114 #[rustfmt::skip]
115 type YUVAToRGBMatrixHighBitDepth = unsafe extern "C" fn(
116     *const u16, c_int, *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int,
117     *const YuvConstants, c_int, c_int, c_int) -> c_int;
118 #[rustfmt::skip]
119 type P010ToRGBMatrix = unsafe extern "C" fn(
120     *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants, c_int,
121     c_int) -> c_int;
122 #[rustfmt::skip]
123 type ARGBToABGR = unsafe extern "C" fn(
124     *const u8, c_int, *mut u8, c_int, c_int, c_int) -> c_int;
125 #[rustfmt::skip]
126 type NVToARGBMatrix = unsafe extern "C" fn(
127     *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants, c_int,
128     c_int) -> c_int;
129 
130 #[derive(Debug)]
131 enum ConversionFunction {
132     YUV400ToRGBMatrix(YUV400ToRGBMatrix),
133     YUVToRGBMatrixFilter(YUVToRGBMatrixFilter),
134     YUVAToRGBMatrixFilter(YUVAToRGBMatrixFilter),
135     YUVToRGBMatrix(YUVToRGBMatrix),
136     YUVAToRGBMatrix(YUVAToRGBMatrix),
137     YUVToRGBMatrixFilterHighBitDepth(YUVToRGBMatrixFilterHighBitDepth),
138     YUVAToRGBMatrixFilterHighBitDepth(YUVAToRGBMatrixFilterHighBitDepth),
139     YUVToRGBMatrixHighBitDepth(YUVToRGBMatrixHighBitDepth),
140     YUVAToRGBMatrixHighBitDepth(YUVAToRGBMatrixHighBitDepth),
141     P010ToRGBMatrix(P010ToRGBMatrix, ARGBToABGR),
142     YUVToAB30Matrix(YUVToRGBMatrixHighBitDepth, ARGBToABGR),
143     NVToARGBMatrix(NVToARGBMatrix),
144 }
145 
146 impl ConversionFunction {
is_yuva(&self) -> bool147     fn is_yuva(&self) -> bool {
148         matches!(
149             self,
150             ConversionFunction::YUVAToRGBMatrixFilter(_)
151                 | ConversionFunction::YUVAToRGBMatrix(_)
152                 | ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(_)
153                 | ConversionFunction::YUVAToRGBMatrixHighBitDepth(_)
154         )
155     }
156 }
157 
find_conversion_function( yuv_format: PixelFormat, yuv_depth: u8, rgb: &rgb::Image, alpha_preferred: bool, ) -> Option<ConversionFunction>158 fn find_conversion_function(
159     yuv_format: PixelFormat,
160     yuv_depth: u8,
161     rgb: &rgb::Image,
162     alpha_preferred: bool,
163 ) -> Option<ConversionFunction> {
164     match (alpha_preferred, yuv_depth, rgb.format, yuv_format) {
165         (_, 8, Format::Rgba, PixelFormat::AndroidNv12) => {
166             // What Android considers to be NV12 is actually NV21 in libyuv.
167             Some(ConversionFunction::NVToARGBMatrix(NV21ToARGBMatrix))
168         }
169         (_, 8, Format::Rgba, PixelFormat::AndroidNv21) => {
170             // What Android considers to be NV21 is actually NV12 in libyuv.
171             Some(ConversionFunction::NVToARGBMatrix(NV12ToARGBMatrix))
172         }
173         (_, 8, Format::Rgb565, PixelFormat::AndroidNv12) => {
174             Some(ConversionFunction::NVToARGBMatrix(NV12ToRGB565Matrix))
175         }
176         (_, 16, Format::Rgba1010102, PixelFormat::AndroidP010) => Some(
177             ConversionFunction::P010ToRGBMatrix(P010ToAR30Matrix, AR30ToAB30),
178         ),
179         (_, 10, Format::Rgba1010102, PixelFormat::Yuv420) => Some(
180             ConversionFunction::YUVToAB30Matrix(I010ToAR30Matrix, AR30ToAB30),
181         ),
182         (_, 16, Format::Rgba, PixelFormat::AndroidP010) => Some(
183             ConversionFunction::P010ToRGBMatrix(P010ToARGBMatrix, ARGBToABGR),
184         ),
185         (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
186             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
187         {
188             Some(ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(
189                 I210AlphaToARGBMatrixFilter,
190             ))
191         }
192         (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
193             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
194         {
195             Some(ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(
196                 I010AlphaToARGBMatrixFilter,
197             ))
198         }
199         (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
200             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
201         {
202             Some(ConversionFunction::YUVToRGBMatrixFilterHighBitDepth(
203                 I210ToARGBMatrixFilter,
204             ))
205         }
206         (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
207             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
208         {
209             Some(ConversionFunction::YUVToRGBMatrixFilterHighBitDepth(
210                 I010ToARGBMatrixFilter,
211             ))
212         }
213 
214         (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => Some(
215             ConversionFunction::YUVAToRGBMatrixHighBitDepth(I410AlphaToARGBMatrix),
216         ),
217         (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
218             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
219         {
220             Some(ConversionFunction::YUVAToRGBMatrixHighBitDepth(
221                 I210AlphaToARGBMatrix,
222             ))
223         }
224         (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
225             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
226         {
227             Some(ConversionFunction::YUVAToRGBMatrixHighBitDepth(
228                 I010AlphaToARGBMatrix,
229             ))
230         }
231         (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => Some(
232             ConversionFunction::YUVToRGBMatrixHighBitDepth(I410ToARGBMatrix),
233         ),
234         (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
235             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
236         {
237             Some(ConversionFunction::YUVToRGBMatrixHighBitDepth(
238                 I210ToARGBMatrix,
239             ))
240         }
241         (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
242             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
243         {
244             Some(ConversionFunction::YUVToRGBMatrixHighBitDepth(
245                 I010ToARGBMatrix,
246             ))
247         }
248         (_, 12, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
249             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
250         {
251             Some(ConversionFunction::YUVToRGBMatrixHighBitDepth(
252                 I012ToARGBMatrix,
253             ))
254         }
255 
256         // The fall through here is intentional. If a high bitdepth function was not found, try to
257         // see if we can use a low bitdepth function with a downshift.
258         //
259         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv400) => {
260             Some(ConversionFunction::YUV400ToRGBMatrix(I400ToARGBMatrix))
261         }
262 
263         (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
264             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
265         {
266             Some(ConversionFunction::YUVAToRGBMatrixFilter(
267                 I422AlphaToARGBMatrixFilter,
268             ))
269         }
270         (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
271             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
272         {
273             Some(ConversionFunction::YUVAToRGBMatrixFilter(
274                 I420AlphaToARGBMatrixFilter,
275             ))
276         }
277 
278         (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv422)
279             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
280         {
281             Some(ConversionFunction::YUVToRGBMatrixFilter(
282                 I422ToRGB24MatrixFilter,
283             ))
284         }
285         (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv420)
286             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
287         {
288             Some(ConversionFunction::YUVToRGBMatrixFilter(
289                 I420ToRGB24MatrixFilter,
290             ))
291         }
292         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
293             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
294         {
295             Some(ConversionFunction::YUVToRGBMatrixFilter(
296                 I422ToARGBMatrixFilter,
297             ))
298         }
299         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
300             if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() =>
301         {
302             Some(ConversionFunction::YUVToRGBMatrixFilter(
303                 I420ToARGBMatrixFilter,
304             ))
305         }
306 
307         (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => {
308             Some(ConversionFunction::YUVAToRGBMatrix(I444AlphaToARGBMatrix))
309         }
310         (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
311             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
312         {
313             Some(ConversionFunction::YUVAToRGBMatrix(I422AlphaToARGBMatrix))
314         }
315         (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
316             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
317         {
318             Some(ConversionFunction::YUVAToRGBMatrix(I420AlphaToARGBMatrix))
319         }
320 
321         (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv444) => {
322             Some(ConversionFunction::YUVToRGBMatrix(I444ToRGB24Matrix))
323         }
324         (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv420)
325             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
326         {
327             Some(ConversionFunction::YUVToRGBMatrix(I420ToRGB24Matrix))
328         }
329 
330         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => {
331             Some(ConversionFunction::YUVToRGBMatrix(I444ToARGBMatrix))
332         }
333         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422)
334             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
335         {
336             Some(ConversionFunction::YUVToRGBMatrix(I422ToARGBMatrix))
337         }
338         (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420)
339             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
340         {
341             Some(ConversionFunction::YUVToRGBMatrix(I420ToARGBMatrix))
342         }
343 
344         (_, _, Format::Argb | Format::Abgr, PixelFormat::Yuv422)
345             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
346         {
347             Some(ConversionFunction::YUVToRGBMatrix(I422ToRGBAMatrix))
348         }
349         (_, _, Format::Argb | Format::Abgr, PixelFormat::Yuv420)
350             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
351         {
352             Some(ConversionFunction::YUVToRGBMatrix(I420ToRGBAMatrix))
353         }
354 
355         (_, _, Format::Rgb565, PixelFormat::Yuv422)
356             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
357         {
358             Some(ConversionFunction::YUVToRGBMatrix(I422ToRGB565Matrix))
359         }
360         (_, _, Format::Rgb565, PixelFormat::Yuv420)
361             if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() =>
362         {
363             Some(ConversionFunction::YUVToRGBMatrix(I420ToRGB565Matrix))
364         }
365 
366         _ => None,
367     }
368 }
369 
370 #[cfg_attr(feature = "disable_cfi", no_sanitize(cfi))]
yuv_to_rgb(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<bool>371 pub(crate) fn yuv_to_rgb(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<bool> {
372     if (rgb.depth != 8 && rgb.depth != 10) || !image.depth_valid() {
373         return Err(AvifError::NotImplemented);
374     }
375     if rgb.depth == 10
376         && (!matches!(
377             image.yuv_format,
378             PixelFormat::AndroidP010 | PixelFormat::Yuv420
379         ) || rgb.format != Format::Rgba1010102)
380     {
381         return Err(AvifError::NotImplemented);
382     }
383 
384     let (matrix_yuv, matrix_yvu) = find_constants(image).ok_or(AvifError::NotImplemented)?;
385     let alpha_preferred = rgb.has_alpha() && image.has_alpha();
386     let conversion_function =
387         find_conversion_function(image.yuv_format, image.depth, rgb, alpha_preferred)
388             .ok_or(AvifError::NotImplemented)?;
389     let is_yvu = matches!(rgb.format, Format::Rgb | Format::Rgba | Format::Argb);
390     let matrix = if is_yvu { matrix_yvu } else { matrix_yuv };
391     let u_plane_index: usize = if is_yvu { 2 } else { 1 };
392     let v_plane_index: usize = if is_yvu { 1 } else { 2 };
393     let filter = if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() {
394         FilterMode_kFilterBilinear
395     } else {
396         FilterMode_kFilterNone
397     };
398     let mut plane_u8: [*const u8; 4] = ALL_PLANES
399         .iter()
400         .map(|x| {
401             if image.has_plane(*x) {
402                 image.planes[x.as_usize()].unwrap_ref().ptr()
403             } else {
404                 std::ptr::null()
405             }
406         })
407         .collect::<Vec<*const u8>>()
408         .try_into()
409         .unwrap();
410     let plane_u16: [*const u16; 4] = ALL_PLANES
411         .iter()
412         .map(|x| {
413             if image.has_plane(*x) {
414                 image.planes[x.as_usize()].unwrap_ref().ptr16()
415             } else {
416                 std::ptr::null()
417             }
418         })
419         .collect::<Vec<*const u16>>()
420         .try_into()
421         .unwrap();
422     let mut plane_row_bytes: [i32; 4] = ALL_PLANES
423         .iter()
424         .map(|x| {
425             if image.has_plane(*x) {
426                 i32_from_u32(image.plane_data(*x).unwrap().row_bytes).unwrap_or_default()
427             } else {
428                 0
429             }
430         })
431         .collect::<Vec<i32>>()
432         .try_into()
433         .unwrap();
434     let rgb_row_bytes = i32_from_u32(rgb.row_bytes)?;
435     let width = i32_from_u32(image.width)?;
436     let height = i32_from_u32(image.height)?;
437     let mut result: c_int;
438     unsafe {
439         let mut high_bd_matched = true;
440         // Apply one of the high bitdepth functions if possible.
441         result = match conversion_function {
442             ConversionFunction::P010ToRGBMatrix(func1, func2) => {
443                 let result = func1(
444                     plane_u16[0],
445                     plane_row_bytes[0] / 2,
446                     plane_u16[1],
447                     plane_row_bytes[1] / 2,
448                     rgb.pixels(),
449                     rgb_row_bytes,
450                     matrix,
451                     width,
452                     height,
453                 );
454                 if result == 0 {
455                     // It is okay to use the same pointer as source and destination for this
456                     // conversion.
457                     func2(
458                         rgb.pixels(),
459                         rgb_row_bytes,
460                         rgb.pixels(),
461                         rgb_row_bytes,
462                         width,
463                         height,
464                     )
465                 } else {
466                     result
467                 }
468             }
469             ConversionFunction::YUVToAB30Matrix(func1, func2) => {
470                 let result = func1(
471                     plane_u16[0],
472                     plane_row_bytes[0] / 2,
473                     plane_u16[1],
474                     plane_row_bytes[1] / 2,
475                     plane_u16[2],
476                     plane_row_bytes[2] / 2,
477                     rgb.pixels(),
478                     rgb_row_bytes,
479                     matrix,
480                     width,
481                     height,
482                 );
483                 if result == 0 {
484                     // It is okay to use the same pointer as source and destination for this
485                     // conversion.
486                     func2(
487                         rgb.pixels(),
488                         rgb_row_bytes,
489                         rgb.pixels(),
490                         rgb_row_bytes,
491                         width,
492                         height,
493                     )
494                 } else {
495                     result
496                 }
497             }
498             ConversionFunction::YUVToRGBMatrixFilterHighBitDepth(func) => func(
499                 plane_u16[0],
500                 plane_row_bytes[0] / 2,
501                 plane_u16[u_plane_index],
502                 plane_row_bytes[u_plane_index] / 2,
503                 plane_u16[v_plane_index],
504                 plane_row_bytes[v_plane_index] / 2,
505                 rgb.pixels(),
506                 rgb_row_bytes,
507                 matrix,
508                 width,
509                 height,
510                 filter,
511             ),
512             ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(func) => func(
513                 plane_u16[0],
514                 plane_row_bytes[0] / 2,
515                 plane_u16[u_plane_index],
516                 plane_row_bytes[u_plane_index] / 2,
517                 plane_u16[v_plane_index],
518                 plane_row_bytes[v_plane_index] / 2,
519                 plane_u16[3],
520                 plane_row_bytes[3] / 2,
521                 rgb.pixels(),
522                 rgb_row_bytes,
523                 matrix,
524                 width,
525                 height,
526                 0, // attenuate
527                 filter,
528             ),
529             ConversionFunction::YUVToRGBMatrixHighBitDepth(func) => func(
530                 plane_u16[0],
531                 plane_row_bytes[0] / 2,
532                 plane_u16[u_plane_index],
533                 plane_row_bytes[u_plane_index] / 2,
534                 plane_u16[v_plane_index],
535                 plane_row_bytes[v_plane_index] / 2,
536                 rgb.pixels(),
537                 rgb_row_bytes,
538                 matrix,
539                 width,
540                 height,
541             ),
542             ConversionFunction::YUVAToRGBMatrixHighBitDepth(func) => func(
543                 plane_u16[0],
544                 plane_row_bytes[0] / 2,
545                 plane_u16[u_plane_index],
546                 plane_row_bytes[u_plane_index] / 2,
547                 plane_u16[v_plane_index],
548                 plane_row_bytes[v_plane_index] / 2,
549                 plane_u16[3],
550                 plane_row_bytes[3] / 2,
551                 rgb.pixels(),
552                 rgb_row_bytes,
553                 matrix,
554                 width,
555                 height,
556                 0, // attenuate
557             ),
558             _ => {
559                 high_bd_matched = false;
560                 -1
561             }
562         };
563         if high_bd_matched {
564             return if result == 0 {
565                 Ok(!image.has_alpha() || conversion_function.is_yuva())
566             } else {
567                 Err(AvifError::ReformatFailed)
568             };
569         }
570         let mut image8 = image::Image::default();
571         if image.depth > 8 {
572             downshift_to_8bit(image, &mut image8, conversion_function.is_yuva())?;
573             plane_u8 = ALL_PLANES
574                 .iter()
575                 .map(|x| {
576                     if image8.has_plane(*x) {
577                         image8.planes[x.as_usize()].unwrap_ref().ptr()
578                     } else {
579                         std::ptr::null()
580                     }
581                 })
582                 .collect::<Vec<*const u8>>()
583                 .try_into()
584                 .unwrap();
585             plane_row_bytes = ALL_PLANES
586                 .iter()
587                 .map(|x| {
588                     if image8.has_plane(*x) {
589                         i32_from_u32(image8.plane_data(*x).unwrap().row_bytes).unwrap_or_default()
590                     } else {
591                         0
592                     }
593                 })
594                 .collect::<Vec<i32>>()
595                 .try_into()
596                 .unwrap();
597         }
598         result = match conversion_function {
599             ConversionFunction::NVToARGBMatrix(func) => func(
600                 plane_u8[0],
601                 plane_row_bytes[0],
602                 plane_u8[1],
603                 plane_row_bytes[1],
604                 rgb.pixels(),
605                 rgb_row_bytes,
606                 matrix,
607                 width,
608                 height,
609             ),
610             ConversionFunction::YUV400ToRGBMatrix(func) => func(
611                 plane_u8[0],
612                 plane_row_bytes[0],
613                 rgb.pixels(),
614                 rgb_row_bytes,
615                 matrix,
616                 width,
617                 height,
618             ),
619             ConversionFunction::YUVToRGBMatrixFilter(func) => func(
620                 plane_u8[0],
621                 plane_row_bytes[0],
622                 plane_u8[u_plane_index],
623                 plane_row_bytes[u_plane_index],
624                 plane_u8[v_plane_index],
625                 plane_row_bytes[v_plane_index],
626                 rgb.pixels(),
627                 rgb_row_bytes,
628                 matrix,
629                 width,
630                 height,
631                 filter,
632             ),
633             ConversionFunction::YUVAToRGBMatrixFilter(func) => func(
634                 plane_u8[0],
635                 plane_row_bytes[0],
636                 plane_u8[u_plane_index],
637                 plane_row_bytes[u_plane_index],
638                 plane_u8[v_plane_index],
639                 plane_row_bytes[v_plane_index],
640                 plane_u8[3],
641                 plane_row_bytes[3],
642                 rgb.pixels(),
643                 rgb_row_bytes,
644                 matrix,
645                 width,
646                 height,
647                 0, // attenuate
648                 filter,
649             ),
650             ConversionFunction::YUVToRGBMatrix(func) => func(
651                 plane_u8[0],
652                 plane_row_bytes[0],
653                 plane_u8[u_plane_index],
654                 plane_row_bytes[u_plane_index],
655                 plane_u8[v_plane_index],
656                 plane_row_bytes[v_plane_index],
657                 rgb.pixels(),
658                 rgb_row_bytes,
659                 matrix,
660                 width,
661                 height,
662             ),
663             ConversionFunction::YUVAToRGBMatrix(func) => func(
664                 plane_u8[0],
665                 plane_row_bytes[0],
666                 plane_u8[u_plane_index],
667                 plane_row_bytes[u_plane_index],
668                 plane_u8[v_plane_index],
669                 plane_row_bytes[v_plane_index],
670                 plane_u8[3],
671                 plane_row_bytes[3],
672                 rgb.pixels(),
673                 rgb_row_bytes,
674                 matrix,
675                 width,
676                 height,
677                 0, // attenuate
678             ),
679             _ => 0,
680         };
681     }
682     if result == 0 {
683         Ok(!image.has_alpha() || conversion_function.is_yuva())
684     } else {
685         Err(AvifError::ReformatFailed)
686     }
687 }
688 
downshift_to_8bit( image: &image::Image, image8: &mut image::Image, alpha: bool, ) -> AvifResult<()>689 fn downshift_to_8bit(
690     image: &image::Image,
691     image8: &mut image::Image,
692     alpha: bool,
693 ) -> AvifResult<()> {
694     image8.width = image.width;
695     image8.height = image.height;
696     image8.depth = 8;
697     image8.yuv_format = image.yuv_format;
698     image8.allocate_planes(Category::Color)?;
699     if alpha {
700         image8.allocate_planes(Category::Alpha)?;
701     }
702     let scale = 1 << (24 - image.depth);
703     for plane in ALL_PLANES {
704         if plane == Plane::A && !alpha {
705             continue;
706         }
707         let pd = image.plane_data(plane);
708         if pd.is_none() {
709             continue;
710         }
711         let pd = pd.unwrap();
712         if pd.width == 0 {
713             continue;
714         }
715         let source_ptr = image.planes[plane.as_usize()].unwrap_ref().ptr16();
716         let pd8 = image8.plane_data(plane).unwrap();
717         let dst_ptr = image8.planes[plane.as_usize()].unwrap_mut().ptr_mut();
718         unsafe {
719             Convert16To8Plane(
720                 source_ptr,
721                 i32_from_u32(pd.row_bytes / 2)?,
722                 dst_ptr,
723                 i32_from_u32(pd8.row_bytes)?,
724                 scale,
725                 i32_from_u32(pd.width)?,
726                 i32_from_u32(pd.height)?,
727             );
728         }
729     }
730     Ok(())
731 }
732 
process_alpha(rgb: &mut rgb::Image, multiply: bool) -> AvifResult<()>733 pub(crate) fn process_alpha(rgb: &mut rgb::Image, multiply: bool) -> AvifResult<()> {
734     if rgb.depth != 8 {
735         return Err(AvifError::NotImplemented);
736     }
737     match rgb.format {
738         Format::Rgba | Format::Bgra => {}
739         _ => return Err(AvifError::NotImplemented),
740     }
741     let result = unsafe {
742         if multiply {
743             ARGBAttenuate(
744                 rgb.pixels(),
745                 i32_from_u32(rgb.row_bytes)?,
746                 rgb.pixels(),
747                 i32_from_u32(rgb.row_bytes)?,
748                 i32_from_u32(rgb.width)?,
749                 i32_from_u32(rgb.height)?,
750             )
751         } else {
752             ARGBUnattenuate(
753                 rgb.pixels(),
754                 i32_from_u32(rgb.row_bytes)?,
755                 rgb.pixels(),
756                 i32_from_u32(rgb.row_bytes)?,
757                 i32_from_u32(rgb.width)?,
758                 i32_from_u32(rgb.height)?,
759             )
760         }
761     };
762     if result == 0 {
763         Ok(())
764     } else {
765         Err(AvifError::ReformatFailed)
766     }
767 }
768 
convert_to_half_float(rgb: &mut rgb::Image, scale: f32) -> AvifResult<()>769 pub(crate) fn convert_to_half_float(rgb: &mut rgb::Image, scale: f32) -> AvifResult<()> {
770     let res = unsafe {
771         HalfFloatPlane(
772             rgb.pixels() as *const u16,
773             i32_from_u32(rgb.row_bytes)?,
774             rgb.pixels() as *mut u16,
775             i32_from_u32(rgb.row_bytes)?,
776             scale,
777             i32_from_u32(rgb.width * rgb.channel_count())?,
778             i32_from_u32(rgb.height)?,
779         )
780     };
781     if res == 0 {
782         Ok(())
783     } else {
784         Err(AvifError::InvalidArgument)
785     }
786 }
787