Optional module · pdftoolkit/pdf-codecs 0.1.0
Jpeg2000\Jpeg2000Decoder decodes one JP2/JPX image or one raw JPEG 2000 codestream from supplied bytes. It returns component planes and colour metadata. It does not open files, fetch fragments, invoke a process, perform colour management or render a PDF. PhpDecoder dispatches Codec::Jpeg2000 to this implementation.
use PDFToolkit\Codecs\Codec;
use PDFToolkit\Codecs\DecodeLimits;
use PDFToolkit\Codecs\DecodeRequest;
use PDFToolkit\Codecs\Jpeg2000\Jpeg2000Decoder;
$decoder = new Jpeg2000Decoder(new DecodeLimits(
maxInputBytes: 8 * 1024 * 1024,
maxOutputBytes: 16 * 1024 * 1024,
maxPixels: 4_000_000,
maxWork: 40_000_000,
));
$image = $decoder->decode(new DecodeRequest(Codec::Jpeg2000, $encodedBytes));
$firstSample = $image->components[0]->sample(0, 0);
Each invocation owns its headers, contexts, accounting and tile state. A decoder object can be reused. PHP 64-bit integers are required; no production extension, native library or process is used.
Native values and metadata
Each ComponentPlane has its own dimensions, origin, horizontal/vertical separation, signedness and precision from 1 through 38 bits. Rows are MSB-first and byte padded; signed samples use precision-bit two's complement. sample() returns the native signed or unsigned integer. It does not shift signed samples to an unsigned range or expand a subsampled component. Palette and component mappings produce the declared output channels and preserve their declared precision.
ColorInformation exposes the chosen interpretation and every original ColorSpecification. The latter retains the method, signed precedence, approximation, exact method payload, ICC profile bytes, enumerated space and extra parameters. Among interpretable enumerated/ICC alternatives, selection uses highest precedence, then lowest approximation, then file order. Vendor methods are retained as alternatives. Selecting an ICC declaration does not mean that a colour-management engine has accepted that profile. Malformed ICC headers are rejected.
space is a colour family, not a conversion: RGB may denote sRGB, e-sRGB or ROMM-RGB. Consumers must inspect enumeratedSpace, the ICC profile and parameters before interpreting the samples. The library does not convert CMYK, YCbCr, Lab or ICC values to sRGB. The Reader exposes bytes and metadata; the PDF consumer (for example HTML) applies PDF colour-space overrides, supported-colour fallback and SMaskInData policy.
ChannelDefinition preserves the original channel number, type and association and identifies the resulting outputChannel. Colour channels follow their declared colour associations; each opacity plane retains associated or unassociated alpha. Multiple channel associations are preserved. DecodedImage::chromaKey retains native opct type-2 values without applying transparency; PDF ignores this JPX chroma-key declaration. The public constructors reject untyped lists, inconsistent selections, invalid indices, incompatible alpha declarations and out-of-range chroma keys.
Implemented decoding paths
| Area | Implemented behavior |
|---|---|
| Part 1 image geometry | Image/tile origins, rectangular tiles, tile parts, component sampling grids, mixed signedness and precision |
| Packet order | LRCP, RLCP, RPCL, PCRL and CPRL; layers, precincts, POC changes including later tile parts |
| Headers | COD/COC and QCD/QCC inheritance independent of marker order; PPM/PPT, including split marker segments; SOP and EPH |
| Entropy coding | MQ, raw selective bypass, context reset, termination on every pass, vertical causal contexts, predictable-termination coding and segmentation symbols |
| Reconstruction | No quantization, scalar-derived and scalar-expounded quantization; integer 5/3 and floating-point 9/7 inverse wavelets; reversible/irreversible three-component transforms; ROI max-shift through 255 |
| JPX baseline transforms | One irreversible array decorrelation stage/collection, CBD, MCT integer16/integer32/float32/float64 arrays, MCC/MCO, gamma and lookup-table non-linearity; component/tile overrides |
| Containers | JP2 and the first JPX baseline layer/codestream; bounded boxes and reader requirements, jpch/jplh field inheritance, palettes, cmap, cdef, opct, ICC/EnumCS/extra parameters |
| Internal references | Ordered fragments and cross-referenced box contents resolved from the supplied byte string, including metadata references |
The first JPX layer uses one codestream. Subsequent layers and composition/animation instructions do not replace it. Identity registration is accepted; registration requiring a composition/resampling engine is explicit UnsupportedFeature. External fragments are rejected without any network or filesystem access. Other Part 2 transformations, non-baseline component/wavelet transforms, HTJ2K and compression types other than JPEG 2000 are not claimed. Unsupported required capabilities or active coding features fail explicitly. Unknown marker segments are discarded using their bounded length, as required by T.800 A.1.
This is a decoder, not a complete codestream-conformance verifier. It checks framing, boundaries, supported syntax and output invariants; it does not certify every encoder termination choice or every unused metadata field. Predictable-termination streams decode using the same MQ procedure; successful decoding is not a proof of all termination-padding constraints. Raw codestream input is a library convenience; PDF container requirements remain the responsibility of the PDF consumer.
Limits and failures
DecodeLimits bounds input/output bytes, dimensions, pixels, components, work, cumulative allocation estimates, marker/packet counts, box/decomposition depth, metadata and tile count. Accounting is shared across tiles, intermediate arrays, palettes, fragments and transformed samples. Allocation accounting is conservative and cumulative; it is not a PHP process-memory limit. The default allocation budget can reject a large image before its pixel limit. Increase a limit explicitly only for a trusted workload with sufficient memory.
MalformedImage reports inconsistent/truncated input, UnsupportedFeature an unsupported capability, and ResourceLimit an exhausted limit. No partially decoded image is returned. Integer 5/3 output is compared exactly. Irreversible wavelets, matrix and non-linear transforms use PHP doubles and round/clamp to each declared output range; test tolerances are recorded per fixture rather than promising identical rounding for every external implementation.
Evidence and reproduction
tests/Fixtures/jpx/manifest.json records every encoded and expected-plane hash and labels its oracle. tools/validate_jpx.py regenerates the fixtures, checks retained hashes and compares PHP native geometry and samples. The test oracle is OpenJPEG 2.5.4 at commit 6c4a29b00211eb0430fa0e5e890f1ce5c80f409f, accessed through its C API so CLI conversions cannot silently change sample precision.
python3 tools/build_jpx_oracle.py --output _build/openjpeg
python3 tools/validate_jpx.py --php php --oracle _build/openjpeg/jpx-oracle --output _build/jpx
vendor/bin/phpunit --filter Jpeg2000
The build tool needs Git, CMake, a C compiler and Make on Linux. The Python tools use only the standard library. OpenJPEG is test-only. CI runs PHP 8.3, 8.4 and 8.5. The retained Gray ICC profile is an original LittleCMS D50/gamma-2.2 fixture; its provenance and hash are in the manifest.
Evidence is deliberately separated:
- OpenJPEG decode comparisons cover native 1/2/4/8/12/16/24/31-bit values, signed/unsigned and mixed components, coding styles, progressions, grids, lossless/lossy reconstruction and scalar-derived quantization. Lossless source-to-encoded round trips are checked separately when the independent encoder supports them.
- OpenJPEG cannot independently decode all tested 255-bit ROI shifts; that vector is encoded independently and compared with the original samples.
- Precisions 32–38 use normative integer vectors derived from T.800 E-1/E-2/E-8 and the DC shift, using a retained independently encoded coefficient seed. These are not described as OpenJPEG 32–38-bit decoding tests.
- Part 2 array/non-linear transforms use separate Python equations from T.801 and its corrigenda. OpenJPEG's own matrix encoder output was rejected by its decoder, so it is not counted as a successful matrix oracle.
- Packet relocation, inheritance, palette/alpha/colour metadata and internal references use independently specified byte transformations and expected native outputs. MQ reference vectors check parity with the attributed implementation; they are not an independent oracle. The real OpenJPEG codestreams exercise MQ independently.
The fixture count is a regression inventory, not proof that every legal codestream is covered.
Normative and implementation sources
- ITU-T T.800 (06/2019): Annexes A/B headers, geometry and packets; C/D arithmetic/code-block coding; E/F quantization/wavelets; G transforms; H ROI; I JP2.
- ITU-T T.801 (08/2002): A.3.6–10, J.3.1.1.1, Annex K, M.9.2 and M.11; with Corrigendum 3 and Corrigendum 4. The 2023 text was not reviewed. Corrigendum 4 governs the gamma equation, MCT override identity and MCC index zero.
- ISO 32000-2:2020 EC3 §7.4.9 defines the PDF-specific boundary, including precision 1–38, baseline JPX restrictions and opacity/colour policies.
- MQ and code-block scan organization are adapted from Mozilla PDF.js, Apache-2.0. See
NOTICEandresources/jpeg2000/NOTICEandresources/arithmetic/NOTICE. Native-width coefficients, bounds, ROI handling, bypass, transforms and container state are implemented as request-scoped PHP objects.