goopenjpeg

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: BSD-2-Clause, MIT Imports: 6 Imported by: 0

README

goopenjpeg

Go JPEG 2000 decoder — no CGO for callers (purego + embedded native library).

Aligned with pylibjpeg-openjpeg for DICOM transfer syntaxes:

UID Description
1.2.840.10008.1.2.4.90 JPEG 2000 Lossless Only
1.2.840.10008.1.2.4.91 JPEG 2000
1.2.840.10008.1.2.4.201–203 HTJ2K

Status

Phase 2 (current): decode + encode API.

  • Done: DecodeImage, GetImageParameters, DecodePixelData, Encode / EncodePixelData, purego loader
  • Encode: JPEG 2000 lossless (default) and lossy via compression ratios; HTJ2K (.201–.203) via OpenJPH; J2K / JP2 containers

Installation

go get github.com/godicom-dev/goopenjpeg

Prebuilt OpenJPEG libraries are embedded per platform in native/libs/ — no CMake required for go get users.

Usage

Decode a JPEG 2000 codestream

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

package main

import (
	"fmt"
	"log"
	"os"

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

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

	// Shorthand for J2K codestream (0xff 0x4f 0xff 0x51 …)
	img, err := goopenjpeg.Decode(data)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%dx%d, %d components, precision %d signed=%v\n",
		img.Width, img.Height, img.Components, img.Precision, img.IsSigned)

	// Pixels are planar-interleaved (RGB: R,G,B per pixel), native precision.
	_ = img.Pixels
}

JP2 file or explicit codec:

img, err := goopenjpeg.DecodeImage("image.jp2", goopenjpeg.CodecJP2)

Codec values: CodecJ2K (0), CodecJPT (1), CodecJP2 (2).

Read parameters without decoding pixels
params, err := goopenjpeg.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)
DICOM encapsulated frame

For a single JPEG 2000 frame from DICOM Pixel Data (one item in the encapsulated sequence):

var j2kFrame []byte // one frame from (7FE0,0010)

// Version 2: raw decoded bytes (no extra colour handling)
raw, err := goopenjpeg.DecodePixelData(j2kFrame, goopenjpeg.PixelDataOptions{
	Version: goopenjpeg.PixelDataV2,
	Codec:   goopenjpeg.CodecJ2K,
})

// Version 1: same decode path; PhotometricInterpretation required for API parity
_, err = goopenjpeg.DecodePixelData(j2kFrame, goopenjpeg.PixelDataOptions{
	Version:                   goopenjpeg.PixelDataV1,
	Codec:                     goopenjpeg.CodecJ2K,
	PhotometricInterpretation: "MONOCHROME2",
})
Encode HTJ2K (.201–.203)

OpenJPEG decodes HTJ2K but does not encode it; encoding uses embedded OpenJPH.

// .201 HTJ2K Lossless (LRCP)
enc, err := goopenjpeg.Encode(pixels, goopenjpeg.EncodeOptions{
    Columns: 512, Rows: 512, SamplesPerPixel: 1, BitsStored: 8,
    ColourSpace: goopenjpeg.ColourGray,
    Codec: goopenjpeg.CodecHTJ2K,
    ProgressionOrder: goopenjpeg.ProgressionLRCP,
})

// .202 HTJ2K Lossless RPCL
enc, err := goopenjpeg.Encode(pixels, goopenjpeg.EncodeOptions{
    // ...
    ProgressionOrder: goopenjpeg.ProgressionRPCL,
})

// .203 HTJ2K (lossy)
enc, err := goopenjpeg.Encode(pixels, goopenjpeg.EncodeOptions{
    // ...
    CompressionRatios: []float64{10},
})
Encode a frame (lossless J2K)
enc, err := goopenjpeg.Encode(pixels, goopenjpeg.EncodeOptions{
    Columns: 512, Rows: 512, SamplesPerPixel: 1, BitsStored: 16,
    ColourSpace: goopenjpeg.ColourGray,
    Codec:       goopenjpeg.CodecJ2K,
})
Accessing pixels
// 8-bit sample at (y, x), component c
b := img.ByteAt(y, x, c)

// 16-bit little-endian sample
u := img.Uint16At(y, x, c)
Library version
ver, err := goopenjpeg.OpenJPEGVersion() // e.g. "2.5.4"

API

func DecodeImage(stream any, codec Codec) (*Image, error)
func GetImageParameters(stream any, codec Codec) (*Params, error)
func DecodePixelData(src []byte, opts PixelDataOptions) ([]byte, error)
func Encode(src []byte, opts EncodeOptions) ([]byte, error)
func EncodePixelData(src []byte, opts PixelDataOptions, frame EncodeOptions) ([]byte, error)
func OpenJPEGVersion() (string, error)

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

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 goopenjpeg (or godicom, which does) keeps compiling and running on a platform with no prebuilt library, and only JPEG 2000 fails:

img, err := goopenjpeg.Decode(data)
if errors.Is(err, goopenjpeg.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 total about 7.7 MB, but a binary only ever carries the one it can load — every //go:embed sits behind a per-platform build tag, so the other five are not compiled in:

target embedded added to the binary
linux/amd64 goopenjpeg_linux_amd64.so ~1.6 MB
linux/arm64 goopenjpeg_linux_arm64.so ~1.4 MB
darwin/amd64 goopenjpeg_darwin_amd64.dylib ~1.3 MB
darwin/arm64 goopenjpeg_darwin_arm64.dylib ~1.0 MB
windows/amd64 goopenjpeg_amd64.dll ~1.3 MB
windows/arm64 goopenjpeg_arm64.dll ~1.1 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.

Layout

goopenjpeg/           # public Go API
native/               # purego + go:embed prebuilt libs
lib/
  openjpeg/           # submodule → uclouvain/openjpeg (decode + J2K encode)
  openjph/            # submodule → aous72/OpenJPH (HTJ2K encode)
  interface/          # decode glue (from pylibjpeg-openjpeg, memory streams)
  capi/               # C ABI for purego
ref/pylibjpeg-openjpeg/

Development

git clone --recurse-submodules https://github.com/godicom-dev/goopenjpeg.git
cd goopenjpeg
go test ./...          # uses prebuilt libs in native/libs/
make build-native      # optional: rebuild embedded OpenJPEG (requires CMake)

CI (build.yml): checks (fmt, vet, embed set) + cross-build + build-native → commit native/libs/ on main → test → release on tags.

Tagged releases attach per-platform libraries to GitHub Releases.

References

Documentation

Index

Constants

View Source
const (
	ColourUnspecified = 0
	ColourSRGB        = 1
	ColourGray        = 2
	ColourSYCC        = 3
	ColourEYCC        = 4
	ColourCMYK        = 5
)

Colour spaces matching OpenJPEG OPJ_COLOR_SPACE.

Variables

View Source
var ErrUnsupportedPlatform = native.ErrUnsupportedPlatform

ErrUnsupportedPlatform reports that this GOOS/GOARCH has no prebuilt native library, so JPEG 2000 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

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

DecodePixelData decodes encapsulated JPEG 2000 pixel data for DICOM. Version 2 returns raw decoded bytes; version 1 matches pylibjpeg v1 behaviour.

func Encode added in v1.1.0

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

Encode encodes little-endian colour-by-pixel samples to JPEG 2000.

func EncodePixelData added in v1.1.0

func EncodePixelData(src []byte, opts PixelDataOptions, frame EncodeOptions) ([]byte, error)

EncodePixelData encodes a single DICOM frame for JPEG 2000 transfer syntaxes.

func OpenJPEGVersion

func OpenJPEGVersion() (string, error)

OpenJPEGVersion returns the linked openjpeg library version.

func ReadStream

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

ReadStream reads JPEG 2000 data from bytes, a file path, or an io.Reader.

Types

type Codec

type Codec int

Codec selects the JPEG 2000 container format (pylibjpeg-openjpeg codec argument).

const (
	CodecJ2K   Codec = 0 // codestream (.j2k, .jpc, .j2c)
	CodecJPT   Codec = 1 // JPT-stream
	CodecJP2   Codec = 2 // JP2 file format (.jp2)
	CodecHTJ2K Codec = 3 // HTJ2K codestream (encode only; decode uses CodecJ2K)
)

type EncodeOptions added in v1.1.0

type EncodeOptions struct {
	Columns         int
	Rows            int
	SamplesPerPixel int
	BitsStored      int
	IsSigned        bool
	ColourSpace     int
	UseMCT          bool
	Codec           Codec
	// ProgressionOrder applies to HTJ2K encode (.201 LRCP, .202 RPCL).
	ProgressionOrder ProgressionOrder
	// CompressionRatios empty => lossless (DWT 5-3).
	CompressionRatios []float64
}

EncodeOptions configures Encode / EncodePixelData.

type Image

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

Image holds decoded pixel data in native precision, planar-interleaved (DICOM order).

func Decode

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

Decode is a shorthand for DecodeImage with CodecJ2K.

func DecodeImage

func DecodeImage(stream any, codec Codec) (*Image, error)

DecodeImage decodes JPEG 2000 data (pylibjpeg-openjpeg decode()).

func (*Image) ByteAt

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

func (*Image) BytesPerSample

func (img *Image) BytesPerSample() int

func (*Image) Uint16At

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

type Params

type Params struct {
	Width       int
	Height      int
	Components  int
	Precision   int
	IsSigned    bool
	ColourSpace int
}

Params holds JPEG 2000 image parameters without decoding pixels.

func GetImageParameters

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

GetImageParameters reads JPEG 2000 parameters without decoding pixels.

func GetParameters

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

GetParameters is a shorthand for GetImageParameters with CodecJ2K.

func (*Params) Columns

func (p *Params) Columns() int

func (*Params) NrComponents

func (p *Params) NrComponents() int

func (*Params) Rows

func (p *Params) Rows() int

type PixelDataOptions

type PixelDataOptions struct {
	Version                   PixelDataVersion
	Codec                     Codec
	PhotometricInterpretation string
}

PixelDataOptions configures DecodePixelData for DICOM handlers.

type PixelDataVersion

type PixelDataVersion int

PixelDataVersion selects decode_pixel_data behaviour.

const (
	PixelDataV1 PixelDataVersion = 1
	PixelDataV2 PixelDataVersion = 2
)

type ProgressionOrder added in v1.2.0

type ProgressionOrder int

ProgressionOrder selects HTJ2K packet progression (DICOM .201 vs .202).

const (
	ProgressionLRCP ProgressionOrder = 0 // HTJ2K Lossless (.201)
	ProgressionRPCL ProgressionOrder = 1 // HTJ2K Lossless RPCL (.202)
)

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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