///|
/// Resolve every extent before adding offsets, retaining the idat boundary.
fn ga_grid_extents(
data : Array[Byte],
item : GaItem,
idat : GaBox?,
) -> Array[AvifGridExtent] raise GridAnimError {
if item.offs.length() == 0 {
raise GridAnimError::Invalid
}
let (base, bound) = if item.method_id == 1 {
match idat {
Some(box) => (box.payload, box.end)
None => raise GridAnimError::Invalid
}
} else {
(0, data.length())
}
let extents : Array[AvifGridExtent] = []
for i in 0.. bound - length ||
item.base > bound - length - base ||
offset > bound - length - base - item.base {
raise GridAnimError::Invalid
}
extents.push({
offset: base + item.base + offset,
length,
construction_method: item.method_id,
})
}
extents
}
///|
/// Read the primary HEIF grid and sized dimg references. ImageGrid is an item
/// payload (version, flags, rows, columns), not an ISO BMFF FullBox.
fn avif_grid_descriptor_for_item(
data : Array[Byte],
item_id : Int?,
) -> AvifGridDescriptor? {
try {
let boxes : Array[GaBox] = []
ga_walk(data, 0, data.length(), -1, boxes)
let meta = match ga_find(boxes, -1, ga_meta) {
Some(box) => box
None => raise GridAnimError::Invalid
}
let mi = ga_idx(boxes, meta.start)
let pitm = match ga_find(boxes, mi, ga_pitm) {
Some(box) => box
None => raise GridAnimError::Invalid
}
let version = ga_u8(data, pitm.payload, pitm.end)
if version > 1 {
raise GridAnimError::Unsupported
}
let primary = match item_id {
Some(value) => value
None =>
if version == 0 {
ga_u16(data, pitm.payload + 4, pitm.end)
} else {
ga_u32(data, pitm.payload + 4, pitm.end)
}
}
let items = ga_items(data, boxes, meta)
let index = ga_item_idx(items, primary)
if index < 0 || items[index].typ != ga_grid {
return None
}
let iloc = match ga_find(boxes, mi, ga_iloc) {
Some(box) => box
None => raise GridAnimError::Invalid
}
ga_parse_iloc(data, iloc, items)
let idat = ga_find(boxes, mi, ga_idat)
let payload : Array[Byte] = []
for extent in ga_grid_extents(data, items[index], idat) {
if extent.length > 12 - payload.length() {
raise GridAnimError::Invalid
}
for i in extent.offset..<(extent.offset + extent.length) {
payload.push(data[i])
}
}
let end = payload.length()
if ga_u8(payload, 0, end) != 0 {
raise GridAnimError::Unsupported
}
let flags = ga_u8(payload, 1, end)
if (flags & 254) != 0 {
raise GridAnimError::Unsupported
}
let rows = ga_u8(payload, 2, end) + 1
let columns = ga_u8(payload, 3, end) + 1
let wide = (flags & 1) != 0
if end != (if wide { 12 } else { 8 }) {
raise GridAnimError::Invalid
}
let width = if wide {
ga_u32(payload, 4, end)
} else {
ga_u16(payload, 4, end)
}
let height = if wide {
ga_u32(payload, 8, end)
} else {
ga_u16(payload, 6, end)
}
if width <= 0 || height <= 0 {
raise GridAnimError::Invalid
}
let iref = match ga_find(boxes, mi, ga_iref) {
Some(box) => box
None => raise GridAnimError::Invalid
}
let ref_version = ga_u8(data, iref.payload, iref.end)
if ref_version > 1 {
raise GridAnimError::Unsupported
}
let references : Array[GaBox] = []
// iref has a FullBox prefix followed directly by reference boxes.
ga_walk(data, iref.payload + 4, iref.end, -1, references)
let ids : Array[Int] = []
let id_width = if ref_version == 0 { 2 } else { 4 }
for reference in references {
if reference.parent != -1 || reference.typ != ga_dimg {
continue
}
let start = reference.payload
let from = if ref_version == 0 {
ga_u16(data, start, reference.end)
} else {
ga_u32(data, start, reference.end)
}
let count = ga_u16(data, start + id_width, reference.end)
let mut cursor = start + id_width + 2
if count != (reference.end - cursor) / id_width ||
(reference.end - cursor) % id_width != 0 {
raise GridAnimError::Invalid
}
for _ in 0.. None
}
}
///|
/// Extract the primary grid and its ordered, validated item extents.
pub fn avif_grid_descriptor(data : Array[Byte]) -> AvifGridDescriptor? {
avif_grid_descriptor_for_item(data, None)
}
///|
/// Tie grid declarations to the actual tile sequences before automatic decode.
/// The public extent-based composition helper also serves callers that already
/// parsed their container, so its lower-level API does not reparse properties.
fn avif_grid_properties_match(
data : Array[Byte],
descriptor : AvifGridDescriptor,
limits? : DecodeLimits? = None,
) -> Bool {
try {
let boxes : Array[AuxBox] = []
aux_walk(data, 0, data.length(), -1, boxes)
let meta = match aux_find(boxes, -1, aux_t_meta) {
Some(box) => box
None => raise AuxParseError::Invalid
}
let parsed = aux_parse_items(data, boxes, meta)
let items = parsed.0
aux_parse_ipma(data, boxes, parsed.2, items)
let props = aux_parse_props(data, boxes, parsed.2)
let primary = aux_item_index(items, descriptor.item_id)
if primary < 0 {
return false
}
let mut seen_ispe = false
for ai in items[primary].props {
if ai <= 0 || ai > props.length() {
return false
}
let prop = props[ai - 1]
if prop.kind == aux_t_ispe {
if seen_ispe ||
prop.width != descriptor.output_width ||
prop.height != descriptor.output_height {
return false
}
seen_ispe = true
}
}
if !seen_ispe {
return false
}
let mut first : (Int, Int, Int, Bool)? = None
let mut color : (Bool, Int, Int, Int, Bool, Bool)? = None
for tile in descriptor.tiles {
let ti = aux_item_index(items, tile.item_id)
if ti < 0 {
return false
}
let info = aux_image_info(items[ti], props)
if info.0 < 64 || info.1 < 64 {
return false
}
match first {
None => first = Some(info)
Some(previous) => if previous != info { return false }
}
let payload : Array[Byte] = []
for extent in tile.extents {
if !decode_payload_allowed(payload.length(), extent.length, limits) {
return false
}
for i in extent.offset..<(extent.offset + extent.length) {
payload.push(data[i])
}
}
let sequence = match av1_sequence_info(payload) {
Some(value) => value
None => return false
}
if info.0 > sequence.width ||
info.1 > sequence.height ||
sequence.bit_depth != info.2 ||
sequence.monochrome != info.3 {
return false
}
// MIAF aligns edges only along axes that are chroma-subsampled.
if !sequence.monochrome &&
(
(
sequence.subsampling_x &&
((info.0 | descriptor.output_width) & 1) != 0
) ||
(
sequence.subsampling_y &&
((info.1 | descriptor.output_height) & 1) != 0
)
) {
return false
}
for ai in items[ti].props {
if ai > 0 && props[ai - 1].kind == aux_t_av1c {
let flags = props[ai - 1].data[2].to_int()
if ((flags & 8) != 0) != sequence.subsampling_x ||
((flags & 4) != 0) != sequence.subsampling_y {
return false
}
}
}
let tile_color = (
sequence.full_range,
sequence.matrix_coefficients,
sequence.color_primaries,
sequence.transfer_characteristics,
sequence.subsampling_x,
sequence.subsampling_y,
)
match color {
None => color = Some(tile_color)
Some(previous) => if previous != tile_color { return false }
}
}
true
} catch {
_ => false
}
}