j2k 0.9.0
j2k: ^0.9.0 copied to clipboard
Pure Dart JPEG 2000 codec: decodes JP2 and J2K files to 8- or 16-bit pixels with alpha, encodes pixel buffers and PGM/PPM, and runs on the VM, dart2js and dart2wasm.
j2k #
Pure Dart JPEG 2000 codec, published as package:j2k. It decodes JP2 files
and raw J2K codestreams to 8- or 16-bit pixels, encodes pixel buffers and
PGM/PPM, and runs unchanged on the Dart VM, dart2js and dart2wasm: the public
API is byte-oriented and never imports dart:io.
The decoder is a port of the JJ2000 reference implementation and is bit-exact against it on the bundled conformance subset. See Origin and licenses.
Features #
- Decoder: codestream parsing, EBCOT/MQ entropy decoding, ROI de-scaling, dequantization, reversible 5x3 and irreversible 9x7 inverse wavelets, inverse RCT/ICT, and JP2 colour handling: enumerated sRGB/greyscale/sYCC, restricted ICC profiles, palettes, and channel definitions (alpha).
- Output: gray, gray+alpha, RGB, RGBA or raw multi-component samples, tightly packed and interleaved, 8 bits per sample by default or 16 on request.
- Header probe: width, height, components, bit depths, tiling and alpha without decoding a pixel, so callers can apply size policies first.
- Budgets:
maxPixelsandmaxDimensionreject oversized images from the SIZ marker before any allocation. - Typed errors: a sealed
Jpeg2000Exceptionhierarchy separates "not a JPEG 2000 file", "truncated", "corrupted", "unsupported feature" and "over budget". - Encoder: interleaved pixel buffers (1 to 16 bits per sample, with or without alpha) or binary PGM/PPM bytes to raw J2K or JP2, lossless or rate-controlled, with optional tiling.
- Command line:
jp2decandjp2enc.
Installation #
dart pub add j2k
Decoding #
import 'dart:typed_data';
import 'package:j2k/j2k.dart';
Jpeg2000Image decode(Uint8List jp2OrJ2kBytes) {
final image = decodeJpeg2000(
jp2OrJ2kBytes,
options: Jpeg2000DecodeOptions(
maxPixels: 64 * 1024 * 1024,
onWarning: (message) => print('jpeg2000: $message'),
),
);
print('${image.width}x${image.height} ${image.format}');
// image.pixels: Uint8List, row-major, `image.components` bytes per pixel.
// Pixel (x, y) starts at (y * image.width + x) * image.components.
// With image.hasAlpha the last channel is alpha; check
// image.alphaIsPremultiplied before compositing.
return image;
}
Jpeg2000Image fields:
| Field | Meaning |
|---|---|
format |
gray, grayAlpha, rgb, rgba or multiComponent |
components |
channels per pixel, alpha included |
colorComponents |
leading colour channels (1 or 3; all channels for multiComponent) |
hasAlpha, alphaIsPremultiplied |
from the JP2 cdef box, or the 2/4-channel convention when there is none |
bitsPerSample |
8 or 16, as requested by outputBitDepth |
pixels |
the sample bytes; with 16-bit samples use the pixels16 view |
sourceBitsPerComponent |
bit depth of each channel in the file, before rescaling |
Decode options:
| Option | Default | Effect |
|---|---|---|
applyColorSpace |
true |
apply JP2 colour metadata (ICC, palette, channel definitions) |
applyComponentTransform |
true |
apply the inverse RCT/ICT signalled in the codestream |
outputBitDepth |
8 |
8 or 16; deeper sources are shifted down, shallower ones rescaled to the full range |
rate / bytes |
none | stop after this many bits per pixel or bytes (progressive preview) |
resolution |
none | discard this many highest resolution levels |
maxPixels / maxDimension |
none | throw Jpeg2000BudgetException before allocating |
onWarning |
none | receive non-fatal diagnostics; nothing is ever printed |
Probing without decoding #
final info = probeJpeg2000(bytes);
if (info.pixelCount > budget) {
throw StateError('too large: ${info.width}x${info.height}');
}
print('${info.components} components, ${info.bitsPerComponent} bits, '
'alpha=${info.hasAlpha}, tiles=${info.tileColumns}x${info.tileRows}');
Errors #
All input problems are subtypes of the sealed Jpeg2000Exception; API misuse
is an ArgumentError.
try {
decodeJpeg2000(bytes);
} on Jpeg2000FormatException {
// Neither a JP2 container nor a J2K codestream.
} on Jpeg2000TruncatedException {
// The data ends early; a retry with the complete file may work.
} on Jpeg2000CorruptedException {
// The file violates the standard.
} on Jpeg2000UnsupportedException {
// Valid, but uses a feature this codec does not implement yet.
} on Jpeg2000BudgetException catch (e) {
// Larger than options.maxPixels / maxDimension: e.budget, e.limit, e.actual.
}
Encoding #
From an interleaved pixel buffer (Uint8List up to 8 bits per sample,
Uint16List above that; with 2 or 4 components the last one is alpha unless
hasAlpha: false):
final jp2 = encodeJpeg2000Pixels(
rgbaBytes,
width: 640,
height: 480,
components: 4,
options: const Jpeg2000EncodeOptions(wrapInJp2: true), // lossless
);
final j2k = encodeJpeg2000Pixels(
gray16Samples, // Uint16List
width: 512,
height: 512,
components: 1,
bitsPerSample: 16,
options: const Jpeg2000EncodeOptions(
lossless: false,
rate: 1.0, // bits per pixel
tileWidth: 256,
tileHeight: 256,
),
);
From binary PGM (P5) or PPM (P6) bytes, 8 or 16 bits per sample:
final j2k = encodeJpeg2000(ppmBytes);
The JP2 wrapper carries greyscale or sRGB colour metadata and, when there is
alpha, a channel definition box, so the file decodes back as rgba or
grayAlpha.
Files, paths and browser blobs #
decodeJpeg2000Source and encodeJpeg2000Source accept bytes everywhere. On
the VM they also accept a dart:io File or a path; in browsers they accept a
package:web Blob or File.
import 'package:j2k/j2k.dart';
import 'package:web/web.dart' as web;
Future<void> decodeBrowserFile(web.File file) async {
final image = await decodeJpeg2000Source(file);
print(image.pixels.length);
}
Command line #
dart run j2k:decode -i input.jp2 -o output.ppm # also .pgm, .pgx, .bmp
dart run j2k:encode -i input.ppm -o output.j2k -lossless on
dart run j2k:encode -i input.ppm -o output.jp2 -lossless on -file_format on
dart run j2k:encode -i input.ppm -o output.j2k -rate 1.0
After dart pub global activate j2k the tools are available as
jp2dec and jp2enc.
Limitations #
- Output is 8 or 16 bits per sample; other source depths are rescaled (the
original depth is reported in
sourceBitsPerComponent). - Raw codestreams with subsampled components and no JP2 colour metadata throw
Jpeg2000UnsupportedException; JP2 files resample through the colour pipeline. - Custom (non 5x3 / 9x7) wavelet kernels, progression orders outside the five standard ones, and Part 2 (JPX) extensions are not supported.
- The encoder takes unsigned samples only; signed components and per-component depths are not exposed.
- Decoding is single-threaded and, for now, several times slower than native codecs; see doc/BENCHMARKS.md. Decode large images off the UI thread.
Development #
Run the same checks as CI:
dart format --output=none --set-exit-if-changed lib test bin benchmark example
dart analyze
dart test -j 1
dart test -p chrome test/jpeg2000_public_api_test.dart
dart run benchmark/codec_benchmark.dart
dart compile js -O2 -o build/codec_benchmark.js benchmark/codec_benchmark.dart
dart compile wasm -o build/codec_benchmark.wasm benchmark/codec_benchmark.dart
node benchmark/run_wasm_benchmark.mjs build/codec_benchmark.mjs build/codec_benchmark.wasm
test/architecture/public_facade_imports_test.dart walks the import graph
from lib/j2k.dart the way pub.dev does and fails if dart:io becomes
reachable, which would cost the package its Web and Wasm support.
Fixtures live in test/fixtures (synthetic JP2/J2K files with decoded
references, a conformance subset with bit-exact references, and small MQ and
entropy fixtures). They are not published with the package.
Origin and licenses #
The Dart code is released under the MIT license (see LICENSE).
It is a port of JJ2000, the Java reference implementation of JPEG 2000
Part 1 written by EPFL, Ericsson and Canon Research Centre France. The JJ2000
license requires its copyright notice to accompany every copy or derivative
work; it is reproduced in LICENSE-JJ2000.txt and applies to the ported
algorithms alongside the MIT terms.