Skip to content

Conversation

@richardssam
Copy link
Contributor

@richardssam richardssam commented Apr 12, 2025

Description

OIIO did already have a decompressor for HTJ2K via the OpenJpeg library, but nothing for encoding. This adds the encoder using the OpenJPH library, which will be needed for the HTJ2K encoding for OpenEXR too.

NEW:
This is now a merge with the OpenJpeg plugin, the reader will attempt to use the OpenJPH library (if found) to read the input file, if it cannot read it, it will fall back on OpenJpeg to read it. For writing with the OpenJpeg library, if the compression flag is set to htj2k or the file extension is j2c the OpenJPH library will be called to write out the file.

If you want to do a lossy encode you can do it with:
oiiotool -i INPUTFILE --attrib jph:qstep 0.03 -o OUTPUTFILE.j2c

oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03 -o OUTPUTFILE.j2k

Other options include:
oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03 --attrib jph:num_decomps 3 --attrib jph:block_size 32,32 --attrib jph:prog_order CPRL --attrib jph:precincts 128,128,256,256 -o OUTPUTFILE.j2c

Tests

Yes, added a htj2k test, it tests encoding a dox and a jpeg file, both lossless and lossy.

Checklist:

  • I have read the contribution guidelines.
  • I have updated the documentation, if applicable. (Check if there is no
    need to update the documentation, for example if this is a bug fix that
    doesn't change the API.)
  • I have ensured that the change is tested somewhere in the testsuite
    (adding new test cases if necessary).
  • If I added or modified a C++ API call, I have also amended the
    corresponding Python bindings (and if altering ImageBufAlgo functions, also
    exposed the new functionality as oiiotool options).
  • My code follows the prevailing code style of this project. If I haven't
    already run clang-format before submitting, I definitely will look at the CI
    test that runs clang-format and fix anything that it highlights as being
    nonconforming.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Apr 12, 2025

CLA Signed

The committers listed above are authorized under a signed CLA.

@lgritz
Copy link
Collaborator

lgritz commented Apr 12, 2025

Don't mind the MacOS-13 failure, that is unrelated and already fixed in main. (If you were to rebase on top of the current main, I believe that failure would go away.)

Comment on lines +5 to +6
# Module to find OPENJPH.
#
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking: OpenJPH doesn't have an exported cmake config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think so no. I'll check what they have done with OpenEXR, so we dont have two different approaches.

Copy link

@kmilos kmilos Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more recent 0.21.x version do, and I guess 0.21.2 should be the minimum required due to some other bugfixes as well. See also AcademySoftwareFoundation/openexr#1883

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we never had OpenJPH as a dependency before, there is no need to preserve any back compatibility, so we can set the minimum version to anything we want. If the most recent version makes the build process more foolproof by supplying the exported cmake config files and eliminating the need for a FindOpenJPH module, I think that's a totally valid reason to make it the minimum going forward.

An example of a potential counter-argument would be: if OpenJPH is widely used and an older version is probably on most developer's machines already, requiring the very newest is an extra burden for them, so maybe it's worth accommodating older versions. (But I suspect this is not the case with OpenJPH as it would be with something like libtiff or libjpeg... I bet most developers will have to install OpenJPH for the first time only after we add it as a dependency, so it shouldn't matter much if we require them to have a new version.)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I tend to agree; 0.21.x is available OOTB only recently, in the upcoming Fedora 42, Ubuntu 25.04, Debian 13 (trixie), etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, so I did clean up the cmake part of findJPH to use pkg-config.
However, it did require changes to add_oiio_plugin to add support for link_directories.
Otherwise the oiio build was asking for libraries it couldnt find. I'm not a cmake expert, so definately could use some eyes on this part, but it seems like I'm now taking advantage of something that should have been there already.

@lgritz
Copy link
Collaborator

lgritz commented Apr 12, 2025

The code is all fine, Sam, and I'm 100% in favor of supporting htj2k in file types where it's possible.

This is a bit of an odd situation, though.

As I understand it, this is just a flavor of jpeg-2000, which we could read all along, but couldn't write because of limitations of openjpeg (though we could write other j2k varieties). And this is reflected in that ".j2c" is only used for this compression (is that true?) so we could read them but the openjpeg-based writer would not recognize that extension?

So the result is that reads are via the existing jpeg2000 plugin, but now we have an output-only (first time for that) support for j2c via a different plugin, but it's really still considered a jpeg2000 file?

Did you consider putting the contents of jpgoutput.cpp in the jpeg2000.imageio directory and essentially folding it all into the j2k writer, with the extension and/or compression method requested determining whether the implementation uses openjpeg or openjph to do the heavy lifting?

What do you think the future is here? Do you expect openjpeg to eventually support htj2k? Or for openjph to handle input, too? Is the right way for us to talk about this and for users to think about this as if it were a different file format, or as a compression of an existing file format?

I don't have any preconception of the right answers to these, I'm just prompting thoughts about the right way to organize it and where to put the code.

@richardssam
Copy link
Contributor Author

In an ideal world, OpenJPH would be folded into OpenJpeg, and the original HTJ2K reader was written by the OpenJph developer, but I get the sense that it may not be happening any time soon, which is slightly sad.

I originally wrote this as a separate module, thinking that at some point it would simply go away, but perhaps you are right and I could see if I can blend it into the jpeg2000 plugin. The advantage of that potentially is that we could then use the OpenJPH reader where approprate which is much faster. I think if we do integrate it, I need to make sure its as clean as possible to pull out, when is does become obsolete.

Is it worth a brief discussion at the next OIIO meeting?

@lgritz
Copy link
Collaborator

lgritz commented Apr 12, 2025

I originally wrote this as a separate module, thinking that at some point it would simply go away, but perhaps you are right and I could see if I can blend it into the jpeg2000 plugin. The advantage of that potentially is that we could then use the OpenJPH reader where approprate which is much faster.

So in addition to OpenJPH able to write j2c while openjpeg can't, you're saying that OpenJPH is also faster at reading some (but not all?) of the j2k varieties that openjpeg can read?

That definitely makes me feel like it should be folded into the existing jpeg2000 reader, which can decide which of two underlying libraries to use, based on the particularities of the file.

I think if we do integrate it, I need to make sure its as clean as possible to pull out, when is does become obsolete.

If the logic for which library is used changes over time, or eventually there is no case left when libopenjph is needed (or vice versa), we'll adjust.

Is it worth a brief discussion at the next OIIO meeting?

If you want? But I think you can just proceed as you think best and we can discuss here unless you really think it would benefit from live talk.

@michaeldsmith
Copy link

michaeldsmith commented Apr 13, 2025

@lgritz I wanted to follow-up on your question:

As I understand it, this is just a flavor of jpeg-2000, which we could read all along, but couldn't write because of limitations of openjpeg (though we could write other j2k varieties). And this is reflected in that ".j2c" is only used for this compression (is that true?) so we could read them but the openjpeg-based writer would not recognize that extension?

HTJ2K (JPEG 2000 Part-15) was standardized as an extension to JPEG 2000 Part-1 and published in 2019. It replaces the slow entropy coder from Part-1 with a fast entropy coder, but keeps everything else the same from Part-1.

OpenJPH has been developed as an encoder/decoder that uses the entropy coder of Part-15 codestreams, it was not designed to encode or decode codestreams that use the Part-1 entropy coder. OpenJPEG has supported encoding and decoding of Part-1 codestreams for close to 20 years and in May 2022, the OpenJPEG 2.5.0 release included decoding support for Part-15. This means that any tools (like OIIO, Imagemagick, PDF, RV, etc) that use OpenJPEG v2.5.0 or later can decode HTJ2K codestreams.

Additionally, more esoteric features are standardized in JPEG 2000 Part-2. Because the Part-2 features are more esoteric, many JPEG 2000 libraries don't support most or all of Part-2 features. JPEG 2000 is a flexible suite of standards that allows different Parts to be used. For example, it is possible to combine features from Part-1, Part-2 and Part-15. If a codestream uses features from multiple Parts of JPEG2000, the CAP (capabilities) marker segment (defined in A.5.2 of Part-1) is used to signal the features from the different JPEG 2000 Parts that are needed to decode the codestream.

OpenJPH has also added support for some Part-2 features, like Non-linear Transform (NLT) that is used for lossless float compression and Arbitrary Transform Kernels (ATK) and Arbitrary Decomposition Styles (ADS,DFS) that are used for sub-frame latency applications.

All the JPEG 2000 Parts have commonly used .j2c as a file extension for the raw JPEG 2000 codestream. Sometimes you will also see ".j2k" in use. Decoders can check the first 4 bytes of a file they expect is raw codestream, which should start 0xFF 0x4F 0xFF 0x51.

JPEG 2000 Part-1 also has defined a box-based JP2 file format (using .jp2 extension) that wraps the raw codestream with additional information, including color space, metadata, etc. JPEG 2000 Part-2 has a more flexible JPX box-based file format (using .jpx file format). JPEG 2000 Part-15 has modernized box-base file format JPH (using .jph) extension. Also(!!!) there has been new work on encapsulating JPEG 2000 in HEIF, standardized in JPEG 2000 Part-16.

Our industry's standard for wrapping JPEG 2000 in MXF SMPTE ST422, that is used by DCP and IMF for example, does not wrap the JPEG 2000 file formats (JP2, JPX, JPH, HEIF), instead it simply wraps the J2C raw codesteam.

@lgritz lgritz added enhancement Improvement of existing/working features. file formats Image file formats, ImageInput, ImageOutput labels Apr 21, 2025
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
…ugin).

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
…ove reader speed support.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
…rt of the main library.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
…h is compiled in.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
@richardssam
Copy link
Contributor Author

This is now a merge with the OpenJpeg plugin, the reader will attempt to use the OpenJPH library (if found) to read the input file, if it cannot read it, it will fall back on OpenJpeg to read it. For writing with the OpenJpeg library, if the compression flag is set to htj2k or the file extension is j2c the OpenJPH library will be called to write out the file.

I also want to point out the changes to the add_oiio_plugin code, to add support for the target_link_directories which was needed to get openjph properly linked in using a fairly vanilla pkg-config lookup (I think). This feels like a fairly standard option in cmake, so I'm hoping this is the correct fix (I'm not a cmake expert).

Copy link
Collaborator

@lgritz lgritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This LGTM.

I noted a few extremely minor questions / suggestions, if you are so inclined. (But consider them all optional.)

@lgritz
Copy link
Collaborator

lgritz commented May 31, 2025

OpenJPEG has supported encoding and decoding of Part-1 codestreams for close to 20 years and in May 2022, the OpenJPEG 2.5.0 release included decoding support for Part-15. This means that any tools (like OIIO, Imagemagick, PDF, RV, etc) that use OpenJPEG v2.5.0 or later can decode HTJ2K codestreams.

@richardssam @michaeldsmith Does this indicate that we should bump our OpenJPEG minimums?

  • Bring its VERSION_MIN to 2.2 (from its current true min of 2.0, with a "recommended" 2.2; note that 2.2 was released in 2017, so that's going unreasonably far back).
  • And make 2.5 the RECOMMENDED_MIN with the reason that it supports the HTJ2K.

Or even, raise the minimum to 2.5.0? It was released in May 2022, so technically is allowed under our "3 years back" rule, but it's cutting it pretty close. I have not checked which version is standard with various Linux distros, so maybe that's too aggressive if a lot of people have stock machines with 2.3, say, but will then find OIIO no longer will build OpenJPEG support without their taking the trouble to build a newer one from source as an extra step.

This is a question for whether we should have a second PR to raise this minimum. I wouldn't want it bundled into this PR. (One reason is that we could, conceivably, backport the capabilities of this PR to the release branch if you or others need it in tagged releases before the big annual release in September. That would be ok since it doesn't break backward compatibility, but raising raising the minimum version of a dependency is a breaking change that we don't allow to be backported to release branches.)

…rsion.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
… line within the function.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
@richardssam
Copy link
Contributor Author

@lgritz this code doesnt force an upgrade to OpenJpeg-2.5 if anything you should be able to run 2.2 with OpenJPH and get the HTJ2K benefits without needing to goto 2.5 (although there may be other improvements).
@michaeldsmith can answer better if there are other reasons to bump sooner.

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Copy link
Collaborator

@lgritz lgritz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@lgritz lgritz merged commit 4bb092e into AcademySoftwareFoundation:main Jun 1, 2025
70 checks passed
zachlewis pushed a commit to zachlewis/OpenImageIO that referenced this pull request Aug 1, 2025
… library. (AcademySoftwareFoundation#4699)

OIIO did already have a decompressor for HTJ2K via the OpenJpeg library,
but nothing for encoding. This adds the encoder using the OpenJPH
library, which will be needed for the HTJ2K encoding for OpenEXR too.

This is a merge with the OpenJpeg plugin, the reader will attempt to
use the OpenJPH library (if found) to read the input file, if it cannot
read it, it will fall back on OpenJpeg to read it. For writing with the
OpenJpeg library, if the compression flag is set to htj2k or the file
extension is j2c the OpenJPH library will be called to write out the
file.

If you want to do a lossy encode you can do it with:

```
oiiotool -i INPUTFILE --attrib jph:qstep 0.03 -o OUTPUTFILE.j2c

oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03 -o
OUTPUTFILE.j2k
```

Other options include:

```oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03
--attrib jph:num_decomps 3 --attrib jph:block_size 32,32 --attrib
jph:prog_order CPRL --attrib jph:precincts 128,128,256,256 -o
OUTPUTFILE.j2c
```

Added a htj2k test, it tests encoding a dox and a jpeg file, both
lossless and lossy.

---------

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
zachlewis pushed a commit to zachlewis/OpenImageIO that referenced this pull request Sep 1, 2025
… library. (AcademySoftwareFoundation#4699)

OIIO did already have a decompressor for HTJ2K via the OpenJpeg library,
but nothing for encoding. This adds the encoder using the OpenJPH
library, which will be needed for the HTJ2K encoding for OpenEXR too.

This is a merge with the OpenJpeg plugin, the reader will attempt to
use the OpenJPH library (if found) to read the input file, if it cannot
read it, it will fall back on OpenJpeg to read it. For writing with the
OpenJpeg library, if the compression flag is set to htj2k or the file
extension is j2c the OpenJPH library will be called to write out the
file.

If you want to do a lossy encode you can do it with:

```
oiiotool -i INPUTFILE --attrib jph:qstep 0.03 -o OUTPUTFILE.j2c

oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03 -o
OUTPUTFILE.j2k
```

Other options include:

```oiiotool -i INPUTFILE --compression htj2k --attrib jph:qstep 0.03
--attrib jph:num_decomps 3 --attrib jph:block_size 32,32 --attrib
jph:prog_order CPRL --attrib jph:precincts 128,128,256,256 -o
OUTPUTFILE.j2c
```

Added a htj2k test, it tests encoding a dox and a jpeg file, both
lossless and lossy.

---------

Signed-off-by: Sam.Richards@taurich.org <Sam.Richards@taurich.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improvement of existing/working features. file formats Image file formats, ImageInput, ImageOutput

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants