///|
/// Decode each coded grid cell independently, then retain its native samples
/// while placing the visible rectangle. Color conversion and alpha UNORM
/// narrowing belong to the caller, after the grid has been assembled.
fn avif_decode_grid_frame(
data : Array[Byte],
descriptor : AvifGridDescriptor,
budget? : DecodeBudget? = None,
) -> Av1FramePlanes? {
let width = descriptor.output_width
let height = descriptor.output_height
let rows = descriptor.rows
let columns = descriptor.columns
if width <= 0 ||
height <= 0 ||
rows <= 0 ||
columns <= 0 ||
rows > 256 ||
columns > 256 ||
descriptor.tiles.length() != rows * columns {
return None
}
if width > MAX_IMAGE_PIXELS / height {
return None
}
if !decode_reserve(budget, width, height, picture=false, padded=false) {
return None
}
// The extent API also accepts raw concatenated samples. When the input is
// an AVIF grid, preserve each coded item's selectors and actual ispe size.
let container_grid = avif_grid_descriptor_for_item(
data,
Some(descriptor.item_id),
)
is Some(_)
let decode_cell = fn(tile : AvifGridTile) -> Av1FramePlanes? {
let sample : Array[Byte] = []
for extent in tile.extents {
if !decode_payload_allowed(
sample.length(),
extent.length,
budget.map(value => value.limits),
) {
return None
}
if extent.offset < 0 ||
extent.length < 0 ||
extent.offset > data.length() - extent.length ||
extent.length > 0x7FFFFFFF - sample.length() {
return None
}
for index in extent.offset..<(extent.offset + extent.length) {
sample.push(data[index])
}
}
if sample.length() == 0 {
return None
}
if !container_grid {
return av1_decode_frame_planes(sample, allow_monochrome=true, budget~)
}
let (cell_width, cell_height, operating_point, spatial_layer) = match
avif_item_decode_config(data, tile.item_id) {
Some(value) => value
None => return None
}
let frame = match
av1_decode_frame_planes(
sample,
allow_monochrome=true,
operating_point~,
spatial_layer~,
budget~,
) {
Some(value) => value
None => return None
}
if frame.width != cell_width || frame.height != cell_height {
return None
}
Some(frame)
}
let first = match decode_cell(descriptor.tiles[0]) {
Some(value) => value
None => return None
}
let plane_count = first.planes.length()
// MIAF 7.3.11.4.2 requires both cell dimensions to be at least 64,
// including monochrome and alpha cells. Only the output can be smaller.
if first.width < 64 ||
first.height < 64 ||
(plane_count != 1 && plane_count != 3) ||
(first.bit_depth != 8 && first.bit_depth != 10 && first.bit_depth != 12) {
return None
}
// Division checks complete coverage and ensures that the final row and
// column actually intersect the output without overflowing coordinates.
if (width - 1) / first.width != columns - 1 ||
(height - 1) / first.height != rows - 1 {
return None
}
if first.sub_x < 0 || first.sub_x > 1 || first.sub_y < 0 || first.sub_y > 1 {
return None
}
// Only subsampled axes require aligned cell and output boundaries. 4:2:2
// has no vertical restriction; 4:4:4 may crop either output axis to odd size.
if plane_count == 3 &&
(
((first.width | width) & ((1 << first.sub_x) - 1)) != 0 ||
((first.height | height) & ((1 << first.sub_y) - 1)) != 0
) {
return None
}
let planes : Array[Array[Int]] = Array::makei(plane_count, fn(plane) {
let divisor_x = if plane == 0 { 1 } else { 1 << first.sub_x }
let divisor_y = if plane == 0 { 1 } else { 1 << first.sub_y }
Array::make(width / divisor_x * (height / divisor_y), 0)
})
for index in 0.. value
None => return None
}
}
if frame.width != first.width ||
frame.height != first.height ||
frame.bit_depth != first.bit_depth ||
frame.planes.length() != plane_count ||
frame.full_range != first.full_range ||
frame.matrix_coefficients != first.matrix_coefficients ||
frame.color_primaries != first.color_primaries ||
frame.transfer_characteristics != first.transfer_characteristics ||
frame.sub_x != first.sub_x ||
frame.sub_y != first.sub_y {
return None
}
let ox = index % columns * first.width
let oy = index / columns * first.height
let copy_width = if width - ox < first.width {
width - ox
} else {
first.width
}
let copy_height = if height - oy < first.height {
height - oy
} else {
first.height
}
for plane in 0..