All formats

libheif Go binding

LanguageGo (cgo)
LicenseLGPL-3.0 (part of libheif)
Repositorygithub.com/strukturag/libheif/go/heif
Documentationpkg.go.dev/github.com/strukturag/libheif/go/heif
Depends onlibheif (with libde265 to decode, x265 to encode), a C compiler
Read / writeyes / yes, HEIC and AVIF

The heif package is the official Go binding, maintained in the libheif repository. It wraps contexts, image handles, decoding and encoding, thumbnails and depth images, and converts decoded images to standard Go image.Image values. Importing it registers the heif and avif formats with image.Decode.

It needs cgo and the libheif development files at build time, and the libheif shared library at run time, which makes cross-compiling harder. If you only decode and want to avoid cgo, see gen2brain/heic.

Installation#

Shell
sudo apt install libheif-dev libheif-plugin-libde265 libheif-plugin-x265   # Debian / Ubuntu
brew install libheif                                                       # macOS
go get github.com/strukturag/libheif/go/heif

Basic usage: convert HEIC to PNG#

Go
package main

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

	_ "github.com/strukturag/libheif/go/heif" // registers HEIF and AVIF with image.Decode
)

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

	img, format, err := image.Decode(in)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%s %v", format, img.Bounds())

	out, err := os.Create("out.png")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	if err := png.Encode(out, img); err != nil {
		log.Fatal(err)
	}
}

image.Decode always decodes the primary image.

How to use the context API#

The lower-level API gives access to all images, sizes before decoding, and decoding options:

Go
import "github.com/strukturag/libheif/go/heif"

ctx, err := heif.NewContext()
if err != nil {
	log.Fatal(err)
}
if err := ctx.ReadFromFile("in.heic"); err != nil {
	log.Fatal(err)
}
handle, err := ctx.GetPrimaryImageHandle()
if err != nil {
	log.Fatal(err)
}
fmt.Println(handle.GetWidth(), handle.GetHeight(), handle.HasAlphaChannel())

img, err := handle.DecodeImage(heif.ColorspaceUndefined, heif.ChromaUndefined, nil)
if err != nil {
	log.Fatal(err)
}
goImg, err := img.GetImage() // image.Image

ColorspaceUndefined and ChromaUndefined keep the native format (usually YCbCr); pass heif.ColorspaceRGB, heif.ChromaInterleavedRGBA to get RGBA.

How to encode an image to HEIC#

EncodeFromImage accepts *image.RGBA, *image.RGBA64, *image.Gray and *image.YCbCr. Convert other types first:

Go
import (
	"image"
	"image/draw"

	"github.com/strukturag/libheif/go/heif"
)

func saveHEIC(path string, src image.Image, quality int) error {
	rgba := image.NewRGBA(src.Bounds())
	draw.Draw(rgba, rgba.Bounds(), src, src.Bounds().Min, draw.Src)

	ctx, err := heif.EncodeFromImage(rgba, heif.CompressionHEVC, quality,
		heif.LosslessModeDisabled, heif.LoggingLevelNone)
	if err != nil {
		return err // e.g. no HEVC encoder in this libheif build
	}
	return ctx.WriteToFile(path)
}

Use heif.CompressionAV1 to write AVIF instead.

How to get the thumbnail#

Go
for _, id := range handle.GetListOfThumbnailIDs() {
	thumb, err := handle.GetThumbnail(id)
	if err != nil {
		continue
	}
	fmt.Println("thumbnail", thumb.GetWidth(), thumb.GetHeight())
}

How to get the depth map#

Go
if handle.HasDepthImage() {
	for _, id := range handle.GetListOfDepthImageIDs() {
		depth, err := handle.GetDepthImageHandle(id)
		if err != nil {
			continue
		}
		img, err := depth.DecodeImage(heif.ColorspaceUndefined, heif.ChromaUndefined, nil)
		// ...
		_ = img
	}
}

Tips#