golibjpeg

package module
v1.3.0 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 6 Imported by: 0

README

golibjpeg

Go JPEG codec — baseline JPEG, JPEG-LS, JPEG XT decode; JPEG / JPEG-LS encode. No CGO dependency.

Overview

golibjpeg is a Go library for decoding and encoding JPEG images with native precision (8‑bit and 16‑bit). It bundles a platform-specific shared library extracted at runtime via FFI (ebitengine/purego), avoiding the need for CGO.

Supported formats:

  • JPEG (ISO 10918‑1, baseline / lossless)
  • JPEG‑LS (ISO 14495, lossless / near‑lossless)
  • JPEG XT (ISO 18477, HDR — decode only)

API

Aligned with pylibjpeg-libjpeg libjpeg.utils:

// Decode JPEG/JPEG-LS/JPEG XT (colour_transform matches Python default 0)
func DecodeImage(stream any, colourTransform ColourTransform) (*Image, error)

// Encode interleaved little-endian pixels to JPEG / JPEG-LS
func Encode(src []byte, opts EncodeOptions) ([]byte, error)

// DICOM encapsulated pixel data
func DecodePixelData(src []byte, opts PixelDataOptions) ([]byte, error)
func EncodePixelData(src []byte, desc PixelDataDescriptor, opts EncodePixelDataOptions) ([]byte, error)

// Read parameters without decoding
func GetImageParameters(stream any) (*Params, error)

// Shorthands
func Decode(data []byte) (*Image, error)
func GetParameters(data []byte) (*Params, error)

stream may be []byte, file path (string), or io.Reader.

ColourTransform constants: ColourTransformNone (0), ColourTransformYCbCr (1), ColourTransformRCT (2), ColourTransformFreeform (3).

No CGO: native code is loaded via purego + //go:embed prebuilt libraries.

How it works

  • Go wraps a C++ shared library via purego (no CGO).
  • The native library is embedded per platform with //go:embed and extracted to a temp directory on first use.
  • C++ decode logic follows pylibjpeg-libjpeg (lib/interface/ + thorfdbg/libjpeg).
  • Stripe‑based decoding processes 8 lines at a time, reducing memory pressure.
  • Output pixels are in native precision (8‑bit or 16‑bit), planar‑interleaved.

Project layout

golibjpeg.go          # public API
native/               # purego loader + embedded prebuilt libs
lib/
  libjpeg/            # submodule → thorfdbg/libjpeg
  interface/          # decode + streamhook (from pylibjpeg-libjpeg)
  capi/               # C ABI for purego
ref/pylibjpeg-libjpeg # read-only reference submodule
testdata/             # optional conformance JPEGs (see testdata/README.md)

Installation

go get github.com/godicom-dev/golibjpeg

Usage

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/godicom-dev/golibjpeg"
)

func main() {
	data, err := os.ReadFile("image.jpg")
	if err != nil {
		log.Fatal(err)
	}

	// Decode with auto-detection of format
	img, err := golibjpeg.Decode(data)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%dx%d, %d components, precision %d\n",
		img.Width, img.Height, img.Components, img.Precision)

	// img.Pixels is RGB bytes (or grayscale if source is grayscale)
	// Process pixels as needed...
	_ = img.Pixels
}

With explicit format:

import "github.com/godicom-dev/golibjpeg"

// Force JPEG-LS decoding
img, err := golibjpeg.DecodeWithFormat(data, golibjpeg.FormatJPEGLS)

Encode to JPEG baseline:

out, err := golibjpeg.Encode(pixels, golibjpeg.EncodeOptions{
	Columns: 512, Rows: 512, SamplesPerPixel: 3, BitsPerSample: 8,
	FrameType: golibjpeg.FrameBaseline, ColourTransform: golibjpeg.ColourTransformYCbCr,
	Quality: 90,
})

JPEG-LS lossless:

out, err := golibjpeg.Encode(frame, golibjpeg.EncodeOptions{
	Columns: 512, Rows: 512, SamplesPerPixel: 1, BitsPerSample: 16,
	FrameType: golibjpeg.FrameJPEGLS, LSInterleaving: golibjpeg.LSInterleaveSample,
})

Read image parameters without decoding pixels:

params, err := golibjpeg.GetParameters(data)
if err != nil {
	log.Fatal(err)
}
fmt.Printf("%dx%d, %d components, precision %d\n",
	params.Width, params.Height, params.Components, params.Precision)

Platform support

OS amd64 arm64
Windows
macOS
Linux

Anywhere else this module still builds — it just cannot decode or encode. Every function returns an error wrapping ErrUnsupportedPlatform instead, so a program that imports golibjpeg (or godicom, which does) keeps compiling and running on a platform with no prebuilt library, and only JPEG and JPEG-LS fail:

img, err := golibjpeg.Decode(data)
if errors.Is(err, golibjpeg.ErrUnsupportedPlatform) {
	// no library for this GOOS/GOARCH; err names which one
}

The cross-build CI job compiles the module for a spread of platforms outside the table — js/wasm and wasip1/wasm among them — so this stays true. Loading is lazy and never panics: a read-only or noexec TMPDIR also surfaces as an error from the first call.

What each platform costs your binary

The six libraries together are about 10 MB, but a binary only ever carries the one it can load — the //go:embed directives are behind per-platform build tags, so the other five are not compiled in:

$ GOOS=windows GOARCH=arm64 go build -o app .
$ GOOS=linux   GOARCH=386   go build -o app .   # off the matrix: nothing embedded
target embedded added to the binary
linux/amd64 golibjpeg_linux_amd64.so ~2.2 MB
linux/arm64 golibjpeg_linux_arm64.so ~2.1 MB
darwin/amd64 golibjpeg_darwin_amd64.dylib ~1.5 MB
darwin/arm64 golibjpeg_darwin_arm64.dylib ~1.4 MB
windows/amd64 golibjpeg_amd64.dll ~1.5 MB
windows/arm64 golibjpeg_arm64.dll ~1.4 MB
anything else nothing

The checks CI job asserts that set per platform with go list -f '{{.EmbedFiles}}', because a libs/* glob or a forgotten build tag would put all six into every binary and nothing else would notice.

go get does download all six, since they live in one module — that cost is paid once in the module cache, not per build and not per user binary.

Dependencies

Development

Native libraries in native/libs/ are not built locally by default. They are produced by GitHub Actions (build-libs.yml) when lib/** changes on main, then auto-committed to the repository.

git clone --recurse-submodules https://github.com/godicom-dev/golibjpeg.git
cd golibjpeg
go test ./...

To rebuild native libraries on CI without changing lib/:

gh workflow run build-libs.yml

Optional local native build (requires CMake):

make build-native
CI workflows

build.yml runs two jobs on their own — checks (gofmt, go vet, and the one-embedded-library-per-platform assertion) and cross-build (compile for 7 platforms with no prebuilt library) — plus this chain in order:

  1. build-native — build shared library on 6 platforms, upload artifacts
  2. commit-native — on push to main, write artifacts into native/libs/ and commit
  3. test — download artifact per platform, then go test
  4. release — on v* tags, attach libraries to GitHub Release

Committed files in native/libs/ let go get work without a local CMake install.

Reference tests (reference_compliance_test.go) mirror ref/pylibjpeg-libjpeg/libjpeg/tests/test_parameters.py and test_decode.py (REF_JPG table, 23 images). Fetch testdata before running:

bash scripts/fetch-testdata.sh
go test ./...
Release workflow
  1. Merge changes to main and wait for build workflow (build → commit native/libs/ → test).
  2. build workflow runs go test on all platforms using embedded libs.
  3. Create and push a tag: git tag v1.0.1 && git push origin v1.0.1.
  4. CI attaches the committed libraries from native/libs/ to a GitHub Release.

References

This Go port follows pylibjpeg-libjpeg for native decode behaviour and tests, and pylibjpeg for the overall plugin-style integration model used by pydicom.

Documentation

Index

Constants

View Source
const (
	PhotometricMonochrome1 = "MONOCHROME1"
	PhotometricMonochrome2 = "MONOCHROME2"
	PhotometricRGB         = "RGB"
	PhotometricYBRFull     = "YBR_FULL"
	PhotometricYBRFull422  = "YBR_FULL_422"
)

PhotometricInterpretation values supported by DecodePixelData (v1).

Variables

View Source
var ErrUnsupportedPlatform = native.ErrUnsupportedPlatform

ErrUnsupportedPlatform reports that this GOOS/GOARCH has no prebuilt native library, so JPEG and JPEG-LS data can be neither decoded nor encoded here. Every function in this package returns an error wrapping it rather than panicking, which keeps the module importable everywhere Go builds. Test for it with errors.Is. The README lists the platforms that do have a library.

Functions

func DecodePixelData added in v1.1.0

func DecodePixelData(src []byte, opts PixelDataOptions) ([]byte, error)

DecodePixelData decodes encapsulated JPEG pixel data for DICOM handlers. Version 1 applies a colour transform from PhotometricInterpretation. Version 2 returns raw decoded bytes with no colour transform.

func Encode added in v1.2.0

func Encode(src []byte, opts EncodeOptions) ([]byte, error)

Encode compresses interleaved little-endian pixel samples to JPEG / JPEG-LS.

func EncodePixelData added in v1.2.0

func EncodePixelData(src []byte, desc PixelDataDescriptor, opts EncodePixelDataOptions) ([]byte, error)

EncodePixelData encodes one DICOM frame for JPEG / JPEG-LS transfer syntaxes.

func ReadStream added in v1.1.0

func ReadStream(stream any) ([]byte, error)

ReadStream reads JPEG data from bytes, a file path, or an io.Reader. This mirrors pylibjpeg-libjpeg stream handling for decode() and get_parameters().

Types

type ColourTransform added in v1.1.0

type ColourTransform int

ColourTransform matches libjpeg JPGFLAG_MATRIX_COLORTRANSFORMATION_* values used by pylibjpeg-libjpeg decode().

const (
	ColourTransformNone     ColourTransform = 0
	ColourTransformYCbCr    ColourTransform = 1
	ColourTransformRCT      ColourTransform = 2
	ColourTransformFreeform ColourTransform = 3
)

type EncodeOptions added in v1.2.0

type EncodeOptions struct {
	Columns         int
	Rows            int
	SamplesPerPixel int
	BitsPerSample   int
	FrameType       FrameType
	ColourTransform ColourTransform
	// Quality applies to lossy JPEG (1–100). Ignored for lossless / JPEG-LS.
	Quality int
	// ErrorBound is the JPEG-LS NEAR parameter (0 = lossless).
	ErrorBound int
	// LSInterleaving applies when FrameType is FrameJPEGLS.
	LSInterleaving LSInterleaving
}

EncodeOptions configures Encode / EncodePixelData.

type EncodePixelDataOptions added in v1.2.0

type EncodePixelDataOptions struct {
	PhotometricInterpretation string
	FrameType                 FrameType
	Quality                   int
	ErrorBound                int
	LSInterleaving            LSInterleaving
}

EncodePixelDataOptions configures EncodePixelData for DICOM handlers.

type Format

type Format int

Format selects a colour transform for DecodeWithFormat. Deprecated: prefer ColourTransform with DecodeImage.

const (
	FormatAuto   Format = -1
	FormatJPEG   Format = 1
	FormatJPEGLS Format = 2
	FormatJPEGXT Format = 3
)

type FrameType added in v1.2.0

type FrameType int

FrameType selects the JPEG encoding process (libjpeg JPGFLAG_* frame types).

const (
	FrameBaseline   FrameType = native.FrameBaseline
	FrameSequential FrameType = native.FrameSequential
	FrameLossless   FrameType = native.FrameLossless
	FrameJPEGLS     FrameType = native.FrameJPEGLS
)

type Image

type Image struct {
	Pixels     []byte
	Width      int
	Height     int
	Components int
	Precision  int
}

Image holds decoded pixel data in native precision (8- or 16-bit), planar-interleaved.

func Decode

func Decode(data []byte) (*Image, error)

Decode decodes with no colour transform (pylibjpeg default colour_transform=0).

func DecodeImage added in v1.1.0

func DecodeImage(stream any, colourTransform ColourTransform) (*Image, error)

DecodeImage decodes JPEG/JPEG-LS/JPEG XT data from stream. colourTransform matches pylibjpeg-libjpeg decode(colour_transform=...).

func DecodeWithFormat

func DecodeWithFormat(data []byte, format Format) (*Image, error)

DecodeWithFormat decodes using a legacy Format colour-transform selector.

func (*Image) ByteAt added in v1.1.0

func (img *Image) ByteAt(y, x, c int) byte

ByteAt returns one byte from the interleaved pixel buffer.

func (*Image) BytesPerSample added in v1.1.0

func (img *Image) BytesPerSample() int

func (*Image) CornerSamples added in v1.1.0

func (img *Image) CornerSamples() (topLeft, bottomRight []int)

CornerSamples returns top-left and bottom-right samples as integers. For multi-byte precision each component is truncated to the low byte for 8-bit cases.

func (*Image) Uint16At added in v1.1.0

func (img *Image) Uint16At(y, x, c int) uint16

Uint16At returns a little-endian sample when precision > 8.

type LSInterleaving added in v1.2.0

type LSInterleaving int

LSInterleaving configures JPEG-LS component interleaving.

const (
	LSInterleaveNone   LSInterleaving = native.LSInterleaveNone
	LSInterleaveLine   LSInterleaving = native.LSInterleaveLine
	LSInterleaveSample LSInterleaving = native.LSInterleaveSample
)

type Params

type Params struct {
	Width      int
	Height     int
	Components int
	Precision  int
}

Params holds JPEG image parameters without decoding pixels.

func GetImageParameters added in v1.1.0

func GetImageParameters(stream any) (*Params, error)

GetImageParameters reads JPEG parameters without decoding pixels.

func GetParameters

func GetParameters(data []byte) (*Params, error)

GetParameters is an alias for GetImageParameters using a byte buffer.

func (*Params) Columns added in v1.1.0

func (p *Params) Columns() int

func (*Params) NrComponents added in v1.1.0

func (p *Params) NrComponents() int

func (*Params) Rows added in v1.1.0

func (p *Params) Rows() int

type PixelDataDescriptor added in v1.2.0

type PixelDataDescriptor struct {
	Columns         int
	Rows            int
	SamplesPerPixel int
	BitsAllocated   int
	BitsStored      int
}

PixelDataDescriptor holds image geometry for EncodePixelData.

type PixelDataOptions added in v1.1.0

type PixelDataOptions struct {
	Version                   PixelDataVersion
	PhotometricInterpretation string
}

PixelDataOptions configures DecodePixelData (pylibjpeg decode_pixel_data).

type PixelDataVersion added in v1.1.0

type PixelDataVersion int

PixelDataVersion selects decode_pixel_data behaviour.

const (
	PixelDataV1 PixelDataVersion = 1
	PixelDataV2 PixelDataVersion = 2
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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