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