blob: 4dd1776629da5e9303ec5d100cc021b2edb0203e [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::*;
use std::num::NonZero;
#[derive(Debug, Default)]
pub struct DecodeSample {
pub item_id: u32, // 1-based. 0 if it comes from a track.
pub offset: u64,
pub size: usize,
pub spatial_id: u8,
pub sync: bool,
}
impl DecodeSample {
pub(crate) fn partial_data<'a>(
&'a self,
io: &'a mut Box<impl decoder::IO + ?Sized>,
buffer: &'a Option<Vec<u8>>,
size: usize,
) -> AvifResult<&'a [u8]> {
match buffer {
Some(x) => {
let start_offset = usize_from_u64(self.offset)?;
let end_offset = checked_add!(start_offset, size)?;
let range = start_offset..end_offset;
check_slice_range(x.len(), &range)?;
Ok(&x[range])
}
None => {
let data = io.read(self.offset, size)?;
if data.len() != size {
Err(AvifError::TruncatedData)
} else {
Ok(data)
}
}
}
}
pub(crate) fn data<'a>(
&'a self,
io: &'a mut Box<impl decoder::IO + ?Sized>,
buffer: &'a Option<Vec<u8>>,
) -> AvifResult<&'a [u8]> {
self.partial_data(io, buffer, self.size)
}
}
#[derive(Debug, Default)]
pub struct DecodeInput {
pub samples: Vec<DecodeSample>,
pub all_layers: bool,
pub category: Category,
}
#[derive(Debug, Default)]
pub struct Overlay {
pub canvas_fill_value: [u16; 4],
pub width: u32,
pub height: u32,
pub horizontal_offsets: Vec<i32>,
pub vertical_offsets: Vec<i32>,
}
#[derive(Debug, Default)]
pub(crate) struct TileInfo {
pub tile_count: u32,
pub decoded_tile_count: u32,
pub grid: Grid,
pub overlay: Overlay,
}
impl TileInfo {
pub(crate) fn is_grid(&self) -> bool {
self.grid.rows > 0 && self.grid.columns > 0
}
pub(crate) fn is_overlay(&self) -> bool {
!self.overlay.horizontal_offsets.is_empty() && !self.overlay.vertical_offsets.is_empty()
}
pub(crate) fn is_derived_image(&self) -> bool {
self.is_grid() || self.is_overlay()
}
pub(crate) fn grid_tile_count(&self) -> AvifResult<u32> {
if self.is_grid() {
checked_mul!(self.grid.rows, self.grid.columns)
} else {
Ok(1)
}
}
pub(crate) fn decoded_row_count(&self, image_height: u32, tile_height: u32) -> u32 {
if self.decoded_tile_count == 0 {
return 0;
}
if self.decoded_tile_count == self.tile_count || !self.is_grid() {
return image_height;
}
std::cmp::min(
(self.decoded_tile_count / self.grid.columns) * tile_height,
image_height,
)
}
pub(crate) fn is_fully_decoded(&self) -> bool {
self.tile_count == self.decoded_tile_count
}
}
#[derive(Default)]
pub struct Tile {
pub width: u32,
pub height: u32,
pub operating_point: u8,
pub image: Image,
pub input: DecodeInput,
pub codec_index: usize,
pub codec_config: CodecConfiguration,
}
impl Tile {
pub(crate) fn create_from_item(
item: &mut Item,
allow_progressive: bool,
image_count_limit: Option<NonZero<u32>>,
size_hint: u64,
) -> AvifResult<Tile> {
if size_hint != 0 && item.size as u64 > size_hint {
return Err(AvifError::BmffParseFailed("exceeded size_hint".into()));
}
let mut tile = Tile {
width: item.width,
height: item.height,
operating_point: item.operating_point(),
image: Image::default(),
codec_config: item
.codec_config()
.ok_or(AvifError::BmffParseFailed("missing av1C property".into()))?
.clone(),
..Tile::default()
};
let mut layer_sizes: [usize; MAX_AV1_LAYER_COUNT] = [0; MAX_AV1_LAYER_COUNT];
let mut layer_count: usize = 0;
let a1lx = item.a1lx();
let has_a1lx = a1lx.is_some();
if let Some(a1lx) = a1lx {
let mut remaining_size: usize = item.size;
for i in 0usize..3 {
layer_count += 1;
if a1lx[i] > 0 {
// >= instead of > because there must be room for the last layer
if a1lx[i] >= remaining_size {
return Err(AvifError::BmffParseFailed(format!(
"a1lx layer index [{i}] does not fit in item size"
)));
}
layer_sizes[i] = a1lx[i];
remaining_size -= a1lx[i];
} else {
layer_sizes[i] = remaining_size;
remaining_size = 0;
break;
}
}
if remaining_size > 0 {
assert!(layer_count == 3);
layer_count += 1;
layer_sizes[3] = remaining_size;
}
}
let lsel;
let has_lsel;
match item.lsel() {
Some(x) => {
lsel = *x;
has_lsel = true;
}
None => {
lsel = 0;
has_lsel = false;
}
}
// Progressive images offer layers via the a1lxProp, but don't specify a layer selection with
// lsel.
item.progressive = has_a1lx && (!has_lsel || lsel == 0xFFFF);
let base_item_offset = if item.extents.len() == 1 { item.extents[0].offset } else { 0 };
if has_lsel && lsel != 0xFFFF {
// Layer selection. This requires that the underlying AV1 codec decodes all layers, and
// then only returns the requested layer as a single frame. To the user of libavif,
// this appears to be a single frame.
tile.input.all_layers = true;
let mut sample_size: usize = 0;
let layer_id = usize_from_u16(lsel)?;
if layer_count > 0 {
// Optimization: If we're selecting a layer that doesn't require the entire image's
// payload (hinted via the a1lx box).
if layer_id >= layer_count {
return Err(AvifError::InvalidImageGrid(
"lsel layer index not found in a1lx.".into(),
));
}
let layer_id_plus_1 = layer_id + 1;
for layer_size in layer_sizes.iter().take(layer_id_plus_1) {
checked_incr!(sample_size, *layer_size);
}
} else {
// This layer payload subsection is not known. Use the whole payload.
sample_size = item.size;
}
let sample = DecodeSample {
item_id: item.id,
offset: base_item_offset,
size: sample_size,
spatial_id: lsel as u8,
sync: true,
};
tile.input.samples.push(sample);
} else if item.progressive && allow_progressive {
// Progressive image. Decode all layers and expose them all to the
// user.
if let Some(limit) = image_count_limit {
if layer_count as u32 > limit.get() {
return Err(AvifError::BmffParseFailed(
"exceeded image_count_limit (progressive)".into(),
));
}
}
tile.input.all_layers = true;
let mut offset = 0;
for (i, layer_size) in layer_sizes.iter().take(layer_count).enumerate() {
let sample = DecodeSample {
item_id: item.id,
offset: checked_add!(base_item_offset, offset)?,
size: *layer_size,
spatial_id: 0xff,
sync: i == 0, // Assume all layers depend on the first layer.
};
tile.input.samples.push(sample);
offset = checked_add!(offset, *layer_size as u64)?;
}
} else {
// Typical case: Use the entire item's payload for a single frame output
let sample = DecodeSample {
item_id: item.id,
offset: base_item_offset,
size: item.size,
// Legal spatial_id values are [0,1,2,3], so this serves as a sentinel value for
// "do not filter by spatial_id"
spatial_id: 0xff,
sync: true,
};
tile.input.samples.push(sample);
}
Ok(tile)
}
pub(crate) fn create_from_track(
track: &Track,
image_count_limit: Option<NonZero<u32>>,
size_hint: u64,
category: Category,
) -> AvifResult<Tile> {
let properties = track
.get_properties()
.ok_or(AvifError::BmffParseFailed("".into()))?;
let codec_config = find_property!(properties, CodecConfiguration)
.ok_or(AvifError::BmffParseFailed("".into()))?
.clone();
let mut tile = Tile {
width: track.width,
height: track.height,
operating_point: 0, // No way to set operating point via tracks
input: DecodeInput {
category,
..DecodeInput::default()
},
codec_config,
..Tile::default()
};
let sample_table = &track.sample_table.unwrap_ref();
if let Some(limit) = image_count_limit {
let mut limit = limit.get();
for (chunk_index, _chunk_offset) in sample_table.chunk_offsets.iter().enumerate() {
// Figure out how many samples are in this chunk.
let sample_count = sample_table.get_sample_count_of_chunk(chunk_index as u32);
if sample_count == 0 {
return Err(AvifError::BmffParseFailed(
"chunk with 0 samples found".into(),
));
}
if sample_count > limit {
return Err(AvifError::BmffParseFailed(
"exceeded image_count_limit".into(),
));
}
limit -= sample_count;
}
}
let mut sample_size_index: usize = 0;
for (chunk_index, chunk_offset) in sample_table.chunk_offsets.iter().enumerate() {
// Figure out how many samples are in this chunk.
let sample_count = sample_table.get_sample_count_of_chunk(chunk_index as u32);
if sample_count == 0 {
return Err(AvifError::BmffParseFailed(
"chunk with 0 samples found".into(),
));
}
let mut sample_offset = *chunk_offset;
for _ in 0..sample_count {
let sample_size = sample_table.sample_size(sample_size_index)?;
let sample_size_hint = checked_add!(sample_offset, sample_size as u64)?;
if size_hint != 0 && sample_size_hint > size_hint {
return Err(AvifError::BmffParseFailed("exceeded size_hint".into()));
}
let sample = DecodeSample {
item_id: 0,
offset: sample_offset,
size: sample_size,
// Legal spatial_id values are [0,1,2,3], so this serves as a sentinel value for "do
// not filter by spatial_id"
spatial_id: 0xff,
// Assume first sample is always sync (in case stss box was missing).
sync: tile.input.samples.is_empty(),
};
tile.input.samples.push(sample);
checked_incr!(sample_offset, sample_size as u64);
checked_incr!(sample_size_index, 1);
}
}
for sync_sample_number in &sample_table.sync_samples {
let index = usize_from_u32(*sync_sample_number)?;
// sample_table.sync_samples is 1-based.
if index == 0 || index > tile.input.samples.len() {
return Err(AvifError::BmffParseFailed(format!(
"invalid sync sample number {}",
index
)));
}
tile.input.samples[index - 1].sync = true;
}
Ok(tile)
}
pub(crate) fn max_sample_size(&self) -> usize {
match self.input.samples.iter().max_by_key(|sample| sample.size) {
Some(sample) => sample.size,
None => 0,
}
}
}