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 |
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
go get github.com/godicom-dev/goopenjpegPrebuilt OpenJPEG libraries are embedded per platform in native/libs/ — no CMake required for go get users.
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).
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)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",
})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},
})enc, err := goopenjpeg.Encode(pixels, goopenjpeg.EncodeOptions{
Columns: 512, Rows: 512, SamplesPerPixel: 1, BitsStored: 16,
ColourSpace: goopenjpeg.ColourGray,
Codec: goopenjpeg.CodecJ2K,
})// 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)ver, err := goopenjpeg.OpenJPEGVersion() // e.g. "2.5.4"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)| 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.
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.
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/
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.
- golibjpeg — same purego architecture for ISO 10918 / JPEG-LS
- gorle — DICOM RLE Lossless
- pylibjpeg-openjpeg — behaviour and tests reference