goavif

package module
v0.0.0-...-d9bd8f1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 20, 2026 License: MIT Imports: 12 Imported by: 0

README

goavif

Documentation

Overview

Package goavif provides AVIF image decoding via CGO bindings to libavif.

All CGO and unsafe code is isolated in this file.

Package goavif provides AVIF image decoding via CGO bindings to libavif.

This package implements the standard Go image decoding interface, making it a drop-in replacement for image/jpeg, image/png, and similar packages. The API design mirrors image/gif for consistency with animated image handling.

Basic Usage

For single images or the first frame of an animation:

img, err := goavif.Decode(reader)
if err != nil {
    log.Fatal(err)
}
// img is *image.NRGBA (8-bit) or *image.NRGBA64 (10/12-bit)

For metadata without decoding pixels (faster):

cfg, err := goavif.DecodeConfig(reader)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Size: %dx%d, Depth: %d-bit\n", cfg.Width, cfg.Height, cfg.BitDepth)

For animated AVIF files:

avif, err := goavif.DecodeAll(reader)
if err != nil {
    log.Fatal(err)
}
for i, frame := range avif.Image {
    fmt.Printf("Frame %d: delay %dms\n", i, avif.Delay[i])
}

Auto-Detection

Import the package with a blank identifier to enable automatic AVIF detection:

import _ "github.com/julez-dev/goavif"

// Now image.Decode will auto-detect AVIF files
img, format, err := image.Decode(reader)
if format == "avif" {
    // Handle AVIF-specific features
}

Image Types

The package returns different Go image types based on bit depth:

  • 8-bit AVIF: *image.NRGBA (non-premultiplied alpha)
  • 10/12-bit AVIF: *image.NRGBA64 (scaled to 16-bit range)

For HDR images (10/12-bit), values are scaled:

  • 10-bit: value * 65535 / 1023
  • 12-bit: value * 65535 / 4095

Metadata

The Config struct provides rich metadata access:

  • BitDepth: Color depth per channel (8, 10, or 12)
  • HasAlpha: Whether alpha channel is present
  • ImageCount: Number of frames (1 for still images)
  • ICC: Raw ICC color profile bytes (nil if absent)
  • XMP: Raw XMP metadata bytes (nil if absent)
  • EXIF: Parsed EXIF data including camera info and GPS
  • ColorPrimaries, TransferCharacteristics, MatrixCoefficients: CICP values

Error Handling

Errors are wrapped with context and support errors.Is() for type checking:

img, err := goavif.Decode(reader)
if errors.Is(err, goavif.ErrInvalidData) {
    // Not a valid AVIF file
}
if errors.Is(err, goavif.ErrTruncatedData) {
    // Incomplete file
}

For detailed error information:

var decErr *goavif.DecodeError
if errors.As(err, &decErr) {
    fmt.Printf("Operation: %s, Code: %d, Message: %s\n",
        decErr.Op, decErr.Code, decErr.Message)
}

Build Requirements

This package requires libavif to be installed on the system:

# Arch Linux
pacman -S libavif

# Ubuntu/Debian
apt install libavif-dev

# macOS
brew install libavif

# Verify installation
pkg-config --modversion libavif

The package uses pkg-config to locate libavif, so ensure pkg-config is available and the libavif.pc file is in the PKG_CONFIG_PATH.

Thread Safety

Each decode operation creates an independent decoder instance, so concurrent calls to Decode, DecodeAll, and DecodeConfig are safe. The underlying libavif library handles multi-threaded decoding automatically based on runtime.NumCPU().

Limitations

This package is decode-only; encoding is not supported. The entire input is read into memory before decoding (no streaming). Progressive/layered decoding is not supported.

Example (AutoDetection)
package main

import (
	"fmt"
	"image"
	"log"
	"os"
)

func main() {
	// With the blank import, image.Decode auto-detects AVIF
	// import _ "github.com/julez-dev/goavif"

	f, err := os.Open("testdata/simple.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// Standard library image.Decode works with AVIF
	img, format, err := image.Decode(f)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Format: %s\n", format)
	fmt.Printf("Bounds: %v\n", img.Bounds())
}
Output:
Format: avif
Bounds: (0,0)-(4,4)
Example (Decode)
package main

import (
	"fmt"
	"image"
	"log"
	"os"

	"github.com/julez-dev/goavif"
)

func main() {
	// Open an AVIF file
	f, err := os.Open("testdata/simple.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// Decode the image
	img, err := goavif.Decode(f)
	if err != nil {
		log.Fatal(err)
	}

	// The image is ready to use
	bounds := img.Bounds()
	fmt.Printf("Decoded %dx%d image\n", bounds.Dx(), bounds.Dy())

	// Check the concrete type for bit depth
	switch img.(type) {
	case *image.NRGBA:
		fmt.Println("8-bit image")
	case *image.NRGBA64:
		fmt.Println("HDR image (10/12-bit)")
	}
}
Output:
Decoded 4x4 image
8-bit image
Example (DecodeAll)
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/julez-dev/goavif"
)

func main() {
	// Open an animated AVIF file
	f, err := os.Open("testdata/animated.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// Decode all frames
	avif, err := goavif.DecodeAll(f)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Total frames: %d\n", len(avif.Image))
	fmt.Printf("Loop count: %d (0=infinite)\n", avif.LoopCount)

	// Process each frame
	for i := range avif.Image {
		fmt.Printf("Frame %d: delay %dms\n", i, avif.Delay[i])
	}
}
Output:
Total frames: 3
Loop count: 0 (0=infinite)
Frame 0: delay 100ms
Frame 1: delay 100ms
Frame 2: delay 100ms
Example (DecodeConfig)
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/julez-dev/goavif"
)

func main() {
	// Open an AVIF file
	f, err := os.Open("testdata/animated.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	// Get metadata without decoding pixels (faster)
	cfg, err := goavif.DecodeConfig(f)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Size: %dx%d\n", cfg.Width, cfg.Height)
	fmt.Printf("Bit depth: %d\n", cfg.BitDepth)
	fmt.Printf("Has alpha: %v\n", cfg.HasAlpha)
	fmt.Printf("Frame count: %d\n", cfg.ImageCount)
}
Output:
Size: 4x4
Bit depth: 8
Has alpha: false
Frame count: 3
Example (ErrorHandling)
package main

import (
	"bytes"
	"errors"
	"fmt"

	"github.com/julez-dev/goavif"
)

func main() {
	// Try to decode invalid data
	invalidData := []byte("not a valid AVIF file")
	_, err := goavif.Decode(bytes.NewReader(invalidData))

	// Check for specific error types using errors.Is
	if errors.Is(err, goavif.ErrInvalidData) {
		fmt.Println("Error: invalid AVIF data")
	}

	// Get detailed error info using errors.As
	var decErr *goavif.DecodeError
	if errors.As(err, &decErr) {
		fmt.Printf("Operation: %s\n", decErr.Op)
		fmt.Printf("Code: %d\n", decErr.Code)
	}
}
Output:
Error: invalid AVIF data
Operation: parse
Code: 9
Example (Metadata)
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/julez-dev/goavif"
)

func main() {
	f, err := os.Open("testdata/with_exif.avif")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	cfg, err := goavif.DecodeConfig(f)
	if err != nil {
		log.Fatal(err)
	}

	// Check for EXIF data
	if cfg.EXIF != nil {
		fmt.Printf("Camera: %s %s\n", cfg.EXIF.Make, cfg.EXIF.Model)
		fmt.Printf("Orientation: %d\n", cfg.EXIF.Orientation)
		if cfg.EXIF.ISOSpeed > 0 {
			fmt.Printf("ISO: %d\n", cfg.EXIF.ISOSpeed)
		}
	}

	// Check for ICC profile
	if cfg.ICC != nil {
		fmt.Printf("ICC profile: %d bytes\n", len(cfg.ICC))
	} else {
		fmt.Println("No ICC profile")
	}
}
Output:
Camera: TestCam Model X100
Orientation: 6
ISO: 400
No ICC profile

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidData   = errors.New("avif: invalid data")
	ErrTruncatedData = errors.New("avif: truncated data")
	ErrNoContent     = errors.New("avif: no content")
	ErrNoCodec       = errors.New("avif: no codec available")
	ErrUnsupported   = errors.New("avif: unsupported format")
	ErrOutOfMemory   = errors.New("avif: out of memory")
	ErrIO            = errors.New("avif: I/O error")
	ErrInternalError = errors.New("avif: internal error")
)

Sentinel errors for common decode failures.

Functions

func Decode

func Decode(r io.Reader) (image.Image, error)

Decode reads an AVIF image from r and returns it as an image.Image.

For 8-bit images, the returned image is *image.NRGBA. For 10/12-bit images, the returned image is *image.NRGBA64 with values scaled to the 16-bit range.

For animated AVIFs, only the first frame is decoded. Use DecodeAll to decode all frames.

func Version

func Version() string

Version returns the libavif version string (e.g., "1.3.0").

Types

type AVIF

type AVIF struct {
	// Image contains all frames of the animation.
	// For still images, this contains a single frame.
	Image []image.Image

	// Delay contains the delay for each frame in milliseconds.
	// Delay[i] is the delay after displaying Image[i].
	// For still images, this may be empty or contain a single zero value.
	Delay []int

	// LoopCount controls the number of times the animation loops.
	//   0: loop forever
	//  -1: play once (no loop)
	//   n: loop n+1 times (play n+1 times total)
	LoopCount int

	// Config contains image metadata from the first frame.
	Config Config
}

AVIF represents an animated AVIF image. It mirrors the structure of image/gif.GIF for familiarity.

func DecodeAll

func DecodeAll(r io.Reader) (*AVIF, error)

DecodeAll reads an animated AVIF from r and returns all frames.

For still images, the returned AVIF contains a single frame. The Delay slice contains the duration in milliseconds for each frame. LoopCount follows gif.GIF semantics: 0=infinite, -1=once, n=play n+1 times.

type Config

type Config struct {
	image.Config

	// BitDepth is the bit depth per channel (8, 10, or 12).
	BitDepth int

	// HasAlpha indicates whether the image has an alpha channel.
	HasAlpha bool

	// ImageCount is the number of images/frames in the AVIF.
	// For still images this is 1; for animations it's the frame count.
	ImageCount int

	// Duration is the total duration of an animated AVIF.
	// For still images this is 0.
	Duration time.Duration

	// ICC contains the raw ICC color profile bytes.
	// Nil if no ICC profile is embedded.
	ICC []byte

	// XMP contains the raw XMP metadata bytes.
	// Nil if no XMP metadata is embedded.
	XMP []byte

	// ColorPrimaries indicates the color primaries (e.g., BT.709, BT.2020).
	// Values match CICP (Coding-Independent Code Points).
	ColorPrimaries uint16

	// TransferCharacteristics indicates the transfer function (e.g., sRGB, PQ, HLG).
	// Values match CICP.
	TransferCharacteristics uint16

	// MatrixCoefficients indicates the matrix coefficients for YUV conversion.
	// Values match CICP.
	MatrixCoefficients uint16

	// EXIF contains parsed EXIF metadata.
	// Nil if no EXIF data is embedded or parsing failed.
	EXIF *EXIF
}

Config contains AVIF image metadata. It embeds image.Config for basic dimensions and color model.

func DecodeConfig

func DecodeConfig(r io.Reader) (Config, error)

DecodeConfig reads the AVIF image configuration from r without decoding the actual pixel data. This is faster than Decode when you only need image metadata.

type DecodeError

type DecodeError struct {
	Code    ResultCode // libavif result code
	Message string     // human-readable message from libavif
	Op      string     // operation that failed (e.g., "parse", "decode")
}

DecodeError wraps libavif errors with context.

func (*DecodeError) Error

func (e *DecodeError) Error() string

func (*DecodeError) Is

func (e *DecodeError) Is(target error) bool

Is implements errors.Is support for sentinel error matching.

func (*DecodeError) Unwrap

func (e *DecodeError) Unwrap() error

Unwrap returns the underlying sentinel error if applicable.

type EXIF

type EXIF struct {
	// Make is the camera manufacturer (EXIF tag 0x010F).
	Make string

	// Model is the camera model (EXIF tag 0x0110).
	Model string

	// Orientation indicates how the image should be rotated/flipped
	// for correct display (EXIF tag 0x0112).
	// Values: 1=normal, 2=flip-h, 3=rotate-180, 4=flip-v,
	// 5=transpose, 6=rotate-90-cw, 7=transverse, 8=rotate-270-cw.
	Orientation int

	// DateTime is when the image was created (EXIF tag 0x0132 or 0x9003).
	// Zero value if not present or unparseable.
	DateTime time.Time

	// ExposureTime is the exposure duration in seconds (EXIF tag 0x829A).
	// Typically a fraction like 1/125. Nil if not present.
	ExposureTime *big.Rat

	// FNumber is the f-stop value (EXIF tag 0x829D).
	// For f/2.8, this would be 2.8. Nil if not present.
	FNumber *big.Rat

	// FocalLength is the lens focal length in millimeters (EXIF tag 0x920A).
	// Nil if not present.
	FocalLength *big.Rat

	// ISOSpeed is the ISO sensitivity (EXIF tag 0x8827).
	// 0 if not present.
	ISOSpeed int

	// GPSLatitude is the latitude in decimal degrees (positive=N, negative=S).
	// Derived from EXIF GPS tags 0x0001-0x0002.
	// 0 if not present (use GPSValid to check).
	GPSLatitude float64

	// GPSLongitude is the longitude in decimal degrees (positive=E, negative=W).
	// Derived from EXIF GPS tags 0x0003-0x0004.
	// 0 if not present (use GPSValid to check).
	GPSLongitude float64

	// GPSValid indicates whether GPS coordinates are present.
	// Check this before using GPSLatitude/GPSLongitude since 0,0 is valid.
	GPSValid bool
}

EXIF contains parsed EXIF metadata from the image. Fields are nil/zero when not present in the source data.

type ResultCode

type ResultCode int

ResultCode represents libavif result codes.

const (
	ResultOK                          ResultCode = 0
	ResultUnknownError                ResultCode = 1
	ResultInvalidFtyp                 ResultCode = 2
	ResultNoContent                   ResultCode = 3
	ResultNoYUVFormatSelected         ResultCode = 4
	ResultReformatFailed              ResultCode = 5
	ResultUnsupportedDepth            ResultCode = 6
	ResultEncodeColorFailed           ResultCode = 7
	ResultEncodeAlphaFailed           ResultCode = 8
	ResultBMFFParseFailed             ResultCode = 9
	ResultMissingImageItem            ResultCode = 10
	ResultDecodeColorFailed           ResultCode = 11
	ResultDecodeAlphaFailed           ResultCode = 12
	ResultColorAlphaSizeMismatch      ResultCode = 13
	ResultISPESizeMismatch            ResultCode = 14
	ResultNoCodecAvailable            ResultCode = 15
	ResultNoImagesRemaining           ResultCode = 16
	ResultInvalidExifPayload          ResultCode = 17
	ResultInvalidImageGrid            ResultCode = 18
	ResultInvalidCodecSpecificOption  ResultCode = 19
	ResultTruncatedData               ResultCode = 20
	ResultIONotSet                    ResultCode = 21
	ResultIOError                     ResultCode = 22
	ResultWaitingOnIO                 ResultCode = 23
	ResultInvalidArgument             ResultCode = 24
	ResultNotImplemented              ResultCode = 25
	ResultOutOfMemory                 ResultCode = 26
	ResultCannotChangeSetting         ResultCode = 27
	ResultIncompatibleImage           ResultCode = 28
	ResultInternalError               ResultCode = 29
	ResultEncodeGainMapFailed         ResultCode = 30
	ResultDecodeGainMapFailed         ResultCode = 31
	ResultInvalidToneMappedImage      ResultCode = 32
	ResultEncodeSampleTransformFailed ResultCode = 33
	ResultDecodeSampleTransformFailed ResultCode = 34
)

libavif result codes.

Directories

Path Synopsis
cmd
avif2gif command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL