Skip to main content

otf_pixels_codec_avif/
decoder.rs

1//! The AVIF decoder: container parsing and the [`Decoder`] implementation.
2//!
3//! [`AvifDecoder::new`] parses only boxes, so `metadata()` stays a no-decode
4//! operation (SPEC §Guarantees 3): it answers dimensions, bit depth, chroma
5//! format, and alpha without touching the bitstream. [`AvifDecoder::read_row`]
6//! reconstructs the whole primary frame on the first call and serves rows from
7//! the cache — an AVIF still is one AV1 key frame with no prefix that yields a
8//! partial raster, so the decode is inherently whole-image (SPEC §Memory).
9//!
10//! The AV1 reconstruction covers the intra still, in any tiling, in 4:4:4, 4:2:2
11//! and 4:2:0 at 8, 10 and 12 bits, and the raster conversion handles the
12//! identity matrix, the BT.601/709/2020 YUV matrices and YCgCo at full and studio
13//! range (`yuv.rs`), to `Rgb8` or — for 10/12-bit — full-range `Rgb16`.
14//! Monochrome pictures decode to grey, and an alpha auxiliary item — a second
15//! AV1 image — becomes the alpha channel, un-premultiplying the colour when a
16//! `prem` reference says it was premultiplied.
17//! Every AV1 intra coding tool an encoder uses for stills is decoded —
18//! segmentation, delta-q/delta-lf, quantizer matrices, any tiling. Anything
19//! outside that (other matrices, intra block copy, grids,
20//! film grain) is reported as [`PixelsError::Unsupported`] rather than decoded
21//! wrong.
22
23use crate::boxes::{FourCc, Reader};
24use crate::meta::Meta;
25use crate::props::{Av1Config, Colour, Subsampling};
26use crate::yuv::{Depths, Layout, YuvMatrix, identity_to_rgb, plane_to_grey, yuv_to_rgb};
27use otf_pixels_core::{
28    Codec, DecodeCapability, Decoder, Format, ImageDescriptor, Limits, Orientation, PixelFormat,
29    PixelsError, Result, Source,
30};
31
32/// The most container bytes read before a file is called hostile.
33///
34/// The container addresses its payload by absolute file offset, so the whole
35/// file must be resident before any of it can be interpreted and there is no
36/// bound derivable from the image dimensions: a small `meta` can be followed
37/// by an unbounded `mdat`. `max_pixels` bounds the output; this bounds the
38/// input, exactly as the WebP decoder does for the same reason.
39const MAX_CONTAINER: usize = 256 * 1024 * 1024;
40
41/// What the container says about the primary image.
42///
43/// Everything here comes from boxes, so it is available without touching the
44/// AV1 bitstream.
45#[derive(Debug, Clone)]
46pub struct AvifInfo {
47    /// The primary item's dimensions, from its `ispe`.
48    pub width: u32,
49    /// See [`AvifInfo::width`].
50    pub height: u32,
51    /// The AV1 configuration of the primary item, or of its first tile when
52    /// the primary item is a grid.
53    pub config: Av1Config,
54    /// Whether the file carries an alpha plane as an auxiliary item.
55    pub has_alpha: bool,
56    /// Whether the primary item is a derived grid rather than a single coded
57    /// image.
58    pub is_grid: bool,
59    /// The primary item's `colr` colour information, if it has any. Its `nclx`
60    /// matrix, when present, overrides the one in the AV1 sequence header.
61    pub colour: Option<Colour>,
62    /// The primary item's ICC profile, from an ICC `colr`.
63    pub icc: Option<Vec<u8>>,
64    /// The primary item's `irot`/`imir` transform. Reported through
65    /// [`Decoder::orientation`], never applied to the decoded rows.
66    pub orientation: Orientation,
67}
68
69/// An alpha plane's coded image: a second AV1 still, usually monochrome.
70#[derive(Debug)]
71struct AlphaItem {
72    /// Its coded AV1 bytes.
73    frame_data: Vec<u8>,
74    /// Its `av1C` configuration OBUs.
75    config_obus: Vec<u8>,
76    /// Whether the colour is premultiplied by it (a `prem` reference from the
77    /// colour item to this one).
78    premultiplied: bool,
79}
80
81/// Decodes an AVIF stream.
82#[derive(Debug)]
83pub struct AvifDecoder {
84    descriptor: ImageDescriptor,
85    info: AvifInfo,
86    /// The primary item's coded AV1 bytes, retained for the lazy pixel decode.
87    /// `None` for a grid, whose per-tile decode is not implemented.
88    frame_data: Option<Vec<u8>>,
89    /// The alpha auxiliary item, when there is one and it can be decoded.
90    alpha: Option<AlphaItem>,
91    /// The primary item's EXIF block, from an `Exif` item describing it.
92    exif: Option<Vec<u8>>,
93    /// The reconstructed interleaved raster, produced on the first row read.
94    raster: Option<Vec<u8>>,
95    /// Rows already served.
96    row: u32,
97}
98
99impl AvifDecoder {
100    /// Read the container and describe the primary image.
101    ///
102    /// Decodes no pixels: this parses boxes only, which is what makes
103    /// `metadata()` free.
104    ///
105    /// # Errors
106    ///
107    /// Returns [`PixelsError::Malformed`] for a stream that is not a
108    /// structurally valid AVIF, [`PixelsError::Unsupported`] for a valid file
109    /// using a feature this decoder does not implement, or
110    /// [`PixelsError::LimitExceeded`] if the image exceeds `limits`.
111    pub fn new<S: Source>(source: S, limits: Limits) -> Result<Self> {
112        let bytes = read_all(source)?;
113        let info = parse_container(&bytes)?;
114        let (frame_data, alpha) = if info.is_grid {
115            (None, None)
116        } else {
117            (Some(locate_primary_frame(&bytes)?), locate_alpha(&bytes)?)
118        };
119        let pixel = pixel_format(&info);
120        let descriptor = ImageDescriptor::with_limits(info.width, info.height, pixel, &limits)?;
121        let exif = locate_exif(&bytes);
122        Ok(Self {
123            descriptor,
124            info,
125            frame_data,
126            alpha,
127            exif,
128            raster: None,
129            row: 0,
130        })
131    }
132
133    /// What the container said about this image.
134    #[must_use]
135    pub const fn info(&self) -> &AvifInfo {
136        &self.info
137    }
138}
139
140/// Drain `source` into a bounded buffer.
141fn read_all<S: Source>(mut source: S) -> Result<Vec<u8>> {
142    let mut bytes = Vec::new();
143    let mut chunk = [0_u8; 64 * 1024];
144    loop {
145        if bytes.len() > MAX_CONTAINER {
146            return Err(PixelsError::malformed(
147                "avif",
148                format!("stream exceeds {MAX_CONTAINER} bytes"),
149            ));
150        }
151        match source.read(&mut chunk)? {
152            0 => break,
153            read => {
154                let Some(filled) = chunk.get(..read) else {
155                    break;
156                };
157                bytes.extend_from_slice(filled);
158            }
159        }
160    }
161    Ok(bytes)
162}
163
164/// Parse the container down to the facts about the primary image.
165fn parse_container(bytes: &[u8]) -> Result<AvifInfo> {
166    let mut reader = Reader::new(bytes);
167
168    // The first box must be `ftyp`. Checking it here rather than trusting
169    // sniffing means a decoder handed bytes directly still validates them.
170    let first = reader
171        .next_box()
172        .ok_or_else(|| PixelsError::malformed("avif", "the file holds no boxes"))??;
173    if first.kind != FourCc::new(b"ftyp") {
174        return Err(PixelsError::malformed(
175            "avif",
176            format!("the file begins with '{}' rather than 'ftyp'", first.kind),
177        ));
178    }
179    if !probe(bytes) {
180        return Err(PixelsError::malformed(
181            "avif",
182            "the ftyp box declares no brand this decoder recognises",
183        ));
184    }
185
186    let meta_reader = reader
187        .find(b"meta")?
188        .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
189    let meta = Meta::parse(meta_reader)?;
190
191    let primary = meta
192        .primary_item()
193        .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
194
195    // An essential property we cannot interpret was declared to change how the
196    // pixels are to be read, so decoding anyway would produce a confidently
197    // wrong image.
198    if let Some(kind) = meta.properties.essential_unknown(primary.id) {
199        return Err(PixelsError::unsupported(format!(
200            "avif: the primary item requires the '{kind}' property, which this decoder does not implement"
201        )));
202    }
203
204    if !primary.is_coded_image() && !primary.is_grid() {
205        return Err(PixelsError::unsupported(format!(
206            "avif: the primary item has type '{}', which is not an image this decoder produces",
207            primary.kind
208        )));
209    }
210
211    let extents = meta.properties.extents(primary.id).ok_or_else(|| {
212        PixelsError::malformed(
213            "avif",
214            format!(
215                "item {} has no ispe property, so its size is unknown",
216                primary.id
217            ),
218        )
219    })?;
220
221    // A grid's own configuration lives on its tiles: the grid item has no
222    // coded data of its own, only a dimg reference to the items that do.
223    let config_item = if primary.is_grid() {
224        *meta
225            .referenced(primary.id, b"dimg")
226            .first()
227            .ok_or_else(|| {
228                PixelsError::malformed(
229                    "avif",
230                    format!("grid item {} references no tiles", primary.id),
231                )
232            })?
233    } else {
234        primary.id
235    };
236
237    let config = meta
238        .properties
239        .av1_config(config_item)
240        .ok_or_else(|| {
241            PixelsError::malformed(
242                "avif",
243                format!("item {config_item} has no av1C property, so it is not a coded AV1 image"),
244            )
245        })?
246        .clone();
247
248    Ok(AvifInfo {
249        width: extents.width,
250        height: extents.height,
251        config,
252        has_alpha: meta.alpha_item(primary.id).is_some(),
253        is_grid: primary.is_grid(),
254        colour: meta.properties.colour(primary.id).cloned(),
255        icc: meta.properties.icc_profile(primary.id).map(<[u8]>::to_vec),
256        orientation: meta.properties.orientation(primary.id),
257    })
258}
259
260/// Locate and copy out the primary coded image item's AV1 bytes.
261fn locate_primary_frame(bytes: &[u8]) -> Result<Vec<u8>> {
262    let mut reader = Reader::new(bytes);
263    reader.next_box();
264    let meta_reader = reader
265        .find(b"meta")?
266        .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
267    let meta = Meta::parse(meta_reader)?;
268    let primary = meta
269        .primary_item()
270        .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
271    Ok(meta.item_data(bytes, primary)?.into_owned())
272}
273
274/// The EXIF block of the item that describes the primary item (a `cdsc`
275/// reference from an `Exif` item), past its TIFF header offset.
276///
277/// Metadata never fails a decode: anything malformed is just no EXIF.
278fn locate_exif(bytes: &[u8]) -> Option<Vec<u8>> {
279    let mut reader = Reader::new(bytes);
280    reader.next_box();
281    let meta = Meta::parse(reader.find(b"meta").ok()??).ok()?;
282    let primary = meta.primary_item()?.id;
283    let item = meta.items.iter().find(|item| {
284        item.kind == FourCc::new(b"Exif") && meta.referenced(item.id, b"cdsc").contains(&primary)
285    })?;
286    let data = meta.item_data(bytes, item).ok()?;
287    // ISO/IEC 23008-12 A.2.1: a 32-bit offset to the TIFF header, then the
288    // block.
289    let offset = u32::from_be_bytes(data.get(..4)?.try_into().ok()?);
290    let start = 4_usize.checked_add(usize::try_from(offset).ok()?)?;
291    Some(data.get(start..)?.to_vec())
292}
293
294/// Locate the primary item's alpha auxiliary item, if it has one.
295fn locate_alpha(bytes: &[u8]) -> Result<Option<AlphaItem>> {
296    let mut reader = Reader::new(bytes);
297    reader.next_box();
298    let meta_reader = reader
299        .find(b"meta")?
300        .ok_or_else(|| PixelsError::malformed("avif", "the file has no meta box"))?;
301    let meta = Meta::parse(meta_reader)?;
302    let primary = meta
303        .primary_item()
304        .ok_or_else(|| PixelsError::malformed("avif", "the file names no primary image item"))?;
305    let Some(alpha) = meta.alpha_item(primary.id) else {
306        return Ok(None);
307    };
308    let config = meta.properties.av1_config(alpha.id).ok_or_else(|| {
309        PixelsError::malformed(
310            "avif",
311            format!("alpha item {} has no av1C property", alpha.id),
312        )
313    })?;
314    Ok(Some(AlphaItem {
315        frame_data: meta.item_data(bytes, alpha)?.into_owned(),
316        config_obus: config.config_obus.clone(),
317        // `prem` runs from the colour (master) item to its alpha item.
318        premultiplied: meta.referenced(primary.id, b"prem").contains(&alpha.id),
319    }))
320}
321
322/// Decode one coded AV1 still to its sample planes and sequence header.
323fn decode_frame(
324    config_obus: &[u8],
325    frame_data: &[u8],
326) -> Result<(crate::av1::DecodedFrame, crate::av1::SequenceHeader)> {
327    use crate::av1::{StillPicture, decode_still};
328
329    let still = StillPicture::parse(config_obus, frame_data)?;
330    let groups = still.tile_group_data(frame_data)?;
331    let frame = decode_still(&still.sequence, &still.frame, &groups)?;
332    Ok((frame, still.sequence))
333}
334
335/// Decode the primary AV1 still and convert it to the interleaved raster the
336/// descriptor promises.
337fn decode_raster(
338    info: &AvifInfo,
339    pixel: PixelFormat,
340    frame_data: &[u8],
341    alpha: Option<&AlphaItem>,
342) -> Result<Vec<u8>> {
343    let (frame, sequence) = decode_frame(&info.config.config_obus, frame_data)?;
344    let color = &sequence.color;
345    let layout = Layout {
346        width: info.width as usize,
347        height: info.height as usize,
348        subsampling_x: usize::from(color.subsampling_x),
349        subsampling_y: usize::from(color.subsampling_y),
350    };
351    let depths = Depths::for_input(color.bit_depth);
352    let (colour, channels) = if color.mono_chrome {
353        let luma = frame
354            .planes
355            .first()
356            .ok_or_else(|| PixelsError::malformed("avif", "the luma plane is missing"))?;
357        (plane_to_grey(luma, layout, depths, color.color_range), 1)
358    } else {
359        (
360            colour_to_rgb(info, &frame.planes, color, layout, depths)?,
361            3,
362        )
363    };
364    let mut colour = colour;
365    let alpha = match (info.has_alpha, alpha) {
366        (false, _) => None,
367        (true, Some(item)) => {
368            let samples = decode_alpha(item, layout, depths)?;
369            if item.premultiplied {
370                unpremultiply(&mut colour, channels, &samples, depths.output_max());
371            }
372            Some(samples)
373        }
374        // The container declares alpha but it could not be located (a grid's
375        // alpha, say); the descriptor promises it, so this cannot continue.
376        (true, None) => {
377            return Err(PixelsError::unsupported(
378                "avif: this file's alpha plane is not decodable yet",
379            ));
380        }
381    };
382    Ok(pack(pixel, &colour, channels, alpha.as_deref()))
383}
384
385/// Convert a colour (non-monochrome) frame's planes to RGB samples.
386fn colour_to_rgb(
387    info: &AvifInfo,
388    planes: &[crate::av1::Plane],
389    color: &crate::av1::ColorConfig,
390    layout: Layout,
391    depths: Depths,
392) -> Result<Vec<u16>> {
393    // The container's nclx, when present, is what the file declares; the
394    // sequence header is the fallback. The range is the sequence header's:
395    // it is what governs the decoded samples.
396    let matrix = match &info.colour {
397        Some(Colour::Nclx { matrix, .. }) => *matrix,
398        _ => u16::from(color.matrix_coefficients),
399    };
400    if matrix != 0 {
401        let yuv = YuvMatrix::new(matrix, color.color_range, depths)?;
402        return yuv_to_rgb(planes, layout, &yuv);
403    }
404    if (layout.subsampling_x, layout.subsampling_y) != (0, 0) {
405        return Err(PixelsError::malformed(
406            "avif",
407            "the identity colour matrix requires 4:4:4, but the chroma is subsampled",
408        ));
409    }
410    identity_to_rgb(planes, layout, depths)
411}
412
413/// Decode the alpha item to samples at the colour's output depth. Alpha is
414/// its image's luma — any chroma it codes is ignored — expanded to full range
415/// if the item was coded at studio range.
416fn decode_alpha(item: &AlphaItem, layout: Layout, colour_depths: Depths) -> Result<Vec<u16>> {
417    let (frame, sequence) = decode_frame(&item.config_obus, &item.frame_data)?;
418    let luma = frame
419        .planes
420        .first()
421        .ok_or_else(|| PixelsError::malformed("avif", "the alpha plane is missing"))?;
422    let depths = Depths {
423        input: sequence.color.bit_depth,
424        output: colour_depths.output,
425    };
426    let alpha_layout = Layout {
427        subsampling_x: 0,
428        subsampling_y: 0,
429        ..layout
430    };
431    Ok(plane_to_grey(
432        luma,
433        alpha_layout,
434        depths,
435        sequence.color.color_range,
436    ))
437}
438
439/// Turn colour premultiplied by `alpha` (a `prem` reference) back into the
440/// straight colour the API carries (SPEC §Pixel formats), as libavif does:
441/// each sample scaled by `max / alpha`, rounded and capped at `max`, and
442/// fully transparent pixels black. Precision lost to premultiplication at
443/// low alpha stays lost.
444fn unpremultiply(colour: &mut [u16], channels: usize, alpha: &[u16], max: i64) {
445    for (px, &a) in colour.chunks_exact_mut(channels.max(1)).zip(alpha) {
446        let a = i64::from(a);
447        if a >= max {
448            continue;
449        }
450        for sample in px {
451            *sample = if a == 0 {
452                0
453            } else {
454                ((i64::from(*sample) * max * 2 + a) / (2 * a)).min(max) as u16
455            };
456        }
457    }
458}
459
460/// Interleave colour samples (`channels` per pixel: 1 grey or 3 RGB) and
461/// optional alpha into the raster `pixel` describes — one byte per sample for
462/// the 8-bit formats, two native-endian bytes for the 16-bit ones. Grey with
463/// alpha at 16 bits has no format of its own (SPEC §Pixel formats), so it is
464/// widened to RGBA.
465fn pack(pixel: PixelFormat, colour: &[u16], channels: usize, alpha: Option<&[u16]>) -> Vec<u8> {
466    let wide = matches!(
467        pixel,
468        PixelFormat::Rgb16 | PixelFormat::Rgba16 | PixelFormat::Gray16
469    );
470    let out_colour = match pixel {
471        PixelFormat::Gray8 | PixelFormat::Gray16 | PixelFormat::GrayA8 => 1,
472        _ => 3,
473    };
474    let pixels = colour.len() / channels.max(1);
475    let mut raster = Vec::with_capacity(pixels * (out_colour + 1) * if wide { 2 } else { 1 });
476    let mut put = |v: u16| {
477        if wide {
478            raster.extend_from_slice(&v.to_ne_bytes());
479        } else {
480            raster.push(v as u8);
481        }
482    };
483    for (i, px) in colour.chunks_exact(channels.max(1)).enumerate() {
484        for c in 0..out_colour {
485            // Grey widened to RGB repeats its one sample.
486            put(px.get(c).or_else(|| px.first()).copied().unwrap_or(0));
487        }
488        if let Some(alpha) = alpha {
489            put(alpha.get(i).copied().unwrap_or(0));
490        }
491    }
492    raster
493}
494
495/// The pixel format this image decodes to.
496///
497/// AV1 codes YUV; the engine's formats are RGB and greyscale, so the mapping
498/// happens here and the colour conversion happens at decode. Ten- and
499/// twelve-bit samples widen to sixteen because SPEC §Pixel formats has no
500/// narrower wide type, and they are rescaled to its full 0..=65535 range —
501/// the conversion computes RGB at 16 bits directly (`yuv.rs`).
502fn pixel_format(info: &AvifInfo) -> PixelFormat {
503    let wide = info.config.bit_depth > 8;
504    let monochrome = info.config.subsampling == Subsampling::Monochrome;
505    match (monochrome, info.has_alpha, wide) {
506        (true, false, false) => PixelFormat::Gray8,
507        (true, false, true) => PixelFormat::Gray16,
508        (true, true, false) => PixelFormat::GrayA8,
509        // SPEC §Pixel formats has no GrayA16, so wide greyscale with alpha
510        // widens to RGBA rather than silently dropping either the alpha or
511        // the low bits.
512        (true, true, true) => PixelFormat::Rgba16,
513        (false, false, false) => PixelFormat::Rgb8,
514        (false, false, true) => PixelFormat::Rgb16,
515        (false, true, false) => PixelFormat::Rgba8,
516        (false, true, true) => PixelFormat::Rgba16,
517    }
518}
519
520impl Decoder for AvifDecoder {
521    fn descriptor(&self) -> ImageDescriptor {
522        self.descriptor
523    }
524
525    fn orientation(&self) -> Orientation {
526        self.info.orientation
527    }
528
529    fn icc_profile(&self) -> Option<&[u8]> {
530        self.info.icc.as_deref()
531    }
532
533    /// The EXIF block of an `Exif` item describing the primary image.
534    fn exif(&self) -> Option<&[u8]> {
535        self.exif.as_deref()
536    }
537
538    fn capability(&self) -> DecodeCapability {
539        // A grid AVIF stores independently coded tiles and could serve
540        // regions, but this decoder does not yet implement `read_region`.
541        // Claiming `Regions` before it does would be a lie the scheduler acts
542        // on, which is the mistake `codec.rs` records having made once for
543        // JPEG's scaled decode.
544        DecodeCapability::Sequential
545    }
546
547    fn read_row(&mut self, out: &mut [u8]) -> Result<()> {
548        // Validate the call even though it cannot yet be served, so that the
549        // contract is already enforced when the bitstream decoder lands.
550        if self.row >= self.descriptor.height {
551            return Err(PixelsError::invalid_argument(
552                "out",
553                format!("all {} rows have already been read", self.descriptor.height),
554            ));
555        }
556        let row_bytes = self.descriptor.row_bytes();
557        if out.len() != row_bytes {
558            return Err(PixelsError::invalid_argument(
559                "out",
560                format!("row buffer is {} bytes, expected {row_bytes}", out.len()),
561            ));
562        }
563
564        // Reconstruct the whole frame on the first row: an AVIF still is coded
565        // as one AV1 frame with no prefix that yields a partial raster, so the
566        // decode is inherently whole-image (SPEC §Memory). Later rows are served
567        // from the cached raster.
568        if self.raster.is_none() {
569            let frame_data = self
570                .frame_data
571                .as_deref()
572                .ok_or_else(|| PixelsError::unsupported("avif: grid images are not decoded yet"))?;
573            self.raster = Some(decode_raster(
574                &self.info,
575                self.descriptor.pixel,
576                frame_data,
577                self.alpha.as_ref(),
578            )?);
579        }
580        let raster = self.raster.as_deref().unwrap_or(&[]);
581        let start = self.row as usize * row_bytes;
582        let src = raster
583            .get(start..start + row_bytes)
584            .ok_or_else(|| PixelsError::malformed("avif", "the reconstructed raster is short"))?;
585        out.copy_from_slice(src);
586        self.row += 1;
587        Ok(())
588    }
589}
590
591/// Whether `prefix` starts with an ISOBMFF file declaring a brand this
592/// decoder recognises.
593///
594/// Detection is by magic bytes only (SPEC §Formats). `ftyp` at offset 4 marks
595/// the whole ISOBMFF family, which also holds MP4 and HEIC, so the brands are
596/// what actually identify an AVIF. The major brand alone is not enough: many
597/// encoders write `mif1` as the major brand and put `avif` in the compatible
598/// list, so both are scanned.
599#[must_use]
600pub fn probe(prefix: &[u8]) -> bool {
601    if prefix.get(4..8) != Some(&crate::SIGNATURE_FTYP[..]) {
602        return false;
603    }
604    let Some(brands) = prefix.get(8..) else {
605        return false;
606    };
607    // Words from offset 8: index 0 is the major brand, index 1 is the minor
608    // version — a number, not a brand — and the rest are compatible brands.
609    brands
610        .chunks_exact(4)
611        .enumerate()
612        .filter(|(index, _)| *index != 1)
613        .any(|(_, brand)| is_known_brand(brand))
614}
615
616/// Whether these four bytes name a brand this decoder claims.
617fn is_known_brand(brand: &[u8]) -> bool {
618    crate::BRANDS_STILL
619        .iter()
620        .chain(core::iter::once(&crate::BRAND_SEQUENCE))
621        .any(|known| known == brand)
622}
623
624/// The AVIF entry in a sniffing registry.
625#[derive(Debug, Clone, Copy, Default)]
626pub struct AvifCodec;
627
628impl Codec for AvifCodec {
629    fn format(&self) -> Format {
630        Format::Avif
631    }
632
633    fn magic_len(&self) -> usize {
634        // Enough for `ftyp`, the major brand, the minor version and four
635        // compatible brands, which covers every file this has been tried on.
636        // `probe` reads whatever it is given, so a shorter prefix still works
637        // when the brand appears early.
638        32
639    }
640
641    fn probe(&self, prefix: &[u8]) -> bool {
642        probe(prefix)
643    }
644}
645
646#[cfg(test)]
647#[allow(
648    clippy::unwrap_used,
649    clippy::indexing_slicing,
650    reason = "tests operate on known-good values and assert shapes directly"
651)]
652mod tests {
653    use super::*;
654
655    #[test]
656    fn unpremultiplying_restores_straight_colour() {
657        // RGB per pixel: opaque (untouched), half (doubled, rounded, capped),
658        // transparent (black), and a 16-bit sample.
659        let mut colour = vec![10, 20, 30, 50, 64, 200, 9, 9, 9];
660        unpremultiply(&mut colour, 3, &[255, 128, 0], 255);
661        assert_eq!(colour, vec![10, 20, 30, 100, 128, 255, 0, 0, 0]);
662        let mut wide = vec![1000, 30000, 65535];
663        unpremultiply(&mut wide, 3, &[32768], 65535);
664        // 30000 * 65535 / 32768 = 59999.08.
665        assert_eq!(wide, vec![2000, 59999, 65535]);
666        // Grey with alpha: one colour channel.
667        let mut grey = vec![60, 255];
668        unpremultiply(&mut grey, 1, &[120, 255], 255);
669        assert_eq!(grey, vec![128, 255]);
670    }
671    use otf_pixels_core::ErrorCode;
672
673    fn boxed(kind: &[u8; 4], payload: &[u8]) -> Vec<u8> {
674        let mut out = Vec::new();
675        let total = u32::try_from(8 + payload.len()).unwrap();
676        out.extend_from_slice(&total.to_be_bytes());
677        out.extend_from_slice(kind);
678        out.extend_from_slice(payload);
679        out
680    }
681
682    fn ftyp(major: &[u8; 4], compatible: &[&[u8; 4]]) -> Vec<u8> {
683        let mut payload = Vec::from(*major);
684        payload.extend_from_slice(&0_u32.to_be_bytes());
685        for brand in compatible {
686            payload.extend_from_slice(*brand);
687        }
688        boxed(b"ftyp", &payload)
689    }
690
691    /// An `av1C` for 8-bit 4:2:0, profile 0.
692    fn av1c() -> Vec<u8> {
693        boxed(b"av1C", &[0x81, 0x00, 0x0C, 0x00])
694    }
695
696    fn ispe(width: u32, height: u32) -> Vec<u8> {
697        let mut payload = vec![0, 0, 0, 0];
698        payload.extend_from_slice(&width.to_be_bytes());
699        payload.extend_from_slice(&height.to_be_bytes());
700        boxed(b"ispe", &payload)
701    }
702
703    fn infe(id: u16, kind: &[u8; 4], hidden: bool) -> Vec<u8> {
704        let mut payload = vec![2, 0, 0, u8::from(hidden)];
705        payload.extend_from_slice(&id.to_be_bytes());
706        payload.extend_from_slice(&[0, 0]);
707        payload.extend_from_slice(kind);
708        payload.push(0);
709        boxed(b"infe", &payload)
710    }
711
712    fn iinf(entries: &[Vec<u8>]) -> Vec<u8> {
713        let mut payload = vec![0, 0, 0, 0];
714        payload.extend_from_slice(&u16::try_from(entries.len()).unwrap().to_be_bytes());
715        for entry in entries {
716            payload.extend_from_slice(entry);
717        }
718        boxed(b"iinf", &payload)
719    }
720
721    fn iloc(rows: &[(u16, u32, u32)]) -> Vec<u8> {
722        let mut payload = vec![0, 0, 0, 0, 0x44, 0x00];
723        payload.extend_from_slice(&u16::try_from(rows.len()).unwrap().to_be_bytes());
724        for (id, offset, length) in rows {
725            payload.extend_from_slice(&id.to_be_bytes());
726            payload.extend_from_slice(&[0, 0]);
727            payload.extend_from_slice(&1_u16.to_be_bytes());
728            payload.extend_from_slice(&offset.to_be_bytes());
729            payload.extend_from_slice(&length.to_be_bytes());
730        }
731        boxed(b"iloc", &payload)
732    }
733
734    fn pitm(id: u16) -> Vec<u8> {
735        let mut payload = vec![0, 0, 0, 0];
736        payload.extend_from_slice(&id.to_be_bytes());
737        boxed(b"pitm", &payload)
738    }
739
740    /// An `ipma` associating each `(item, [properties])` pair, all
741    /// non-essential.
742    fn ipma(rows: &[(u16, &[u8])]) -> Vec<u8> {
743        let mut payload = vec![0, 0, 0, 0];
744        payload.extend_from_slice(&u32::try_from(rows.len()).unwrap().to_be_bytes());
745        for (item, properties) in rows {
746            payload.extend_from_slice(&item.to_be_bytes());
747            payload.push(u8::try_from(properties.len()).unwrap());
748            payload.extend_from_slice(properties);
749        }
750        boxed(b"ipma", &payload)
751    }
752
753    fn iprp(properties: &[Vec<u8>], associations: &[(u16, &[u8])]) -> Vec<u8> {
754        let mut ipco = Vec::new();
755        for property in properties {
756            ipco.extend_from_slice(property);
757        }
758        let mut payload = boxed(b"ipco", &ipco);
759        payload.extend_from_slice(&ipma(associations));
760        boxed(b"iprp", &payload)
761    }
762
763    fn meta_box(children: &[Vec<u8>]) -> Vec<u8> {
764        let mut payload = vec![0, 0, 0, 0];
765        for child in children {
766            payload.extend_from_slice(child);
767        }
768        boxed(b"meta", &payload)
769    }
770
771    /// A minimal single-item AVIF: one `av01` item, `ispe` then `av1C`.
772    fn minimal_avif(width: u32, height: u32) -> Vec<u8> {
773        let mut file = ftyp(b"avif", &[b"mif1"]);
774        file.extend_from_slice(&meta_box(&[
775            pitm(1),
776            iinf(&[infe(1, b"av01", false)]),
777            iloc(&[(1, 0, 1)]),
778            iprp(&[ispe(width, height), av1c()], &[(1, &[1, 2])]),
779        ]));
780        file.extend_from_slice(&boxed(b"mdat", &[0]));
781        file
782    }
783
784    #[test]
785    fn reads_dimensions_without_decoding_pixels() {
786        let file = minimal_avif(320, 240);
787        let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
788        let descriptor = decoder.descriptor();
789        assert_eq!((descriptor.width, descriptor.height), (320, 240));
790        assert_eq!(descriptor.pixel, PixelFormat::Rgb8);
791        assert_eq!(decoder.info().config.bit_depth, 8);
792        assert_eq!(decoder.info().config.subsampling, Subsampling::Yuv420);
793        assert!(!decoder.info().has_alpha);
794        assert!(!decoder.info().is_grid);
795    }
796
797    #[test]
798    fn an_alpha_auxiliary_item_widens_the_pixel_format() {
799        let mut auxl = Vec::new();
800        auxl.extend_from_slice(&2_u16.to_be_bytes()); // from the alpha item
801        auxl.extend_from_slice(&1_u16.to_be_bytes());
802        auxl.extend_from_slice(&1_u16.to_be_bytes()); // to the colour item
803        let mut iref_payload = vec![0, 0, 0, 0];
804        iref_payload.extend_from_slice(&boxed(b"auxl", &auxl));
805
806        let mut auxc_payload = vec![0, 0, 0, 0];
807        auxc_payload.extend_from_slice(crate::meta::URN_ALPHA.as_bytes());
808        auxc_payload.push(0);
809
810        let mut file = ftyp(b"avif", &[]);
811        file.extend_from_slice(&meta_box(&[
812            pitm(1),
813            iinf(&[infe(1, b"av01", false), infe(2, b"av01", true)]),
814            iloc(&[(1, 0, 1), (2, 0, 1)]),
815            boxed(b"iref", &iref_payload),
816            iprp(
817                &[ispe(8, 8), av1c(), boxed(b"auxC", &auxc_payload)],
818                &[(1, &[1, 2]), (2, &[1, 2, 3])],
819            ),
820        ]));
821
822        let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
823        assert!(decoder.info().has_alpha);
824        assert_eq!(decoder.descriptor().pixel, PixelFormat::Rgba8);
825    }
826
827    #[test]
828    fn a_grid_takes_its_configuration_from_its_first_tile() {
829        let mut dimg = Vec::new();
830        dimg.extend_from_slice(&1_u16.to_be_bytes()); // from the grid
831        dimg.extend_from_slice(&2_u16.to_be_bytes()); // two tiles
832        dimg.extend_from_slice(&2_u16.to_be_bytes());
833        dimg.extend_from_slice(&3_u16.to_be_bytes());
834        let mut iref_payload = vec![0, 0, 0, 0];
835        iref_payload.extend_from_slice(&boxed(b"dimg", &dimg));
836
837        let mut file = ftyp(b"avif", &[]);
838        file.extend_from_slice(&meta_box(&[
839            pitm(1),
840            iinf(&[
841                infe(1, b"grid", false),
842                infe(2, b"av01", true),
843                infe(3, b"av01", true),
844            ]),
845            iloc(&[(1, 0, 1), (2, 0, 1), (3, 0, 1)]),
846            boxed(b"iref", &iref_payload),
847            // The grid carries the full size; the tiles carry the av1C.
848            iprp(
849                &[ispe(128, 64), av1c(), ispe(64, 64)],
850                &[(1, &[1]), (2, &[3, 2]), (3, &[3, 2])],
851            ),
852        ]));
853
854        let decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
855        assert!(decoder.info().is_grid);
856        assert_eq!(
857            (decoder.descriptor().width, decoder.descriptor().height),
858            (128, 64)
859        );
860        assert_eq!(decoder.info().config.bit_depth, 8);
861    }
862
863    #[test]
864    fn pack_interleaves_colour_and_alpha_in_the_formats_layout() {
865        // Grey + alpha at 8 bits: one byte each, grey first.
866        assert_eq!(
867            pack(PixelFormat::GrayA8, &[10, 20], 1, Some(&[200, 100])),
868            [10, 200, 20, 100]
869        );
870        // RGB + alpha at 8 bits.
871        assert_eq!(
872            pack(PixelFormat::Rgba8, &[1, 2, 3], 3, Some(&[4])),
873            [1, 2, 3, 4]
874        );
875        // Wide grey + alpha has no format of its own and widens to RGBA16:
876        // the grey sample repeats, native-endian.
877        let wide = pack(PixelFormat::Rgba16, &[0x1234], 1, Some(&[0xFFFF]));
878        let samples: Vec<u16> = wide
879            .chunks_exact(2)
880            .map(|b| u16::from_ne_bytes([b[0], b[1]]))
881            .collect();
882        assert_eq!(samples, [0x1234, 0x1234, 0x1234, 0xFFFF]);
883        // No alpha: grey stays one sample per pixel.
884        assert_eq!(pack(PixelFormat::Gray8, &[7, 8], 1, None), [7, 8]);
885    }
886
887    #[test]
888    fn monochrome_and_wide_samples_choose_the_right_pixel_format() {
889        fn format_for(av1c_bytes: [u8; 4], has_alpha: bool) -> PixelFormat {
890            let info = AvifInfo {
891                width: 1,
892                height: 1,
893                config: Av1Config {
894                    seq_profile: av1c_bytes[1] >> 5,
895                    seq_level_idx0: 0,
896                    seq_tier0: 0,
897                    bit_depth: 8,
898                    subsampling: Subsampling::Yuv420,
899                    chroma_sample_position: 0,
900                    config_obus: Vec::new(),
901                },
902                has_alpha,
903                is_grid: false,
904                colour: None,
905                icc: None,
906                orientation: Orientation::Normal,
907            };
908            pixel_format(&info)
909        }
910        assert_eq!(format_for([0x81, 0, 0x0C, 0], false), PixelFormat::Rgb8);
911        assert_eq!(format_for([0x81, 0, 0x0C, 0], true), PixelFormat::Rgba8);
912
913        let mono = |depth: u8, alpha: bool| {
914            pixel_format(&AvifInfo {
915                width: 1,
916                height: 1,
917                config: Av1Config {
918                    seq_profile: 0,
919                    seq_level_idx0: 0,
920                    seq_tier0: 0,
921                    bit_depth: depth,
922                    subsampling: Subsampling::Monochrome,
923                    chroma_sample_position: 0,
924                    config_obus: Vec::new(),
925                },
926                has_alpha: alpha,
927                is_grid: false,
928                colour: None,
929                icc: None,
930                orientation: Orientation::Normal,
931            })
932        };
933        assert_eq!(mono(8, false), PixelFormat::Gray8);
934        assert_eq!(mono(8, true), PixelFormat::GrayA8);
935        assert_eq!(mono(10, false), PixelFormat::Gray16);
936        // No GrayA16 exists, so this widens rather than dropping a channel.
937        assert_eq!(mono(12, true), PixelFormat::Rgba16);
938    }
939
940    #[test]
941    fn the_row_buffer_size_contract_is_enforced_before_decoding() {
942        let file = minimal_avif(4, 4);
943        let mut decoder = AvifDecoder::new(&file[..], Limits::default()).unwrap();
944
945        // A wrong-sized buffer is an argument error, checked before the decode
946        // is reached.
947        let mut wrong = [0_u8; 3];
948        assert_eq!(
949            decoder.read_row(&mut wrong).unwrap_err().code(),
950            ErrorCode::InvalidArgument
951        );
952
953        // The synthetic fixture carries no real coded frame, so the decode
954        // itself fails cleanly rather than panicking. (Real decodes are proven
955        // bit-exact against libavif in tests/reference.rs.)
956        let mut row = vec![0_u8; decoder.descriptor().row_bytes()];
957        assert!(decoder.read_row(&mut row).is_err());
958    }
959
960    #[test]
961    fn dimensions_are_limited_before_anything_is_allocated() {
962        let file = minimal_avif(100_000, 100_000);
963        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
964        assert_eq!(error.code(), ErrorCode::LimitExceeded);
965    }
966
967    #[test]
968    fn an_essential_property_we_do_not_understand_refuses_the_file() {
969        let mut file = ftyp(b"avif", &[]);
970        file.extend_from_slice(&meta_box(&[
971            pitm(1),
972            iinf(&[infe(1, b"av01", false)]),
973            iloc(&[(1, 0, 1)]),
974            // Property 3 is unknown and marked essential (high bit set).
975            iprp(
976                &[ispe(8, 8), av1c(), boxed(b"zzzz", &[0; 4])],
977                &[(1, &[1, 2, 0x83])],
978            ),
979        ]));
980
981        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
982        assert_eq!(error.code(), ErrorCode::Unsupported);
983        assert!(error.to_string().contains("zzzz"), "{error}");
984    }
985
986    #[test]
987    fn a_file_without_the_boxes_an_image_needs_is_rejected() {
988        // No meta at all.
989        let mut file = ftyp(b"avif", &[]);
990        file.extend_from_slice(&boxed(b"mdat", &[0; 4]));
991        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
992        assert_eq!(error.code(), ErrorCode::Malformed);
993        assert!(error.to_string().contains("no meta box"), "{error}");
994
995        // A meta with no items.
996        let mut file = ftyp(b"avif", &[]);
997        file.extend_from_slice(&meta_box(&[pitm(1)]));
998        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
999        assert_eq!(error.code(), ErrorCode::Malformed);
1000        assert!(
1001            error.to_string().contains("no primary image item"),
1002            "{error}"
1003        );
1004
1005        // An item with no ispe, so no dimensions.
1006        let mut file = ftyp(b"avif", &[]);
1007        file.extend_from_slice(&meta_box(&[
1008            pitm(1),
1009            iinf(&[infe(1, b"av01", false)]),
1010            iloc(&[(1, 0, 1)]),
1011            iprp(&[av1c()], &[(1, &[1])]),
1012        ]));
1013        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1014        assert_eq!(error.code(), ErrorCode::Malformed);
1015        assert!(error.to_string().contains("no ispe"), "{error}");
1016
1017        // An item with no av1C, so it is not a coded AV1 image.
1018        let mut file = ftyp(b"avif", &[]);
1019        file.extend_from_slice(&meta_box(&[
1020            pitm(1),
1021            iinf(&[infe(1, b"av01", false)]),
1022            iloc(&[(1, 0, 1)]),
1023            iprp(&[ispe(8, 8)], &[(1, &[1])]),
1024        ]));
1025        let error = AvifDecoder::new(&file[..], Limits::default()).unwrap_err();
1026        assert_eq!(error.code(), ErrorCode::Malformed);
1027        assert!(error.to_string().contains("no av1C"), "{error}");
1028    }
1029
1030    #[test]
1031    fn a_stream_that_is_not_an_avif_is_rejected() {
1032        for bytes in [
1033            &b""[..],
1034            &b"not an avif at all"[..],
1035            &b"\x89PNG\r\n\x1a\n"[..],
1036        ] {
1037            let error = AvifDecoder::new(bytes, Limits::default()).unwrap_err();
1038            assert_eq!(error.code(), ErrorCode::Malformed, "for {bytes:?}");
1039        }
1040    }
1041
1042    #[test]
1043    fn probe_needs_a_brand_not_just_an_isobmff_header() {
1044        assert!(probe(&ftyp(b"avif", &[])));
1045        // The common real-world shape: mif1 major, avif compatible.
1046        assert!(probe(&ftyp(b"mif1", &[b"avif", b"miaf"])));
1047        assert!(probe(&ftyp(b"MA1B", &[])));
1048        assert!(probe(&ftyp(b"avis", &[b"avif"])));
1049
1050        // Other ISOBMFF families must not be claimed.
1051        assert!(!probe(&ftyp(b"isom", &[b"mp42"])));
1052        assert!(!probe(&ftyp(b"heic", &[b"heix"])));
1053        assert!(!probe(&ftyp(b"qt  ", &[])));
1054
1055        // Short prefixes are declined, never indexed past.
1056        assert!(!probe(b""));
1057        assert!(!probe(b"\0\0\0\x20ftyp"));
1058        assert!(!probe(b"\x89PNG\r\n\x1a\n"));
1059    }
1060
1061    /// The minor version sits between the major brand and the compatible
1062    /// brands, and is a number. A file whose minor version happened to spell
1063    /// a brand must not be claimed on that basis.
1064    #[test]
1065    fn the_minor_version_is_not_read_as_a_brand() {
1066        let mut payload = Vec::from(*b"isom");
1067        payload.extend_from_slice(b"avif"); // minor version, not a brand
1068        payload.extend_from_slice(b"mp42");
1069        assert!(!probe(&boxed(b"ftyp", &payload)));
1070    }
1071
1072    #[test]
1073    fn the_codec_entry_reports_the_format() {
1074        assert_eq!(AvifCodec.format(), Format::Avif);
1075        assert!(AvifCodec.probe(&ftyp(b"avif", &[])));
1076        assert!(!AvifCodec.probe(b"short"));
1077    }
1078}