Pure-Go JAB Code encoder and decoder.
JAB Code is a high-capacity color matrix barcode standardized as ISO/IEC 23634:2022.
This module is experimental. The API may still change, and scanning real-world captures is still being hardened. When you find any errors, please report them as issues.
Single- and multi-symbol encode/decode work, including normative 4- and 8-color ISO modes, docked secondary symbols, diagnostics, and a frame-sequence decoder. Tagged builds add high-color, BSI and historical decoder families. The main active work is print-capture robustness, stream integration, performance, and validation of the ISO target.
go get github.com/srlehn/jabcodeInstall the CLI:
go install github.com/srlehn/jabcode/cmd/jabcode@latestpackage main
import (
"bytes"
"image/png"
"os"
"github.com/srlehn/jabcode"
)
func main() {
img, err := jabcode.NewEncoder(
jabcode.WithColors(8),
jabcode.WithModuleSize(12),
).Encode([]byte("hello"))
if err != nil {
panic(err)
}
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
panic(err)
}
if err := os.WriteFile("hello.png", buf.Bytes(), 0o644); err != nil {
panic(err)
}
}Decoding accepts any image.Image, so file format support is provided by the
decoders registered by the caller.
f, err := os.Open("hello.png")
if err != nil {
panic(err)
}
defer f.Close()
img, err := png.Decode(f)
if err != nil {
panic(err)
}
data, err := jabcode.Decode(img)
if err != nil {
panic(err)
}
_ = dataFor ordered, coherent frame sequences, use jabcode.NewStream(). Frames may
come from a live camera, network video, or a decoded recording. The stream
reuses previous read hypotheses and compatible evidence within a fixed
per-frame work budget, and automatically consumes every decoder capability
compiled into the build through one integrated detector pipeline. Call
Stream.Reset before reusing it for an unrelated coherent sequence.
Decode and Stream accept an already constructed image.Image; they cannot
bound storage that a byte-buffer adapter allocated first. Adapters for
untrusted camera buffers must validate width, height, stride, and their
products and enforce an input-dimension limit before allocating the image.
A permanent regular-Go js/wasm gate executes one fixed opaque-byte
symbol through the public plan and Stream APIs. It proves the platform and byte
contract, not production readiness for repeating loops of changing symbols;
that transition and emission work remains separate.
Native applications that only render symbols can import the dependency-light encoder package directly:
import "github.com/srlehn/jabcode/jabenc"
img, err := jabenc.New(
jabenc.WithColors(8),
jabenc.WithModuleSize(12),
).Encode([]byte("hello"))That package does not depend on the read or detect packages, Vulki, or purego.
With CGO_ENABLED=0, an encoder-only consumer remains statically linked. The
root package keeps the same encoder facade for applications that also decode.
For fixed-size binary transport frames, create one immutable byte-mode plan and use its exact capacity before splitting any data:
plan, err := jabenc.NewOpaquePlan(
image.Pt(8, 8),
jabenc.WithColors(8),
jabenc.WithModuleSize(4),
)
if err != nil {
panic(err)
}
frameCapacity := plan.Capacity()
img, err := plan.Encode(frame[:frameCapacity])The plan fixes color count, side versions, ECC level, module size, and output geometry. It rejects empty data and any payload one byte beyond its reported capacity.
Encode payload bytes from stdin to PNG:
printf hello | jabcode encode --output hello.pngFor shell demos, literal text input is also available:
jabcode encode --input "hello" --output hello.pngDecode an image to stdout:
jabcode decode hello.pngWrite decoded bytes to a file:
jabcode decode --output payload.bin hello.pngISO decode output is the reader transmission defined by the standard: it
starts with the ]j1, ]j4 or ]j5 symbology identifier, encodes ECI
assignments as a backslash plus six digits, and doubles literal data
backslashes. Consumers that need application bytes use DecodeMessage or
Stream.DecodeMessage. Their Message.Data is decoded directly from the mode
stream, while Message.ReaderTransmission retains the standards-facing form
and Message.Controls records ECI, FNC1, and ISO/IEC 15434 structure.
jabcode decode registers PNG, JPEG, HEIC, AVIF, TIFF, and WebP decoders
(including WebP VP8 and VP8L).
Detector diagnostics for difficult captures write the payload to stdout and the
diagnostic report to stderr; annotated diagnostic images go to --diag-out.
The diagnostic mode observes the authoritative read once and does not replay a
second decode pipeline:
jabcode decode --diag --diag-out ./diag-images capture.png > payload.binMulti-symbol encodes use one compact symbol spec per symbol:
jabcode encode --symbols 0:4x4:0,2:4x4:0 --output cascade.png < payload.bin- The default encoder targets ISO/IEC 23634:2022 with the normative 4- and 8-color modes. Its Annex F range reduction still lacks an independent wire oracle, so strict-conformance verification is not yet complete.
- Decoder build tags are additive. Untagged
Decodeaccepts ISO only;jabcode_high_color,jabcode_bsi, andjabcode_legacyadd their compiled routes to the same automatic read. The CLI-only--onlyflag restricts the read to a comma-separated subset of them, for debugging and conformance work. jabcode_high_coloradds decoding of the non-standard ISO-derived 16- through 256-color modes.jabcode_non_iso_encodeadds the public encoder profile selector withhcandbsioutput. Use the corresponding decoder tag as well when the same binary must read what it writes. Physical robustness decreases with color density: capture limits range from camera-grade 16/32 colors to scanner-grade 128 colors, while 256 colors remain pixel-exact only. SeeWithColorsfor details.jabcode_legacyadds read-only current and pre-v2.0 C-reference formats, including docked multi-symbol codes. No legacy encoder is exposed.jabcode_bsiadds exact BSI TR-03137 primary and recursively docked-secondary decoding.jabcode_non_iso_encodeexposesProfileBSIand CLI--profile bsifor single- and multi-symbol output. BSI supports its specified 4- through 256-color layouts; the CLI warns above 8 colors because capture robustness still falls as palette density rises.- Decoder builds embed the precomputed LDPC pivot catalogs so the first GPU
decode does not spend seconds constructing them. This adds roughly 13 MB for
ISO and another 13 MB when
jabcode_bsiorjabcode_legacycompiles the C-family generator. Decodeis intended to return errors, not panic, on malformed or hostile images. Callers should still bound untrusted image dimensions before decoding.- Native large resolution-pyramid reads use Vulkan preprocessing automatically
when the selected adapter reports a discrete-GPU device type. There is no GPU
build tag or required runtime configuration; smaller images, unavailable
Vulkan and software implementations such as llvmpipe use the CPU path
transparently.
GOOS=jsbuilds run the same reader over a WebGPU session when a large enough frame has a browser GPU, and over the CPU path otherwise; Vulki and purego are excluded there. Regular Gojs/wasmis the tested browser target. The OS-only build constraint also selects the CPU files for GopherJS without making a GopherJS execution or language compatibility claim. The native GPU path persists a Vulkan pipeline cache under the user cache directory (vulki/pipeline-*.bin); setVULKI_PIPELINE_CACHE=offto disable it orVULKI_PIPELINE_CACHE_PATHto relocate it.
- Root package: public
Encoderfacade,Decode, andStream. jabenc: dependency-light public encoder API for sender-only consumers.internal/encode: data encoding, matrix placement, masking, and rendering.internal/core: shared pixel buffers, geometry, decoded-symbol types, and status values used by the read path.internal/read,internal/detect,internal/decode: image search, detection, sampling, metadata, palette, ECC, and payload decoding.internal/diag: staged text and image diagnostics over the decoder.internal/ecc,internal/palette,internal/spec,internal/tables: shared format machinery.cmd/: user-facing CLIs.
More detail:
- ARCHITECTURE.md describes the package boundaries, invariants, robustness extensions, and verification strategy.
- WIRE_FORMAT.md records the C-reference wire format and known ISO and pre-ISO deltas.