gen2brain/avif
| Language | Go |
|---|---|
| License | MIT |
| Repository | github.com/gen2brain/avif |
| Documentation | pkg.go.dev/github.com/gen2brain/avif |
| Depends on | nothing at build time (libavif, dav1d and libaom are embedded as WebAssembly and run with wazero); uses a system libavif through purego when installed |
| Read / write | yes / 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#
avif.Dynamic()returns nil when a system libavif was loaded; use it to check which path is active.- Pass
avif.Options{AutoRotate: true}toDecodeorDecodeAllto apply the file’s rotation and mirroring (irot,imir). avif.DecodeExifreads common Exif fields (camera, date, GPS, orientation).