| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use super::rgb; |
| use super::rgb::*; |
| |
| use crate::image::*; |
| use crate::internal_utils::*; |
| use crate::*; |
| |
| use libyuv_sys::bindings::*; |
| |
| use std::os::raw::c_int; |
| |
| fn find_constants(image: &image::Image) -> Option<(&YuvConstants, &YuvConstants)> { |
| let matrix_coefficients = if image.yuv_format == PixelFormat::Yuv400 |
| && image.matrix_coefficients == MatrixCoefficients::Identity |
| { |
| MatrixCoefficients::Bt601 |
| } else { |
| image.matrix_coefficients |
| }; |
| // Android MediaCodec always uses Yuv420. So use Bt601 instead of Identity in that case. |
| #[cfg(feature = "android_mediacodec")] |
| let matrix_coefficients = if matrix_coefficients == MatrixCoefficients::Identity { |
| MatrixCoefficients::Bt601 |
| } else { |
| matrix_coefficients |
| }; |
| unsafe { |
| match image.yuv_range { |
| YuvRange::Full => match matrix_coefficients { |
| MatrixCoefficients::Bt709 => Some((&kYuvF709Constants, &kYvuF709Constants)), |
| MatrixCoefficients::Bt470bg |
| | MatrixCoefficients::Bt601 |
| | MatrixCoefficients::Unspecified => Some((&kYuvJPEGConstants, &kYvuJPEGConstants)), |
| MatrixCoefficients::Bt2020Ncl => Some((&kYuvV2020Constants, &kYvuV2020Constants)), |
| MatrixCoefficients::ChromaDerivedNcl => match image.color_primaries { |
| ColorPrimaries::Srgb | ColorPrimaries::Unspecified => { |
| Some((&kYuvF709Constants, &kYvuF709Constants)) |
| } |
| ColorPrimaries::Bt470bg | ColorPrimaries::Bt601 => { |
| Some((&kYuvJPEGConstants, &kYvuJPEGConstants)) |
| } |
| ColorPrimaries::Bt2020 => Some((&kYuvV2020Constants, &kYvuV2020Constants)), |
| _ => None, |
| }, |
| _ => None, |
| }, |
| YuvRange::Limited => match matrix_coefficients { |
| MatrixCoefficients::Bt709 => Some((&kYuvH709Constants, &kYvuH709Constants)), |
| MatrixCoefficients::Bt470bg |
| | MatrixCoefficients::Bt601 |
| | MatrixCoefficients::Unspecified => Some((&kYuvI601Constants, &kYvuI601Constants)), |
| MatrixCoefficients::Bt2020Ncl => Some((&kYuv2020Constants, &kYvu2020Constants)), |
| MatrixCoefficients::ChromaDerivedNcl => match image.color_primaries { |
| ColorPrimaries::Srgb | ColorPrimaries::Unspecified => { |
| Some((&kYuvH709Constants, &kYvuH709Constants)) |
| } |
| ColorPrimaries::Bt470bg | ColorPrimaries::Bt601 => { |
| Some((&kYuvI601Constants, &kYvuI601Constants)) |
| } |
| ColorPrimaries::Bt2020 => Some((&kYuv2020Constants, &kYvu2020Constants)), |
| _ => None, |
| }, |
| _ => None, |
| }, |
| } |
| } |
| } |
| |
| #[rustfmt::skip] |
| type YUV400ToRGBMatrix = unsafe extern "C" fn( |
| *const u8, c_int, *mut u8, c_int, *const YuvConstants, c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type YUVToRGBMatrixFilter = unsafe extern "C" fn( |
| *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants, |
| c_int, c_int, FilterMode) -> c_int; |
| #[rustfmt::skip] |
| type YUVAToRGBMatrixFilter = unsafe extern "C" fn( |
| *const u8, c_int, *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, |
| *const YuvConstants, c_int, c_int, c_int, FilterMode) -> c_int; |
| #[rustfmt::skip] |
| type YUVToRGBMatrix = unsafe extern "C" fn( |
| *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants, |
| c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type YUVAToRGBMatrix = unsafe extern "C" fn( |
| *const u8, c_int, *const u8, c_int, *const u8, c_int, *const u8, c_int, *mut u8, c_int, |
| *const YuvConstants, c_int, c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type YUVToRGBMatrixFilterHighBitDepth = unsafe extern "C" fn( |
| *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants, |
| c_int, c_int, FilterMode) -> c_int; |
| #[rustfmt::skip] |
| type YUVAToRGBMatrixFilterHighBitDepth = unsafe extern "C" fn( |
| *const u16, c_int, *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, |
| *const YuvConstants, c_int, c_int, c_int, FilterMode) -> c_int; |
| #[rustfmt::skip] |
| type YUVToRGBMatrixHighBitDepth = unsafe extern "C" fn( |
| *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants, |
| c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type YUVAToRGBMatrixHighBitDepth = unsafe extern "C" fn( |
| *const u16, c_int, *const u16, c_int, *const u16, c_int, *const u16, c_int, *mut u8, c_int, |
| *const YuvConstants, c_int, c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type P010ToRGBMatrix = unsafe extern "C" fn( |
| *const u16, c_int, *const u16, c_int, *mut u8, c_int, *const YuvConstants, c_int, |
| c_int) -> c_int; |
| #[rustfmt::skip] |
| type ARGBToABGR = unsafe extern "C" fn( |
| *const u8, c_int, *mut u8, c_int, c_int, c_int) -> c_int; |
| #[rustfmt::skip] |
| type NVToARGBMatrix = unsafe extern "C" fn( |
| *const u8, c_int, *const u8, c_int, *mut u8, c_int, *const YuvConstants, c_int, |
| c_int) -> c_int; |
| |
| #[derive(Debug)] |
| enum ConversionFunction { |
| YUV400ToRGBMatrix(YUV400ToRGBMatrix), |
| YUVToRGBMatrixFilter(YUVToRGBMatrixFilter), |
| YUVAToRGBMatrixFilter(YUVAToRGBMatrixFilter), |
| YUVToRGBMatrix(YUVToRGBMatrix), |
| YUVAToRGBMatrix(YUVAToRGBMatrix), |
| YUVToRGBMatrixFilterHighBitDepth(YUVToRGBMatrixFilterHighBitDepth), |
| YUVAToRGBMatrixFilterHighBitDepth(YUVAToRGBMatrixFilterHighBitDepth), |
| YUVToRGBMatrixHighBitDepth(YUVToRGBMatrixHighBitDepth), |
| YUVAToRGBMatrixHighBitDepth(YUVAToRGBMatrixHighBitDepth), |
| P010ToRGBMatrix(P010ToRGBMatrix, ARGBToABGR), |
| NVToARGBMatrix(NVToARGBMatrix), |
| } |
| |
| impl ConversionFunction { |
| fn is_yuva(&self) -> bool { |
| matches!( |
| self, |
| ConversionFunction::YUVAToRGBMatrixFilter(_) |
| | ConversionFunction::YUVAToRGBMatrix(_) |
| | ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(_) |
| | ConversionFunction::YUVAToRGBMatrixHighBitDepth(_) |
| ) |
| } |
| } |
| |
| fn find_conversion_function( |
| yuv_format: PixelFormat, |
| yuv_depth: u8, |
| rgb: &rgb::Image, |
| alpha_preferred: bool, |
| ) -> Option<ConversionFunction> { |
| match (alpha_preferred, yuv_depth, rgb.format, yuv_format) { |
| (_, 8, Format::Rgba, PixelFormat::AndroidNv12) => { |
| // What Android considers to be NV12 is actually NV21 in libyuv. |
| Some(ConversionFunction::NVToARGBMatrix(NV21ToARGBMatrix)) |
| } |
| (_, 8, Format::Rgba, PixelFormat::AndroidNv21) => { |
| // What Android considers to be NV21 is actually NV12 in libyuv. |
| Some(ConversionFunction::NVToARGBMatrix(NV12ToARGBMatrix)) |
| } |
| (_, 16, Format::Rgba1010102, PixelFormat::AndroidP010) => Some( |
| ConversionFunction::P010ToRGBMatrix(P010ToAR30Matrix, AR30ToAB30), |
| ), |
| (_, 16, Format::Rgba, PixelFormat::AndroidP010) => Some( |
| ConversionFunction::P010ToRGBMatrix(P010ToARGBMatrix, ARGBToABGR), |
| ), |
| (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth( |
| I210AlphaToARGBMatrixFilter, |
| )) |
| } |
| (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth( |
| I010AlphaToARGBMatrixFilter, |
| )) |
| } |
| (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilterHighBitDepth( |
| I210ToARGBMatrixFilter, |
| )) |
| } |
| (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilterHighBitDepth( |
| I010ToARGBMatrixFilter, |
| )) |
| } |
| |
| (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => Some( |
| ConversionFunction::YUVAToRGBMatrixHighBitDepth(I410AlphaToARGBMatrix), |
| ), |
| (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixHighBitDepth( |
| I210AlphaToARGBMatrix, |
| )) |
| } |
| (true, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixHighBitDepth( |
| I010AlphaToARGBMatrix, |
| )) |
| } |
| (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => Some( |
| ConversionFunction::YUVToRGBMatrixHighBitDepth(I410ToARGBMatrix), |
| ), |
| (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixHighBitDepth( |
| I210ToARGBMatrix, |
| )) |
| } |
| (_, 10, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixHighBitDepth( |
| I010ToARGBMatrix, |
| )) |
| } |
| (_, 12, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixHighBitDepth( |
| I012ToARGBMatrix, |
| )) |
| } |
| |
| // The fall through here is intentional. If a high bitdepth function was not found, try to |
| // see if we can use a low bitdepth function with a downshift. |
| // |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv400) => { |
| Some(ConversionFunction::YUV400ToRGBMatrix(I400ToARGBMatrix)) |
| } |
| |
| (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixFilter( |
| I422AlphaToARGBMatrixFilter, |
| )) |
| } |
| (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrixFilter( |
| I420AlphaToARGBMatrixFilter, |
| )) |
| } |
| |
| (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilter( |
| I422ToRGB24MatrixFilter, |
| )) |
| } |
| (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilter( |
| I420ToRGB24MatrixFilter, |
| )) |
| } |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilter( |
| I422ToARGBMatrixFilter, |
| )) |
| } |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrixFilter( |
| I420ToARGBMatrixFilter, |
| )) |
| } |
| |
| (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => { |
| Some(ConversionFunction::YUVAToRGBMatrix(I444AlphaToARGBMatrix)) |
| } |
| (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrix(I422AlphaToARGBMatrix)) |
| } |
| (true, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVAToRGBMatrix(I420AlphaToARGBMatrix)) |
| } |
| |
| (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv444) => { |
| Some(ConversionFunction::YUVToRGBMatrix(I444ToRGB24Matrix)) |
| } |
| (_, _, Format::Rgb | Format::Bgr, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I420ToRGB24Matrix)) |
| } |
| |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv444) => { |
| Some(ConversionFunction::YUVToRGBMatrix(I444ToARGBMatrix)) |
| } |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I422ToARGBMatrix)) |
| } |
| (_, _, Format::Rgba | Format::Bgra, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I420ToARGBMatrix)) |
| } |
| |
| (_, _, Format::Argb | Format::Abgr, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I422ToRGBAMatrix)) |
| } |
| (_, _, Format::Argb | Format::Abgr, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I420ToRGBAMatrix)) |
| } |
| |
| (_, _, Format::Rgb565, PixelFormat::Yuv422) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I422ToRGB565Matrix)) |
| } |
| (_, _, Format::Rgb565, PixelFormat::Yuv420) |
| if rgb.chroma_upsampling.nearest_neighbor_filter_allowed() => |
| { |
| Some(ConversionFunction::YUVToRGBMatrix(I420ToRGB565Matrix)) |
| } |
| |
| _ => None, |
| } |
| } |
| |
| #[cfg_attr(feature = "disable_cfi", no_sanitize(cfi))] |
| pub(crate) fn yuv_to_rgb(image: &image::Image, rgb: &mut rgb::Image) -> AvifResult<bool> { |
| if (rgb.depth != 8 && rgb.depth != 10) || !image.depth_valid() { |
| return Err(AvifError::NotImplemented); |
| } |
| if rgb.depth == 10 |
| && (image.yuv_format != PixelFormat::AndroidP010 || rgb.format != Format::Rgba1010102) |
| { |
| return Err(AvifError::NotImplemented); |
| } |
| |
| let (matrix_yuv, matrix_yvu) = find_constants(image).ok_or(AvifError::NotImplemented)?; |
| let alpha_preferred = rgb.has_alpha() && image.has_alpha(); |
| let conversion_function = |
| find_conversion_function(image.yuv_format, image.depth, rgb, alpha_preferred) |
| .ok_or(AvifError::NotImplemented)?; |
| let is_yvu = matches!(rgb.format, Format::Rgb | Format::Rgba | Format::Argb); |
| let matrix = if is_yvu { matrix_yvu } else { matrix_yuv }; |
| let u_plane_index: usize = if is_yvu { 2 } else { 1 }; |
| let v_plane_index: usize = if is_yvu { 1 } else { 2 }; |
| let filter = if rgb.chroma_upsampling.bilinear_or_better_filter_allowed() { |
| FilterMode_kFilterBilinear |
| } else { |
| FilterMode_kFilterNone |
| }; |
| let mut plane_u8: [*const u8; 4] = ALL_PLANES |
| .iter() |
| .map(|x| { |
| if image.has_plane(*x) { |
| image.planes[x.as_usize()].unwrap_ref().ptr() |
| } else { |
| std::ptr::null() |
| } |
| }) |
| .collect::<Vec<*const u8>>() |
| .try_into() |
| .unwrap(); |
| let plane_u16: [*const u16; 4] = ALL_PLANES |
| .iter() |
| .map(|x| { |
| if image.has_plane(*x) { |
| image.planes[x.as_usize()].unwrap_ref().ptr16() |
| } else { |
| std::ptr::null() |
| } |
| }) |
| .collect::<Vec<*const u16>>() |
| .try_into() |
| .unwrap(); |
| let mut plane_row_bytes: [i32; 4] = ALL_PLANES |
| .iter() |
| .map(|x| { |
| if image.has_plane(*x) { |
| i32_from_u32(image.plane_data(*x).unwrap().row_bytes).unwrap_or_default() |
| } else { |
| 0 |
| } |
| }) |
| .collect::<Vec<i32>>() |
| .try_into() |
| .unwrap(); |
| let rgb_row_bytes = i32_from_u32(rgb.row_bytes)?; |
| let width = i32_from_u32(image.width)?; |
| let height = i32_from_u32(image.height)?; |
| let mut result: c_int; |
| unsafe { |
| let mut high_bd_matched = true; |
| // Apply one of the high bitdepth functions if possible. |
| result = match conversion_function { |
| ConversionFunction::P010ToRGBMatrix(func1, func2) => { |
| let result = func1( |
| plane_u16[0], |
| plane_row_bytes[0] / 2, |
| plane_u16[1], |
| plane_row_bytes[1] / 2, |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| ); |
| if result == 0 { |
| // It is okay to use the same pointer as source and destination for this |
| // conversion. |
| func2( |
| rgb.pixels(), |
| rgb_row_bytes, |
| rgb.pixels(), |
| rgb_row_bytes, |
| width, |
| height, |
| ) |
| } else { |
| result |
| } |
| } |
| ConversionFunction::YUVToRGBMatrixFilterHighBitDepth(func) => func( |
| plane_u16[0], |
| plane_row_bytes[0] / 2, |
| plane_u16[u_plane_index], |
| plane_row_bytes[u_plane_index] / 2, |
| plane_u16[v_plane_index], |
| plane_row_bytes[v_plane_index] / 2, |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| filter, |
| ), |
| ConversionFunction::YUVAToRGBMatrixFilterHighBitDepth(func) => func( |
| plane_u16[0], |
| plane_row_bytes[0] / 2, |
| plane_u16[u_plane_index], |
| plane_row_bytes[u_plane_index] / 2, |
| plane_u16[v_plane_index], |
| plane_row_bytes[v_plane_index] / 2, |
| plane_u16[3], |
| plane_row_bytes[3] / 2, |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| 0, // attenuate |
| filter, |
| ), |
| ConversionFunction::YUVToRGBMatrixHighBitDepth(func) => func( |
| plane_u16[0], |
| plane_row_bytes[0] / 2, |
| plane_u16[u_plane_index], |
| plane_row_bytes[u_plane_index] / 2, |
| plane_u16[v_plane_index], |
| plane_row_bytes[v_plane_index] / 2, |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| ), |
| ConversionFunction::YUVAToRGBMatrixHighBitDepth(func) => func( |
| plane_u16[0], |
| plane_row_bytes[0] / 2, |
| plane_u16[u_plane_index], |
| plane_row_bytes[u_plane_index] / 2, |
| plane_u16[v_plane_index], |
| plane_row_bytes[v_plane_index] / 2, |
| plane_u16[3], |
| plane_row_bytes[3] / 2, |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| 0, // attenuate |
| ), |
| _ => { |
| high_bd_matched = false; |
| -1 |
| } |
| }; |
| if high_bd_matched { |
| return if result == 0 { |
| Ok(!image.has_alpha() || conversion_function.is_yuva()) |
| } else { |
| Err(AvifError::ReformatFailed) |
| }; |
| } |
| let mut image8 = image::Image::default(); |
| if image.depth > 8 { |
| downshift_to_8bit(image, &mut image8, conversion_function.is_yuva())?; |
| plane_u8 = ALL_PLANES |
| .iter() |
| .map(|x| { |
| if image8.has_plane(*x) { |
| image8.planes[x.as_usize()].unwrap_ref().ptr() |
| } else { |
| std::ptr::null() |
| } |
| }) |
| .collect::<Vec<*const u8>>() |
| .try_into() |
| .unwrap(); |
| plane_row_bytes = ALL_PLANES |
| .iter() |
| .map(|x| { |
| if image8.has_plane(*x) { |
| i32_from_u32(image8.plane_data(*x).unwrap().row_bytes).unwrap_or_default() |
| } else { |
| 0 |
| } |
| }) |
| .collect::<Vec<i32>>() |
| .try_into() |
| .unwrap(); |
| } |
| result = match conversion_function { |
| ConversionFunction::NVToARGBMatrix(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| plane_u8[1], |
| plane_row_bytes[1], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| ), |
| ConversionFunction::YUV400ToRGBMatrix(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| ), |
| ConversionFunction::YUVToRGBMatrixFilter(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| plane_u8[u_plane_index], |
| plane_row_bytes[u_plane_index], |
| plane_u8[v_plane_index], |
| plane_row_bytes[v_plane_index], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| filter, |
| ), |
| ConversionFunction::YUVAToRGBMatrixFilter(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| plane_u8[u_plane_index], |
| plane_row_bytes[u_plane_index], |
| plane_u8[v_plane_index], |
| plane_row_bytes[v_plane_index], |
| plane_u8[3], |
| plane_row_bytes[3], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| 0, // attenuate |
| filter, |
| ), |
| ConversionFunction::YUVToRGBMatrix(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| plane_u8[u_plane_index], |
| plane_row_bytes[u_plane_index], |
| plane_u8[v_plane_index], |
| plane_row_bytes[v_plane_index], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| ), |
| ConversionFunction::YUVAToRGBMatrix(func) => func( |
| plane_u8[0], |
| plane_row_bytes[0], |
| plane_u8[u_plane_index], |
| plane_row_bytes[u_plane_index], |
| plane_u8[v_plane_index], |
| plane_row_bytes[v_plane_index], |
| plane_u8[3], |
| plane_row_bytes[3], |
| rgb.pixels(), |
| rgb_row_bytes, |
| matrix, |
| width, |
| height, |
| 0, // attenuate |
| ), |
| _ => 0, |
| }; |
| } |
| if result == 0 { |
| Ok(!image.has_alpha() || conversion_function.is_yuva()) |
| } else { |
| Err(AvifError::ReformatFailed) |
| } |
| } |
| |
| fn downshift_to_8bit( |
| image: &image::Image, |
| image8: &mut image::Image, |
| alpha: bool, |
| ) -> AvifResult<()> { |
| image8.width = image.width; |
| image8.height = image.height; |
| image8.depth = 8; |
| image8.yuv_format = image.yuv_format; |
| image8.allocate_planes(Category::Color)?; |
| if alpha { |
| image8.allocate_planes(Category::Alpha)?; |
| } |
| let scale = 1 << (24 - image.depth); |
| for plane in ALL_PLANES { |
| if plane == Plane::A && !alpha { |
| continue; |
| } |
| let pd = image.plane_data(plane); |
| if pd.is_none() { |
| continue; |
| } |
| let pd = pd.unwrap(); |
| if pd.width == 0 { |
| continue; |
| } |
| let source_ptr = image.planes[plane.as_usize()].unwrap_ref().ptr16(); |
| let pd8 = image8.plane_data(plane).unwrap(); |
| let dst_ptr = image8.planes[plane.as_usize()].unwrap_mut().ptr_mut(); |
| unsafe { |
| Convert16To8Plane( |
| source_ptr, |
| i32_from_u32(pd.row_bytes / 2)?, |
| dst_ptr, |
| i32_from_u32(pd8.row_bytes)?, |
| scale, |
| i32_from_u32(pd.width)?, |
| i32_from_u32(pd.height)?, |
| ); |
| } |
| } |
| Ok(()) |
| } |
| |
| pub(crate) fn process_alpha(rgb: &mut rgb::Image, multiply: bool) -> AvifResult<()> { |
| if rgb.depth != 8 { |
| return Err(AvifError::NotImplemented); |
| } |
| match rgb.format { |
| Format::Rgba | Format::Bgra => {} |
| _ => return Err(AvifError::NotImplemented), |
| } |
| let result = unsafe { |
| if multiply { |
| ARGBAttenuate( |
| rgb.pixels(), |
| i32_from_u32(rgb.row_bytes)?, |
| rgb.pixels(), |
| i32_from_u32(rgb.row_bytes)?, |
| i32_from_u32(rgb.width)?, |
| i32_from_u32(rgb.height)?, |
| ) |
| } else { |
| ARGBUnattenuate( |
| rgb.pixels(), |
| i32_from_u32(rgb.row_bytes)?, |
| rgb.pixels(), |
| i32_from_u32(rgb.row_bytes)?, |
| i32_from_u32(rgb.width)?, |
| i32_from_u32(rgb.height)?, |
| ) |
| } |
| }; |
| if result == 0 { |
| Ok(()) |
| } else { |
| Err(AvifError::ReformatFailed) |
| } |
| } |
| |
| pub(crate) fn convert_to_half_float(rgb: &mut rgb::Image, scale: f32) -> AvifResult<()> { |
| let res = unsafe { |
| HalfFloatPlane( |
| rgb.pixels() as *const u16, |
| i32_from_u32(rgb.row_bytes)?, |
| rgb.pixels() as *mut u16, |
| i32_from_u32(rgb.row_bytes)?, |
| scale, |
| i32_from_u32(rgb.width * rgb.channel_count())?, |
| i32_from_u32(rgb.height)?, |
| ) |
| }; |
| if res == 0 { |
| Ok(()) |
| } else { |
| Err(AvifError::InvalidArgument) |
| } |
| } |