All formats

gen2brain/jpegxl

LanguageGo
LicenseMIT (libjxl inside is BSD-3-Clause)
Repositorygithub.com/gen2brain/jpegxl
Documentationpkg.go.dev/github.com/gen2brain/jpegxl
Depends onwazero (pure Go WebAssembly runtime); optionally a system libjxl
Read / writeyes / yes (still images; animations are read only)

The package embeds libjxl compiled to WebAssembly and runs it with wazero, so it needs no C compiler and cross-compiles like pure Go code. At startup it first tries to load a system libjxl shared library through purego, which is faster, and falls back to the embedded WebAssembly.

It follows the conventions of the standard image packages: Decode, DecodeConfig, Encode, and it registers the jxl format with image.RegisterFormat. Decoded images are *image.NRGBA or *image.NRGBA64.

Installation#

Shell
go get github.com/gen2brain/jpegxl

Build tags: nodynamic always uses the embedded WebAssembly, wasm2go transpiles the WebAssembly to Go instead of using wazero.

Basic usage: convert JXL to PNG#

Go
package main

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

	"github.com/gen2brain/jpegxl"
)

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

	img, err := jpegxl.Decode(in)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("%d x %d", img.Bounds().Dx(), img.Bounds().Dy())

	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)
	}
}

How to convert PNG or JPEG to JPEG XL#

Go
package main

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

	"github.com/gen2brain/jpegxl"
)

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

	out, err := os.Create("photo.jxl")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	// Quality 0-100 (default 75), Effort 1-10 (default 7); Lossless ignores Quality
	if err := jpegxl.Encode(out, img, jpegxl.Options{Quality: 90, Effort: 7}); err != nil {
		log.Fatal(err)
	}
}

The JPEG input is decoded to pixels and re-encoded; this package doesn’t do lossless JPEG recompression.

How to save a lossless JPEG XL#

Go
err := jpegxl.Encode(out, img, jpegxl.Options{Lossless: true})

How to read JXL with image.Decode#

Importing the package registers the format, so code that accepts any image works with JXL too:

Go
import (
	"image"
	_ "github.com/gen2brain/jpegxl"
)

img, format, err := image.Decode(f) // format == "jxl"

How to get width and height without decoding#

Go
cfg, err := jpegxl.DecodeConfig(f)
if err == nil {
	fmt.Println(cfg.Width, cfg.Height)
}

How to read the frames of an animated JXL#

Go
anim, err := jpegxl.DecodeAll(f)
if err != nil {
	log.Fatal(err)
}
for i, frame := range anim.Image {
	fmt.Println(i, frame.Bounds(), anim.Delay[i])
}

How to read Exif metadata#

Go
exif, err := jpegxl.DecodeExif(f)
if err == nil {
	fmt.Println(exif.Make, exif.Model, exif.DateTimeOriginal, exif.Orientation)
} else if errors.Is(err, jpegxl.ErrNoExif) {
	fmt.Println("no Exif")
}

Tips#