blob: aaefe6b3db9984e3a73718fba0cf34a72c40b624 [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.
pub mod io;
pub mod pixels;
pub mod stream;
use crate::parser::mp4box::*;
use crate::utils::*;
use crate::*;
use std::num::NonZero;
use std::ops::Range;
// 'clap' fractions do not follow this pattern: both numerators and denominators
// are used as i32, but they are signalled as u32 according to the specification
// as of 2024. This may be fixed in later versions of the specification, see
// https://github.com/AOMediaCodec/libavif/pull/1749#discussion_r1391612932.
#[derive(Clone, Copy, Debug, Default)]
pub struct IFraction(pub i32, pub i32);
impl TryFrom<UFraction> for IFraction {
type Error = AvifError;
fn try_from(uf: UFraction) -> AvifResult<IFraction> {
Ok(IFraction(uf.0 as i32, i32_from_u32(uf.1)?))
}
}
impl IFraction {
fn gcd(a: i32, b: i32) -> i32 {
let mut a = if a < 0 { -a as i64 } else { a as i64 };
let mut b = if b < 0 { -b as i64 } else { b as i64 };
while b != 0 {
let r = a % b;
a = b;
b = r;
}
a as i32
}
pub(crate) fn simplified(n: i32, d: i32) -> Self {
let mut fraction = IFraction(n, d);
fraction.simplify();
fraction
}
pub(crate) fn simplify(&mut self) {
let gcd = Self::gcd(self.0, self.1);
if gcd > 1 {
self.0 /= gcd;
self.1 /= gcd;
}
}
pub(crate) fn get_i32(&self) -> i32 {
assert!(self.1 != 0);
self.0 / self.1
}
pub(crate) fn get_u32(&self) -> AvifResult<u32> {
u32_from_i32(self.get_i32())
}
pub(crate) fn is_integer(&self) -> bool {
self.0 % self.1 == 0
}
fn common_denominator(&mut self, val: &mut IFraction) -> AvifResult<()> {
self.simplify();
if self.1 == val.1 {
return Ok(());
}
let self_d = self.1;
self.0 = self
.0
.checked_mul(val.1)
.ok_or(AvifError::UnknownError("".into()))?;
self.1 = self
.1
.checked_mul(val.1)
.ok_or(AvifError::UnknownError("".into()))?;
val.0 = val
.0
.checked_mul(self_d)
.ok_or(AvifError::UnknownError("".into()))?;
val.1 = val
.1
.checked_mul(self_d)
.ok_or(AvifError::UnknownError("".into()))?;
Ok(())
}
pub(crate) fn add(&mut self, val: &IFraction) -> AvifResult<()> {
let mut val = *val;
val.simplify();
self.common_denominator(&mut val)?;
self.0 = self
.0
.checked_add(val.0)
.ok_or(AvifError::UnknownError("".into()))?;
self.simplify();
Ok(())
}
pub(crate) fn sub(&mut self, val: &IFraction) -> AvifResult<()> {
let mut val = *val;
val.simplify();
self.common_denominator(&mut val)?;
self.0 = self
.0
.checked_sub(val.0)
.ok_or(AvifError::UnknownError("".into()))?;
self.simplify();
Ok(())
}
}
macro_rules! conversion_function {
($func:ident, $to: ident, $from:ty) => {
pub(crate) fn $func(value: $from) -> AvifResult<$to> {
$to::try_from(value).or(Err(AvifError::BmffParseFailed("".into())))
}
};
}
conversion_function!(usize_from_u64, usize, u64);
conversion_function!(usize_from_u32, usize, u32);
conversion_function!(usize_from_u16, usize, u16);
#[cfg(feature = "android_mediacodec")]
conversion_function!(usize_from_isize, usize, isize);
conversion_function!(u64_from_usize, u64, usize);
conversion_function!(u32_from_usize, u32, usize);
conversion_function!(u32_from_u64, u32, u64);
conversion_function!(u32_from_i32, u32, i32);
conversion_function!(i32_from_u32, i32, u32);
#[cfg(feature = "android_mediacodec")]
conversion_function!(isize_from_i32, isize, i32);
#[cfg(any(feature = "capi", feature = "android_mediacodec"))]
conversion_function!(isize_from_u32, isize, u32);
conversion_function!(isize_from_usize, isize, usize);
#[cfg(feature = "android_mediacodec")]
conversion_function!(i32_from_usize, i32, usize);
macro_rules! clamp_function {
($func:ident, $type:ty) => {
pub(crate) fn $func(value: $type, low: $type, high: $type) -> $type {
if value < low {
low
} else if value > high {
high
} else {
value
}
}
};
}
clamp_function!(clamp_u16, u16);
clamp_function!(clamp_f32, f32);
clamp_function!(clamp_i32, i32);
macro_rules! find_property {
($properties:expr, $property_name:ident) => {
$properties.iter().find_map(|p| match p {
ItemProperty::$property_name(value) => Some(value.clone()),
_ => None,
})
};
}
// Returns the colr nclx property. Returns an error if there are multiple ones.
pub(crate) fn find_nclx(properties: &[ItemProperty]) -> AvifResult<Option<&Nclx>> {
let mut single_nclx: Option<&Nclx> = None;
for property in properties {
if let ItemProperty::ColorInformation(ColorInformation::Nclx(nclx)) = property {
if single_nclx.is_some() {
return Err(AvifError::BmffParseFailed(
"multiple nclx were found".into(),
));
}
single_nclx = Some(nclx);
}
}
Ok(single_nclx)
}
// Returns the colr icc property. Returns an error if there are multiple ones.
pub(crate) fn find_icc(properties: &[ItemProperty]) -> AvifResult<Option<&Vec<u8>>> {
let mut single_icc: Option<&Vec<u8>> = None;
for property in properties {
if let ItemProperty::ColorInformation(ColorInformation::Icc(icc)) = property {
if single_icc.is_some() {
return Err(AvifError::BmffParseFailed("multiple icc were found".into()));
}
single_icc = Some(icc);
}
}
Ok(single_icc)
}
pub(crate) fn check_limits(
width: u32,
height: u32,
size_limit: Option<NonZero<u32>>,
dimension_limit: Option<NonZero<u32>>,
) -> bool {
if height == 0 {
return false;
}
if let Some(limit) = size_limit {
if width > limit.get() / height {
return false;
}
}
if let Some(limit) = dimension_limit {
if width > limit.get() || height > limit.get() {
return false;
}
}
true
}
fn limited_to_full(min: i32, max: i32, full: i32, v: u16) -> u16 {
let v = v as i32;
clamp_i32(
(((v - min) * full) + ((max - min) / 2)) / (max - min),
0,
full,
) as u16
}
pub(crate) fn limited_to_full_y(depth: u8, v: u16) -> u16 {
match depth {
8 => limited_to_full(16, 235, 255, v),
10 => limited_to_full(64, 940, 1023, v),
12 => limited_to_full(256, 3760, 4095, v),
_ => 0,
}
}
pub(crate) fn create_vec_exact<T>(size: usize) -> AvifResult<Vec<T>> {
let mut v = Vec::<T>::new();
let allocation_size = size
.checked_mul(std::mem::size_of::<T>())
.ok_or(AvifError::OutOfMemory)?;
// TODO: b/342251590 - Do not request allocations of more than what is allowed in Chromium's
// partition allocator. This is the allowed limit in the chromium fuzzers. The value comes
// from:
// https://source.chromium.org/chromium/chromium/src/+/main:base/allocator/partition_allocator/src/partition_alloc/partition_alloc_constants.h;l=433-440;drc=c0265133106c7647e90f9aaa4377d28190b1a6a9.
// Requesting an allocation larger than this value will cause the fuzzers to crash instead of
// returning null. Remove this check once that behavior is fixed.
if u64_from_usize(allocation_size)? >= 2_145_386_496 {
return Err(AvifError::OutOfMemory);
}
if v.try_reserve_exact(size).is_err() {
return Err(AvifError::OutOfMemory);
}
Ok(v)
}
#[cfg(test)]
pub(crate) fn assert_eq_f32_array(a: &[f32], b: &[f32]) {
assert_eq!(a.len(), b.len());
for i in 0..a.len() {
assert!((a[i] - b[i]).abs() <= std::f32::EPSILON);
}
}
pub(crate) fn check_slice_range(len: usize, range: &Range<usize>) -> AvifResult<()> {
if range.start >= len || range.end > len {
return Err(AvifError::NoContent);
}
Ok(())
}
pub(crate) fn is_auxiliary_type_alpha(aux_type: &str) -> bool {
aux_type == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
|| aux_type == "urn:mpeg:hevc:2015:auxid:1"
}
pub(crate) fn validate_grid_image_dimensions(image: &Image, grid: &Grid) -> AvifResult<()> {
if checked_mul!(image.width, grid.columns)? < grid.width
|| checked_mul!(image.height, grid.rows)? < grid.height
{
return Err(AvifError::InvalidImageGrid(
"Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), Section 6.6.2.3.1)".into(),
));
}
if checked_mul!(image.width, grid.columns)? < grid.width
|| checked_mul!(image.height, grid.rows)? < grid.height
{
return Err(AvifError::InvalidImageGrid(
"Grid image tiles do not completely cover the image (HEIF (ISO/IEC 23008-12:2017), \
Section 6.6.2.3.1)"
.into(),
));
}
if checked_mul!(image.width, grid.columns - 1)? >= grid.width
|| checked_mul!(image.height, grid.rows - 1)? >= grid.height
{
return Err(AvifError::InvalidImageGrid(
"Grid image tiles in the rightmost column and bottommost row do not overlap the \
reconstructed image grid canvas. See MIAF (ISO/IEC 23000-22:2019), Section \
7.3.11.4.2, Figure 2"
.into(),
));
}
// ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
// - the tile_width shall be greater than or equal to 64, and should be a multiple of 64
// - the tile_height shall be greater than or equal to 64, and should be a multiple of 64
// The "should" part is ignored here.
if image.width < 64 || image.height < 64 {
return Err(AvifError::InvalidImageGrid(format!(
"Grid image tile width ({}) or height ({}) cannot be smaller than 64. See MIAF \
(ISO/IEC 23000-22:2019), Section 7.3.11.4.2",
image.width, image.height
)));
}
// ISO/IEC 23000-22:2019, Section 7.3.11.4.2:
// - when the images are in the 4:2:2 chroma sampling format the horizontal tile offsets
// and widths, and the output width, shall be even numbers;
// - when the images are in the 4:2:0 chroma sampling format both the horizontal and
// vertical tile offsets and widths, and the output width and height, shall be even
// numbers.
if ((image.yuv_format == PixelFormat::Yuv420 || image.yuv_format == PixelFormat::Yuv422)
&& (grid.width % 2 != 0 || image.width % 2 != 0))
|| (image.yuv_format == PixelFormat::Yuv420
&& (grid.height % 2 != 0 || image.height % 2 != 0))
{
return Err(AvifError::InvalidImageGrid(format!(
"Grid image width ({}) or height ({}) or tile width ({}) or height ({}) shall be \
even if chroma is subsampled in that dimension. See MIAF \
(ISO/IEC 23000-22:2019), Section 7.3.11.4.2",
grid.width, grid.height, image.width, image.height
)));
}
Ok(())
}