Skip to content

format: AVIF is the twenty third format, and its encoder is somebody else's - #23

Merged
donislawdev merged 3 commits into
mainfrom
format-avif
Aug 30, 2026
Merged

format: AVIF is the twenty third format, and its encoder is somebody else's#23
donislawdev merged 3 commits into
mainfrom
format-avif

Conversation

@donislawdev

Copy link
Copy Markdown
Owner

AVIF joins the list, and it is the first format here whose pixels are coded by
somebody else's encoder rather than by code in this repository.

tfg generate --format avif --size 300kb
tfg generate --format avif --size 2mb --set width=1920 --set height=1080

What it does

One frame, 8 bit, 4:2:0, in an ISO base media container. width, height and
quality can be set, and the picture goes up to 40 megapixels - the same
ceiling PNG, JPG and GIF use - so Full HD and 4K are both in reach. Left alone,
the picture is the largest of a fixed set that fits the size asked for, up to
640x480, which is what JPG does.

Every size from its minimum of 311 B upwards is reachable, with no gaps. The
padding travels in a free box, the box the container sets aside for space that
means nothing, and it takes any length at all.

Why a borrowed encoder

AV1 is too large to write by hand for one format. The coefficient tables alone
in the nearest implementation are fourteen times the size of this project's
entire WebP encoder, and the whole of it is half again the size of this whole
program. The encoder is github.com/gen2brain/gav1d, pinned, written in Go,
with an empty go.mod of its own - so it brings nothing else along, needs no C
compiler, and links no socket.

Raising it moves bytes, which puts it in the same class as the Go compiler this
project already pins.

Three things measurement changed

The library this started with reached the network. gen2brain/avif runs its
codec as WebAssembly through wazero, and wazero imports net for WASI
sockets - so the command line binary linked a socket for the first time. That
binary not linking net is a promise this project keeps and checks, so the road
was abandoned after being built and measured. gav1d costs 2.30 MB against
6.02 MB
, encodes five times faster, allocates twenty six times less, and
reaches nothing.

The encoder's AVX2 path reads past the end of a buffer. A 640x256 picture
kills the process - in two runs out of three, so it turns on what the heap looks
like rather than on the input alone. One size out of 240 crashed with the
assembly and none without it, and the bytes are identical either way. The tag
that removes it lives in .github/build-tags, and every command that compiles
this project reads that file rather than carrying a copy. Probe:
tools/probes/avifasm.

Planning coded the picture to learn its size, so previewing a thousand files
cost 51 seconds where BMP costs a fifth of one, and twenty thousand never
finished inside the test timeout. Each step of the size ladder now carries a
measured ceiling, planning is arithmetic again, and the encode happens while
writing. A thousand files now preview in 0.208 s.

Evidence

  • Byte stability on three systems and two architectures: Windows, Linux
    (WSL) and macOS on arm64 produce the same five files, byte for byte. Pinned in
    generator-golden.json as well.
  • Readers, with a negative control: Pillow reads every padded file and
    refuses a truncated one and a corrupted payload. ffprobe accepts all three
    broken files, so it witnesses nothing - the evidence rests on Pillow and on a
    structural checker that walks the box tree, which catches five deliberate
    breaks out of five.
  • Opened by a person: ImageGlass shows it at 320x240, 300 KB, with the label
    readable.
  • Seven new guards, each proven by a mutation. Six stale mutation entries
    repaired along the way, three of them older than this branch.

Binaries: command line 8 010 752 -> 10 417 664 B, window 52 041 709 B.

Full record, including the roads turned down and what they cost:
docs/STACK.md section 4.2.1.

🤖 Generated with Claude Code

…else's

The first format here whose pixels are coded by a library rather than by code
in this repository. AV1 is too large to write by hand for one format - the
coefficient tables alone in the nearest implementation are fourteen times the
size of this project's whole WebP encoder - so the encoder is
github.com/gen2brain/gav1d, pinned, pure Go, with an empty go.mod of its own.

Every size from the minimum of 311 B upwards is reachable with no gaps. The
padding travels in a free box, which is what the container sets aside for space
that means nothing, and it takes any length at all. Measured against Pillow on
files this encoder produces, with a negative control: Pillow refuses a
truncated file and a corrupted payload, while ffprobe accepts both and
therefore witnesses nothing.

The picture goes up to 40 megapixels, the same ceiling PNG, JPG and GIF use, so
Full HD and 4K are both in reach. Left alone it is the largest of a fixed set
that fits the size asked for, up to 640x480, which is what JPG does.

Three things were found by measuring rather than by reading, and each is
guarded now:

- The library this started with reached the network. gen2brain/avif runs its
  codec as WebAssembly through wazero, and wazero imports net for WASI sockets,
  so the command line binary linked a socket for the first time. That binary
  not linking net is a promise this project keeps and checks, so the road was
  abandoned after being built. gav1d costs 2.30 MB against 6.02 MB and reaches
  nothing.

- The encoder's AVX2 path reads past the end of a buffer. A 640x256 picture
  kills the process, in two runs out of three, so it turns on what the heap
  looks like. One size out of 240 crashed with the assembly and none without
  it, and the bytes are identical either way. The tag that removes it lives in
  .github/build-tags and every command that compiles this project reads that
  file rather than carrying a copy.

- Planning coded the picture to learn its size, so previewing a thousand files
  cost 51 seconds where BMP costs a fifth of one. Each step of the size ladder
  now carries a measured ceiling, planning is arithmetic again, and the encode
  happens while writing.

Byte stability is measured on three systems and two architectures rather than
on one: Windows, Linux and macOS on arm64 produce the same five files, byte for
byte.
The dependency gate asks two questions - whether the module graph moved, and
what cmd/tfg actually links - and the second one still named the two modules
this binary carried before AVIF.

The half that reached the runner rather than the local gate is the interesting
part: preflight was only asking the first question, so it said 66 modules
unchanged while CI failed on a list nobody local was reading. It asks both now,
and takes the second list from this file rather than carrying a copy.
The runner installs the distribution's Pillow, which cannot open an AVIF at
all - PIL.UnidentifiedImageError on a file the structural checker had just
called sound, while every other picture format passed. So the job was failing
on the file for a reason that had nothing to do with the file.

The newest Pillow is now installed for the same interpreter the oracle reaches
for, and then ASKED whether it can read one. No version is named: the question
is what this Pillow can do, not when the support landed. A Pillow that cannot
would make this job green while proving nothing about AVIF, which is the exact
failure the job exists to prevent.

The package comment for avif also stopped being true when the encode moved out
of planning, and says what the code does now.
@donislawdev
donislawdev merged commit 2392c40 into main Aug 30, 2026
18 checks passed
@donislawdev
donislawdev deleted the format-avif branch August 30, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant