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