Skip to content
Draft
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
3 changes: 3 additions & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,9 @@ macro (oiio_add_all_tests)
# Self-contained decompression-bomb regression (ships its own tiny fixture);
# exercises both the C++ and C-API readers via the openexr:core attribute.
oiio_add_tests (openexr-bomb)
# Self-contained multi-part colorInteropID inheritance test (ships its own
# tiny fixture); exercises both the C++ and C-API readers.
oiio_add_tests (openexr-multipart-colorspace)
# if (NOT DEFINED ENV{${PROJECT_NAME}_CI})
# oiio_add_tests (openexr-damaged
# IMAGEDIR openexr-images
Expand Down
2 changes: 2 additions & 0 deletions src/openexr.imageio/exr_pvt.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ class OpenEXRInput final : public ImageInput {
int m_miplevel; ///< What MIP level are we looking at?
std::vector<float> m_missingcolor; ///< Color for missing tile/scanline
std::string m_filename; // filename, if known
std::string m_file_color_interop_id;

void init()
{
Expand All @@ -248,6 +249,7 @@ class OpenEXRInput final : public ImageInput {
m_local_io.reset();
m_missingcolor.clear();
m_filename.clear();
m_file_color_interop_id.clear();
}

bool read_native_scanlines_individually(int subimage, int miplevel,
Expand Down
24 changes: 22 additions & 2 deletions src/openexr.imageio/exrinput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,17 @@ OpenEXRInput::valid_file(Filesystem::IOProxy* ioproxy) const



// Color space shared by all parts of the file, taken from the first part.
static std::string
file_color_interop_id(const Imf::MultiPartInputFile* multipart)
{
const Imf::StringAttribute* attr
= multipart->header(0).findTypedAttribute<Imf::StringAttribute>(
"colorInteropID");
return attr ? attr->value() : std::string();
}


bool
OpenEXRInput::open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config)
Expand Down Expand Up @@ -326,6 +337,8 @@ OpenEXRInput::open(const std::string& name, ImageSpec& newspec,
m_subimage = -1;
m_miplevel = -1;

m_file_color_interop_id = file_color_interop_id(m_input_multipart);

// Set up for the first subimage ("part"). This will trigger reading
// information about all the parts.
bool ok = seek_subimage(0, 0);
Expand Down Expand Up @@ -731,8 +744,15 @@ OpenEXRInput::PartInfo::parse_header(OpenEXRInput* in,
// Try to figure out the color space for some unambiguous cases
if (spec.get_int_attribute("acesImageContainerFlag") == 1) {
spec.set_colorspace("lin_ap0_scene");
} else if (auto c = spec.find_attribute("colorInteropID", TypeString)) {
spec.set_colorspace(c->get_ustring());
} else {
// Follow the color interop forum recommendation for OpenEXR files,
// inheriting the colorInteropID from the first part.
string_view interop_id = spec.get_string_attribute("colorInteropID");
if (!interop_id.empty()) {
spec.set_colorspace(interop_id);
} else if (!in->m_file_color_interop_id.empty()) {
spec.set_colorspace(in->m_file_color_interop_id);
}
}

// Squash some problematic texture metadata if we suspect it's wrong
Expand Down
30 changes: 28 additions & 2 deletions src/openexr.imageio/exrinput_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ class OpenEXRCoreInput final : public ImageInput {
int m_nsubimages; ///< How many subimages are there?
std::vector<float> m_missingcolor; ///< Color for missing tile/scanline
std::string m_filename; // filename, if known
std::string m_file_color_interop_id;

void init()
{
Expand All @@ -221,6 +222,7 @@ class OpenEXRCoreInput final : public ImageInput {
m_local_io.reset();
m_missingcolor.clear();
m_filename.clear();
m_file_color_interop_id.clear();
}

bool valid_file_or_proxy(const std::string& filename,
Expand Down Expand Up @@ -340,6 +342,21 @@ OpenEXRCoreInput::valid_file_or_proxy(const std::string& filename,



// Color space shared by all parts of the file, taken from the first part.
static std::string
file_color_interop_id(exr_context_t ctxt)
{
int32_t length = 0;
const char* interop = nullptr;
if (exr_attr_get_string(ctxt, 0, "colorInteropID", &length, &interop)
!= EXR_ERR_SUCCESS
|| !interop)
return std::string();

return std::string(interop, size_t(length));
}


bool
OpenEXRCoreInput::open(const std::string& name, ImageSpec& newspec,
const ImageSpec& config)
Expand Down Expand Up @@ -439,6 +456,8 @@ OpenEXRCoreInput::open(const std::string& name, ImageSpec& newspec,
m_subimage = -1;
m_miplevel = -1;

m_file_color_interop_id = file_color_interop_id(m_exr_context);

// Set up for the first subimage ("part"). This will trigger reading
// information about all the parts.
bool ok = seek_subimage(0, 0);
Expand Down Expand Up @@ -825,8 +844,15 @@ OpenEXRCoreInput::PartInfo::parse_header(OpenEXRCoreInput* in,
// Try to figure out the color space for some unambiguous cases
if (spec.get_int_attribute("acesImageContainerFlag") == 1) {
spec.set_colorspace("lin_ap0_scene");
} else if (auto c = spec.find_attribute("colorInteropID", TypeString)) {
spec.set_colorspace(c->get_ustring());
} else {
// Follow the color interop forum recommendation for OpenEXR files,
// inheriting the colorInteropID from the first part.
string_view interop_id = spec.get_string_attribute("colorInteropID");
if (!interop_id.empty()) {
spec.set_colorspace(interop_id);
} else if (!in->m_file_color_interop_id.empty()) {
spec.set_colorspace(in->m_file_color_interop_id);
}
}

// Squash some problematic texture metadata if we suspect it's wrong
Expand Down
39 changes: 39 additions & 0 deletions src/openexr.imageio/exroutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,39 @@ OpenEXROutput::open(const std::string& name, const ImageSpec& userspec,
}


// Follow the color interop forum recommendation for OpenEXR files,
// where the colorInteropID in later parts must match the first part,
// except when "data" or missing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could add a note here, suggesting to replace this function with checkColorMetadata from the OpenEXR library, once that's available. It does some additional checks, such as validating acesImageContainer flag and chromaticities. (Though I realize that OIIO will need its own copy for a while due to the need to support older EXR versions.)

static std::string
validate_color_interop_ids(const std::vector<Imf::Header>& headers)
{
string_view file_interop_id;

for (size_t s = 0; s < headers.size(); ++s) {
const Imf::StringAttribute* attr
= headers[s].findTypedAttribute<Imf::StringAttribute>(
"colorInteropID");
string_view interop_id = attr ? string_view(attr->value())
: string_view();

if (s == 0) {
file_interop_id = interop_id;
continue;
}

if (interop_id.empty() || interop_id == "data"
|| interop_id == file_interop_id)
continue;

return Strutil::fmt::format(
"OpenEXR subimage {} has color space \"{}\", different from \"{}\" in the first subimage",
s, interop_id, file_interop_id);
}

return "";
}



bool
OpenEXROutput::open(const std::string& name, int subimages,
Expand Down Expand Up @@ -758,6 +791,12 @@ OpenEXROutput::open(const std::string& name, int subimages,
}
}

std::string interop_id_error = validate_color_interop_ids(m_headers);
if (!interop_id_error.empty()) {
errorfmt("{}", interop_id_error);
return false;
}

m_spec = m_subimagespecs[0];
sanity_check_channelnames();
compute_pixeltypes(m_spec);
Expand Down
Loading
Loading