| // 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::gainmap::*; |
| use super::io::*; |
| use super::types::*; |
| |
| use crate::image::*; |
| use crate::internal_utils::*; |
| use crate::utils::clap::*; |
| use crate::utils::*; |
| use crate::*; |
| |
| use std::os::raw::c_int; |
| use std::os::raw::c_void; |
| |
| pub type avifPixelAspectRatioBox = PixelAspectRatio; |
| |
| /// cbindgen:rename-all=CamelCase |
| #[derive(Clone, Copy, Debug, Default)] |
| #[repr(C)] |
| pub struct avifCleanApertureBox { |
| // The u32 members below are actually i32 values, see |
| // https://github.com/AOMediaCodec/libavif/pull/1749#discussion_r1391583768. |
| pub width_n: u32, |
| pub width_d: u32, |
| pub height_n: u32, |
| pub height_d: u32, |
| pub horiz_off_n: u32, |
| pub horiz_off_d: u32, |
| pub vert_off_n: u32, |
| pub vert_off_d: u32, |
| } |
| |
| impl From<&Option<CleanAperture>> for avifCleanApertureBox { |
| fn from(clap_op: &Option<CleanAperture>) -> Self { |
| match clap_op { |
| Some(clap) => Self { |
| width_n: clap.width.0, |
| width_d: clap.width.1, |
| height_n: clap.height.0, |
| height_d: clap.height.1, |
| horiz_off_n: clap.horiz_off.0, |
| horiz_off_d: clap.horiz_off.1, |
| vert_off_n: clap.vert_off.0, |
| vert_off_d: clap.vert_off.1, |
| }, |
| None => Self::default(), |
| } |
| } |
| } |
| |
| impl From<&avifCleanApertureBox> for CleanAperture { |
| fn from(clap: &avifCleanApertureBox) -> Self { |
| Self { |
| width: UFraction(clap.width_n, clap.width_d), |
| height: UFraction(clap.height_n, clap.height_d), |
| horiz_off: UFraction(clap.horiz_off_n, clap.horiz_off_d), |
| vert_off: UFraction(clap.vert_off_n, clap.vert_off_d), |
| } |
| } |
| } |
| |
| #[derive(Clone, Copy, Debug, Default)] |
| #[repr(C)] |
| pub struct avifImageRotation { |
| pub angle: u8, |
| } |
| |
| #[derive(Clone, Copy, Debug, Default)] |
| #[repr(C)] |
| pub struct avifImageMirror { |
| pub axis: u8, |
| } |
| |
| #[derive(Clone, Debug)] |
| #[repr(C)] |
| pub struct avifImage { |
| pub width: u32, |
| pub height: u32, |
| pub depth: u32, |
| |
| pub yuvFormat: PixelFormat, |
| pub yuvRange: YuvRange, |
| pub yuvChromaSamplePosition: ChromaSamplePosition, |
| pub yuvPlanes: [*mut u8; AVIF_PLANE_COUNT_YUV], |
| pub yuvRowBytes: [u32; AVIF_PLANE_COUNT_YUV], |
| pub imageOwnsYUVPlanes: avifBool, |
| |
| pub alphaPlane: *mut u8, |
| pub alphaRowBytes: u32, |
| pub imageOwnsAlphaPlane: avifBool, |
| pub alphaPremultiplied: avifBool, |
| |
| pub icc: avifRWData, |
| pub colorPrimaries: ColorPrimaries, |
| pub transferCharacteristics: TransferCharacteristics, |
| pub matrixCoefficients: MatrixCoefficients, |
| |
| pub clli: avifContentLightLevelInformationBox, |
| pub transformFlags: avifTransformFlags, |
| pub pasp: avifPixelAspectRatioBox, |
| pub clap: avifCleanApertureBox, |
| pub irot: avifImageRotation, |
| pub imir: avifImageMirror, |
| |
| pub exif: avifRWData, |
| pub xmp: avifRWData, |
| pub gainMap: *mut avifGainMap, |
| } |
| |
| impl Default for avifImage { |
| fn default() -> Self { |
| avifImage { |
| width: 0, |
| height: 0, |
| depth: 0, |
| yuvFormat: Default::default(), |
| yuvRange: YuvRange::Full, |
| yuvChromaSamplePosition: Default::default(), |
| yuvPlanes: [std::ptr::null_mut(); 3], |
| yuvRowBytes: [0; 3], |
| imageOwnsYUVPlanes: AVIF_FALSE, |
| alphaPlane: std::ptr::null_mut(), |
| alphaRowBytes: 0, |
| imageOwnsAlphaPlane: AVIF_FALSE, |
| alphaPremultiplied: AVIF_FALSE, |
| icc: Default::default(), |
| colorPrimaries: Default::default(), |
| transferCharacteristics: Default::default(), |
| matrixCoefficients: Default::default(), |
| clli: Default::default(), |
| transformFlags: AVIF_TRANSFORM_NONE, |
| pasp: Default::default(), |
| clap: Default::default(), |
| irot: Default::default(), |
| imir: Default::default(), |
| exif: Default::default(), |
| xmp: Default::default(), |
| gainMap: std::ptr::null_mut(), |
| } |
| } |
| } |
| |
| impl From<&Image> for avifImage { |
| fn from(image: &Image) -> Self { |
| let mut dst_image: avifImage = avifImage { |
| width: image.width, |
| height: image.height, |
| depth: image.depth as u32, |
| yuvFormat: image.yuv_format, |
| yuvRange: image.yuv_range, |
| yuvChromaSamplePosition: image.chroma_sample_position, |
| alphaPremultiplied: image.alpha_premultiplied as avifBool, |
| icc: (&image.icc).into(), |
| colorPrimaries: image.color_primaries, |
| transferCharacteristics: image.transfer_characteristics, |
| matrixCoefficients: image.matrix_coefficients, |
| clli: image.clli.unwrap_or_default(), |
| transformFlags: { |
| let mut flags = 0; |
| if image.pasp.is_some() { |
| flags |= AVIF_TRANSFORM_PASP; |
| } |
| if image.clap.is_some() { |
| flags |= AVIF_TRANSFORM_CLAP; |
| } |
| if image.irot_angle.is_some() { |
| flags |= AVIF_TRANSFORM_IROT; |
| } |
| if image.imir_axis.is_some() { |
| flags |= AVIF_TRANSFORM_IMIR; |
| } |
| flags |
| }, |
| pasp: image.pasp.unwrap_or_default(), |
| clap: (&image.clap).into(), |
| irot: avifImageRotation { |
| angle: image.irot_angle.unwrap_or_default(), |
| }, |
| imir: avifImageMirror { |
| axis: image.imir_axis.unwrap_or_default(), |
| }, |
| exif: (&image.exif).into(), |
| xmp: (&image.xmp).into(), |
| ..Self::default() |
| }; |
| for i in 0usize..3 { |
| if !image.has_plane(i.into()) { |
| continue; |
| } |
| dst_image.yuvPlanes[i] = if image.depth > 8 { |
| image.planes[i].unwrap_ref().ptr16() as *mut u8 |
| } else { |
| image.planes[i].unwrap_ref().ptr() as *mut u8 |
| }; |
| dst_image.yuvRowBytes[i] = image.row_bytes[i]; |
| } |
| if image.has_plane(Plane::A) { |
| dst_image.alphaPlane = if image.depth > 8 { |
| image.planes[3].unwrap_ref().ptr16() as *mut u8 |
| } else { |
| image.planes[3].unwrap_ref().ptr() as *mut u8 |
| }; |
| dst_image.alphaRowBytes = image.row_bytes[3]; |
| } |
| dst_image |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageCreateEmpty() -> *mut avifImage { |
| Box::into_raw(Box::<avifImage>::default()) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageCreate( |
| width: u32, |
| height: u32, |
| depth: u32, |
| yuvFormat: PixelFormat, |
| ) -> *mut avifImage { |
| Box::into_raw(Box::new(avifImage { |
| width, |
| height, |
| depth, |
| yuvFormat, |
| ..avifImage::default() |
| })) |
| } |
| |
| macro_rules! usize_from_u32_or_fail { |
| ($param: expr) => { |
| match usize_from_u32($param) { |
| Ok(value) => value, |
| Err(_) => return avifResult::UnknownError, |
| } |
| }; |
| } |
| |
| fn copy_plane_helper( |
| mut src_plane_ptr: *const u8, |
| src_row_bytes: u32, |
| mut dst_plane_ptr: *mut u8, |
| dst_row_bytes: u32, |
| mut width: usize, |
| height: usize, |
| pixel_size: usize, |
| ) { |
| width *= pixel_size; |
| for _ in 0..height { |
| unsafe { |
| std::ptr::copy_nonoverlapping(src_plane_ptr, dst_plane_ptr, width); |
| src_plane_ptr = src_plane_ptr.offset(src_row_bytes as isize); |
| dst_plane_ptr = dst_plane_ptr.offset(dst_row_bytes as isize); |
| } |
| } |
| } |
| |
| #[no_mangle] |
| #[allow(unused)] |
| pub unsafe extern "C" fn crabby_avifImageCopy( |
| dstImage: *mut avifImage, |
| srcImage: *const avifImage, |
| planes: avifPlanesFlags, |
| ) -> avifResult { |
| unsafe { |
| crabby_avifImageFreePlanes(dstImage, avifPlanesFlag::AvifPlanesAll as u32); |
| } |
| let dst = unsafe { &mut (*dstImage) }; |
| let src = unsafe { &(*srcImage) }; |
| dst.width = src.width; |
| dst.height = src.height; |
| dst.depth = src.depth; |
| dst.yuvFormat = src.yuvFormat; |
| dst.yuvRange = src.yuvRange; |
| dst.yuvChromaSamplePosition = src.yuvChromaSamplePosition; |
| dst.alphaPremultiplied = src.alphaPremultiplied; |
| dst.colorPrimaries = src.colorPrimaries; |
| dst.transferCharacteristics = src.transferCharacteristics; |
| dst.matrixCoefficients = src.matrixCoefficients; |
| dst.clli = src.clli; |
| dst.transformFlags = src.transformFlags; |
| dst.pasp = src.pasp; |
| dst.clap = src.clap; |
| dst.irot = src.irot; |
| dst.imir = src.imir; |
| let res = unsafe { crabby_avifRWDataSet(&mut dst.icc, src.icc.data, src.icc.size) }; |
| if res != avifResult::Ok { |
| return res; |
| } |
| let res = unsafe { crabby_avifRWDataSet(&mut dst.exif, src.exif.data, src.exif.size) }; |
| if res != avifResult::Ok { |
| return res; |
| } |
| let res = unsafe { crabby_avifRWDataSet(&mut dst.xmp, src.xmp.data, src.xmp.size) }; |
| if res != avifResult::Ok { |
| return res; |
| } |
| let pixel_size: usize = if src.depth > 8 { 2 } else { 1 }; |
| if (planes & 1) != 0 { |
| for plane in 0usize..3 { |
| if src.yuvPlanes[plane].is_null() || src.yuvRowBytes[plane] == 0 { |
| continue; |
| } |
| let plane_height = usize_from_u32_or_fail!(unsafe { |
| crabby_avifImagePlaneHeight(srcImage, plane as i32) |
| }); |
| let plane_width = usize_from_u32_or_fail!(unsafe { |
| crabby_avifImagePlaneWidth(srcImage, plane as i32) |
| }); |
| let plane_size = plane_width * plane_height * pixel_size; |
| dst.yuvPlanes[plane] = unsafe { crabby_avifAlloc(plane_size) } as *mut _; |
| dst.yuvRowBytes[plane] = (pixel_size * plane_width) as u32; |
| copy_plane_helper( |
| src.yuvPlanes[plane], |
| src.yuvRowBytes[plane], |
| dst.yuvPlanes[plane], |
| dst.yuvRowBytes[plane], |
| plane_width, |
| plane_height, |
| pixel_size, |
| ); |
| dst.imageOwnsYUVPlanes = AVIF_TRUE; |
| } |
| } |
| if (planes & 2) != 0 && !src.alphaPlane.is_null() && src.alphaRowBytes != 0 { |
| let plane_height = usize_from_u32_or_fail!(src.height); |
| let plane_width = usize_from_u32_or_fail!(src.width); |
| let plane_size = plane_width * plane_height * pixel_size; |
| dst.alphaPlane = unsafe { crabby_avifAlloc(plane_size) } as *mut _; |
| dst.alphaRowBytes = (pixel_size * plane_width) as u32; |
| copy_plane_helper( |
| src.alphaPlane, |
| src.alphaRowBytes, |
| dst.alphaPlane, |
| dst.alphaRowBytes, |
| plane_width, |
| plane_height, |
| pixel_size, |
| ); |
| dst.imageOwnsAlphaPlane = AVIF_TRUE; |
| } |
| avifResult::Ok |
| } |
| |
| fn avif_image_allocate_planes_helper( |
| image: &mut avifImage, |
| planes: avifPlanesFlags, |
| ) -> AvifResult<()> { |
| if image.width == 0 || image.height == 0 { |
| return Err(AvifError::InvalidArgument); |
| } |
| let channel_size = if image.depth == 8 { 1 } else { 2 }; |
| let y_row_bytes = usize_from_u32(image.width * channel_size)?; |
| let y_size = y_row_bytes |
| .checked_mul(usize_from_u32(image.height)?) |
| .ok_or(avifResult::InvalidArgument)?; |
| if (planes & 1) != 0 && image.yuvFormat != PixelFormat::None { |
| image.imageOwnsYUVPlanes = AVIF_TRUE; |
| if image.yuvPlanes[0].is_null() { |
| image.yuvRowBytes[0] = u32_from_usize(y_row_bytes)?; |
| image.yuvPlanes[0] = unsafe { crabby_avifAlloc(y_size) as *mut u8 }; |
| } |
| if !image.yuvFormat.is_monochrome() { |
| let csx0 = image.yuvFormat.chroma_shift_x().0 as u64; |
| let csx1 = image.yuvFormat.chroma_shift_x().1 as u64; |
| let width = (((image.width as u64) + csx0) >> csx0) << csx1; |
| let csy = image.yuvFormat.chroma_shift_y() as u64; |
| let height = ((image.height as u64) + csy) >> csy; |
| let uv_row_bytes = usize_from_u64(width * channel_size as u64)?; |
| let uv_size = usize_from_u64(uv_row_bytes as u64 * height)?; |
| for plane in 1usize..=2 { |
| if !image.yuvPlanes[plane].is_null() { |
| continue; |
| } |
| image.yuvRowBytes[plane] = u32_from_usize(uv_row_bytes)?; |
| image.yuvPlanes[plane] = unsafe { crabby_avifAlloc(uv_size) as *mut u8 }; |
| } |
| } |
| } |
| if (planes & 2) != 0 { |
| image.imageOwnsAlphaPlane = AVIF_TRUE; |
| image.alphaRowBytes = u32_from_usize(y_row_bytes)?; |
| image.alphaPlane = unsafe { crabby_avifAlloc(y_size) as *mut u8 }; |
| } |
| Ok(()) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageAllocatePlanes( |
| image: *mut avifImage, |
| planes: avifPlanesFlags, |
| ) -> avifResult { |
| let image = unsafe { &mut (*image) }; |
| to_avifResult(&avif_image_allocate_planes_helper(image, planes)) |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageFreePlanes( |
| image: *mut avifImage, |
| planes: avifPlanesFlags, |
| ) { |
| let image = unsafe { &mut (*image) }; |
| if (planes & 1) != 0 { |
| for plane in 0usize..3 { |
| if image.imageOwnsYUVPlanes == AVIF_TRUE { |
| unsafe { |
| crabby_avifFree(image.yuvPlanes[plane] as *mut c_void); |
| } |
| } |
| image.yuvPlanes[plane] = std::ptr::null_mut(); |
| image.yuvRowBytes[plane] = 0; |
| } |
| image.imageOwnsYUVPlanes = AVIF_FALSE; |
| } |
| if (planes & 2) != 0 { |
| if image.imageOwnsAlphaPlane == AVIF_TRUE { |
| unsafe { |
| crabby_avifFree(image.alphaPlane as *mut c_void); |
| } |
| } |
| image.alphaPlane = std::ptr::null_mut(); |
| image.alphaRowBytes = 0; |
| image.imageOwnsAlphaPlane = AVIF_FALSE; |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageDestroy(image: *mut avifImage) { |
| unsafe { |
| crabby_avifImageFreePlanes(image, avifPlanesFlag::AvifPlanesAll as u32); |
| let _ = Box::from_raw(image); |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageUsesU16(image: *const avifImage) -> avifBool { |
| unsafe { to_avifBool(!image.is_null() && (*image).depth > 8) } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageIsOpaque(image: *const avifImage) -> avifBool { |
| unsafe { |
| // TODO: Check for pixel level opacity as well. |
| to_avifBool(!image.is_null() && !(*image).alphaPlane.is_null()) |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImagePlane(image: *const avifImage, channel: c_int) -> *mut u8 { |
| if image.is_null() { |
| return std::ptr::null_mut(); |
| } |
| unsafe { |
| match channel { |
| 0..=2 => (*image).yuvPlanes[channel as usize], |
| 3 => (*image).alphaPlane, |
| _ => std::ptr::null_mut(), |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImagePlaneRowBytes( |
| image: *const avifImage, |
| channel: c_int, |
| ) -> u32 { |
| if image.is_null() { |
| return 0; |
| } |
| unsafe { |
| match channel { |
| 0..=2 => (*image).yuvRowBytes[channel as usize], |
| 3 => (*image).alphaRowBytes, |
| _ => 0, |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImagePlaneWidth( |
| image: *const avifImage, |
| channel: c_int, |
| ) -> u32 { |
| if image.is_null() { |
| return 0; |
| } |
| unsafe { |
| match channel { |
| 0 => (*image).width, |
| 1 | 2 => { |
| if (*image).yuvFormat.is_monochrome() { |
| 0 |
| } else { |
| let shift_x = (*image).yuvFormat.chroma_shift_x(); |
| (((*image).width + shift_x.0) >> shift_x.0) << shift_x.1 |
| } |
| } |
| 3 => { |
| if !(*image).alphaPlane.is_null() { |
| (*image).width |
| } else { |
| 0 |
| } |
| } |
| _ => 0, |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImagePlaneHeight( |
| image: *const avifImage, |
| channel: c_int, |
| ) -> u32 { |
| if image.is_null() { |
| return 0; |
| } |
| unsafe { |
| match channel { |
| 0 => (*image).height, |
| 1 | 2 => { |
| if (*image).yuvFormat.is_monochrome() { |
| 0 |
| } else { |
| let shift_y = (*image).yuvFormat.chroma_shift_y(); |
| ((*image).height + shift_y) >> shift_y |
| } |
| } |
| 3 => { |
| if !(*image).alphaPlane.is_null() { |
| (*image).height |
| } else { |
| 0 |
| } |
| } |
| _ => 0, |
| } |
| } |
| } |
| |
| #[no_mangle] |
| pub unsafe extern "C" fn crabby_avifImageSetViewRect( |
| dstImage: *mut avifImage, |
| srcImage: *const avifImage, |
| rect: *const avifCropRect, |
| ) -> avifResult { |
| let dst = unsafe { &mut (*dstImage) }; |
| let src = unsafe { &(*srcImage) }; |
| let rect = unsafe { &(*rect) }; |
| if rect.width > src.width |
| || rect.height > src.height |
| || rect.x > (src.width - rect.width) |
| || rect.y > (src.height - rect.height) |
| { |
| return avifResult::InvalidArgument; |
| } |
| if !src.yuvFormat.is_monochrome() |
| && ((rect.x & src.yuvFormat.chroma_shift_x().0) != 0 |
| || (rect.y & src.yuvFormat.chroma_shift_y()) != 0) |
| { |
| return avifResult::InvalidArgument; |
| } |
| *dst = avifImage { |
| width: src.width, |
| height: src.height, |
| depth: src.depth, |
| yuvFormat: src.yuvFormat, |
| yuvRange: src.yuvRange, |
| yuvChromaSamplePosition: src.yuvChromaSamplePosition, |
| alphaPremultiplied: src.alphaPremultiplied, |
| colorPrimaries: src.colorPrimaries, |
| transferCharacteristics: src.transferCharacteristics, |
| matrixCoefficients: src.matrixCoefficients, |
| clli: src.clli, |
| transformFlags: src.transformFlags, |
| pasp: src.pasp, |
| clap: src.clap, |
| irot: src.irot, |
| imir: src.imir, |
| ..avifImage::default() |
| }; |
| dst.width = rect.width; |
| dst.height = rect.height; |
| let pixel_size: u32 = if src.depth == 8 { 1 } else { 2 }; |
| for plane in 0usize..3 { |
| if src.yuvPlanes[plane].is_null() { |
| continue; |
| } |
| let chroma_shift = src.yuvFormat.chroma_shift_x(); |
| let x = if plane == 0 { rect.x } else { (rect.x >> chroma_shift.0) << chroma_shift.1 }; |
| let y = if plane == 0 { rect.y } else { rect.y >> src.yuvFormat.chroma_shift_y() }; |
| let offset = match isize_from_u32(y * src.yuvRowBytes[plane] + x * pixel_size) { |
| Ok(x) => x, |
| _ => return avifResult::InvalidArgument, |
| }; |
| dst.yuvPlanes[plane] = unsafe { src.yuvPlanes[plane].offset(offset) }; |
| dst.yuvRowBytes[plane] = src.yuvRowBytes[plane]; |
| } |
| if !src.alphaPlane.is_null() { |
| let offset = match isize_from_u32(rect.y * src.alphaRowBytes + rect.x * pixel_size) { |
| Ok(x) => x, |
| _ => return avifResult::InvalidArgument, |
| }; |
| dst.alphaPlane = unsafe { src.alphaPlane.offset(offset) }; |
| dst.alphaRowBytes = src.alphaRowBytes; |
| } |
| avifResult::Ok |
| } |