OpenEXR: Improve handling of ACES2065-1 chromaticities

Use aces_interchange role in OpenColorIO to identify the correct colorspace
for each config. Also use acesImageContainerFlag attribute from the ACES
container format to identify the colorspace.

Write ACES2065-1 chromaticities in EXR files when appropriate. This gets us
closer to supporting output of the ACES container format, though we don't
write acesImageContainerFlag. There are various restrictions that must be met
which are not very practical, and even exr2aces doesn't write it.

Pull Request: https://projects.blender.org/blender/blender/pulls/135823
This commit is contained in:
Brecht Van Lommel
2025-03-24 12:00:07 +01:00
parent f7054a2d2f
commit f5c3b8be5c
4 changed files with 56 additions and 17 deletions

View File

@@ -24,6 +24,7 @@ using OCIO_GPUShader = struct OCIO_GPUShader;
#define OCIO_ROLE_DEFAULT_BYTE "default_byte"
#define OCIO_ROLE_DEFAULT_FLOAT "default_float"
#define OCIO_ROLE_DEFAULT_SEQUENCER "default_sequencer"
#define OCIO_ROLE_ACES_INTERCHANGE "aces_interchange"
OCIO_DECLARE_HANDLE(OCIO_ConstConfigRc);
OCIO_DECLARE_HANDLE(OCIO_ConstColorSpaceRc);

View File

@@ -45,13 +45,13 @@ void IMB_colormanagement_assign_byte_colorspace(ImBuf *ibuf, const char *name);
const char *IMB_colormanagement_get_float_colorspace(ImBuf *ibuf);
const char *IMB_colormanagement_get_rect_colorspace(ImBuf *ibuf);
ColorSpace *IMB_colormanagement_space_get_named(const char *name);
bool IMB_colormanagement_space_is_data(ColorSpace *colorspace);
bool IMB_colormanagement_space_is_scene_linear(ColorSpace *colorspace);
bool IMB_colormanagement_space_is_srgb(ColorSpace *colorspace);
bool IMB_colormanagement_space_name_is_data(const char *name);
bool IMB_colormanagement_space_name_is_scene_linear(const char *name);
bool IMB_colormanagement_space_name_is_srgb(const char *name);
bool IMB_set_colorspace_name_if_exists(char dst_colorspace[], const char *name);
BLI_INLINE void IMB_colormanagement_get_luminance_coefficients(float r_rgb[3]);
@@ -507,6 +507,7 @@ enum {
COLOR_ROLE_DEFAULT_SEQUENCER,
COLOR_ROLE_DEFAULT_BYTE,
COLOR_ROLE_DEFAULT_FLOAT,
COLOR_ROLE_ACES_INTERCHANGE,
COLOR_ROLE_DATA,
};

View File

@@ -68,6 +68,7 @@ static char global_role_texture_painting[MAX_COLORSPACE_NAME];
static char global_role_default_byte[MAX_COLORSPACE_NAME];
static char global_role_default_float[MAX_COLORSPACE_NAME];
static char global_role_default_sequencer[MAX_COLORSPACE_NAME];
static char global_role_aces_interchange[MAX_COLORSPACE_NAME];
static ListBase global_colorspaces = {nullptr, nullptr};
static ListBase global_displays = {nullptr, nullptr};
@@ -464,7 +465,8 @@ static void colormanage_cache_handle_release(void *cache_handle)
static bool colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
char *colorspace_name,
const char *role,
const char *backup_role)
const char *backup_role,
const bool optional = false)
{
OCIO_ConstColorSpaceRcPtr *ociocs;
@@ -480,9 +482,10 @@ static bool colormanage_role_color_space_name_get(OCIO_ConstConfigRcPtr *config,
}
if (ociocs == nullptr) {
if (!G.quiet) {
if (!optional && !G.quiet) {
printf("Color management: Error, could not find role \"%s\"\n", role);
}
colorspace_name[0] = '\0';
return false;
}
@@ -513,6 +516,9 @@ static bool colormanage_load_config(OCIO_ConstConfigRcPtr *config)
ok &= colormanage_role_color_space_name_get(
config, global_role_default_float, OCIO_ROLE_DEFAULT_FLOAT, OCIO_ROLE_SCENE_LINEAR);
colormanage_role_color_space_name_get(
config, global_role_aces_interchange, OCIO_ROLE_ACES_INTERCHANGE, nullptr, true);
/* load colorspaces */
const int tot_colorspace = OCIO_configGetNumColorSpaces(config);
for (int index = 0; index < tot_colorspace; index++) {
@@ -1367,6 +1373,8 @@ const char *IMB_colormanagement_role_colorspace_name_get(int role)
return global_role_default_float;
case COLOR_ROLE_DEFAULT_BYTE:
return global_role_default_byte;
case COLOR_ROLE_ACES_INTERCHANGE:
return global_role_aces_interchange;
default:
if (!G.quiet) {
printf("Unknown role was passed to %s\n", __func__);
@@ -1453,6 +1461,11 @@ const char *IMB_colormanagement_get_rect_colorspace(ImBuf *ibuf)
return IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DEFAULT_BYTE);
}
ColorSpace *IMB_colormanagement_space_get_named(const char *name)
{
return colormanage_colorspace_get_named(name);
}
bool IMB_colormanagement_space_is_data(ColorSpace *colorspace)
{
return (colorspace && colorspace->is_data);
@@ -1507,14 +1520,6 @@ bool IMB_colormanagement_space_name_is_srgb(const char *name)
return (colorspace && IMB_colormanagement_space_is_srgb(colorspace));
}
bool IMB_set_colorspace_name_if_exists(char dst_colorspace[], const char *name)
{
ColorSpace *colorspace = colormanage_colorspace_get_named(name);
if (colorspace) {
BLI_strncpy(dst_colorspace, name, IM_MAX_SPACE);
}
}
blender::float3x3 IMB_colormanagement_get_xyz_to_scene_linear()
{
return blender::float3x3(imbuf_xyz_to_scene_linear);

View File

@@ -45,6 +45,7 @@
#include <OpenEXR/ImfCompressionAttribute.h>
#include <OpenEXR/ImfIO.h>
#include <OpenEXR/ImfInputFile.h>
#include <OpenEXR/ImfIntAttribute.h>
#include <OpenEXR/ImfOutputFile.h>
#include <OpenEXR/ImfPixelType.h>
#include <OpenEXR/ImfPreviewImage.h>
@@ -479,6 +480,21 @@ static void openexr_header_metadata(Header *header, ImBuf *ibuf)
/* Convert meters to inches. */
addXDensity(*header, ibuf->ppm[0] * 0.0254);
}
/* Write chromaticities for ACES-2065-1, as required by ACES container format. */
ColorSpace *colorspace = (ibuf->float_buffer.data) ? ibuf->float_buffer.colorspace :
(ibuf->byte_buffer.data) ? ibuf->byte_buffer.colorspace :
nullptr;
if (colorspace) {
const char *aces_colorspace = IMB_colormanagement_role_colorspace_name_get(
COLOR_ROLE_ACES_INTERCHANGE);
const char *ibuf_colorspace = IMB_colormanagement_colorspace_get_name(colorspace);
if (aces_colorspace && STREQ(aces_colorspace, ibuf_colorspace)) {
header->insert("chromaticities", TypedAttribute<Chromaticities>(CHROMATICITIES_ACES_2065_1));
header->insert("adoptedNeutral", TypedAttribute<V2f>(CHROMATICITIES_ACES_2065_1.white));
}
}
}
static void openexr_header_metadata_callback(void *data,
@@ -2135,15 +2151,31 @@ static void imb_exr_set_known_colorspace(const Header &header, char colorspace[I
return;
}
/* Read ACES container format metadata. */
const IntAttribute *header_aces_container = header.findTypedAttribute<IntAttribute>(
"acesImageContainerFlag");
const ChromaticitiesAttribute *header_chromaticities =
header.findTypedAttribute<ChromaticitiesAttribute>("chromaticities");
if (header_chromaticities) {
const Chromaticities &val = header_chromaticities->value();
if (imb_check_chromaticity_matches(val, CHROMATICITIES_XYZ_E)) {
IMB_set_colorspace_name_if_exists(colorspace, "Linear CIE-XYZ E");
if ((header_aces_container && header_aces_container->value() == 1) ||
(header_chromaticities &&
imb_check_chromaticity_matches(header_chromaticities->value(), CHROMATICITIES_ACES_2065_1)))
{
const char *known_colorspace = IMB_colormanagement_role_colorspace_name_get(
COLOR_ROLE_ACES_INTERCHANGE);
if (known_colorspace) {
BLI_strncpy(colorspace, known_colorspace, IMA_MAX_SPACE);
return;
}
else if (imb_check_chromaticity_matches(val, CHROMATICITIES_ACES_2065_1)) {
IMB_set_colorspace_name_if_exists(colorspace, "ACES2065-1");
}
else if (header_chromaticities &&
(imb_check_chromaticity_matches(header_chromaticities->value(), CHROMATICITIES_XYZ_E)))
{
/* Only works for the Blender default configuration due to fixed name. */
const char *known_colorspace = "Linear CIE-XYZ E";
if (IMB_colormanagement_space_get_named(known_colorspace)) {
BLI_strncpy(colorspace, known_colorspace, IMA_MAX_SPACE);
return;
}
}