gen2brain/jpegxl
| Language | Go |
|---|---|
| License | MIT (libjxl inside is BSD-3-Clause) |
| Repository | github.com/gen2brain/jpegxl |
| Documentation | pkg.go.dev/github.com/gen2brain/jpegxl |
| Depends on | wazero (pure Go WebAssembly runtime); optionally a system libjxl |
| Read / write | yes / 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#
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#
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#
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#
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:
import (
"image"
_ "github.com/gen2brain/jpegxl"
)
img, format, err := image.Decode(f) // format == "jxl"
How to get width and height without decoding#
cfg, err := jpegxl.DecodeConfig(f)
if err == nil {
fmt.Println(cfg.Width, cfg.Height)
}
How to read the frames of an animated JXL#
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#
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#
- Call
jpegxl.Dynamic()to check whether the system libjxl was loaded; it returns the load error, or nil. - The first call compiles the WebAssembly module, which takes a moment. Call
jpegxl.InitDecoder()orjpegxl.InitEncoder()at startup to do it ahead of time.