blob: ab7ad1d841bc74759f17a070198bc35a6e90a828 [file] [log] [blame]
// 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
}