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) -> DecodeResult

First complete frame, owned std/image.Image with straight RGBA8 pixels.

DefaultEngine :: () -> Av1Engine

dav1d is the default engine; libaom is the explicit comparison oracle.

LibaomOracle :: () -> Av1Engine
StaticNativeAPI :: () -> Interop.AvifAPI
EncodeRGBA8 :: (source: RgbaView, metadata: EncodeMetadata, options: EncodeOptions) -> EncodeResult
StaticNativeEncodeAPI :: () -> AvifEncodeAPI
Reencode :: (input: BorrowedBytes, options: EncodeOptions) -> EncodeResult

Source ↗

avif/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 :: 8

A 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) -> DecodeResult
Av1Engine :: struct { name: string; capabilities: EngineCapabilities; decode_container: ContainerDecode; }
DecodeWithEngine :: (engine: Av1Engine, input: BorrowedBytes, options: *DecodeOptions) -> DecodeResult

Source ↗

avif/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) -> bool

Prefix 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) -> BoxResult
ValidateContainer :: (input: BorrowedBytes) -> DecodeError

Signature policy matches the existing bridge: first box ftyp, bounded to 4096 bytes, accepting major or compatible avif/avis brands.

Preflight :: (input: BorrowedBytes, limits: DecodeLimits) -> DecodeError

Conservative container metadata scan before native allocation. No item graph interpretation or OBU extraction is claimed by this initial A-01 slice.

Source ↗

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 :: 1

Explicit libavif codec choices. AUTO is never requested, so an unavailable codec fails with UnsupportedCapability instead of silently switching.

CodecDav1d :: 2

Automatic 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 :: 4

Automatic 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 :: 2097152
THREADED_MIN_PIXELS_420 :: 8388608
PixelFormatYUV420 :: 3
OnlineCores :: () -> s32
AutoThreads :: () -> s32

Usable cores capped at AUTO_THREADS.

DecodeThreads :: (pixels: s64, format: s32) -> s32

Automatic thread count for a source frame of `format` (avifPixelFormat).

AvifVersion :: #type () -> *u8
AvifCreate :: #type () -> *RawDecoder
AvifDestroy :: #type (decoder: *RawDecoder)
AvifMemory :: #type (decoder: *RawDecoder, data: *u8, size: usize) -> s32
AvifStep :: #type (decoder: *RawDecoder) -> s32
AvifDefaults :: #type (rgb: *RawRGB, image: *RawImage)
AvifConvert :: #type (image: *RawImage, rgb: *RawRGB) -> s32
AvifCrop :: #type (crop: *RawCrop, aperture: *u32, width: u32, height: u32, format: s32, diagnostics: *void) -> s32
AvifAPI :: struct { version: AvifVersion; create: AvifCreate; destroy: AvifDestroy; memory: AvifMemory; parse: AvifStep; next: AvifStep; defaults: AvifDefaults; convert: AvifConvert; crop: AvifCrop; }
ValidateAPI :: (api: AvifAPI) -> DecodeError
BridgeDecode :: (api: AvifAPI, input: BorrowedBytes, options: *DecodeOptions, codec: s32, threads: s32) -> DecodeResult
DecodeWithAPI :: (api: AvifAPI, input: BorrowedBytes, options: DecodeOptions, codec: s32, threads: s32) -> DecodeResult

Explicit 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) -> DecodeIntoResult
ProbeResult :: struct { is_ok: bool; metadata: ImageMetadata; error: DecodeError; }
BridgeProbe :: (api: AvifAPI, input: BorrowedBytes, options: DecodeOptions, codec: s32) -> ProbeResult

Container-only bounded parse: never calls next/defaults/convert and never allocates pixels. DecodeInto re-parses and revalidates all output dimensions.

Source ↗

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 :: () -> EncodeMetadata
EncodeOptions :: struct { quality: s32; limits: DecodeLimits; cancellation: *Cancellation; workers: *WorkerBudget; }
DefaultEncodeOptions :: () -> EncodeOptions
EncodeResult :: struct { is_ok: bool; bytes: Vec(u8); width: s32; height: s32; error: DecodeError; }
EncodeFailure :: (error: DecodeError) -> EncodeResult
RawEncoder :: 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) -> *RawImage
AvifImageDestroy :: #type (image: *RawImage)
AvifSetMetadata :: #type (image: *RawImage, data: *u8, count: usize) -> s32
AvifEncoderCreate :: #type () -> *RawEncoder
AvifEncoderDestroy :: #type (encoder: *RawEncoder)
AvifEncoderWrite :: #type (encoder: *RawEncoder, image: *RawImage, output: *RawBytes) -> s32
AvifDataFree :: #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) -> DecodeError
EncodeNativeError :: (code: s32) -> DecodeError
EncodeRGBA8WithAPI :: (api: AvifEncodeAPI, source: RgbaView, metadata: EncodeMetadata, options: EncodeOptions) -> EncodeResult

No resize or orientation bake: AVIF transforms carry the stored-pixel orientation.

ReencodeWithAPI :: (decode_api: AvifAPI, api: AvifEncodeAPI, input: BorrowedBytes, options: EncodeOptions) -> EncodeResult

Re-encode a still 8-bit AVIF retaining metadata and transforms.

Source ↗

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

Source ↗

Documents