All formats

gen2brain/avif

LanguageGo
LicenseMIT
Repositorygithub.com/gen2brain/avif
Documentationpkg.go.dev/github.com/gen2brain/avif
Depends onnothing at build time (libavif, dav1d and libaom are embedded as WebAssembly and run with wazero); uses a system libavif through purego when installed
Read / writeyes / yes, still and animated, alpha, Exif

gen2brain/avif wraps libavif. Decoding uses dav1d and encoding uses libaom. By default it looks for a shared libavif library on the system (loaded with purego, still no cgo) and falls back to its embedded WebAssembly build, so go build and cross-compiling work without a C toolchain. The WebAssembly path is slower than native libavif, and encoding AVIF is slow in any case.

Importing the package registers the avif format with the standard image package, so image.Decode and image.DecodeConfig recognize AVIF files.

Installation#

Shell
go get github.com/gen2brain/avif

Build tags: nodynamic always uses the embedded WebAssembly; wasm2go translates it to pure Go instead of running it with wazero.

Basic usage: convert PNG to AVIF and back#

Go
package main

import (
	"fmt"
	"image"
	"image/png"
	"log"
	"os"

	"github.com/gen2brain/avif"
)

func main() {
	in, err := os.Open("in.png")
	if err != nil {
		log.Fatal(err)
	}
	defer in.Close()
	img, err := png.Decode(in)
	if err != nil {
		log.Fatal(err)
	}

	out, err := os.Create("out.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	if err := avif.Encode(out, img, avif.Options{Quality: 60, Speed: 8}); err != nil {
		log.Fatal(err)
	}

	f, err := os.Open("out.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	decoded, format, err := image.Decode(f) // works because the avif package is imported
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(format, decoded.Bounds().Dx(), "x", decoded.Bounds().Dy())
}

How to get AVIF width and height without decoding#

Go
f, err := os.Open("photo.avif")
if err != nil {
	log.Fatal(err)
}
defer f.Close()
cfg, err := avif.DecodeConfig(f)
if err != nil {
	log.Fatal(err)
}
fmt.Println(cfg.Width, "x", cfg.Height)

How to set quality, speed and lossless mode#

Go
opts := avif.Options{
	Quality:           60,                        // 0..100, default 60
	QualityAlpha:      80,                        // 0..100, for the alpha plane
	Speed:             6,                         // 0 (slowest, smallest) .. 10 (fastest)
	ChromaSubsampling: image.YCbCrSubsampleRatio444, // 444, 422 or 420
}
err := avif.Encode(out, img, opts)

// lossless: ignores Quality and uses 4:4:4
err = avif.Encode(out, img, avif.Options{Lossless: true})

How to read frames of an animated AVIF#

Go
f, err := os.Open("anim.avif")
if err != nil {
	log.Fatal(err)
}
defer f.Close()
anim, err := avif.DecodeAll(f)
if err != nil {
	log.Fatal(err)
}
for i, frame := range anim.Image {
	fmt.Printf("frame %d: %v, %.3f s\n", i, frame.Bounds(), anim.Delay[i])
}

How to create an animated AVIF#

Delay is in seconds, one value per frame. LoopCount 0 repeats forever:

Go
anim := &avif.AVIF{
	Image: frames,                   // []image.Image, all the same size
	Delay: []float64{0.1, 0.1, 0.1}, // seconds
}
out, err := os.Create("anim.avif")
if err != nil {
	log.Fatal(err)
}
defer out.Close()
if err := avif.EncodeAll(out, anim, avif.Options{Quality: 50, Speed: 8}); err != nil {
	log.Fatal(err)
}

Tips#