use crate::boxes::{FourCc, Reader};
use crate::meta::Meta;
use crate::props::{Av1Config, Colour, Subsampling};
use crate::yuv::{Depths, Layout, YuvMatrix, identity_to_rgb, plane_to_grey, yuv_to_rgb};
use otf_pixels_core::{
Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
PixelsError, Result, Source,
};
const MAX_CONTAINER: usize = 256 * 1024 * 1024;
#[derive(Debug, Clone)]
pub struct AvifInfo {
pub width: u32,
pub height: u32,
pub config: Av1Config,
pub has_alpha: bool,
pub is_grid: bool,
pub colour: Option<Colour>,
pub icc: Option<Vec<u8>>,
pub orientation: Orientation,
}
#[derive(Debug)]
struct AlphaItem {
frame_data: Vec<u8>,
config_obus: Vec<u8>,
premultiplied: bool,
}
#[derive(Debug)]
pub struct AvifDecoder {
descriptor: ImageDescriptor,
info: AvifInfo,
frame_data: Option<Vec<u8>>,
alpha: Option<AlphaItem>,
raster: Option<Vec<u8>>,
row: u32,
}
impl AvifDecoder {
pub fn new<S: Source>(source: S, limits: Limits) -> Result<Self> {
let bytes = read_all(source)?;
let info = parse_container(&bytes)?;
let (frame_data, alpha) = if info.is_grid {
(None, None)
} else {
(Some(locate_primary_frame(&bytes)?), locate_alpha(&bytes)?)
};
let pixel = pixel_format(&info);
let descriptor = ImageDescriptor::with_limits(info.width, info.height, pixel, &limits)?;
Ok(Self {
descriptor,
info,
frame_data,
alpha,
raster: None,
row: 0,
})
}
#[must_use]
pub const fn info(&self) -> &AvifInfo {
&self.info
}
}
fn read_all<S: Source>(mut source: S) -> Result<Vec<u8>> {
let mut bytes = Vec::new();
let mut chunk = [0_u8; 64 * 1024];
loop {
if bytes.len() > MAX_CONTAINER {
return Err(PixelsError::malformed(
"avif",
format!("stream exceeds {MAX_CONTAINER} bytes"),
));
}
match source.read(&mut chunk)? {
0 => break,
read => {
let Some(filled) = chunk.get(..read) else {
break;
};
bytes.extend_from_slice(filled);
}
}
}
Ok(bytes)
}
fn parse_container(bytes: &[u8]) -> Result<AvifInfo> {
let mut reader = Reader::new(bytes);
let first = reader
.next_box()
.ok_or_else(|| PixelsError::malformed("avif", "the file holds no boxes"))??;
if first.kind != FourCc::new(b"ftyp") {
return Err(PixelsError::malformed(
"avif",
format!("the file begins with '{}' rather than 'ftyp'", first.kind),
));
}
if !probe(bytes) {
return Err(PixelsError::malformed(
"avif",
"the ftyp box declares no brand this decoder recognises",
));
}
let meta_reader = reader
.find(b"meta")?
.ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
let meta = Meta::parse(meta_reader)?;
let primary = meta
.primary_item()
.ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
if let Some(kind) = meta.properties.essential_unknown(primary.id) {
return Err(PixelsError::unsupported(format!(
"avif: the primary item requires the '{kind}' property, which this decoder does not implement"
)));
}
if !primary.is_coded_image() && !primary.is_grid() {
return Err(PixelsError::unsupported(format!(
"avif: the primary item has type '{}', which is not an image this decoder produces",
primary.kind
)));
}
let extents = meta.properties.extents(primary.id).ok_or_else(|| {
PixelsError::malformed(
"avif",
format!(
"item {} has no ispe property, so its size is unknown",
primary.id
),
)
})?;
let config_item = if primary.is_grid() {
*meta
.referenced(primary.id, b"dimg")
.first()
.ok_or_else(|| {
PixelsError::malformed(
"avif",
format!("grid item {} references no tiles", primary.id),
)
})?
} else {
primary.id
};
let config = meta
.properties
.av1_config(config_item)
.ok_or_else(|| {
PixelsError::malformed(
"avif",
format!("item {config_item} has no av1C property, so it is not a coded AV1 image"),
)
})?
.clone();
Ok(AvifInfo {
width: extents.width,
height: extents.height,
config,
has_alpha: meta.alpha_item(primary.id).is_some(),
is_grid: primary.is_grid(),
colour: meta.properties.colour(primary.id).cloned(),
icc: meta.properties.icc_profile(primary.id).map(<[u8]>::to_vec),
orientation: meta.properties.orientation(primary.id),
})
}
fn locate_primary_frame(bytes: &[u8]) -> Result<Vec<u8>> {
let mut reader = Reader::new(bytes);
reader.next_box();
let meta_reader = reader
.find(b"meta")?
.ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
let meta = Meta::parse(meta_reader)?;
let primary = meta
.primary_item()
.ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
Ok(meta.item_data(bytes, primary)?.into_owned())
}
fn locate_alpha(bytes: &[u8]) -> Result<Option<AlphaItem>> {
let mut reader = Reader::new(bytes);
reader.next_box();
let meta_reader = reader
.find(b"meta")?
.ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
let meta = Meta::parse(meta_reader)?;
let primary = meta
.primary_item()
.ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
let Some(alpha) = meta.alpha_item(primary.id) else {
return Ok(None);
};
let config = meta.properties.av1_config(alpha.id).ok_or_else(|| {
PixelsError::malformed(
"avif",
format!("alpha item {} has no av1C property", alpha.id),
)
})?;
Ok(Some(AlphaItem {
frame_data: meta.item_data(bytes, alpha)?.into_owned(),
config_obus: config.config_obus.clone(),
premultiplied: meta.referenced(primary.id, b"prem").contains(&alpha.id),
}))
}
fn decode_frame(
config_obus: &[u8],
frame_data: &[u8],
) -> Result<(crate::av1::DecodedFrame, crate::av1::SequenceHeader)> {
use crate::av1::{StillPicture, decode_still};
let still = StillPicture::parse(config_obus, frame_data)?;
let groups = still.tile_group_data(frame_data)?;
let frame = decode_still(&still.sequence, &still.frame, &groups)?;
Ok((frame, still.sequence))
}
fn decode_raster(
info: &AvifInfo,
pixel: PixelFormat,
frame_data: &[u8],
alpha: Option<&AlphaItem>,
) -> Result<Vec<u8>> {
let (frame, sequence) = decode_frame(&info.config.config_obus, frame_data)?;
let color = &sequence.color;
let layout = Layout {
width: info.width as usize,
height: info.height as usize,
subsampling_x: usize::from(color.subsampling_x),
subsampling_y: usize::from(color.subsampling_y),
};
let depths = Depths::for_input(color.bit_depth);
let (colour, channels) = if color.mono_chrome {
let luma = frame
.planes
.first()
.ok_or_else(|| PixelsError::malformed("avif", "the luma plane is missing"))?;
(plane_to_grey(luma, layout, depths, color.color_range), 1)
} else {
(
colour_to_rgb(info, &frame.planes, color, layout, depths)?,
3,
)
};
let mut colour = colour;
let alpha = match (info.has_alpha, alpha) {
(false, _) => None,
(true, Some(item)) => {
let samples = decode_alpha(item, layout, depths)?;
if item.premultiplied {
unpremultiply(&mut colour, channels, &samples, depths.output_max());
}
Some(samples)
}
(true, None) => {
return Err(PixelsError::unsupported(
"avif: this file's alpha plane is not decodable yet",
));
}
};
Ok(pack(pixel, &colour, channels, alpha.as_deref()))
}
fn colour_to_rgb(
info: &AvifInfo,
planes: &[crate::av1::Plane],
color: &crate::av1::ColorConfig,
layout: Layout,
depths: Depths,
) -> Result<Vec<u16>> {
let matrix = match &info.colour {
Some(Colour::Nclx { matrix, .. }) => *matrix,
_ => u16::from(color.matrix_coefficients),
};
if matrix != 0 {
let yuv = YuvMatrix::new(matrix, color.color_range, depths)?;
return yuv_to_rgb(planes, layout, &yuv);
}
if (layout.subsampling_x, layout.subsampling_y) != (0, 0) {
return Err(PixelsError::malformed(
"avif",
"the identity colour matrix requires 4:4:4, but the chroma is subsampled",
));
}
identity_to_rgb(planes, layout, depths)
}
fn decode_alpha(item: &AlphaItem, layout: Layout, colour_depths: Depths) -> Result<Vec<u16>> {
let (frame, sequence) = decode_frame(&item.config_obus, &item.frame_data)?;
let luma = frame
.planes
.first()
.ok_or_else(|| PixelsError::malformed("avif", "the alpha plane is missing"))?;
let depths = Depths {
input: sequence.color.bit_depth,
output: colour_depths.output,
};
let alpha_layout = Layout {
subsampling_x: 0,
subsampling_y: 0,
..layout
};
Ok(plane_to_grey(
luma,
alpha_layout,
depths,
sequence.color.color_range,
))
}
fn unpremultiply(colour: &mut [u16], channels: usize, alpha: &[u16], max: i64) {
for (px, &a) in colour.chunks_exact_mut(channels.max(1)).zip(alpha) {
let a = i64::from(a);
if a >= max {
continue;
}
for sample in px {
*sample = if a == 0 {
0
} else {
((i64::from(*sample) * max * 2 + a) / (2 * a)).min(max) as u16
};
}
}
}
fn pack(pixel: PixelFormat, colour: &[u16], channels: usize, alpha: Option<&[u16]>) -> Vec<u8> {
let wide = matches!(
pixel,
PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Gray16
);
let out_colour = match pixel {
PixelFormat::Gray8 | PixelFormat::Gray16 | PixelFormat::GrayA8 => 1,
_ => 3,
};
let pixels = colour.len() / channels.max(1);
let mut raster = Vec::with_capacity(pixels * (out_colour + 1) * if wide { 2 } else { 1 });
let mut put = |v: u16| {
if wide {
raster.extend_from_slice(&v.to_ne_bytes());
} else {
raster.push(v as u8);
}
};
for (i, px) in colour.chunks_exact(channels.max(1)).enumerate() {
for c in 0..out_colour {
put(px.get(c).or_else(|| px.first()).copied().unwrap_or(0));
}
if let Some(alpha) = alpha {
put(alpha.get(i).copied().unwrap_or(0));
}
}
raster
}
fn pixel_format(info: &AvifInfo) -> PixelFormat {
let wide = info.config.bit_depth > 8;
let monochrome = info.config.subsampling == Subsampling::Monochrome;
match (monochrome, info.has_alpha, wide) {
(true, false, false) => PixelFormat::Gray8,
(true, false, true) => PixelFormat::Gray16,
(true, true, false) => PixelFormat::GrayA8,
(true, true, true) => PixelFormat::Rgba16,
(false, false, false) => PixelFormat::Rgb8,
(false, false, true) => PixelFormat::Rgb16,
(false, true, false) => PixelFormat::Rgba8,
(false, true, true) => PixelFormat::Rgba16,
}
}
impl Decoder for AvifDecoder {
fn descriptor(&self) -> ImageDescriptor {
self.descriptor
}
fn orientation(&self) -> Orientation {
self.info.orientation
}
fn icc_profile(&self) -> Option<&[u8]> {
self.info.icc.as_deref()
}
fn capability(&self) -> DecodeCapability {
DecodeCapability::Sequential
}
fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
if self.row >= self.descriptor.height {
return Err(PixelsError::invalid_argument(
"out",
format!("all {} rows have already been read", self.descriptor.height),
));
}
let row_bytes = self.descriptor.row_bytes();
if out.len() != row_bytes {
return Err(PixelsError::invalid_argument(
"out",
format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
));
}
if self.raster.is_none() {
let frame_data = self
.frame_data
.as_deref()
.ok_or_else(|| PixelsError::unsupported("avif: grid images are not decoded yet"))?;
self.raster = Some(decode_raster(
&self.info,
self.descriptor.pixel,
frame_data,
self.alpha.as_ref(),
)?);
}
let raster = self.raster.as_deref().unwrap_or(&[]);
let start = self.row as usize * row_bytes;
let src = raster
.get(start..start + row_bytes)
.ok_or_else(|| PixelsError::malformed("avif", "the reconstructed raster is short"))?;
out.copy_from_slice(src);
self.row += 1;
Ok(())
}
}
#[must_use]
pub fn probe(prefix: &[u8]) -> bool {
if prefix.get(4..8) != Some(&crate::SIGNATURE_FTYP[..]) {
return false;
}
let Some(brands) = prefix.get(8..) else {
return false;
};
brands
.chunks_exact(4)
.enumerate()
.filter(|(index, _)| *index != 1)
.any(|(_, brand)| is_known_brand(brand))
}
fn is_known_brand(brand: &[u8]) -> bool {
crate::BRANDS_STILL
.iter()
.chain(core::iter::once(&crate::BRAND_SEQUENCE))
.any(|known| known == brand)
}
#[derive(Debug, Clone, Copy, Default)]
pub struct AvifCodec;
impl Codec for AvifCodec {
fn format(&self) -> Format {
Format::Avif
}
fn magic_len(&self) -> usize {
32
}
fn probe(&self, prefix: &[u8]) -> bool {
probe(prefix)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "tests operate on known-good values and assert shapes directly"
)]
mod tests {
use super::*;
#[test]
fn unpremultiplying_restores_straight_colour() {
let mut colour = vec![10, 20, 30, 50, 64, 200, 9, 9, 9];
unpremultiply(&mut colour, 3, &[255, 128, 0], 255);
assert_eq!(colour, vec![10, 20, 30, 100, 128, 255, 0, 0, 0]);
let mut wide = vec![1000, 30000, 65535];
unpremultiply(&mut wide, 3, &[32768], 65535);
assert_eq!(wide, vec![2000, 59999, 65535]);
let mut grey = vec![60, 255];
unpremultiply(&mut grey, 1, &[120, 255], 255);
assert_eq!(grey, vec![128, 255]);
}
use otf_pixels_core::ErrorCode;
fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
let total = u32::try_from(8 + payload.len()).unwrap();
out.extend_from_slice(&total.to_be_bytes());
out.extend_from_slice(kind);
out.extend_from_slice(payload);
out
}
fn ftyp(major: &[u8; 4], compatible: &[&[u8; 4]]) -> Vec<u8> {
let mut payload = Vec::from(*major);
payload.extend_from_slice(&0_u32.to_be_bytes());
for brand in compatible {
payload.extend_from_slice(*brand);
}
boxed(b"ftyp", &payload)
}
fn av1c() -> Vec<u8> {
boxed(b"av1C", &[0x81, 0x00, 0x0C, 0x00])
}
fn ispe(width: u32, height: u32) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0];
payload.extend_from_slice(&width.to_be_bytes());
payload.extend_from_slice(&height.to_be_bytes());
boxed(b"ispe", &payload)
}
fn infe(id: u16, kind: &[u8; 4], hidden: bool) -> Vec<u8> {
let mut payload = vec![2, 0, 0, u8::from(hidden)];
payload.extend_from_slice(&id.to_be_bytes());
payload.extend_from_slice(&[0, 0]);
payload.extend_from_slice(kind);
payload.push(0);
boxed(b"infe", &payload)
}
fn iinf(entries: &[Vec<u8>]) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0];
payload.extend_from_slice(&u16::try_from(entries.len()).unwrap().to_be_bytes());
for entry in entries {
payload.extend_from_slice(entry);
}
boxed(b"iinf", &payload)
}
fn iloc(rows: &[(u16, u32, u32)]) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
payload.extend_from_slice(&u16::try_from(rows.len()).unwrap().to_be_bytes());
for (id, offset, length) in rows {
payload.extend_from_slice(&id.to_be_bytes());
payload.extend_from_slice(&[0, 0]);
payload.extend_from_slice(&1_u16.to_be_bytes());
payload.extend_from_slice(&offset.to_be_bytes());
payload.extend_from_slice(&length.to_be_bytes());
}
boxed(b"iloc", &payload)
}
fn pitm(id: u16) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0];
payload.extend_from_slice(&id.to_be_bytes());
boxed(b"pitm", &payload)
}
fn ipma(rows: &[(u16, &[u8])]) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0];
payload.extend_from_slice(&u32::try_from(rows.len()).unwrap().to_be_bytes());
for (item, properties) in rows {
payload.extend_from_slice(&item.to_be_bytes());
payload.push(u8::try_from(properties.len()).unwrap());
payload.extend_from_slice(properties);
}
boxed(b"ipma", &payload)
}
fn iprp(properties: &[Vec<u8>], associations: &[(u16, &[u8])]) -> Vec<u8> {
let mut ipco = Vec::new();
for property in properties {
ipco.extend_from_slice(property);
}
let mut payload = boxed(b"ipco", &ipco);
payload.extend_from_slice(&ipma(associations));
boxed(b"iprp", &payload)
}
fn meta_box(children: &[Vec<u8>]) -> Vec<u8> {
let mut payload = vec![0, 0, 0, 0];
for child in children {
payload.extend_from_slice(child);
}
boxed(b"meta", &payload)
}
fn minimal_avif(width: u32, height: u32) -> Vec<u8> {
let mut file = ftyp(b"avif", &[b"mif1"]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[infe(1, b"av01", false)]),
iloc(&[(1, 0, 1)]),
iprp(&[ispe(width, height), av1c()], &[(1, &[1, 2])]),
]));
file.extend_from_slice(&boxed(b"mdat", &[0]));
file
}
#[test]
fn reads_dimensions_without_decoding_pixels() {
let file = minimal_avif(320, 240);
let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
let descriptor = decoder.descriptor();
assert_eq!((descriptor.width, descriptor.height), (320, 240));
assert_eq!(descriptor.pixel, PixelFormat::Rgb8);
assert_eq!(decoder.info().config.bit_depth, 8);
assert_eq!(decoder.info().config.subsampling, Subsampling::Yuv420);
assert!(!decoder.info().has_alpha);
assert!(!decoder.info().is_grid);
}
#[test]
fn an_alpha_auxiliary_item_widens_the_pixel_format() {
let mut auxl = Vec::new();
auxl.extend_from_slice(&2_u16.to_be_bytes()); auxl.extend_from_slice(&1_u16.to_be_bytes());
auxl.extend_from_slice(&1_u16.to_be_bytes()); let mut iref_payload = vec![0, 0, 0, 0];
iref_payload.extend_from_slice(&boxed(b"auxl", &auxl));
let mut auxc_payload = vec![0, 0, 0, 0];
auxc_payload.extend_from_slice(crate::meta::URN_ALPHA.as_bytes());
auxc_payload.push(0);
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[infe(1, b"av01", false), infe(2, b"av01", true)]),
iloc(&[(1, 0, 1), (2, 0, 1)]),
boxed(b"iref", &iref_payload),
iprp(
&[ispe(8, 8), av1c(), boxed(b"auxC", &auxc_payload)],
&[(1, &[1, 2]), (2, &[1, 2, 3])],
),
]));
let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
assert!(decoder.info().has_alpha);
assert_eq!(decoder.descriptor().pixel, PixelFormat::Rgba8);
}
#[test]
fn a_grid_takes_its_configuration_from_its_first_tile() {
let mut dimg = Vec::new();
dimg.extend_from_slice(&1_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes()); dimg.extend_from_slice(&2_u16.to_be_bytes());
dimg.extend_from_slice(&3_u16.to_be_bytes());
let mut iref_payload = vec![0, 0, 0, 0];
iref_payload.extend_from_slice(&boxed(b"dimg", &dimg));
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[
infe(1, b"grid", false),
infe(2, b"av01", true),
infe(3, b"av01", true),
]),
iloc(&[(1, 0, 1), (2, 0, 1), (3, 0, 1)]),
boxed(b"iref", &iref_payload),
iprp(
&[ispe(128, 64), av1c(), ispe(64, 64)],
&[(1, &[1]), (2, &[3, 2]), (3, &[3, 2])],
),
]));
let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
assert!(decoder.info().is_grid);
assert_eq!(
(decoder.descriptor().width, decoder.descriptor().height),
(128, 64)
);
assert_eq!(decoder.info().config.bit_depth, 8);
}
#[test]
fn pack_interleaves_colour_and_alpha_in_the_formats_layout() {
assert_eq!(
pack(PixelFormat::GrayA8, &[10, 20], 1, Some(&[200, 100])),
[10, 200, 20, 100]
);
assert_eq!(
pack(PixelFormat::Rgba8, &[1, 2, 3], 3, Some(&[4])),
[1, 2, 3, 4]
);
let wide = pack(PixelFormat::Rgba16, &[0x1234], 1, Some(&[0xFFFF]));
let samples: Vec<u16> = wide
.chunks_exact(2)
.map(|b| u16::from_ne_bytes([b[0], b[1]]))
.collect();
assert_eq!(samples, [0x1234, 0x1234, 0x1234, 0xFFFF]);
assert_eq!(pack(PixelFormat::Gray8, &[7, 8], 1, None), [7, 8]);
}
#[test]
fn monochrome_and_wide_samples_choose_the_right_pixel_format() {
fn format_for(av1c_bytes: [u8; 4], has_alpha: bool) -> PixelFormat {
let info = AvifInfo {
width: 1,
height: 1,
config: Av1Config {
seq_profile: av1c_bytes[1] >> 5,
seq_level_idx0: 0,
seq_tier0: 0,
bit_depth: 8,
subsampling: Subsampling::Yuv420,
chroma_sample_position: 0,
config_obus: Vec::new(),
},
has_alpha,
is_grid: false,
colour: None,
icc: None,
orientation: Orientation::Normal,
};
pixel_format(&info)
}
assert_eq!(format_for([0x81, 0, 0x0C, 0], false), PixelFormat::Rgb8);
assert_eq!(format_for([0x81, 0, 0x0C, 0], true), PixelFormat::Rgba8);
let mono = |depth: u8, alpha: bool| {
pixel_format(&AvifInfo {
width: 1,
height: 1,
config: Av1Config {
seq_profile: 0,
seq_level_idx0: 0,
seq_tier0: 0,
bit_depth: depth,
subsampling: Subsampling::Monochrome,
chroma_sample_position: 0,
config_obus: Vec::new(),
},
has_alpha: alpha,
is_grid: false,
colour: None,
icc: None,
orientation: Orientation::Normal,
})
};
assert_eq!(mono(8, false), PixelFormat::Gray8);
assert_eq!(mono(8, true), PixelFormat::GrayA8);
assert_eq!(mono(10, false), PixelFormat::Gray16);
assert_eq!(mono(12, true), PixelFormat::Rgba16);
}
#[test]
fn the_row_buffer_size_contract_is_enforced_before_decoding() {
let file = minimal_avif(4, 4);
let mut decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
let mut wrong = [0_u8; 3];
assert_eq!(
decoder.read_row(&mut wrong).unwrap_err().code(),
ErrorCode::InvalidArgument
);
let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
assert!(decoder.read_row(&mut row).is_err());
}
#[test]
fn dimensions_are_limited_before_anything_is_allocated() {
let file = minimal_avif(100_000, 100_000);
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::LimitExceeded);
}
#[test]
fn an_essential_property_we_do_not_understand_refuses_the_file() {
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[infe(1, b"av01", false)]),
iloc(&[(1, 0, 1)]),
iprp(
&[ispe(8, 8), av1c(), boxed(b"zzzz", &[0; 4])],
&[(1, &[1, 2, 0x83])],
),
]));
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Unsupported);
assert!(error.to_string().contains("zzzz"), "{error}");
}
#[test]
fn a_file_without_the_boxes_an_image_needs_is_rejected() {
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&boxed(b"mdat", &[0; 4]));
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Malformed);
assert!(error.to_string().contains("no meta box"), "{error}");
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[pitm(1)]));
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Malformed);
assert!(
error.to_string().contains("no primary image item"),
"{error}"
);
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[infe(1, b"av01", false)]),
iloc(&[(1, 0, 1)]),
iprp(&[av1c()], &[(1, &[1])]),
]));
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Malformed);
assert!(error.to_string().contains("no ispe"), "{error}");
let mut file = ftyp(b"avif", &[]);
file.extend_from_slice(&meta_box(&[
pitm(1),
iinf(&[infe(1, b"av01", false)]),
iloc(&[(1, 0, 1)]),
iprp(&[ispe(8, 8)], &[(1, &[1])]),
]));
let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Malformed);
assert!(error.to_string().contains("no av1C"), "{error}");
}
#[test]
fn a_stream_that_is_not_an_avif_is_rejected() {
for bytes in [
&b""[..],
&b"not an avif at all"[..],
&b"\x89PNG\r\n\x1a\n"[..],
] {
let error = AvifDecoder::new(bytes, Limits::default()).unwrap_err();
assert_eq!(error.code(), ErrorCode::Malformed, "for {bytes:?}");
}
}
#[test]
fn probe_needs_a_brand_not_just_an_isobmff_header() {
assert!(probe(&ftyp(b"avif", &[])));
assert!(probe(&ftyp(b"mif1", &[b"avif", b"miaf"])));
assert!(probe(&ftyp(b"MA1B", &[])));
assert!(probe(&ftyp(b"avis", &[b"avif"])));
assert!(!probe(&ftyp(b"isom", &[b"mp42"])));
assert!(!probe(&ftyp(b"heic", &[b"heix"])));
assert!(!probe(&ftyp(b"qt ", &[])));
assert!(!probe(b""));
assert!(!probe(b"\0\0\0\x20ftyp"));
assert!(!probe(b"\x89PNG\r\n\x1a\n"));
}
#[test]
fn the_minor_version_is_not_read_as_a_brand() {
let mut payload = Vec::from(*b"isom");
payload.extend_from_slice(b"avif"); payload.extend_from_slice(b"mp42");
assert!(!probe(&boxed(b"ftyp", &payload)));
}
#[test]
fn the_codec_entry_reports_the_format() {
assert_eq!(AvifCodec.format(), Format::Avif);
assert!(AvifCodec.probe(&ftyp(b"avif", &[])));
assert!(!AvifCodec.probe(b"short"));
}
}