Add the dependency

In your project’s ziran.toml; pin ref to a commit for reproducible builds.

[dependencies.image_codec]
git = "https://github.com/ziranlang/image-codec.git"
ref = "master"

Overview

Consumers import image_codec/api when the dependency is named image_codec. Exports also expose individual ordinary modules. See API, fixtures and benchmarks.

From this canonical checkout, with Ziran at ../../ziran:

build-queue -- sh tests/contracts.sh
build-queue -- sh tests/corpus.sh --verify
build-queue -- sh tests/benchmark_contract.sh
build-queue -- sh tests/package_import.sh

ZIRAN_ROOT and ZIRAN select an existing toolchain. Tests use no display, network, fixture downloads or native codecs. Package import uses local overrides; a toolchain that requires committed HEADs rejects an unborn package. Direct module tests exercise the same source without a milestone commit. Generated outputs stay under ignored build/. The rebuilt toolchain must provide VecResize and VecResizeBounded; a version string alone does not identify this development capability before upstream integration.

image-codec/src/api.zi

api

Import with #import "image_codec/api".

StrictColor :: 0

Only RGBA8 output and strict color are defined here. Codec engines must explicitly reject unsupported source features rather than ignore them.

FastColor :: 1
DecodeOptions :: struct { limits: DecodeLimits; cancellation: *Cancellation; workers: *WorkerBudget; color_mode: s32; }
DefaultOptions :: () -> DecodeOptions
ValidateOptions :: (options: *DecodeOptions) -> DecodeError
DecodeProcedure :: #type (input: BorrowedBytes, metadata: ImageMetadata, options: *DecodeOptions) -> DecodeResult

Useful reference provider: already decoded, tightly packed RGBA8 bytes.

DecodeRGBA8 :: (input: BorrowedBytes, metadata: ImageMetadata, options: *DecodeOptions) -> DecodeResult

Useful reference provider: already decoded, tightly packed RGBA8 bytes. Metadata is supplied by the caller, not guessed from an unframed file.

Source ↗

image-codec/src/errors.zi

errors

Zero is success. Error offsets are input byte offsets, or -1 if unknown.

Import with #import "image_codec/errors".

DecodeError :: struct { code: s32; offset: s64; }
DecodeOK :: 0
InvalidLimits :: 1
InvalidInput :: 2
TruncatedInput :: 3
MalformedInput :: 4
UnsupportedCapability :: 5
LimitExceeded :: 6
OutputTooSmall :: 7
DecodeCancelled :: 8
AllocationFailed :: 9
WorkerBudgetExceeded :: 10
InputOutputFailed :: 11
CorrectnessMismatch :: 12
Error :: (code: s32, offset: s64) -> DecodeError
ErrorText :: (code: s32) -> string

Source ↗

image-codec/src/limits.zi

limits

Import with #import "image_codec/limits".

DecodeLimits :: struct { max_input_bytes: s64; max_width: s32; max_height: s32; max_pixels: s64; max_output_bytes: s64; max_working_bytes: s64; max_metadata_bytes: s64; max_frames: s32; max_decoded_frames: s32; max_workers: s32; }
DefaultLimits :: () -> DecodeLimits
ValidateLimits :: (limits: DecodeLimits) -> DecodeError
ValidateDecodedFrames :: (count: s32, limits: DecodeLimits) -> DecodeError
RequiredSpan :: (width: s32, height: s32, sample_bytes: s32, stride: s64) -> (s64, DecodeError)

Division checks precede multiplication, including hostile strides.

ValidatePixelDimensions :: (width: s32, height: s32, limits: DecodeLimits) -> DecodeError

Storage-neutral: planar/YUV engines apply their own output byte budget. Positive s32 sides widen to s64, so the pixel product cannot overflow.

ValidateDimensions :: (width: s32, height: s32, limits: DecodeLimits) -> DecodeError

Tight RGBA8 output: four bytes per pixel must fit max_output_bytes.

ValidateInputSize :: (count: s64, limits: DecodeLimits) -> DecodeError

Source ↗

image-codec/src/metadata.zi

metadata

Import with #import "image_codec/metadata".

ImageMetadata :: struct { width: s32; height: s32; source_bit_depth: s32; has_alpha: bool; source_premultiplied: bool; frame_count: s32; color_known: bool; color_primaries: s32; transfer_characteristics: s32; matrix_coefficients: s32; full_range: bool; metadata_bytes: s64; }

Output dimensions after transforms. Zero source values mean unknown. Image output is always packed straight-alpha RGBA8 in top-to-bottom rows.

ValidateMetadata :: (metadata: ImageMetadata, limits: DecodeLimits) -> DecodeError

Source ↗

image-codec/src/bytes.zi

bytes

Import with #import "image_codec/bytes".

BorrowedBytes :: struct { data: []u8; }

A synchronous borrow, never an owner. The caller keeps its backing storage alive and unchanged throughout use. Ziran []u8 is not a const slice.

BorrowBytes :: (data: []u8) -> BorrowedBytes
ByteRange :: (input: BorrowedBytes, offset: s64, count: s64) -> (BorrowedBytes, DecodeError)

Source ↗

image-codec/src/buffer.zi

buffer

Import with #import "image_codec/buffer".

Plane :: struct { width: s32; height: s32; stride: s64; sample_bytes: s32; storage: Vec(u8); }

One component plane. Samples are 1 or 2 bytes; multi-byte samples use little endian storage. The owning Vec may include row padding.

PlaneView :: struct { width: s32; height: s32; stride: s64; sample_bytes: s32; data: []u8; }
OutputBuffer :: struct { data: []u8; stride: s64; }
ValidatePlane :: (plane: PlaneView, limits: DecodeLimits) -> DecodeError
ValidateOutput :: (output: OutputBuffer, width: s32, height: s32, limits: DecodeLimits) -> DecodeError

Source ↗

image-codec/src/image.zi

image

Import with #import "image_codec/image".

DecodeResult :: struct { is_ok: bool; image: Image; metadata: ImageMetadata; error: DecodeError; }
DecodeFailure :: (error: DecodeError) -> DecodeResult
AllocateImage :: (metadata: ImageMetadata, limits: DecodeLimits, token: *Cancellation) -> DecodeResult

One fallible bulk allocation. Check cancellation around allocation; pixel providers and row copies add their own checkpoints during actual work.

CopyImageToOutput :: (image: *Image, output: OutputBuffer, limits: DecodeLimits, token: *Cancellation) -> DecodeError

Caller-owned output borrows its storage. Validation and pre-cancellation precede writes; cancellation during work can leave complete rows written.

Source ↗

image-codec/src/scheduler.zi

scheduler

Import with #import "image_codec/scheduler".

Cancellation :: struct { requested: bool; }

Cooperative, single-thread state. Threaded engines must serialize access or adapt their own atomic cancellation to these checkpoints.

Cancel :: (token: *Cancellation)
CancellationError :: (token: *Cancellation) -> DecodeError
WorkerBudget :: struct { max_workers: s32; active_workers: s32; peak_workers: s32; max_working_bytes: s64; working_bytes: s64; peak_working_bytes: s64; }

Counts all execution lanes, including the caller. No threads are launched. Engine reservations cover scratch only, separate from input/output bytes.

NewWorkerBudget :: (limits: DecodeLimits) -> WorkerBudget
ValidateWorkerBudget :: (budget: *WorkerBudget, limits: DecodeLimits) -> DecodeError
AcquireWorker :: (budget: *WorkerBudget, token: *Cancellation) -> DecodeError
ReleaseWorker :: (budget: *WorkerBudget) -> DecodeError
ReserveWorkingBytes :: (budget: *WorkerBudget, bytes: s64, token: *Cancellation) -> DecodeError
ReleaseWorkingBytes :: (budget: *WorkerBudget, bytes: s64) -> DecodeError

Source ↗

image-codec/src/benchmark.zi

benchmark

Import with #import "image_codec/benchmark".

MaxBenchmarkIterations :: 1024
TimingSummary :: struct { p50_ns: s64; p95_ns: s64; total_ns: s64; iterations: s32; error: DecodeError; }
SummarizeTimings :: (samples: []s64) -> TimingSummary

Nearest-rank percentiles. Sorts caller-owned sample storage in place.

FileBytes :: struct { data: Vec(u8); error: DecodeError; }
FileFailure :: (error: DecodeError) -> FileBytes
ReadBoundedFile :: (path: string, maximum: s64, token: *Cancellation) -> FileBytes
SamePixels :: (picture: *Image, expected: []u8) -> bool
ImageHash :: (picture: *Image) -> u64
HashText :: (hash: u64) -> string
BenchmarkResult :: struct { is_ok: bool; timing: TimingSummary; samples_ns: [1024]s64; output_hash: u64; input_bytes: s64; output_bytes: s64; pixel_count: s64; active_workers: s32; error: DecodeError; }
BenchmarkFile :: (path: string, expected: []u8, metadata: ImageMetadata, options: *DecodeOptions, provider: DecodeProcedure, iterations: s32) -> BenchmarkResult

Each sample opens/reads/closes input then calls the provider. Allocation of input/output is timed. Output validation, hash and destruction are untimed. An initial correctness decode MUST pass before the first timed sample.

BenchmarkJSON :: (result: *BenchmarkResult, provider_name: string, toolchain_version: string) -> string

JSON null represents unavailable measurement, never an invented zero.

Source ↗

Documents