Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/openexr.imageio/exr_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ void split_name(string_view fullname, string_view& layer, string_view& suffix);
// Do the channels appear to be R, G, B (or known common aliases)?
bool channels_are_rgb(const ImageSpec& spec);

// If `compression` (an Imf::Compression / exr_compression_t) is one of the
// HTJ2K family, make sure OpenJPH's one-time encoder setup has happened on
// a single thread (works around a race in OpenJPH < 0.27).
void prime_htj2k_if_needed(int compression);

} // namespace pvt


Expand Down
1 change: 1 addition & 0 deletions src/openexr.imageio/exrinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,7 @@ OpenEXRInput::PartInfo::parse_header(OpenEXRInput* in,
compressattr = header->findTypedAttribute<Imf::CompressionAttribute>(
"compression");
if (compressattr) {
pvt::prime_htj2k_if_needed(compressattr->value());
const char* comp = NULL;
switch (compressattr->value()) {
case Imf::NO_COMPRESSION: comp = "none"; break;
Expand Down
1 change: 1 addition & 0 deletions src/openexr.imageio/exrinput_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ OpenEXRCoreInput::PartInfo::parse_header(OpenEXRCoreInput* in,
exr_compression_t comptype;
rv = exr_get_compression(ctxt, subimage, &comptype);
if (rv == EXR_ERR_SUCCESS) {
pvt::prime_htj2k_if_needed(comptype);
const char* comp = NULL;
switch (comptype) {
case EXR_COMPRESSION_NONE: comp = "none"; break;
Expand Down
60 changes: 60 additions & 0 deletions src/openexr.imageio/exroutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,63 @@ class OpenEXROutputStream final : public Imf::OStream {



// OpenJPH before 0.27 lazily initializes its block encoder tables without
// any synchronization, and both encoding and decoding run that setup
// (zeroing the tables before filling them). When OpenEXR compresses or
// decompresses several HTJ2K chunks in parallel, the first operations in
// the process can race and write corrupt data. Encode one tiny single-chunk
// image, on one thread, before any real HTJ2K I/O so the initialization is
// complete before it matters. OpenEXR records the OpenJPH version it was
// built against, so skip this entirely when that is new enough.
#if defined(IMF_HTJ2K256_COMPRESSION) \
&& (OPENEXR_OPENJPH_VERSION_MAJOR + 0) * 10000 \
+ (OPENEXR_OPENJPH_VERSION_MINOR + 0) * 100 \
< 2700
# define OIIO_EXR_PRIME_HTJ2K 1
#endif

namespace pvt {

void
prime_htj2k_if_needed(int compression)
{
#ifdef OIIO_EXR_PRIME_HTJ2K
if (compression != Imf::HTJ2K256_COMPRESSION
# ifdef IMF_HTJ2K32_COMPRESSION
&& compression != Imf::HTJ2K32_COMPRESSION
# endif
# ifdef IMF_LJ2K_COMPRESSION
&& compression != Imf::LJ2K_COMPRESSION
# endif
)
return;
static std::once_flag primed;
std::call_once(primed, []() {
try {
Filesystem::IOVecOutput io;
OpenEXROutputStream stream("htj2k-prime", &io);
Imf::Header header(8, 8);
header.compression() = Imf::HTJ2K256_COMPRESSION;
header.channels().insert("Y", Imf::Channel(Imf::HALF));
half pixels[8 * 8] = {};
Imf::FrameBuffer fb;
fb.insert("Y", Imf::Slice(Imf::HALF, (char*)pixels, sizeof(half),
8 * sizeof(half)));
Imf::OutputFile out(stream, header, 0 /* no threads */);
out.setFrameBuffer(fb);
out.writePixels(8);
Comment on lines +145 to +147
} catch (...) {
}
Comment on lines +148 to +149

@lgritz lgritz Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

My robot and I think that Copilot is wrong about this. Ignoring.

});
#else
(void)compression;
#endif
}

} // namespace pvt



class OpenEXROutput final : public ImageOutput {
public:
OpenEXROutput();
Expand Down Expand Up @@ -596,6 +653,7 @@ OpenEXROutput::open(const std::string& name, const ImageSpec& userspec,
e.size() ? e : std::string("unknown error"));
return false;
}
pvt::prime_htj2k_if_needed(m_headers[m_subimage].compression());
m_output_stream.reset(new OpenEXROutputStream(name.c_str(), m_io));
if (m_spec.tile_width) {
m_output_tiled.reset(
Expand Down Expand Up @@ -827,6 +885,8 @@ OpenEXROutput::open(const std::string& name, int subimages,
e.size() ? e : std::string("unknown error"));
return false;
}
for (int s = 0; s < subimages; ++s)
pvt::prime_htj2k_if_needed(m_headers[s].compression());
m_output_stream.reset(new OpenEXROutputStream(name.c_str(), m_io));
m_output_multipart.reset(new Imf::MultiPartOutputFile(*m_output_stream,
&m_headers[0],
Expand Down
Loading