///|
priv suberror AvifError {
Invalid
Unsupported
Diagnostic(DecodeError)
}
///|
pub struct AvifContainer {
width : Int
height : Int
bit_depth : Int
monochrome : Bool
/// Chroma subsampling from the `av1C` box. `yuv420` is both flags set; a
/// 4:2:2 stream sets x only and a 4:4:4 stream sets neither.
subsampling_x : Bool
subsampling_y : Bool
yuv420 : Bool
has_alpha : Bool
// A grid inherits this configuration from its validated coded cells.
av1c : Array[Byte]
payload : Array[Byte]
/// Layer selection for the primary coded item; absent properties select
/// operating point zero and its highest presented spatial layer.
operating_point : Int
spatial_layer : Int?
nclx_primaries : Int?
nclx_transfer : Int?
nclx_matrix : Int?
nclx_full_range : Bool?
}
///|
priv struct Box {
typ : Int
start : Int
payload : Int
end : Int
parent : Int
}
///|
priv struct Item {
id : Int
typ : Int
mut method_id : Int
mut base : Int
offs : Array[Int]
lens : Array[Int]
props : Array[Int]
}
///|
priv struct Prop {
kind : Int
width : Int
height : Int
data : Array[Byte]
p : Int
t : Int
m : Int
full_range : Bool
}
///|
let t_meta = 0x6D657461
///|
let t_iprp = 0x69707270
///|
let t_ipco = 0x6970636F
///|
let t_pitm = 0x7069746D
///|
let t_iinf = 0x69696E66
///|
let t_infe = 0x696E6665
///|
let t_iloc = 0x696C6F63
///|
let t_idat = 0x69646174
///|
let t_ipma = 0x69706D61
///|
let t_ispe = 0x69737065
///|
let t_av1c = 0x61763143
///|
let t_colr = 0x636F6C72
///|
let t_nclx = 0x6E636C78
///|
let t_auxc = 0x61757843
///|
let t_a1op = 0x61316F70
///|
let t_lsel = 0x6C73656C
///|
let t_av01 = 0x61763031
///|
fn u8(d : Array[Byte], p : Int, e : Int) -> Int raise AvifError {
if p < 0 || p >= e || p >= d.length() {
raise AvifError::Diagnostic(
decode_error(
TruncatedInput,
"container",
"Container field extends beyond the available bytes",
offset=Some(p),
),
)
}
d[p].to_int()
}
///|
fn u16(d : Array[Byte], p : Int, e : Int) -> Int raise AvifError {
u8(d, p, e) * 256 + u8(d, p + 1, e)
}
///|
fn u32(d : Array[Byte], p : Int, e : Int) -> Int raise AvifError {
u8(d, p, e) * 16777216 +
u8(d, p + 1, e) * 65536 +
u8(d, p + 2, e) * 256 +
u8(d, p + 3, e)
}
///|
fn u64(d : Array[Byte], p : Int, e : Int) -> Int raise AvifError {
// Box sizes and array offsets must fit Int before any arithmetic.
for i in 0..<4 {
if u8(d, p + i, e) != 0 {
raise AvifError::Unsupported
}
}
if u8(d, p + 4, e) > 127 {
raise AvifError::Unsupported
}
u32(d, p + 4, e)
}
///|
fn idx_box(bs : Array[Box], s : Int) -> Int {
for i in 0.. Box? {
for b in bs {
if b.parent == parent && b.typ == typ {
return Some(b)
}
}
None
}
///|
fn idx_item(xs : Array[Item], id : Int) -> Int {
for i in 0.. Array[Byte] raise AvifError {
if a < 0 || b < a || b > d.length() {
raise AvifError::Invalid
}
let x : Array[Byte] = []
for i in a.. Unit raise AvifError {
if depth > AVIF_MAX_BOX_DEPTH {
raise AvifError::Diagnostic(
decode_error(
LimitExceeded,
"container",
"Container nesting exceeds 64 levels",
offset=Some(from),
),
)
}
let mut p = from
while p < to {
if to - p < 8 {
raise AvifError::Diagnostic(
decode_error(
TruncatedInput,
"container",
"Incomplete box header",
offset=Some(p),
),
)
}
let s32 = u32(d, p, to)
let typ = u32(d, p + 4, to)
let mut h = 8
let mut size = s32
if s32 == 1 {
h = 16
size = u64(d, p + 8, to)
} else if s32 == 0 {
size = to - p
}
if size < h {
raise AvifError::Diagnostic(
decode_error(
InvalidData,
"container",
"Box size is smaller than its header",
offset=Some(p),
),
)
}
if size > to - p {
raise AvifError::Diagnostic(
decode_error(
TruncatedInput,
"container",
"Box payload extends beyond its parent",
offset=Some(p),
),
)
}
let end = p + size
let payload = p + h
let bi = out.length()
out.push({ typ, start: p, payload, end, parent, })
if typ == t_meta {
if end - payload < 4 {
raise AvifError::Invalid
}
walk(d, payload + 4, end, bi, out, depth=depth + 1)
} else if typ == t_iprp || typ == t_ipco {
walk(d, payload, end, bi, out, depth=depth + 1)
} else if typ == t_iinf {
let version = u8(d, payload, end)
let child_start = if version == 0 { payload + 6 } else { payload + 8 }
if child_start > end {
raise AvifError::Invalid
}
walk(d, child_start, end, bi, out, depth=depth + 1)
}
p = end
}
}
///|
/// `av1C` configuration. Returns (bit depth, monochrome, subsampling_x,
/// subsampling_y); byte 2 stores monochrome at bit 4 and the two subsampling
/// flags at bits 3 and 2 (AV1 ISOBMFF binding ยง2.3.3).
fn parse_av1c(
_d : Array[Byte],
p : Prop,
) -> (Int, Bool, Bool, Bool) raise AvifError {
if p.data.length() < 3 {
raise AvifError::Invalid
}
let b0 = p.data[0].to_int()
if (b0 & 0x80) == 0 || (b0 & 0x7F) != 1 {
raise AvifError::Unsupported
}
let f = p.data[2].to_int()
let depth = if (f & 0x40) == 0 {
8
} else if (f & 0x20) != 0 {
12
} else {
10
}
(depth, (f & 0x10) != 0, ((f >> 3) & 1) != 0, ((f >> 2) & 1) != 0)
}
///|
fn avif_container_parse_limited(
data : Array[Byte],
limits? : DecodeLimits? = None,
diagnostic? : DecodeDiagnostic? = None,
) -> AvifContainer? {
if !decode_input_allowed(data.length(), limits) {
return None
}
try {
let bs : Array[Box] = []
walk(data, 0, data.length(), -1, bs)
let meta = match find_box(bs, -1, t_meta) {
Some(x) => x
None => raise AvifError::Invalid
}
let mi = idx_box(bs, meta.start)
let pitm = match find_box(bs, mi, t_pitm) {
Some(x) => x
None => raise AvifError::Invalid
}
let pv = u8(data, pitm.payload, pitm.end)
let primary = if pv == 0 {
u16(data, pitm.payload + 4, pitm.end)
} else {
u32(data, pitm.payload + 4, pitm.end)
}
let iinf = match find_box(bs, mi, t_iinf) {
Some(x) => x
None => raise AvifError::Invalid
}
let ii = idx_box(bs, iinf.start)
let items : Array[Item] = []
for b in bs {
if b.parent == ii && b.typ == t_infe {
let v = u8(data, b.payload, b.end)
if v < 2 {
raise AvifError::Unsupported
}
let id = if v >= 3 {
u32(data, b.payload + 4, b.end)
} else {
u16(data, b.payload + 4, b.end)
}
items.push({
id,
typ: u32(data, b.payload + (if v >= 3 { 10 } else { 8 }), b.end),
method_id: 0,
base: 0,
offs: [],
lens: [],
props: [],
})
}
}
let iloc = match find_box(bs, mi, t_iloc) {
Some(x) => x
None => raise AvifError::Invalid
}
parse_iloc(data, iloc, items)
let iprp = find_box(bs, mi, t_iprp)
parse_ipma(data, bs, iprp, items)
let props = parse_props(data, bs, iprp)
let pi = idx_item(items, primary)
if pi < 0 || (items[pi].typ != t_av01 && items[pi].typ != 0x67726964) {
raise AvifError::Invalid
}
let it = items[pi]
let (operating_point, spatial_layer) = avif_layer_selection(props, it.props)
let mut w = 0
let mut h = 0
let mut depth = 8
let mut mono = false
let mut sub_x = false
let mut sub_y = false
let mut av1 : Array[Byte]? = None
let mut np : Int? = None
let mut nt : Int? = None
let mut nm : Int? = None
let mut nr : Bool? = None
for ai in it.props {
if ai <= 0 || ai > props.length() {
raise AvifError::Invalid
}
let q = props[ai - 1]
if q.kind == t_ispe {
w = q.width
h = q.height
}
if q.kind == t_av1c {
av1 = Some(q.data)
let parsed = parse_av1c(data, q)
depth = parsed.0
mono = parsed.1
sub_x = parsed.2
sub_y = parsed.3
}
if q.kind == t_colr {
np = Some(q.p)
nt = Some(q.t)
nm = Some(q.m)
nr = Some(q.full_range)
}
}
if it.typ == 0x67726964 {
if av1 is Some(_) {
raise AvifError::Invalid
}
let grid = match avif_grid_descriptor_for_item(data, Some(it.id)) {
Some(value) => value
None => raise AvifError::Invalid
}
if !avif_grid_properties_match(data, grid, limits~) {
raise AvifError::Invalid
}
let first = idx_item(items, grid.tiles[0].item_id)
if first < 0 {
raise AvifError::Invalid
}
for ai in items[first].props {
if ai <= 0 || ai > props.length() {
raise AvifError::Invalid
}
let property = props[ai - 1]
if property.kind == t_av1c {
av1 = Some(property.data)
let parsed = parse_av1c(data, property)
depth = parsed.0
mono = parsed.1
sub_x = parsed.2
sub_y = parsed.3
}
}
}
if w <= 0 || h <= 0 || av1 is None || it.offs.length() == 0 {
raise AvifError::Invalid
}
let idat = find_box(bs, mi, t_idat)
let payload : Array[Byte] = []
for i in 0.. (x.payload, x.end)
None => raise AvifError::Invalid
}
} else {
(0, data.length())
}
let len = it.lens[i]
if !decode_payload_allowed(payload.length(), len, limits) {
raise AvifError::Diagnostic(
decode_error(
LimitExceeded,
"container",
"Assembled item payload exceeds the byte budget",
),
)
}
if len < 0 ||
it.base < 0 ||
it.offs[i] < 0 ||
base > bound - len ||
it.base > bound - len - base ||
it.offs[i] > bound - len - base - it.base {
raise AvifError::Invalid
}
let off = base + it.base + it.offs[i]
for j in off..<(off + len) {
payload.push(data[j])
}
}
Some({
width: w,
height: h,
bit_depth: depth,
monochrome: mono,
subsampling_x: sub_x,
subsampling_y: sub_y,
yuv420: sub_x && sub_y,
has_alpha: avif_alpha_descriptor_checked(data, limits~) is Some(_),
av1c: av1.unwrap(),
payload,
operating_point,
spatial_layer,
nclx_primaries: np,
nclx_transfer: nt,
nclx_matrix: nm,
nclx_full_range: nr,
})
} catch {
AvifError::Diagnostic(error) => {
decode_trace_error(diagnostic, error)
None
}
AvifError::Unsupported => {
decode_trace_error(
diagnostic,
decode_error(
Unsupported,
"container",
"Unsupported container field or construction method",
),
)
None
}
_ => {
decode_trace_error(
diagnostic,
decode_error(
InvalidData,
"container",
"Invalid AVIF item, property or extent",
),
)
None
}
}
}
///|
/// Parse AVIF 2.3.2's descriptive operating-point and layer selectors.
/// lsel is a two-byte property, not a FullBox; 0xffff means no fixed layer.
fn avif_layer_selection(
properties : Array[Prop],
associations : Array[Int],
) -> (Int, Int?) raise AvifError {
let mut operating_point = 0
let mut spatial_layer : Int? = None
let mut seen_op = false
let mut seen_layer = false
for index in associations {
if index <= 0 || index > properties.length() {
raise AvifError::Invalid
}
let property = properties[index - 1]
if property.kind == t_a1op {
if seen_op ||
property.data.length() != 1 ||
property.data[0].to_int() > 31 {
raise AvifError::Invalid
}
seen_op = true
operating_point = property.data[0].to_int()
} else if property.kind == t_lsel {
if seen_layer || property.data.length() != 2 {
raise AvifError::Invalid
}
seen_layer = true
let layer = property.data[0].to_int() * 256 + property.data[1].to_int()
if layer != 65535 {
if layer > 3 {
raise AvifError::Invalid
}
spatial_layer = Some(layer)
}
}
}
(operating_point, spatial_layer)
}
///|
/// Resolve selectors on a coded color, alpha, or grid-cell item. Metadata
/// readers share this with the primary container parser so no entry point
/// can silently decode an associated essential selector as the default.
fn avif_item_layer_selection(data : Array[Byte], item_id : Int) -> (Int, Int?)? {
match avif_item_decode_config(data, item_id) {
Some((_, _, operating_point, spatial_layer)) =>
Some((operating_point, spatial_layer))
None => None
}
}
///|
fn avif_item_decode_config(
data : Array[Byte],
item_id : Int,
) -> (Int, Int, Int, Int?)? {
try {
let boxes : Array[Box] = []
walk(data, 0, data.length(), -1, boxes)
let meta = match find_box(boxes, -1, t_meta) {
Some(value) => value
None => return None
}
let iprp = find_box(boxes, idx_box(boxes, meta.start), t_iprp)
let items : Array[Item] = [
{
id: item_id,
typ: t_av01,
method_id: 0,
base: 0,
offs: [],
lens: [],
props: [],
},
]
parse_ipma(data, boxes, iprp, items)
let props = parse_props(data, boxes, iprp)
let (operating_point, spatial_layer) = avif_layer_selection(
props,
items[0].props,
)
let mut width = 0
let mut height = 0
for index in items[0].props {
let prop = props[index - 1]
if prop.kind == t_ispe {
if width != 0 || prop.width <= 0 || prop.height <= 0 {
return None
}
width = prop.width
height = prop.height
}
}
if width == 0 {
return None
}
Some((width, height, operating_point, spatial_layer))
} catch {
_ => None
}
}
///|
fn parse_iloc(
d : Array[Byte],
b : Box,
xs : Array[Item],
) -> Unit raise AvifError {
let p = b.payload
let v = u8(d, p, b.end)
if v > 2 {
raise AvifError::Unsupported
}
let a = u8(d, p + 4, b.end)
let os = a >> 4
let ls = a & 15
let z = u8(d, p + 5, b.end)
let bs = z >> 4
let isz = if v >= 1 { z & 15 } else { 0 }
if (os != 0 && os != 4 && os != 8) ||
(ls != 0 && ls != 4 && ls != 8) ||
(bs != 0 && bs != 4 && bs != 8) {
raise AvifError::Unsupported
}
let mut q = p + 6
let n = if v < 2 { u16(d, q, b.end) } else { u32(d, q, b.end) }
q = q + (if v < 2 { 2 } else { 4 })
for _ in 0..= 1 { u16(d, q, b.end) & 15 } else { 0 }
if method_code != 0 && method_code != 1 {
raise AvifError::Unsupported
}
if v >= 1 {
q = q + 2
}
let data_reference = u16(d, q, b.end)
if data_reference != 0 {
raise AvifError::Unsupported
}
q = q + 2
let base = if bs == 0 {
0
} else if bs == 4 {
u32(d, q, b.end)
} else {
u64(d, q, b.end)
}
q = q + bs
let ec = u16(d, q, b.end)
q = q + 2
let xi = idx_item(xs, id)
for _ in 0..= 1 && isz != 0 {
q = q + isz
}
let off = if os == 0 {
0
} else if os == 4 {
u32(d, q, b.end)
} else {
u64(d, q, b.end)
}
q = q + os
let len = if ls == 0 {
0
} else if ls == 4 {
u32(d, q, b.end)
} else {
u64(d, q, b.end)
}
q = q + ls
if xi >= 0 {
xs[xi].method_id = method_code
xs[xi].base = base
xs[xi].offs.push(off)
xs[xi].lens.push(len)
}
}
}
}
///|
fn parse_ipma(
d : Array[Byte],
bs : Array[Box],
iprp : Box?,
xs : Array[Item],
essential? : Array[(Int, Int)]? = None,
) -> Unit raise AvifError {
let rp = match iprp {
Some(x) => x
None => return
}
let ri = idx_box(bs, rp.start)
let b = match find_box(bs, ri, t_ipma) {
Some(x) => x
None => return
}
let p = b.payload
let version = u8(d, p, b.end)
if version > 1 {
raise AvifError::Unsupported
}
let flags = u32(d, p, b.end) & 0xFFFFFF
let mut q = p + 4
let n = u32(d, q, b.end)
q = q + 4
for _ in 0..= 1 { u32(d, q, b.end) } else { u16(d, q, b.end) }
q = q + (if version >= 1 { 4 } else { 2 })
let c = u8(d, q, b.end)
q = q + 1
let xi = idx_item(xs, id)
for _ in 0..= 0 {
let property = if (flags & 1) != 0 { a & 0x7FFF } else { a & 0x7F }
if property != 0 {
xs[xi].props.push(property)
let mask = if (flags & 1) != 0 { 0x8000 } else { 0x80 }
if (a & mask) != 0 && essential is Some(entries) {
entries.push((id, property))
}
}
}
}
}
}
///|
fn parse_props(
d : Array[Byte],
bs : Array[Box],
iprp : Box?,
) -> Array[Prop] raise AvifError {
let out : Array[Prop] = []
let rp = match iprp {
Some(x) => x
None => return out
}
let ri = idx_box(bs, rp.start)
let co = match find_box(bs, ri, t_ipco) {
Some(x) => x
None => return out
}
let ci = idx_box(bs, co.start)
for b in bs {
if b.parent != ci {
continue
}
if b.typ == t_ispe {
out.push({
kind: b.typ,
width: u32(d, b.payload + 4, b.end),
height: u32(d, b.payload + 8, b.end),
data: [],
p: 0,
t: 0,
m: 0,
full_range: false,
})
} else if b.typ == t_av1c || b.typ == t_a1op || b.typ == t_lsel {
out.push({
kind: b.typ,
width: 0,
height: 0,
data: slice(d, b.payload, b.end),
p: 0,
t: 0,
m: 0,
full_range: false,
})
} else if b.typ == t_colr && u32(d, b.payload, b.end) == t_nclx {
out.push({
kind: b.typ,
width: 0,
height: 0,
data: [],
p: u16(d, b.payload + 4, b.end),
t: u16(d, b.payload + 6, b.end),
m: u16(d, b.payload + 8, b.end),
full_range: (u8(d, b.payload + 10, b.end) & 0x80) != 0,
})
} else if b.typ == t_auxc {
out.push({
kind: b.typ,
width: 0,
height: 0,
data: [],
p: 0,
t: 0,
m: 0,
full_range: false,
})
} else {
// ICC ('prof'/'rICC') is not an nclx declaration. Keep its property
// index without inventing zero-valued CICP fields or a range flag.
out.push({
kind: if b.typ == t_colr {
0
} else {
b.typ
},
width: 0,
height: 0,
data: [],
p: 0,
t: 0,
m: 0,
full_range: false,
})
}
}
out
}