Skip to content

fix(j2k-native): keep the declared color space over unspecified extra components - #323

Merged
jcwal1516 merged 2 commits into
frames-sg:mainfrom
jcoyne:fix/unspecified-extra-components
Nov 8, 2026
Merged

jcwal1516 merged 2 commits into
frames-sg:mainfrom
jcoyne:fix/unspecified-extra-components

Conversation

@jcoyne

@jcoyne jcoyne commented Nov 7, 2026

Copy link
Copy Markdown
Contributor

Problem

Under the default strict settings, every Rgb8/Rgba8 decode of a JP2 that declares sRGB over four codestream components with no cdef box failed:

unsupported: backend color space cannot be mapped to requested 8-bit pixel format

resolve_alpha_and_color_space saw the component count disagree with the declared colour space and fell back to guessing from the count: four components without alpha became CMYK. CMYK has no 8-bit RGB mapping. Kakadu 7.10.2 wrote files like this from RGBA TIFFs when -jp2_alpha wasn't given. Kakadu 8.4.1 writes the cdef box. We hit it in production on an IIIF image server.

Change

ISO/IEC 15444-1 I.5.3.6: without a channel definition, the components after those of the declared colour space are unspecified. They don't change what the colour space is.

  • When a JP2 declares an enumerated or ICC colour space, has no cdef or palette, and has more components than that space (plus alpha), the colour space is kept. The extra components are recorded as Image::ignored_components.
  • Decoded output omits them. This covers finish_decoded_image (bitmaps, borrowed and native planes, decode_into), the native RawBitmap decodes (16-bit output), streamed region rows (through a sink adapter), and the native planes sink. supports_direct_device_plane_reuse returns false for these images.
  • So sRGB with 4 components decodes to Rgb8, and to Rgba8 with opaque alpha, at full resolution, in regions and in scaled regions.

Unchanged:

  • Lenient decoding still infers alpha for exactly one extra component.
  • Raw codestreams keep their component-count interpretation (4 components → CMYK).

Recode calls a hidden Image::keep_ignored_components and re-encodes every component, because it reuses the source JP2 metadata.

build_direct_color_region_plan (HTJ2K batch) now treats DirectPlanUnsupported as "not direct" and falls back to the generic decode. Before, it failed the job, as it already did for these images when they were labelled CMYK.

Tests

New file crates/j2k/tests/unspecified_extra_components.rs. The JP2s are built by hand, because the wrapper adds an RGBA cdef for four-component sRGB.

  • sRGB with 4 components: whole image, region and sub-region Rgb8 match the first three input channels. Rgba8 has opaque alpha. Scaled regions at 1 and 2 levels work. Rgb16 matches the widened samples.
  • Greyscale with 2 components decodes to Gray8.
  • Native output for 4 and 5 sRGB components and 2 greyscale components: ignored_components, no alpha, and the expected plane count from decode_native_components, decode_native and the streamed native planes.
  • Lenient mode still returns the 4th component as alpha.
  • A raw 4-component codestream is still CMYK, with nothing ignored.

Checks run:

  • cargo test -p j2k-native -p j2k passes.
  • cargo xtask clippy, cargo xtask clippy-strict and cargo fmt --check are clean.
  • The production file (640×480, Kakadu v7.10.2) failed at every level before the change and now decodes with decode_region_scaled_pow2_into at levels 0–2.

Notes

  • J2kDecoder::info().components still reports the codestream count (4). Bounded row decoding therefore picks Rgba8 for these files and gets opaque alpha.
  • Decoding still decodes the ignored components and then drops them. Skipping their tier-1 work would be a separate optimization.

jcoyne and others added 2 commits November 6, 2026 20:05
… components

A JP2 declaring sRGB over four codestream components with no cdef box
failed every Rgb8/Rgba8 decode under the default strict settings with
"backend color space cannot be mapped to requested 8-bit pixel format".
The component-count fallback in resolve_alpha_and_color_space replaced
the declared sRGB with CMYK, which has no 8-bit RGB mapping. Kakadu 7
wrote such files from RGBA TIFFs unless -jp2_alpha was given.

Without a channel definition, ISO/IEC 15444-1 I.5.3.6 leaves the
components after those of the declared color space unspecified. When a
JP2 declares an enumerated or ICC color space, has no cdef or palette,
and has more components than that space (plus alpha), keep the color
space and record the extras as Image::ignored_components. Decoded
output omits them: finish_decoded_image, the native RawBitmap decodes,
streamed region rows and the native planes sink keep only the color
and alpha components. Device plane reuse is disabled for such images.

Lenient decoding still infers alpha for exactly one extra component,
and raw codestreams keep their component-count interpretation. Recode
keeps every component, since it reuses the source JP2 metadata. The
HTJ2K batch direct-plan builder now treats DirectPlanUnsupported as
"not direct" and falls back instead of failing the job.
Choose bounded u8/u16 row layouts from resolved color and alpha metadata.
Compare all codestream components during recode validation and cover row
output and preservation with regression tests. Update the API inventories
and review records for the original JP2 color-space change.
@jcwal1516
jcwal1516 merged commit f1a5441 into frames-sg:main Nov 8, 2026
30 of 32 checks passed
jcoyne added a commit to jcoyne/persimmon that referenced this pull request Nov 9, 2026
Picks up the fixes from frames-sg/j2k#323: keep the declared color space
over unspecified extra components, and complete extra-component row
decoding and recode validation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants