Color Management: Builtin support for ACEScg and Rec.2020 linear spaces

With new functions to convert to/from scene linear in the Python API. ACEScg
in particular is common in USD and MaterialX files.

Pull Request: https://projects.blender.org/blender/blender/pulls/145755
This commit is contained in:
Brecht Van Lommel
2025-08-31 02:56:21 +02:00
parent efc8970015
commit ea0fab21d4
6 changed files with 155 additions and 15 deletions

View File

@@ -110,6 +110,14 @@ BLI_INLINE void IMB_colormanagement_aces_to_scene_linear(float scene_linear[3],
const float aces[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_aces(float aces[3],
const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_acescg_to_scene_linear(float scene_linear[3],
const float acescg[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_acescg(float acescg[3],
const float scene_linear[3]);
BLI_INLINE void IMB_colormanagement_rec2020_to_scene_linear(float scene_linear[3],
const float rec2020[3]);
BLI_INLINE void IMB_colormanagement_scene_linear_to_rec2020(float rec2020[3],
const float scene_linear[3]);
blender::float3x3 IMB_colormanagement_get_xyz_to_scene_linear();
blender::float3x3 IMB_colormanagement_get_scene_linear_to_xyz();

View File

@@ -26,8 +26,12 @@ extern blender::float3x3 imbuf_scene_linear_to_xyz;
extern blender::float3x3 imbuf_xyz_to_scene_linear;
extern blender::float3x3 imbuf_scene_linear_to_aces;
extern blender::float3x3 imbuf_aces_to_scene_linear;
extern blender::float3x3 imbuf_scene_linear_to_acescg;
extern blender::float3x3 imbuf_acescg_to_scene_linear;
extern blender::float3x3 imbuf_scene_linear_to_rec709;
extern blender::float3x3 imbuf_rec709_to_scene_linear;
extern blender::float3x3 imbuf_scene_linear_to_rec2020;
extern blender::float3x3 imbuf_rec2020_to_scene_linear;
extern bool imbuf_scene_linear_is_rec709;
#define MAX_COLORSPACE_NAME 64

View File

@@ -98,8 +98,12 @@ float3x3 imbuf_scene_linear_to_xyz = float3x3::zero();
float3x3 imbuf_xyz_to_scene_linear = float3x3::zero();
float3x3 imbuf_scene_linear_to_rec709 = float3x3::zero();
float3x3 imbuf_rec709_to_scene_linear = float3x3::zero();
float3x3 imbuf_scene_linear_to_rec2020 = float3x3::zero();
float3x3 imbuf_rec2020_to_scene_linear = float3x3::zero();
float3x3 imbuf_scene_linear_to_aces = float3x3::zero();
float3x3 imbuf_aces_to_scene_linear = float3x3::zero();
float3x3 imbuf_scene_linear_to_acescg = float3x3::zero();
float3x3 imbuf_acescg_to_scene_linear = float3x3::zero();
bool imbuf_scene_linear_is_rec709 = false;
/* lock used by pre-cached processors getters, so processor wouldn't
@@ -514,6 +518,31 @@ static bool colormanage_role_color_space_name_get(ocio::Config &config,
return true;
}
static void colormanage_update_matrices()
{
/* Load luminance coefficients. */
imbuf_luma_coefficients = g_config->get_default_luma_coefs();
/* Load standard color spaces. */
imbuf_xyz_to_scene_linear = g_config->get_xyz_to_scene_linear_matrix();
imbuf_scene_linear_to_xyz = math::invert(imbuf_xyz_to_scene_linear);
imbuf_scene_linear_to_rec709 = ocio::XYZ_TO_REC709 * imbuf_scene_linear_to_xyz;
imbuf_rec709_to_scene_linear = math::invert(imbuf_scene_linear_to_rec709);
imbuf_scene_linear_to_rec2020 = ocio::XYZ_TO_REC2020 * imbuf_scene_linear_to_xyz;
imbuf_rec2020_to_scene_linear = math::invert(imbuf_scene_linear_to_rec2020);
imbuf_aces_to_scene_linear = imbuf_xyz_to_scene_linear * ocio::ACES_TO_XYZ;
imbuf_scene_linear_to_aces = math::invert(imbuf_aces_to_scene_linear);
imbuf_acescg_to_scene_linear = imbuf_xyz_to_scene_linear * ocio::ACESCG_TO_XYZ;
imbuf_scene_linear_to_acescg = math::invert(imbuf_acescg_to_scene_linear);
imbuf_scene_linear_is_rec709 = math::is_equal(
imbuf_scene_linear_to_rec709, float3x3::identity(), 0.0001f);
}
static bool colormanage_load_config(ocio::Config &config)
{
bool ok = true;
@@ -561,21 +590,7 @@ static bool colormanage_load_config(ocio::Config &config)
}
}
/* Load luminance coefficients. */
imbuf_luma_coefficients = config.get_default_luma_coefs();
/* Load standard color spaces. */
imbuf_xyz_to_scene_linear = config.get_xyz_to_scene_linear_matrix();
imbuf_scene_linear_to_xyz = math::invert(imbuf_xyz_to_scene_linear);
imbuf_scene_linear_to_rec709 = ocio::XYZ_TO_REC709 * imbuf_scene_linear_to_xyz;
imbuf_rec709_to_scene_linear = math::invert(imbuf_scene_linear_to_rec709);
imbuf_aces_to_scene_linear = imbuf_xyz_to_scene_linear * ocio::ACES_TO_XYZ;
imbuf_scene_linear_to_aces = math::invert(imbuf_aces_to_scene_linear);
imbuf_scene_linear_is_rec709 = math::is_equal(
imbuf_scene_linear_to_rec709, float3x3::identity(), 0.0001f);
colormanage_update_matrices();
return ok;
}

View File

@@ -94,4 +94,24 @@ void IMB_colormanagement_scene_linear_to_aces(float aces[3], const float scene_l
mul_v3_m3v3(aces, imbuf_scene_linear_to_aces.ptr(), scene_linear);
}
void IMB_colormanagement_acescg_to_scene_linear(float scene_linear[3], const float acescg[3])
{
mul_v3_m3v3(scene_linear, imbuf_acescg_to_scene_linear.ptr(), acescg);
}
void IMB_colormanagement_scene_linear_to_acescg(float acescg[3], const float scene_linear[3])
{
mul_v3_m3v3(acescg, imbuf_scene_linear_to_acescg.ptr(), scene_linear);
}
void IMB_colormanagement_rec2020_to_scene_linear(float scene_linear[3], const float rec2020[3])
{
mul_v3_m3v3(scene_linear, imbuf_rec2020_to_scene_linear.ptr(), rec2020);
}
void IMB_colormanagement_scene_linear_to_rec2020(float rec2020[3], const float scene_linear[3])
{
mul_v3_m3v3(rec2020, imbuf_scene_linear_to_rec2020.ptr(), scene_linear);
}
#endif /* __IMB_COLORMANAGEMENT_INLINE_H__ */

View File

@@ -15,10 +15,23 @@ static const float3x3 XYZ_TO_REC709{{3.2409699f, -0.9692436f, 0.0556301f},
{-1.5373832f, 1.8759675f, -0.2039770f},
{-0.4986108f, 0.0415551f, 1.0569715f}};
/* Standard XYZ (D65) to linear Rec.2020 transform. */
/* Use four-digit constants instead of higher precisions to match sRGB and Rec.2020 standards.
* See PR #141027 for details. */
static const float3x3 XYZ_TO_REC2020{{1.7166512f, -0.6666844, 0.0176399f},
{-0.3556708f, 1.6164812f, -0.0427706f},
{-0.2533663f, 0.0157685f, 0.9421031f}};
/* Standard ACES to XYZ (D65) transform.
* Matches OpenColorIO builtin transform: UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */
static const float3x3 ACES_TO_XYZ = {{0.938280f, 0.337369f, 0.001174f},
{-0.004451f, 0.729522f, -0.003711f},
{0.016628f, -0.066890f, 1.091595f}};
/* Standard ACEScg to XYZ (D65) transform.
* Matches OpenColorIO builtin transform: UTILITY - ACES-AP1_to_CIE-XYZ-D65_BFD. */
static const float3x3 ACESCG_TO_XYZ = {{0.652238f, 0.267672f, -0.005382f},
{0.128237f, 0.674340f, 0.001369f},
{0.169983f, 0.057988f, 1.093071f}};
} // namespace blender::ocio

View File

@@ -195,6 +195,38 @@ static PyObject *Color_from_aces_to_scene_linear(ColorObject *self)
return Color_CreatePyObject(col, Py_TYPE(self));
}
PyDoc_STRVAR(
/* Wrap. */
Color_from_scene_linear_to_acescg_doc,
".. function:: from_scene_linear_to_acescg()\n"
"\n"
" Convert from scene linear to ACEScg linear color space.\n"
"\n"
" :return: A color in ACEScg linear color space.\n"
" :rtype: :class:`Color`\n");
static PyObject *Color_from_scene_linear_to_acescg(ColorObject *self)
{
float col[3];
IMB_colormanagement_scene_linear_to_acescg(col, self->col);
return Color_CreatePyObject(col, Py_TYPE(self));
}
PyDoc_STRVAR(
/* Wrap. */
Color_from_acescg_to_scene_linear_doc,
".. function:: from_acescg_to_scene_linear()\n"
"\n"
" Convert from ACEScg linear to scene linear color space.\n"
"\n"
" :return: A color in scene linear color space.\n"
" :rtype: :class:`Color`\n");
static PyObject *Color_from_acescg_to_scene_linear(ColorObject *self)
{
float col[3];
IMB_colormanagement_acescg_to_scene_linear(col, self->col);
return Color_CreatePyObject(col, Py_TYPE(self));
}
PyDoc_STRVAR(
/* Wrap. */
Color_from_scene_linear_to_rec709_linear_doc,
@@ -227,6 +259,38 @@ static PyObject *Color_from_rec709_linear_to_scene_linear(ColorObject *self)
return Color_CreatePyObject(col, Py_TYPE(self));
}
PyDoc_STRVAR(
/* Wrap. */
Color_from_scene_linear_to_rec2020_linear_doc,
".. function:: from_scene_linear_to_rec2020_linear()\n"
"\n"
" Convert from scene linear to Rec.2020 linear color space.\n"
"\n"
" :return: A color in Rec.2020 linear color space.\n"
" :rtype: :class:`Color`\n");
static PyObject *Color_from_scene_linear_to_rec2020_linear(ColorObject *self)
{
float col[3];
IMB_colormanagement_scene_linear_to_rec2020(col, self->col);
return Color_CreatePyObject(col, Py_TYPE(self));
}
PyDoc_STRVAR(
/* Wrap. */
Color_from_rec2020_linear_to_scene_linear_doc,
".. function:: from_rec2020_linear_to_scene_linear()\n"
"\n"
" Convert from Rec.2020 linear color space to scene linear color space.\n"
"\n"
" :return: A color in scene linear color space.\n"
" :rtype: :class:`Color`\n");
static PyObject *Color_from_rec2020_linear_to_scene_linear(ColorObject *self)
{
float col[3];
IMB_colormanagement_rec2020_to_scene_linear(col, self->col);
return Color_CreatePyObject(col, Py_TYPE(self));
}
#endif /* !MATH_STANDALONE */
/** \} */
@@ -1220,6 +1284,14 @@ static PyMethodDef Color_methods[] = {
(PyCFunction)Color_from_aces_to_scene_linear,
METH_NOARGS,
Color_from_aces_to_scene_linear_doc},
{"from_scene_linear_to_acescg",
(PyCFunction)Color_from_scene_linear_to_acescg,
METH_NOARGS,
Color_from_scene_linear_to_acescg_doc},
{"from_acescg_to_scene_linear",
(PyCFunction)Color_from_acescg_to_scene_linear,
METH_NOARGS,
Color_from_acescg_to_scene_linear_doc},
{"from_scene_linear_to_rec709_linear",
(PyCFunction)Color_from_scene_linear_to_rec709_linear,
METH_NOARGS,
@@ -1228,6 +1300,14 @@ static PyMethodDef Color_methods[] = {
(PyCFunction)Color_from_rec709_linear_to_scene_linear,
METH_NOARGS,
Color_from_rec709_linear_to_scene_linear_doc},
{"from_scene_linear_to_rec2020_linear",
(PyCFunction)Color_from_scene_linear_to_rec2020_linear,
METH_NOARGS,
Color_from_scene_linear_to_rec2020_linear_doc},
{"from_rec2020_linear_to_scene_linear",
(PyCFunction)Color_from_rec2020_linear_to_scene_linear,
METH_NOARGS,
Color_from_rec2020_linear_to_scene_linear_doc},
#endif /* !MATH_STANDALONE */
{nullptr, nullptr, 0, nullptr},