blob: 1f187eec66d0e08808a8228dd375dc371e87a573 [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 crate::decoder::*;
use crate::internal_utils::stream::*;
use crate::parser::mp4box::*;
use crate::*;
use std::collections::BTreeMap;
use std::num::NonZero;
#[derive(Debug, Default)]
pub struct Item {
pub id: u32,
pub item_type: String,
pub size: usize,
pub width: u32,
pub height: u32,
pub content_type: String,
pub properties: Vec<ItemProperty>,
pub extents: Vec<Extent>,
pub thumbnail_for_id: u32,
pub aux_for_id: u32,
pub desc_for_id: u32,
pub dimg_for_id: u32,
pub dimg_index: u32,
pub prem_by_id: u32,
pub has_unsupported_essential_property: bool,
pub progressive: bool,
pub idat: Vec<u8>,
// Item ids of source items of a derived image item, in the same order as
// they appear in the `dimg` box. E.g. item ids for the cells of a grid
// item, or for the layers of an overlay item.
pub source_item_ids: Vec<u32>,
pub data_buffer: Option<Vec<u8>>,
pub is_made_up: bool, // Placeholder grid alpha item if true.
}
macro_rules! find_property {
($properties:expr, $property_name:ident) => {
$properties.iter().find_map(|p| match p {
ItemProperty::$property_name(value) => Some(value),
_ => None,
})
};
}
impl Item {
pub(crate) fn stream<'a>(&'a mut self, io: &'a mut GenericIO) -> AvifResult<IStream<'a>> {
if !self.idat.is_empty() {
match self.extents.len() {
0 => return Err(AvifError::UnknownError("no extent".into())),
1 => {
let idat = self.idat.as_slice();
let offset = usize_from_u64(self.extents[0].offset)?;
let range = offset..checked_add!(offset, self.size)?;
check_slice_range(idat.len(), &range)?;
return Ok(IStream::create(&idat[range]));
}
_ => {
return Err(AvifError::UnknownError(
"idat with multiple extents is not supported".into(),
));
}
}
}
let io_data = match self.extents.len() {
0 => return Err(AvifError::UnknownError("no extent".into())),
1 => io.read_exact(self.extents[0].offset, self.size)?,
_ => {
if self.data_buffer.is_none() {
// Decoder::prepare_sample() will merge the extents the same way but only for
// image items. It may be necessary here for Exif/XMP metadata for example.
let mut data_buffer: Vec<u8> = create_vec_exact(self.size)?;
for extent in &self.extents {
data_buffer.extend_from_slice(io.read_exact(extent.offset, extent.size)?);
}
self.data_buffer = Some(data_buffer);
}
self.data_buffer.as_ref().unwrap().as_slice()
}
};
Ok(IStream::create(io_data))
}
fn validate_derived_image_dimensions(
width: u32,
height: u32,
size_limit: Option<NonZero<u32>>,
dimension_limit: Option<NonZero<u32>>,
) -> AvifResult<()> {
if width == 0 || height == 0 || !check_limits(width, height, size_limit, dimension_limit) {
return Err(AvifError::InvalidImageGrid(
"invalid derived image dimensions".into(),
));
}
Ok(())
}
pub(crate) fn read_and_parse(
&mut self,
io: &mut GenericIO,
grid: &mut Grid,
overlay: &mut Overlay,
size_limit: Option<NonZero<u32>>,
dimension_limit: Option<NonZero<u32>>,
) -> AvifResult<()> {
if self.is_grid_item() {
let mut stream = self.stream(io)?;
// unsigned int(8) version = 0;
let version = stream.read_u8()?;
if version != 0 {
return Err(AvifError::InvalidImageGrid(
"unsupported version for grid".into(),
));
}
// unsigned int(8) flags;
let flags = stream.read_u8()?;
// unsigned int(8) rows_minus_one;
grid.rows = stream.read_u8()? as u32 + 1;
// unsigned int(8) columns_minus_one;
grid.columns = stream.read_u8()? as u32 + 1;
if (flags & 1) == 1 {
// unsigned int(32) output_width;
grid.width = stream.read_u32()?;
// unsigned int(32) output_height;
grid.height = stream.read_u32()?;
} else {
// unsigned int(16) output_width;
grid.width = stream.read_u16()? as u32;
// unsigned int(16) output_height;
grid.height = stream.read_u16()? as u32;
}
Self::validate_derived_image_dimensions(
grid.width,
grid.height,
size_limit,
dimension_limit,
)?;
if stream.has_bytes_left()? {
return Err(AvifError::InvalidImageGrid(
"found unknown extra bytes in the grid box".into(),
));
}
} else if self.is_overlay_item() {
let reference_count = self.source_item_ids.len();
let mut stream = self.stream(io)?;
// unsigned int(8) version = 0;
let version = stream.read_u8()?;
if version != 0 {
return Err(AvifError::InvalidImageGrid(format!(
"unsupported version {version} for iovl"
)));
}
// unsigned int(8) flags;
let flags = stream.read_u8()?;
for j in 0..4 {
// unsigned int(16) canvas_fill_value;
overlay.canvas_fill_value[j] = stream.read_u16()?;
}
if (flags & 1) == 1 {
// unsigned int(32) output_width;
overlay.width = stream.read_u32()?;
// unsigned int(32) output_height;
overlay.height = stream.read_u32()?;
} else {
// unsigned int(16) output_width;
overlay.width = stream.read_u16()? as u32;
// unsigned int(16) output_height;
overlay.height = stream.read_u16()? as u32;
}
Self::validate_derived_image_dimensions(
overlay.width,
overlay.height,
size_limit,
dimension_limit,
)?;
for _ in 0..reference_count {
if (flags & 1) == 1 {
// unsigned int(32) horizontal_offset;
overlay.horizontal_offsets.push(stream.read_i32()?);
// unsigned int(32) vertical_offset;
overlay.vertical_offsets.push(stream.read_i32()?);
} else {
// unsigned int(16) horizontal_offset;
overlay.horizontal_offsets.push(stream.read_i16()? as i32);
// unsigned int(16) vertical_offset;
overlay.vertical_offsets.push(stream.read_i16()? as i32);
}
}
if stream.has_bytes_left()? {
return Err(AvifError::InvalidImageGrid(
"found unknown extra bytes in the iovl box".into(),
));
}
}
Ok(())
}
pub(crate) fn operating_point(&self) -> u8 {
match find_property!(self.properties, OperatingPointSelector) {
Some(operating_point_selector) => *operating_point_selector,
_ => 0, // default operating point.
}
}
pub(crate) fn harvest_ispe(
&mut self,
alpha_ispe_required: bool,
size_limit: Option<NonZero<u32>>,
dimension_limit: Option<NonZero<u32>>,
) -> AvifResult<()> {
if self.should_skip() {
return Ok(());
}
match find_property!(self.properties, ImageSpatialExtents) {
Some(image_spatial_extents) => {
self.width = image_spatial_extents.width;
self.height = image_spatial_extents.height;
if self.width == 0 || self.height == 0 {
return Err(AvifError::BmffParseFailed(
"item id has invalid size.".into(),
));
}
if !check_limits(
image_spatial_extents.width,
image_spatial_extents.height,
size_limit,
dimension_limit,
) {
return Err(AvifError::BmffParseFailed(
"item dimensions too large".into(),
));
}
}
None => {
// No ispe was found.
if self.is_auxiliary_alpha() {
if alpha_ispe_required {
return Err(AvifError::BmffParseFailed(
"alpha auxiliary image item is missing mandatory ispe".into(),
));
}
} else {
return Err(AvifError::BmffParseFailed(
"item is missing mandatory ispe property".into(),
));
}
}
}
Ok(())
}
pub(crate) fn validate_properties(&self, items: &Items, pixi_required: bool) -> AvifResult<()> {
let codec_config = self
.codec_config()
.ok_or(AvifError::BmffParseFailed("missing av1C property".into()))?;
if self.is_derived_image_item() {
for derived_item_id in &self.source_item_ids {
let derived_item = items.get(derived_item_id).unwrap();
let derived_codec_config =
derived_item
.codec_config()
.ok_or(AvifError::BmffParseFailed(
"missing codec config property".into(),
))?;
if codec_config != derived_codec_config {
return Err(AvifError::BmffParseFailed(
"codec config of derived items do not match".into(),
));
}
}
}
match self.pixi() {
Some(pixi) => {
for depth in &pixi.plane_depths {
if *depth != codec_config.depth() {
return Err(AvifError::BmffParseFailed(
"pixi depth does not match codec config depth".into(),
));
}
}
}
None => {
if pixi_required {
return Err(AvifError::BmffParseFailed("missing pixi property".into()));
}
}
}
Ok(())
}
pub(crate) fn codec_config(&self) -> Option<&CodecConfiguration> {
find_property!(self.properties, CodecConfiguration)
}
pub(crate) fn pixi(&self) -> Option<&PixelInformation> {
find_property!(self.properties, PixelInformation)
}
pub(crate) fn a1lx(&self) -> Option<&[usize; 3]> {
find_property!(self.properties, AV1LayeredImageIndexing)
}
pub(crate) fn lsel(&self) -> Option<&u16> {
find_property!(self.properties, LayerSelector)
}
pub(crate) fn clli(&self) -> Option<&ContentLightLevelInformation> {
find_property!(self.properties, ContentLightLevelInformation)
}
pub(crate) fn is_auxiliary_alpha(&self) -> bool {
matches!(find_property!(&self.properties, AuxiliaryType),
Some(aux_type) if is_auxiliary_type_alpha(aux_type))
}
pub(crate) fn is_image_codec_item(&self) -> bool {
[
"av01",
#[cfg(feature = "heic")]
"hvc1",
]
.contains(&self.item_type.as_str())
}
pub(crate) fn is_grid_item(&self) -> bool {
self.item_type == "grid"
}
pub(crate) fn is_overlay_item(&self) -> bool {
self.item_type == "iovl"
}
pub(crate) fn is_derived_image_item(&self) -> bool {
self.is_grid_item() || self.is_overlay_item() || self.is_tmap()
}
pub(crate) fn is_image_item(&self) -> bool {
// Adding || self.is_tmap() here would cause differences with libavif.
self.is_image_codec_item() || self.is_grid_item() || self.is_overlay_item()
}
pub(crate) fn should_skip(&self) -> bool {
// The item has no payload in idat or mdat. It cannot be a coded image item, a
// non-identity derived image item, or Exif/XMP metadata.
self.size == 0
// An essential property isn't supported by libavif. Ignore the whole item.
|| self.has_unsupported_essential_property
// Probably Exif/XMP or some other data.
|| !self.is_image_item()
// libavif does not support thumbnails.
|| self.thumbnail_for_id != 0
}
fn is_metadata(&self, item_type: &str, color_id: Option<u32>) -> bool {
self.size != 0
&& !self.has_unsupported_essential_property
&& (color_id.is_none() || self.desc_for_id == color_id.unwrap())
&& self.item_type == *item_type
}
pub(crate) fn is_exif(&self, color_id: Option<u32>) -> bool {
self.is_metadata("Exif", color_id)
}
pub(crate) fn is_xmp(&self, color_id: Option<u32>) -> bool {
self.is_metadata("mime", color_id) && self.content_type == "application/rdf+xml"
}
pub(crate) fn is_tmap(&self) -> bool {
self.is_metadata("tmap", None) && self.thumbnail_for_id == 0
}
pub(crate) fn max_extent(&self, sample: &DecodeSample) -> AvifResult<Extent> {
if !self.idat.is_empty() {
return Ok(Extent::default());
}
if sample.size == 0 {
return Err(AvifError::TruncatedData);
}
let mut remaining_offset = sample.offset;
let mut min_offset = u64::MAX;
let mut max_offset = 0;
if self.extents.is_empty() {
return Err(AvifError::TruncatedData);
} else if self.extents.len() == 1 {
min_offset = sample.offset;
max_offset = checked_add!(sample.offset, u64_from_usize(sample.size)?)?;
} else {
let mut remaining_size = sample.size;
for extent in &self.extents {
let mut start_offset = extent.offset;
let mut size = extent.size;
let sizeu64 = u64_from_usize(size)?;
if remaining_offset != 0 {
if remaining_offset >= sizeu64 {
remaining_offset -= sizeu64;
continue;
} else {
checked_incr!(start_offset, remaining_offset);
checked_decr!(size, usize_from_u64(remaining_offset)?);
remaining_offset = 0;
}
}
// TODO(yguyon): Add comment to explain why it is fine to clip the extent size.
let used_extent_size = std::cmp::min(size, remaining_size);
let end_offset = checked_add!(start_offset, u64_from_usize(used_extent_size)?)?;
min_offset = std::cmp::min(min_offset, start_offset);
max_offset = std::cmp::max(max_offset, end_offset);
remaining_size -= used_extent_size;
if remaining_size == 0 {
break;
}
}
if remaining_size != 0 {
return Err(AvifError::TruncatedData);
}
}
Ok(Extent {
offset: min_offset,
size: usize_from_u64(checked_sub!(max_offset, min_offset)?)?,
})
}
}
pub type Items = BTreeMap<u32, Item>;
fn insert_item_if_not_exists(id: u32, items: &mut Items) {
if items.contains_key(&id) {
return;
}
items.insert(
id,
Item {
id,
..Item::default()
},
);
}
pub(crate) fn construct_items(meta: &MetaBox) -> AvifResult<Items> {
let mut items: Items = BTreeMap::new();
for iinf in &meta.iinf {
items.insert(
iinf.item_id,
Item {
id: iinf.item_id,
item_type: iinf.item_type.clone(),
content_type: iinf.content_type.clone(),
..Item::default()
},
);
}
for iloc in &meta.iloc.items {
insert_item_if_not_exists(iloc.item_id, &mut items);
let item = items.get_mut(&iloc.item_id).unwrap();
if !item.extents.is_empty() {
return Err(AvifError::BmffParseFailed(
"item already has extents".into(),
));
}
if iloc.construction_method == 1 {
item.idat.clone_from(&meta.idat);
}
for extent in &iloc.extents {
item.extents.push(Extent {
offset: checked_add!(iloc.base_offset, extent.offset)?,
size: extent.size,
});
checked_incr!(item.size, extent.size);
}
}
let mut ipma_seen: HashSet<u32> = HashSet::with_hasher(NonRandomHasherState);
for association in &meta.iprp.associations {
if association.associations.is_empty() {
continue;
}
if ipma_seen.contains(&association.item_id) {
return Err(AvifError::BmffParseFailed(
"item has duplicate ipma entry".into(),
));
}
ipma_seen.insert(association.item_id);
insert_item_if_not_exists(association.item_id, &mut items);
let item = items.get_mut(&association.item_id).unwrap();
for (property_index_ref, essential_ref) in &association.associations {
let property_index: usize = *property_index_ref as usize;
let essential = *essential_ref;
if property_index == 0 {
if essential {
return Err(AvifError::BmffParseFailed(format!(
"item id {} contains an illegal essential property index 0",
{ item.id }
)));
}
continue;
}
// property_index is 1-based.
if property_index > meta.iprp.properties.len() {
return Err(AvifError::BmffParseFailed(
"invalid property_index in ipma".into(),
));
}
match (&meta.iprp.properties[property_index - 1], essential) {
(ItemProperty::Unknown(_), true) => item.has_unsupported_essential_property = true,
(ItemProperty::AV1LayeredImageIndexing(_), true) => {
return Err(AvifError::BmffParseFailed(
"invalid essential property".into(),
));
}
(
ItemProperty::OperatingPointSelector(_)
| ItemProperty::LayerSelector(_)
// MIAF 2019/Amd. 2:2021: Section 7.3.9:
// All transformative properties associated with coded and derived images
// shall be marked as essential.
| ItemProperty::CleanAperture(_)
| ItemProperty::ImageRotation(_)
| ItemProperty::ImageMirror(_),
false,
) => {
return Err(AvifError::BmffParseFailed(
"required essential property not marked as essential".into(),
));
}
(property, _) => item.properties.push(property.clone()),
}
}
}
for reference in &meta.iref {
insert_item_if_not_exists(reference.from_item_id, &mut items);
let item = items.get_mut(&reference.from_item_id).unwrap();
match reference.reference_type.as_str() {
"thmb" => item.thumbnail_for_id = reference.to_item_id,
"auxl" => item.aux_for_id = reference.to_item_id,
"cdsc" => item.desc_for_id = reference.to_item_id,
"prem" => item.prem_by_id = reference.to_item_id,
"dimg" => {
// derived images refer in the opposite direction.
insert_item_if_not_exists(reference.to_item_id, &mut items);
let dimg_item = items.get_mut(&reference.to_item_id).unwrap();
if dimg_item.dimg_for_id != 0 {
return Err(if dimg_item.dimg_for_id == reference.from_item_id {
// Section 8.11.12.1 of ISO/IEC 14496-12:
// The items linked to are then represented by an array of to_item_IDs;
// within a given array, a given value shall occur at most once.
AvifError::BmffParseFailed(format!(
"multiple dimg references for item ID {}",
dimg_item.dimg_for_id
))
} else {
AvifError::NotImplemented
});
}
dimg_item.dimg_for_id = reference.from_item_id;
dimg_item.dimg_index = reference.index;
}
_ => {
// unknown reference type, ignore.
}
}
}
Ok(items)
}