///|
/// Compose decoded grid tiles into one RGBA image.
///
/// Tile bytes are decoded by the caller through the normal AV1 path. This
/// helper only performs bounded placement and crops tiles at the output edge.
pub fn avif_reconstruct_grid(
rows : Int,
columns : Int,
output_width : Int,
output_height : Int,
tiles : Array[Image],
) -> Image? {
if rows <= 0 ||
columns <= 0 ||
output_width <= 0 ||
output_height <= 0 ||
rows > 256 ||
columns > 256 ||
tiles.length() != rows * columns {
return None
}
if output_width > MAX_IMAGE_PIXELS / output_height {
return None
}
let first = tiles[0]
if first.width <= 0 || first.height <= 0 {
return None
}
// Every cell has the same coded dimensions. Only the output rectangle is
// cropped; it must include part of the final row and column, without holes.
if (output_width - 1) / first.width != columns - 1 ||
(output_height - 1) / first.height != rows - 1 {
return None
}
for tile in tiles {
if tile.width != first.width || tile.height != first.height {
return None
}
}
let out = Image::new(output_width, output_height)
for index in 0.. Image? {
if !decode_samples_allowed(samples, limits) {
return None
}
let budget = decode_budget(limits)
if !decode_reserve(
budget,
output_width,
output_height,
picture=false,
padded=false,
) {
return None
}
if rows <= 0 ||
columns <= 0 ||
rows > 256 ||
columns > 256 ||
samples.length() != rows * columns {
return None
}
let images : Array[Image] = []
for sample in samples {
let frame = match
av1_decode_frame_planes(sample, allow_monochrome=true, budget~) {
Some(value) => value
None => return None
}
let image = match av1_frame_to_rgba(frame) {
Some(value) => value
None => return None
}
images.push(image)
}
avif_reconstruct_grid(rows, columns, output_width, output_height, images)
}
///|
/// Decode a grid directly from a BMFF buffer and its parsed descriptor.
/// Each tile may contain one or more iloc extents; extents are concatenated
/// before passing the sample to the pure AV1 decoder.
fn avif_decode_grid_limited(
data : Array[Byte],
descriptor : AvifGridDescriptor,
limits? : DecodeLimits? = None,
) -> Image? {
if !decode_input_allowed(data.length(), limits) {
return None
}
match avif_decode_grid_frame(data, descriptor, budget=decode_budget(limits)) {
Some(frame) => {
let colored = match avif_grid_descriptor(data) {
Some(primary) if primary.item_id == descriptor.item_id => {
let container = match avif_container_parse_limited(data, limits~) {
Some(value) => value
None => return None
}
avif_frame_apply_color(frame, container)
}
_ => frame
}
av1_frame_to_rgba(colored)
}
None => None
}
}
///|
/// Decode and compose a grid directly from a BMFF buffer. This convenience
/// entrypoint performs descriptor extraction and pixel reconstruction as one
/// operation, so callers cannot accidentally skip the `dimg` ordering or
/// extent validation step.
fn avif_decode_grid_auto_limited(
data : Array[Byte],
limits? : DecodeLimits? = None,
) -> Image? {
if !decode_input_allowed(data.length(), limits) {
return None
}
let descriptor = match avif_grid_descriptor(data) {
Some(value) => value
None => return None
}
if !avif_grid_properties_match(data, descriptor, limits~) {
return None
}
avif_decode_grid_limited(data, descriptor, limits~)
}
///|
test "grid reconstruction crops edge tiles and preserves order" {
let a = Image::new(2, 2)
a.set_pixel(0, 0, b'\x11', b'\x00', b'\x00', b'\xFF')
let b = Image::new(2, 2)
b.set_pixel(0, 0, b'\x22', b'\x00', b'\x00', b'\xFF')
let out = avif_reconstruct_grid(1, 2, 3, 2, [a, b]).unwrap()
assert_eq(out.get_pixel(0, 0), (b'\x11', b'\x00', b'\x00', b'\xFF'))
assert_eq(out.get_pixel(2, 0), (b'\x22', b'\x00', b'\x00', b'\xFF'))
}
///|
test "grid reconstruction rejects mismatched tile counts" {
assert_true(avif_reconstruct_grid(2, 2, 4, 4, []) is None)
}
///|
test "grid reconstruction rejects unequal cells and uncovered output" {
let tile = Image::new(2, 2)
assert_true(
avif_reconstruct_grid(1, 2, 4, 2, [tile, Image::new(1, 2)]) is None,
)
assert_true(avif_reconstruct_grid(1, 2, 5, 2, [tile, tile]) is None)
assert_true(avif_reconstruct_grid(2, 1, 2, 5, [tile, tile]) is None)
assert_true(avif_reconstruct_grid(1, 2, 2, 2, [tile, tile]) is None)
}
///|
/// Multiple iloc extents are concatenated before AV1 decoding. Construction
/// method is metadata only at this stage; the descriptor has already resolved
/// each extent to a data-relative offset.
test "grid decoder concatenates split construction-method extents" {
let sample : Array[Byte] = [
b'\x0A', b'\x06', b'\x18', b'\x15', b'\x7F', b'\xFD', b'\xB0', b'\x08', b'\x32',
b'\x13', b'\x18', b'\x00', b'\x00', b'\x00', b'\x50', b'\x00', b'\x00', b'\x00',
b'\x0A', b'\x05', b'\x79', b'\x52', b'\x6E', b'\x43', b'\xD9', b'\x3A', b'\x46',
b'\x02', b'\x28',
]
let data : Array[Byte] = []
for b in sample {
data.push(b)
}
let descriptor : AvifGridDescriptor = {
item_id: 1,
rows: 1,
columns: 1,
output_width: 64,
output_height: 64,
tiles: [
{
item_id: 2,
extents: [
{ offset: 0, length: 8, construction_method: 1, },
{ offset: 8, length: sample.length() - 8, construction_method: 1, },
],
},
],
}
let image = avif_decode_grid_limited(data, descriptor).unwrap()
assert_eq((image.width, image.height), (64, 64))
assert_eq(image.get_pixel(0, 0).3, b'\xFF')
}
///|
test "grid tile decoder composes ordered AV1 samples" {
let sample : Array[Byte] = [
b'\x0A', b'\x06', b'\x18', b'\x15', b'\x7F', b'\xFD', b'\xB0', b'\x08', b'\x32',
b'\x13', b'\x18', b'\x00', b'\x00', b'\x00', b'\x50', b'\x00', b'\x00', b'\x00',
b'\x0A', b'\x05', b'\x79', b'\x52', b'\x6E', b'\x43', b'\xD9', b'\x3A', b'\x46',
b'\x02', b'\x28',
]
let image = avif_decode_grid_tiles_limited(1, 2, 128, 64, [sample, sample]).unwrap()
assert_eq(image.width, 128)
assert_eq(image.height, 64)
assert_eq(image.get_pixel(0, 0).3, b'\xFF')
}