Pure-Rust AVIF (AV1 Image File Format): the AV1 profile of HEIF /
MIAF. The ISOBMFF / HEIF container — box reader, meta item model,
item payload resolution, derived-image graph, grid / overlay /
identity composition, alpha attachment, clap / irot / imir,
image-sequence tables and the still-image writer — is
oxideav-heif; this crate
owns everything AV1-specific on top of it: av1C and the AV1 item /
sample handling, the av1-avif §2–§8 audits and profile brands, the
AVIF item layout of the muxer, a1op / a1lx / lsel layered items,
the HEIF-extension properties it types beyond the container's set,
avis sequences, HDR metadata authoring and the pixel encoder's tile
election. AV1 pixels come from
oxideav-av1. Zero C
dependencies.
Part of the oxideav framework but usable standalone.
oxideav-avif follows the workspace image-crate API: the same small
root vocabulary as oxideav-png, oxideav-heif, … returning raw
Vec<u8> pixels.
# fn main() -> Result<(), oxideav_avif::Error> {
let bytes = std::fs::read("in.avif").map_err(oxideav_avif::Error::from)?;
if oxideav_avif::probe(&bytes) {
let info = oxideav_avif::info(&bytes)?; // header only: width, height, format, frames, alpha, colour
let img = oxideav_avif::decode(&bytes)?; // AvifImage, native planar layout
let rgba: Vec<u8> = img.to_rgba8(); // tightly packed RGBA, 4 * width bytes per row
let (w, h) = (img.width(), img.height());
let opts = oxideav_avif::EncodeOptions::default().with_quality(90);
let out: Vec<u8> = oxideav_avif::encode_rgba8(w, h, &rgba, &opts)?;
std::fs::write("out.avif", out).map_err(oxideav_avif::Error::from)?;
assert_eq!((info.width, info.height), (w, h));
}
# Ok(())
# }Pixels need the registry feature. AVIF is a container whose
pixels come from the AV1 codec crate, and codec crates in this
workspace are framework-only. So:
| Build | Available |
|---|---|
default-features = false (no oxideav-core, no oxideav-av1) |
probe, info, every type (AvifImage, ImageInfo, ColorInfo, Metadata, RgbImage / RgbaImage, Frame, DecodeOptions, EncodeOptions, PixelFormat, Plane, Error), AvifImage::to_rgb8 / to_rgba8 / from_rgb8 / from_rgba8, the container model (parse → AvifFile, parse_header, inspect → AvifInfo, parse_avis), composition over caller-supplied planes (grid, alpha, transform, overlay) and the container muxer (AvifMuxer, encode_still_av1) around an already-coded AV1 payload |
default (registry on) |
everything above plus decode, decode_with, decode_rgb8, decode_rgba8, decode_all / decode_all_with, decode_from, encode, encode_rgb8, encode_rgba8, encode_to, the pixel encoder (still), avis sequence encode, and the framework Decoder / Encoder |
A standalone caller that pairs the container model with its own AV1
decoder builds an AvifImage::new(width, height, format, planes)?
(plane geometry validated) from the composed planes and gets
to_rgba8() like everyone else.
[dependencies]
oxideav-avif = "0.0" # with pixels (registry on)
# oxideav-avif = { version = "0.0", default-features = false } # container + conversions onlyWith registry the crate also plugs into oxideav-core:
# #[cfg(feature = "registry")] {
let mut ctx = oxideav_core::RuntimeContext::new();
oxideav_avif::register(&mut ctx); // codec "avif" + .avif / .avifs extension hints
// or: oxideav_avif::register_with_av1(&mut ctx.codecs) to install the AV1 codec too
let params = oxideav_core::CodecParameters::video(oxideav_core::CodecId::new("avif"));
let _dec = oxideav_avif::make_decoder(¶ms).unwrap(); // direct factory, same as the registry's
let _enc = oxideav_avif::make_encoder(¶ms).unwrap();
# }The framework AvifDecoder / AvifEncoder are thin adapters over the
functions above: send_packet (one complete AVIF file per packet)
runs decode_with / decode_all_with with the decoder's
DecodeOptions (gain_map=apply / reference_white codec options,
set_options), and emits every frame with its ColorSignal; the
encoder maps a VideoFrame onto the still encoder (quality / q /
alpha_q / chroma / premultiplied options — the schemas are
AvifEncoderOptions / AvifDecoderOptions, declared on the registry
entry, parsed strictly). The decoder honours
Decoder::set_execution_context (the AV1 worker budget; the threads
option fixes it) and the encoder declares its 27 accepted input
layouts (ENCODER_PIXEL_FORMATS) as the registry's
accepted_pixel_formats. From<AvifImage> for VideoFrame and
AvifImage::from_video_frame(&frame, ¶ms) (also
TryFrom<(&VideoFrame, &CodecParameters)>) move pictures between the
two worlds.
Codec id "avif", capability avif_heif_av1_decode;
CodecParameters::extradata is the av1C record.
PixelFormat (= AvifPixelFormat) mirrors oxideav_core::PixelFormat
by name. Decode yields the storage layout; color.matrix == 0
(identity) means the 4:4:4 planes are G, B, R.
Decode (AvifImage::format) |
8-bit | 10 / 12-bit (LE words) |
|---|---|---|
| 4:2:0 / 4:2:2 / 4:4:4 | Yuv420P Yuv422P Yuv444P |
Yuv420P10Le … Yuv444P12Le |
| … with alpha auxiliary | Yuva420P Yuva422P Yuva444P |
Yuva420P10Le … Yuva444P12Le |
| monochrome | Gray8 |
Gray10Le Gray12Le |
| monochrome + alpha (packed) | Ya8 |
Ya16Le (depth on bit_depth) |
| Encode | Layout written |
|---|---|
encode(&AvifImage) |
the image's own layout and depth; alpha as the AV1-coded alpha auxiliary item; Error::Unsupported for sub-sampled chroma at odd extents (never a silent conversion) |
encode_rgb8 / encode_rgba8 |
8-bit 4:2:0 (EncodeOptions::chroma; 4:4:4 at odd extents), MIAF default colour (BT.709 / sRGB / BT.601, full range), alpha as the auxiliary |
encode(&AvifImage::from_rgb8(..)?) |
identity-matrix 4:4:4 — byte-exact RGB at the default lossless quality |
| canvases past 4096 coded pixels per axis | grid tiling (encode_still_auto) |
encode_all(&[Frame], &EncodeOptions) |
the mirror of decode_all: delay-less frames → the primary plus a brst image burst (encode_still_burst; per-member colour / ICC / alpha / clap, Exif / XMP on the primary), timed frames → an avis image sequence (encode_sequence at timescale 1000, each frame's delay in ms; no alpha — Unsupported); all frames share frame 0's geometry, depth and layout; decode_all(encode_all(f)) == f at the lossless default |
decode_all returns every sample of an avis image sequence (with
its delay), the entities of the primary's brst burst group, else
the primary alone. AvifImage::new / from_rgb8 / from_rgba8 are
Results that refuse a zero dimension or a buffer that does not match
the geometry with InvalidData.
EncodeOptions (Default = lossless): base_q_idx (AV1 quantizer,
0 = lossless) or with_quality(0..=100) (base_q_idx = ((100 − q) × 255 + 50) / 100), alpha_q_idx, premultiplied_alpha (prem
signalling), chroma for the RGB one-call paths, embed_exif /
embed_xmp / embed_icc, speed (EncodeSpeed::Fast default /
Balanced / Thorough — the AV1 search effort; Thorough is the
exhaustive search, minutes per 12 MP), threads (AV1 tile-search
workers and the workers a grid's tiles are coded on; the layout
follows the picture size — TILE_LAYOUT_THREADS — so the bytes are
the same at any count), grid (GridTiling::Auto default / Single
/ MaxTile(edge): how encode / encode_still_auto split a picture
into coded items — see "Encode budget" below). StillEncodeOptions
is the deprecated alias.
DecodeOptions (Default = lenient, Some(16384) × Some(16384),
Some(1 << 28) pixels, Some(1 << 30) input bytes; None =
unlimited): max_width, max_height, max_pixels (also caps every
grid / overlay canvas on the way), max_bytes, strict (an AVIF
brand is required, a claimed mif1 must carry its mandatory boxes),
tone_mapped (apply a tmap gain map — default off: the base
image), reference_white_nits (203), layer (render this spatial
layer of a layered primary instead of its lsel), threads (the AV1
worker budget for every coded item / sample — byte-identical at any
budget; None = serial, or the execution context's budget on the
registry decoder).
Registry encoder options (AvifEncoderOptions): quality 0..=100
(100 = lossless; absent = 60, DEFAULT_QUALITY; base_q_idx = ((100 − quality) × 255 + 50) / 100 when q is absent — 60 → 102; the
alpha auxiliary follows quality when alpha_q is absent and
quality is given, and is coded losslessly when neither is), q /
alpha_q (explicit base_q_idx, win over quality), chroma (420
default / 422 / 444: the Y′CbCr layout a packed RGB(A) frame is
coded in at a lossy quality; odd extents 4:4:4; lossless RGB keeps the
byte-exact identity coding), premultiplied, speed (fast default
/ balanced / thorough: the AV1 search effort), threads (AV1
tile-search workers and the grid's tile workers; 0 = the execution
context's budget; the bytes never depend on it), grid (auto
default / none / a tile edge 64..=4096). Measured on a 16 × 16
textured picture: Yuv420P 592 B exact → quality 50 324 B 37.4 dB →
quality 10 294 B 25.0 dB; Rgb24 795 B exact (identity) → 50 329 B
31.0 dB (4:2:0) → 10 309 B 24.7 dB.
The default quality, measured on the 4032 × 3024 device sample
(docs/image/heif/device-samples/iphone_13_pro_max.HEIC decoded to
8-bit 4:2:0, luma PSNR against that decode, 8 workers,
examples/encode_budget):
quality |
bytes | luma PSNR | wall |
|---|---|---|---|
| 30 | 668 043 | 28.76 dB | 1.13 s |
| 40 | 1 027 213 | 31.31 dB | 1.16 s |
| 50 | 1 476 482 | 33.51 dB | 1.21 s |
| 60 (default) | 2 038 848 | 35.77 dB | 1.26 s |
| 70 | 2 623 788 | 38.24 dB | 1.28 s |
| 75 | 2 956 471 | 39.53 dB | 1.29 s |
| 80 | 3 364 962 | 40.90 dB | 1.34 s |
| 90 | 4 814 005 | 44.80 dB | 1.42 s |
| 95 | 6 295 482 | 49.30 dB | 1.44 s |
| 100 (lossless) | 12 004 818 | ∞ | 0.49 s |
60 writes the size the picture's own camera HEIC has (2.18 MB) and is
the quality the HEIF container's AV1 items default to, so one dial
means one thing across the family; a gateway consumer no longer pins
a quality to get a lossy .avif. quality=100 / q=0 is the
explicit lossless path.
The AV1 still encoder's working set follows the coded item's area, so
the registry encoder's grid policy decides the memory and the wall
clock of a large picture: auto codes one item up to
AUTO_GRID_MAX_SINGLE_PIXELS (1 MP) luma samples and a grid of
AUTO_GRID_TILE (512-pixel) tiles above it, the tiles cut straight
from the picture and coded on up to threads workers
(ExecutionContext::effective_workers, each tile with a share of the
budget; the bytes never depend on the worker count). Every coded item
carries its own av1C in the ipco (the container writer never
shares decoder configurations) and readers in the wild cap the ipco
at 100 child boxes, so auto keeps colour + alpha tiles under
AUTO_GRID_MAX_TILES (90) by doubling the tile edge (12 MP with alpha
→ 4 × 3 tiles of 1024); an explicit grid=<edge> is honoured as
given (grid=256 on 12 MP is 192 tiles, which magick identify /
heif-info refuse). elect_grid_for is the election table.
Same picture as above at quality 60, /usr/bin/time -l around
examples/encode_budget (the process holds the source frame and
nothing else before the encode starts); r477 = the single item the
registry wrote before this round:
| input | shape | 1 worker | 4 workers | 8 workers | bytes | luma PSNR |
|---|---|---|---|---|---|---|
| 8-bit 4:2:0 | single item (r477, grid=none) |
7.99 s / 465 MiB | 2.97 s / 553 MiB | 2.20 s / 607 MiB | 1 982 694 | 35.76 dB |
| 8-bit 4:2:0 | 512-pixel grid, 8 × 6 (auto) |
7.77 s / 92 MiB | 2.08 s / 116 MiB | 1.21 s / 162 MiB | 2 038 848 (+2.8 %) | 35.77 dB |
| 8-bit 4:2:0 | 1024-pixel grid, 4 × 3 | 7.25 s / 114 MiB | 2.03 s / 200 MiB | 1.43 s / 299 MiB | 2 015 004 (+1.6 %) | 35.96 dB |
| 8-bit 4:2:0 | 256-pixel grid, 16 × 12 | 10.8 s / 86 MiB | 3.10 s / 104 MiB | 1.88 s / 122 MiB | 2 056 152 (+3.7 %) | 35.92 dB |
packed Rgb24 (lossy → 4:2:0) |
single item (r477) | — | — | 2.24 s / 684 MiB | 1 909 479 | 35.79 dB |
packed Rgb24 |
512 grid, r477 conversion | 7.37 s / 288 MiB | — | 1.26 s / 361 MiB | 1 958 495 | — |
packed Rgb24 |
512 grid, one-pass conversion | 7.37 s / 91 MiB | — | 1.24 s / 161 MiB | 1 958 495 | — |
| 10-bit 4:2:0 | 512 grid (auto) |
7.57 s / 101 MiB | 2.22 s / 136 MiB | 1.27 s / 165 MiB | 2 072 085 | — |
packed Rgba (alpha) |
1024 grid, 4 × 3 + alpha (auto, the tile cap) |
8.59 s / 149 MiB | 2.38 s / 262 MiB | 1.74 s / 330 MiB | 1 940 633 | — |
Where the memory goes (12 MP 8-bit 4:2:0, 512 grid): the source frame
18 MiB; the StillImage the registry builds from it 36 MiB (u16
samples — the one copy left on the planar path); one tile's cut
planes 0.75 MiB per worker; the AV1 encoder's state ≈ 30 MiB for the
first worker and ≈ 10 MiB per further worker (92 → 162 MiB from 1 to
8). The single item's 465 MiB is the AV1 encoder holding the whole
4032 × 3024 picture's state, which no plumbing on this side changes.
The packed-RGB path converts straight from the frame's rows into the
coding planes (H.273 §8.3 per pixel, the chroma of a sub-sampled
layout box-filtered from the rounded 4:4:4 values — sample-identical
to the contract path's conversion, pinned in the tests); the r477
path kept a packed copy, a 16-bit RGB copy and a 4:4:4 intermediate.
tests/interop.rs hands every shape the registry encoder writes to
the AVIF readers installed on the host, each as an opaque process
(skipped when absent, every installed one must accept every file).
On this Mac (macOS 26.6):
| shape (64 × 48 unless noted) | sips |
magick identify |
ffmpeg |
heif-info |
avifdec |
|---|---|---|---|---|---|
Yuv420P default / lossless, YuvJ444P, Yuv422P, Gray8 |
opens | opens | opens | opens | opens |
Yuv420P10Le, Yuv444P12Le, Gray12Le lossless |
opens | opens | opens | opens | opens |
Rgb24 lossy 4:2:0 / chroma=444, Bgr24 lossless (identity), Gbrp8, Gbrap10Le |
opens | opens | opens | opens | opens |
Rgba (alpha auxiliary), Yuva420P lossless, Ya8 |
opens | opens | opens | opens | opens |
Bgra with premultiplied=true (prem reference, HEIF §6.9.1) |
no picture (reader gap) | opens | opens | opens | opens |
| ICC + Exif + XMP items | opens | opens | opens | opens | opens |
1200 × 900 auto grid (3 × 2), with alpha (3 × 2 + alpha), grid=256 (5 × 4) |
opens | opens | opens | opens | opens |
4032 × 3024 auto grid (8 × 6), single item, 10-bit grid, alpha grid |
opens | opens | opens | opens | opens |
4032 × 3024 grid=256 (192 tiles) |
opens | refused: ipco > 100 boxes |
opens | refused: same | opens |
AvifImage::color is the item's colr nclx as ColorInfo { range: ColorRange, primaries, transfer, matrix } (H.273 code
points; ColorRange::{Unspecified, Limited, Full}); with
no CICP the MIAF §7.3.6.4 default applies — BT.709 / sRGB / BT.601,
full range. to_rgb8 / to_rgba8 invert the signalled matrix at
the signalled range exactly (round-to-nearest, chroma replicated to
its co-sited luma samples), bring 10 / 12-bit samples to 8 bits as
round(v × 255 / (2^d − 1)), and read a matrix without a kernel
(reserved / unspecified / constant-luminance / ICtCp / YCgCo) as
BT.601 — try_to_rgb8 reports Unsupported instead.
AvifImage::metadata: icc (an ICC colr), exif (from the TIFF
header on — the HEIF A.2.1 offset word is stripped on decode and
re-written on encode), xmp; gamma is always None. encode
writes them back as an ICC colr next to the nclx, an Exif item
and an XMP mime item; an empty payload is nothing to carry and gets
no item. HDR descriptive properties (mdcv / clli / cclv /
amve) stay on inspect's AvifInfo and the still encoder's
StillProperties.
Through the framework (oxideav-core 0.1.40 blobs) the same three ride
as BlobKind::ICC / EXIF / XMP: the registry decoder puts a
file's ICC / Exif / XMP on every frame it emits (VideoFrame::blobs),
the registry encoder writes a frame's blobs refined over the stream's
(CodecParameters::blobs; a frame blob overrides a stream blob of the
same kind; other kinds have no AVIF item and are dropped), and
Metadata::to_blobs / from_blobs / from_refined_blobs +
AvifImage::from_video_frame keep the contract record and the blobs
one source of truth — so HEIC → AVIF through the image gateway keeps
the profile, the Exif block and the XMP packet.
Enforced before any pixel allocation, as Error::LimitExceeded: the
DecodeOptions bounds above, plus the crate's own caps — a 32 MiB
per-item AV1 payload, MAX_GRID_CANVAS_PIXELS / MAX_OVERLAY_CANVAS_PIXELS
(1 << 28), MAX_ITEM_DECODES (4096 item decodes per file),
MAX_DERIVATION_DEPTH. Hostile input returns Error, never panics
(fuzzed: contract_api, derived_graph_decode, and the
cross-decoder harnesses under fuzz/).
| Layer | Crate | What lives there |
|---|---|---|
ISOBMFF boxes, meta tree, iloc (all construction methods), iref, ipco / ipma, idat, grpl |
oxideav-heif (HeifFile, Meta, boxes) |
parse_header / parse build this crate's Meta from the container's (Meta::from_container); AvifHeader::heif / item_data resolve item bytes |
| Typed properties | oxideav-heif (props) + this crate |
ispe pixi colr pasp clap irot imir iscl auxC clli amve rloc lsel a1op a1lx rref crtt mdft udes altt converted from the container; av1C raw for the AV1 layer; mdcv / cclv read here; the HEIF-extension family (aebr … cmin, transitions, prdi, sstr, txlo, elng, fnch, mskC) parsed here from the raw property bytes |
| Composition | oxideav-heif (compose) |
grid stitch, iovl painting (fill converted with the output's H.273 matrix), alpha attachment, clap → irot → imir in ipma order, MIAF §7.3.6.7 4:4:4 promotion; the decoder walks the graph on the container frame and re-lays the result out as this crate's AvifFrame |
| AVIF rules on top | this crate | av1-avif §4.1 same-depth alpha, nearest-neighbour alpha resize, ispe trim and grid trim with AV1's ceiling chroma (odd 4:2:0 stays 4:2:0), monochrome overlays stay monochrome, degenerate clap passes through, a 4:2:2 quarter turn is refused, lsel / a1op layer selection, the AV1 decode itself |
| Image sequences | oxideav-heif (sequence::parse_movie) + this crate |
first-track sample table, stsd / av1C, tkhd / mdhd / hdlr / elst from the container; sample groups (sbgp / csgp / sgpd), prft / ssix and the §3 / §8 audits here |
| Writer | oxideav-heif (HeifWriter / SequenceWriter) + this crate |
this crate decides brands, item order, property sets, auxC URNs, descriptors, item names and entity groups; the container serialises meta / idat / mdat, raw Exif / XMP bodies, and the avis track (moov with the mandatory ccst, cover still aliasing sample 0) |
Parsing, derived-image composition, image sequences and the writer ride the container crate; the AVIF-side surface below is complete and heavily tested.
Pixel decode is live at every AV1 bit depth. The primary item's
AV1 OBU stream (and every AVIS sample) is handed to
oxideav-av1's
conformance-grade spec-driver decoder; the composition layer then
stitches grid tiles, composites the alpha auxiliary (4:2:0 / 4:2:2 /
4:4:4 / monochrome masters), and applies clap / irot / imir —
at 8, 10 and 12 bits end to end. 10/12-bit output rides little-endian
16-bit word planes (Yuv*P10Le / *P12Le, Gray10Le / Gray12Le,
alpha-composited Yuva*P1xLe, and packed Ya16Le for HBD
monochrome + alpha with the effective depth on the core
significant-bits side channel). Every committed fixture decodes end
to end — including the 3840×2160 alpha composite and the multi-sample
AVIS sequence — and the encode→decode HBD matrix round-trips
sample-exact where lossless.
Derived images decode to pixels. The decoder resolves the
primary's output image per HEIF §6.3 for any item type —
recursively over dimg inputs, cycle- and depth-guarded: grid
tile stitch, iovl overlay composition (§6.6.2.2 canvas + offsets +
layering with the §6.9.1 straight / pre-multiplied alpha rendering,
8/10/12-bit, translucent fills yielding a Yuva* canvas), iden
identity derivations carrying their own clap / irot / imir, and
every item's transformative properties in ipma order. A grid
primary's alpha may be a coded item or a grid of monochrome tiles.
Layered (progressive) items honour lsel / a1op (av1-avif §2.3).
Pixel encode is live (still module). StillImage +
encode_still / encode_still_grid drive oxideav-av1's KEY-frame
encoder across the full (bit depth × chroma format) matrix — 8/10/12
bit × 4:2:0 / 4:2:2 / 4:4:4 / monochrome — plus RGB(A) via the H.273
identity matrix in 4:4:4 (byte-exact lossless round-trips), alpha as
a same-depth monochrome auxiliary (av1-avif §4.1, full-range
signalled in-stream), arbitrary extents (edge-replicated pad to the
coded 8-grid + top-left clap, ispe = coded extents per §2.2.2),
grid tiling with right/bottom trim (64-pixel tile floor + tiling
election, alpha as a hidden alpha grid, pass-through properties on
the grid item), iovl overlays (encode_still_overlay), iden
identity derivations (StillProperties::identity_derivation),
layered / progressive items (encode_still_layered: AV1 spatial
layers + a1lx + lsel), depth-map auxiliaries, and avis image
sequences (sequence::encode_sequence: all-intra or KEY + P groups,
full moov / trak / stbl). The ftyp profile brand
follows the elected AV1 profile (§8: Main → MA1B, High → MA1A,
Professional → general brands only). Lossless encodes decode back
sample-exact through this crate's own decoder; lossy encodes are
PSNR-gated in the test suite; black-box acceptance decodes the
emitted files through an external AVIF decoder binary where one is
installed. The registry Encoder (make_encoder / AvifEncoder)
rides the same pipeline: one video frame in, one complete AVIF file
packet out (quality / q / alpha_q / chroma / premultiplied
codec options; 27 declared input layouts).
The container muxer (mux module) remains available standalone:
given an already-coded AV1 Image Item Data payload plus its av1C
record, AvifMuxer / AvifGridMuxer / AvifOverlayMuxer /
encode_still_av1 emit a conformant AVIF file — ftyp
(avif/mif1/miaf + MA1B/MA1A/no profile brand), the meta
tree serialised by oxideav-heif's HeifWriter (derived-item bodies
in idat, MIAF mdat order, de-duplicated ipco) — with ispe /
pixi / colr / pasp / clap / irot / imir item properties, an
optional AV1-coded alpha auxiliary (auxC + auxl, plus prem from
the master when premultiplied, HEIF 3rd ed. §6.9.1), and optional
grid tiling (dimg). The output round-trips through this crate's
own parse path byte-for-byte (coded payload and every property) and
passes audit_mif1. At this layer the AV1 bitstream is taken
black-box.
The records a user builds or reads through the documented API — the
picture (AvifImage) and the contract records around it, the
container results (AvifFile, AvifInfo, AvisMeta, …), the typed
metadata (Ispe, Clap, Colr, Mdcv, Clli, …), StillImage /
StillProperties / EncodeOptions / DecodeOptions and the muxer
specs — are
#[non_exhaustive]: build them with Type::new(<every field, in declaration order>) (or Default where one exists) and read /
assign their pub fields; the option records take with_<field>
setters (StillProperties::default().with_irot(Some(1))). The enums
that grow with the standard (Property, Colr, AvifError,
AuxKind, DerivationKind, …) are #[non_exhaustive] too: match
them with a _ arm. The parser's and composer's own records (table
entries, descriptors, placements, the HEIF-extension property family)
are #[doc(hidden)] — reachable, outside the semver contract.
AvifFrame / AvifPlane / AvifPixelFormat and StillChroma stay
plain (the sample model AV1 closes).
AVIF (av1-avif, 2025-10) defines no low-overhead profile: mif3 /
mini (HEIF Amd 2:2026 Annex O) are not AVIF brands or boxes, so this
crate's brand rule refuses them. oxideav-heif reads and writes
mini files (an AV1-coded one expands to an avif-equivalent
meta), which is where a .hmg / image/hif2 file opens.
use oxideav_avif::{inspect, parse};
let bytes = std::fs::read("image.avif")?;
let info = inspect(&bytes)?; // AvifInfo: everything the container says
println!("{}x{} bits_per_channel={:?} av1c_len={}",
info.width, info.height, info.bits_per_channel, info.av1c.len());
let file = parse(&bytes)?; // AvifFile: brands, meta, the primary av01 item
for item in &file.meta.items {
println!("item {} type={:?} name={:?}", item.id,
std::str::from_utf8(&item.item_type), item.name);
}
// `file.primary_item_data` is the primary image's AV1 OBU stream and
// `file.av1c` its record — pair them with your own AV1 decoder, then
// `AvifImage::new(w, h, format, planes).to_rgba8()`.
# Ok::<(), Box<dyn std::error::Error>>(())tests/fixtures/monochrome.avif is Monochrome.avif from
AOMediaCodec/av1-avif
(1280×720, monochrome). tests/fixtures/{gray32,midgray,white16,red,black420}.avif
are tiny reference-encoder-produced AVIFs covering each colour-plane
layout. Integration tests walk the full HEIF hierarchy, extract the
primary item, and decode every fixture to pixels end to end (the flat
lossless fixtures assert exact constant planes).
MIT — see LICENSE.
