Repository navigation
Conversation
lgritz
force-pushed
the
lg-j2k-subsampling
branch
2 times, most recently
from
November 6, 2026 06:04
6dbae55 to
b0b387c
Compare
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
The decoder can corrupt color-transformed images and hang on zero-height components, while offset and extent calculations lack targeted regression coverage.
3 open issues
What changed in this PR
This PR updates OpenImageIO’s OpenJPH decoder to handle subsampled HTJ2K components without hanging and reconstruct them at full image resolution.
Changes:
- Decode subsampled components in planar order and expand samples using offset-aware mapping.
- Derive image dimensions from the image extent rather than component 0.
- Add subsampling regression cases and document the decoder behavior.
| File | Description |
|---|---|
testsuite/htj2k/run.py |
Adds valid subsampling cases and a corrupt-file hang regression. |
testsuite/htj2k/ref/out.txt |
Records expected dimensions, pixel hashes, and completion output. |
src/jpeg2000.imageio/jpeg2000input.cpp |
Implements per-component decoding, sample expansion, and canvas sizing. |
.agents/skills/oiio-input-security-audit/references/format-notes.md |
Documents the subsampling fix and OpenJPEG fallback limitations. |
.agents/skills/oiio-input-security-audit/references/bug-catalog.md |
Records the component-size mismatch and its decoding consequences. |
Review effort: Balanced
The OpenJPH (HTJ2K) path assumed every component is full resolution, pulling one full-width line per component for each image row. Subsampled components cause it to fall out of step, and OpenJPH spins indefinitely in codestream::pull(). Found by our nightly fuzzing with corrupt headers, but valid 4:2:0 files hang the same way. Each component is now pulled at its own reconstructed size, and each sample is replicated over the full-resolution pixels it covers (the same nearest-sample mapping as the OpenJPEG path, but honoring the image offset). The image size now comes from the image extent rather than from component 0, which is smaller when it is subsampled. Color-transformed files stay in OpenJPH's interleaved order, which needs equal subsampling, so unequal ones are rejected. Empty components, which also hung pull(), are rejected too. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Larry Gritz <lg@larrygritz.com>
Keep OpenJPH's pull order (interleaved for color-transformed files), reject empty components, add offset/MCT/empty-component tests. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Larry Gritz <lg@larrygritz.com>
lgritz
force-pushed
the
lg-j2k-subsampling
branch
from
November 7, 2026 13:35
b0b387c to
7a70cbf
Compare
Collaborator
Author
|
Addressed the copilot comments and pushed an amentment. |
Claude says: Report column-map allocation failure as a read error, and let the hang regression test fail if the decoder crashes. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Larry Gritz <lg@larrygritz.com>
Claude says: When tiles are smaller than a component's subsampling, the component has no samples in some tiles. OpenJPH 0.24 crashes pulling such a component (0.33 copes), so reject these files in the header check. The hang regression test now checks the reader's error message rather than relying on a bare exit status, so a crash fails the test. Assisted-by: Claude Code / Claude Opus 5.5 Signed-off-by: Larry Gritz <lg@larrygritz.com>
| uint64_t last = toff + (ntiles - 1) * tsz; | ||
| return has_sample(first, std::min(ext, toff + tsz)) | ||
| && has_sample(std::max(off, last), ext) | ||
| && (ntiles <= 2 || tsz >= ds); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


The OpenJPH (HTJ2K) path assumed every component is full resolution, pulling one full-width line per component for each image row. Subsampled components cause it to fall out of step, and OpenJPH spins indefinitely in codestream::pull(). Found by our nightly fuzzing with corrupt headers, but valid 4:2:0 files hang the same way.
Each component is now pulled at its own reconstructed size, and each sample is replicated over the full-resolution pixels it covers (the same nearest-sample mapping as the OpenJPEG path, but honoring the image offset). The image size now comes from the image extent rather than from component 0, which is smaller when it is subsampled.
Color-transformed files stay in OpenJPH's interleaved order, which needs equal subsampling, so unequal ones are rejected. Empty components, which also hung pull(), are rejected too.
Additionally, when tiles are smaller than a component's subsampling, the component has no samples in some tiles. OpenJPH 0.24 crashes pulling such a component (0.33 copes), so reject these files in the header check.
Assisted-by: Claude Code / Claude Opus 5.5