libheif Go binding
| Language | Go (cgo) |
|---|---|
| License | LGPL-3.0 (part of libheif) |
| Repository | github.com/strukturag/libheif/go/heif |
| Documentation | pkg.go.dev/github.com/strukturag/libheif/go/heif |
| Depends on | libheif (with libde265 to decode, x265 to encode), a C compiler |
| Read / write | yes / 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#
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#
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:
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:
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#
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#
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#
ctx.ReadFromMemory(data)reads from a byte slice;ctx.GetListOfTopLevelImageIDs()andctx.GetImageHandle(id)give access to every image in a burst or collection.- Objects are freed by finalizers; there is no
Closemethod.