Add the dependency
In your project’s ziran.toml; pin ref to a commit for reproducible builds.
[dependencies.avif]
git = "https://github.com/ziranlang/avif.git"
ref = "master"Overview
#import "avif/api"
options := DefaultOptions()
result := Decode(BorrowBytes(bytes), options)
// Check result.is_ok before using result.image, an owned std/image.Image.
From a consumer, add https://github.com/ziranlang/avif.git with ziran add.
The manifest pins libavif, dav1d (the default engine) and libaom (the oracle) source packages, plus the
av1 package with the authored AV1 decoder. Local
canonical checkout overrides belong in ignored ziran.local.toml. Production lockfiles resolve committed package and toolchain revisions.
Run the package suite in this checkout:
build-queue -- sh tests/avif.sh
build-queue -- sh tests/bmff.sh
The test harness uses the canonical sibling Ziran checkout when available;
otherwise set ZIRAN_ROOT to a built current toolchain. It builds a real
consumer of this package, resolves its exports and requests its native artifact
through ziran pkg artifact avif:native --platform linux-x86_64-gnu.
Standalone module-path tests additionally verify source and saved .zir.
The stable headless command produced by the suite is:
build/bin/avif tests/fixtures/rgba8.avif build/output.rgba
It writes tight straight-alpha RGBA8 and prints dimensions, source depth, source frame count and alpha presence. Errors have stable shared error codes. See implementation and verification status, API, native recipe and fixture provenance.
avif/src/api.zi
api
Import with #import "avif/api".
Decode :: (input: BorrowedBytes, options: DecodeOptions) -> DecodeResultFirst complete frame, owned std/image.Image with straight RGBA8 pixels.
DefaultEngine :: () -> Av1Enginedav1d is the default engine; libaom is the explicit comparison oracle.
LibaomOracle :: () -> Av1EngineStaticNativeAPI :: () -> Interop.AvifAPIEncodeRGBA8 :: (source: RgbaView, metadata: EncodeMetadata, options: EncodeOptions) -> EncodeResultStaticNativeEncodeAPI :: () -> AvifEncodeAPIReencode :: (input: BorrowedBytes, options: EncodeOptions) -> EncodeResultavif/src/engine.zi
engine
Import with #import "avif/engine".
EngineCapabilities :: struct { container_bridge: bool; obu_planes: bool; strict_rgba8: bool; fast_color: bool; hdr: bool; interruptible: bool; hard_scratch_limit: bool; max_threads: s32; }No native structs or pointers cross this boundary. The initial engine owns container parsing internally; an OBU/plane engine is a separate capability.
MAX_DECODE_THREADS :: 8A native decode may fan out to at most this many threads inside its single worker lease. Engines declare their bound; zero or larger bounds reject.
ContainerDecode :: #type (input: BorrowedBytes, options: *DecodeOptions) -> DecodeResultAv1Engine :: struct { name: string; capabilities: EngineCapabilities; decode_container: ContainerDecode; }DecodeWithEngine :: (engine: Av1Engine, input: BorrowedBytes, options: *DecodeOptions) -> DecodeResultavif/src/bmff.zi
bmff
Import with #import "avif/bmff".
Box :: struct { kind: u32; start: s64; payload: s64; end: s64; }Bounded, allocation-free ISO-BMFF framing. All offsets are absolute.
BoxResult :: struct { box: Box; error: DecodeError; }HasSignature :: (input: BorrowedBytes) -> boolPrefix recognition only: preserve the frozen host's AvifSignature policy. The complete ftyp must be present, but following boxes need not be present or valid. Use ValidateContainer/Preflight for bounded container framing.
ReadBox :: (input: BorrowedBytes, at: s64, end: s64) -> BoxResultValidateContainer :: (input: BorrowedBytes) -> DecodeErrorSignature policy matches the existing bridge: first box ftyp, bounded to 4096 bytes, accepting major or compatible avif/avis brands.
Preflight :: (input: BorrowedBytes, limits: DecodeLimits) -> DecodeErrorConservative container metadata scan before native allocation. No item graph interpretation or OBU extraction is claimed by this initial A-01 slice.
avif/src/bridge.zi
bridge
Import with #import "avif/bridge".
RawBytes :: struct { data: *u8; size: usize; }Pinned libavif 1.3.0 public ABI, allocated/destroyed only by libavif.
RawImage :: struct { width: u32; height: u32; depth: u32; format: s32; range: s32; chroma: s32; planes: [3]*u8; rows: [3]u32; owns_yuv: s32; alpha: *u8; alpha_row: u32; owns_alpha: s32; premultiplied: s32; icc: RawBytes; primaries: u16; transfer: u16; matrix: u16; clli: [2]u16; transforms: u32; aspect: [2]u32; aperture: [8]u32; rotation: u8; mirror: u8; exif: RawBytes; xmp: RawBytes; properties: *void; property_count: usize; gain_map: *void; }RawTiming :: struct { timescale: u64; pts: float64; pts_timescale: u64; duration: float64; duration_timescale: u64; }RawDecoder :: struct { codec: s32; threads: s32; source: s32; progressive: s32; incremental: s32; ignore_exif: s32; ignore_xmp: s32; size_limit: u32; dimension_limit: u32; count_limit: u32; strict: u32; image: *RawImage; image_index: s32; image_count: s32; progressive_state: s32; timing: RawTiming; timescale: u64; duration: float64; duration_timescale: u64; repetitions: s32; alpha_present: s32; }RawRGB :: struct { width: u32; height: u32; depth: u32; format: s32; upsampling: s32; downsampling: s32; avoid_yuv: s32; ignore_alpha: s32; premultiplied: s32; float_pixels: s32; threads: s32; pixels: *u8; row_bytes: u32; }RawCrop :: struct { x: u32; y: u32; width: u32; height: u32; }Explicit libavif codec choices. AUTO is never requested, so an unavailable
CodecLibaom :: 1Explicit libavif codec choices. AUTO is never requested, so an unavailable codec fails with UnsupportedCapability instead of silently switching.
CodecDav1d :: 2Automatic thread policy (`threads=0`), measured on the USB Moto (MT6765, 8x Cortex-A53) and an x86-64 host; receipt in Kryon docs/BACKENDS.md. dav1d's in-frame threads and libavif's YUV->RGB row split slowed small frames on the Moto by up to 2.7x. 4:4:4/4:2:2/4:0:0 frames gain from THREADED_MIN_PIXELS; 4:2:0 frames only from THREADED_MIN_PIXELS_420.
AUTO_THREADS :: 4Automatic thread policy (`threads=0`), measured on the USB Moto (MT6765, 8x Cortex-A53) and an x86-64 host; receipt in Kryon docs/BACKENDS.md. dav1d's in-frame threads and libavif's YUV->RGB row split slowed small frames on the Moto by up to 2.7x. 4:4:4/4:2:2/4:0:0 frames gain from THREADED_MIN_PIXELS; 4:2:0 frames only from THREADED_MIN_PIXELS_420. Both stages share the count, at most AUTO_THREADS usable cores.
THREADED_MIN_PIXELS :: 2097152THREADED_MIN_PIXELS_420 :: 8388608PixelFormatYUV420 :: 3OnlineCores :: () -> s32AutoThreads :: () -> s32Usable cores capped at AUTO_THREADS.
DecodeThreads :: (pixels: s64, format: s32) -> s32Automatic thread count for a source frame of `format` (avifPixelFormat).
AvifVersion :: #type () -> *u8AvifCreate :: #type () -> *RawDecoderAvifDestroy :: #type (decoder: *RawDecoder)AvifMemory :: #type (decoder: *RawDecoder, data: *u8, size: usize) -> s32AvifStep :: #type (decoder: *RawDecoder) -> s32AvifDefaults :: #type (rgb: *RawRGB, image: *RawImage)AvifConvert :: #type (image: *RawImage, rgb: *RawRGB) -> s32AvifCrop :: #type (crop: *RawCrop, aperture: *u32, width: u32, height: u32, format: s32, diagnostics: *void) -> s32AvifAPI :: struct { version: AvifVersion; create: AvifCreate; destroy: AvifDestroy; memory: AvifMemory; parse: AvifStep; next: AvifStep; defaults: AvifDefaults; convert: AvifConvert; crop: AvifCrop; }ValidateAPI :: (api: AvifAPI) -> DecodeErrorBridgeDecode :: (api: AvifAPI, input: BorrowedBytes, options: *DecodeOptions, codec: s32, threads: s32) -> DecodeResultDecodeWithAPI :: (api: AvifAPI, input: BorrowedBytes, options: DecodeOptions, codec: s32, threads: s32) -> DecodeResultExplicit optional native interop: no static library import in this module.
DecodeIntoResult :: struct { is_ok: bool; metadata: ImageMetadata; error: DecodeError; output_may_be_modified: bool; }DecodeIntoWithAPI :: (api: AvifAPI, input: BorrowedBytes, output: OutputBuffer, options: DecodeOptions, codec: s32, threads: s32) -> DecodeIntoResultProbeResult :: struct { is_ok: bool; metadata: ImageMetadata; error: DecodeError; }BridgeProbe :: (api: AvifAPI, input: BorrowedBytes, options: DecodeOptions, codec: s32) -> ProbeResultContainer-only bounded parse: never calls next/defaults/convert and never allocates pixels. DecodeInto re-parses and revalidates all output dimensions.
avif/src/encode.zi
encode
Optional native encoding; no static links or UI dependency.
Import with #import "avif/encode".
EncodeMetadata :: struct { icc: []u8; exif: []u8; xmp: []u8; orientation: s32; source_bit_depth: s32; frame_count: s32; }DefaultEncodeMetadata :: () -> EncodeMetadataEncodeOptions :: struct { quality: s32; limits: DecodeLimits; cancellation: *Cancellation; workers: *WorkerBudget; }DefaultEncodeOptions :: () -> EncodeOptionsEncodeResult :: struct { is_ok: bool; bytes: Vec(u8); width: s32; height: s32; error: DecodeError; }EncodeFailure :: (error: DecodeError) -> EncodeResultRawEncoder :: struct { codec: s32; threads: s32; speed: s32; keyframe_interval: s32; timescale: u64; repetitions: s32; extra_layers: u32; quality: s32; alpha_quality: s32; }Prefix of pinned 1.3.0. Only libavif allocates it; ABI tests cover touched fields.
AvifImageCreate :: #type (width: u32, height: u32, depth: u32, format: s32) -> *RawImageAvifImageDestroy :: #type (image: *RawImage)AvifSetMetadata :: #type (image: *RawImage, data: *u8, count: usize) -> s32AvifEncoderCreate :: #type () -> *RawEncoderAvifEncoderDestroy :: #type (encoder: *RawEncoder)AvifEncoderWrite :: #type (encoder: *RawEncoder, image: *RawImage, output: *RawBytes) -> s32AvifDataFree :: #type (data: *RawBytes)AvifEncodeAPI :: struct { version: AvifVersion; image_create: AvifImageCreate; image_destroy: AvifImageDestroy; defaults: AvifDefaults; from_rgb: AvifConvert; icc: AvifSetMetadata; exif: AvifSetMetadata; xmp: AvifSetMetadata; create: AvifEncoderCreate; destroy: AvifEncoderDestroy; write: AvifEncoderWrite; free: AvifDataFree; }ValidateEncodeAPI :: (api: AvifEncodeAPI) -> DecodeErrorEncodeNativeError :: (code: s32) -> DecodeErrorEncodeRGBA8WithAPI :: (api: AvifEncodeAPI, source: RgbaView, metadata: EncodeMetadata, options: EncodeOptions) -> EncodeResultNo resize or orientation bake: AVIF transforms carry the stored-pixel orientation.
ReencodeWithAPI :: (decode_api: AvifAPI, api: AvifEncodeAPI, input: BorrowedBytes, options: EncodeOptions) -> EncodeResultRe-encode a still 8-bit AVIF retaining metadata and transforms.
avif/src/preview.zi
preview
Headless, explicit Lanczos-3 resize of straight RGBA8. Encoding never calls this implicitly.
Import with #import "avif/preview".
LanczosRGBA8Into :: (source: RgbaView, width: s32, height: s32, output: OutputBuffer, options: DecodeOptions) -> PixelWriteResult