| // 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(()) |
| } |