Blender V5.0
libocio_colorspace.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#if defined(WITH_OPENCOLORIO)
10
11# include <cmath>
12
13# include "BLI_math_color.h"
14
15# include "CLG_log.h"
16
17# include "../description.hh"
19# include "libocio_processor.hh"
20
21static CLG_LogRef LOG = {"color_management"};
22
23namespace blender::ocio {
24
25static bool compare_floats(float a, float b, float abs_diff, int ulp_diff)
26{
27 /* Returns true if the absolute difference is smaller than abs_diff (for numbers near zero)
28 * or their relative difference is less than ulp_diff ULPs. Based on:
29 * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
30 */
31 if (fabsf(a - b) < abs_diff) {
32 return true;
33 }
34
35 if ((a < 0.0f) != (b < 0.0f)) {
36 return false;
37 }
38
39 return (abs((*(int *)&a) - (*(int *)&b)) < ulp_diff);
40}
41
42static bool color_space_is_invertible(const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space)
43{
44 const StringRefNull family = ocio_color_space->getFamily();
45
46 if (ELEM(family, "rrt", "display")) {
47 /* assume display and rrt transformations are not invertible in fact some of them could be,
48 * but it doesn't make much sense to allow use them as invertible. */
49 return false;
50 }
51
52 if (ocio_color_space->isData()) {
53 /* Data color spaces don't have transformation at all. */
54 return true;
55 }
56
57 if (ocio_color_space->getTransform(OCIO_NAMESPACE::COLORSPACE_DIR_TO_REFERENCE)) {
58 /* if there's defined transform to reference space, color space could be converted to scene
59 * linear. */
60 return true;
61 }
62
63 return true;
64}
65
66static void color_space_is_builtin(const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config,
67 const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space,
68 bool &is_scene_linear,
69 bool &is_srgb)
70{
71 OCIO_NAMESPACE::ConstProcessorRcPtr processor = create_ocio_processor_silent(
72 ocio_config, ocio_color_space->getName(), OCIO_NAMESPACE::ROLE_SCENE_LINEAR);
73 if (!processor) {
74 /* Silently ignore if no conversion possible, then it's not scene linear or sRGB. */
75 is_scene_linear = false;
76 is_srgb = false;
77 return;
78 }
79
80 OCIO_NAMESPACE::ConstCPUProcessorRcPtr cpu_processor = processor->getDefaultCPUProcessor();
81
82 is_scene_linear = true;
83 is_srgb = true;
84 for (int i = 0; i < 256; i++) {
85 float v = i / 255.0f;
86
87 float cR[3] = {v, 0, 0};
88 float cG[3] = {0, v, 0};
89 float cB[3] = {0, 0, v};
90 float cW[3] = {v, v, v};
91 cpu_processor->applyRGB(cR);
92 cpu_processor->applyRGB(cG);
93 cpu_processor->applyRGB(cB);
94 cpu_processor->applyRGB(cW);
95
96 /* Make sure that there is no channel crosstalk. */
97 if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
98 fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
99 {
100 is_scene_linear = false;
101 is_srgb = false;
102 break;
103 }
104 /* Make sure that the three primaries combine linearly. */
105 if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
106 !compare_floats(cB[2], cW[2], 1e-6f, 64))
107 {
108 is_scene_linear = false;
109 is_srgb = false;
110 break;
111 }
112 /* Make sure that the three channels behave identically. */
113 if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
114 is_scene_linear = false;
115 is_srgb = false;
116 break;
117 }
118
119 float out_v = (cW[0] + cW[1] + cW[2]) * (1.0f / 3.0f);
120 if (!compare_floats(v, out_v, 1e-6f, 64)) {
121 is_scene_linear = false;
122 }
123 if (!compare_floats(srgb_to_linearrgb(v), out_v, 1e-4f, 64)) {
124 is_srgb = false;
125 }
126 }
127}
128
129LibOCIOColorSpace::LibOCIOColorSpace(const int index,
130 const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config,
131 const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space)
132 : ocio_config_(ocio_config),
133 ocio_color_space_(ocio_color_space),
134 clean_description_(cleanup_description(ocio_color_space->getDescription()))
135{
136 this->index = index;
137
138 is_invertible_ = color_space_is_invertible(ocio_color_space);
139
140 /* In OpenColorIO 2.5 there will be native support for this. For older configs and
141 * older OpenColorIO versions, check the aliases. This a convention used in the
142 * Blender and ACES 2.0 configs. */
143 const int num_aliases = ocio_color_space->getNumAliases();
144 for (int i = 0; i < num_aliases; i++) {
145 StringRefNull alias = ocio_color_space->getAlias(i);
146 if (alias == "srgb_display") {
147 interop_id_ = "srgb_rec709_display";
148 }
149 else if (alias == "displayp3_display") {
150 interop_id_ = "srgb_p3d65_display";
151 }
152 else if (alias == "displayp3_hdr_display") {
153 interop_id_ = "srgbe_p3d65_display";
154 }
155 else if (alias == "p3d65_display") {
156 interop_id_ = "g26_p3d65_display";
157 }
158 else if (alias == "rec1886_rec709_display") {
159 interop_id_ = "g24_rec709_display";
160 }
161 else if (alias == "rec2100_pq_display") {
162 interop_id_ = "pq_rec2020_display";
163 }
164 else if (alias == "rec2100_hlg_display") {
165 interop_id_ = "hlg_rec2020_display";
166 }
167 else if (alias == "st2084_p3d65_display") {
168 interop_id_ = "pq_p3d65_display";
169 }
170 else if (ELEM(alias, "lin_rec709_srgb", "lin_rec709")) {
171 interop_id_ = "lin_rec709_scene";
172 }
173 else if (alias == "lin_rec2020") {
174 interop_id_ = "lin_rec2020_scene";
175 }
176 else if (ELEM(alias, "lin_p3d65", "lin_displayp3")) {
177 interop_id_ = "lin_p3d65_scene";
178 }
179 else if ((alias.startswith("lin_") || alias.startswith("srgb_") || alias.startswith("g18_") ||
180 alias.startswith("g22_") || alias.startswith("g24_") || alias.startswith("g26_") ||
181 alias.startswith("pq_") || alias.startswith("hlg_")) &&
182 (alias.endswith("_scene") || alias.endswith("_display")))
183 {
184 interop_id_ = alias;
185 }
186
187 if (!interop_id_.is_empty()) {
188 break;
189 }
190 }
191
192 /* Special case that we can not handle as an alias, because it's a role too. */
193 if (interop_id_.is_empty()) {
194 const char *data_name = ocio_config->getRoleColorSpace(OCIO_NAMESPACE::ROLE_DATA);
195 if (data_name && STREQ(ocio_color_space->getName(), data_name)) {
196 interop_id_ = "data";
197 }
198 }
199
201 "Add colorspace: %s (interop ID: %s)",
202 name().c_str(),
203 interop_id_.is_empty() ? "<none>" : interop_id_.c_str());
204}
205
206bool LibOCIOColorSpace::is_scene_linear() const
207{
208 ensure_srgb_scene_linear_info();
209 return is_scene_linear_;
210}
211
212bool LibOCIOColorSpace::is_srgb() const
213{
214 ensure_srgb_scene_linear_info();
215 return is_srgb_;
216}
217
218const CPUProcessor *LibOCIOColorSpace::get_to_scene_linear_cpu_processor() const
219{
220 return to_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
221 OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_processor(
222 ocio_config_, ocio_color_space_->getName(), OCIO_NAMESPACE::ROLE_SCENE_LINEAR);
223 if (!ocio_processor) {
224 return nullptr;
225 }
226 return std::make_unique<LibOCIOCPUProcessor>(ocio_processor->getDefaultCPUProcessor());
227 });
228}
229
230const CPUProcessor *LibOCIOColorSpace::get_from_scene_linear_cpu_processor() const
231{
232 return from_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
233 OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_processor(
234 ocio_config_, OCIO_NAMESPACE::ROLE_SCENE_LINEAR, ocio_color_space_->getName());
235 if (!ocio_processor) {
236 return nullptr;
237 }
238 return std::make_unique<LibOCIOCPUProcessor>(ocio_processor->getDefaultCPUProcessor());
239 });
240}
241
242void LibOCIOColorSpace::ensure_srgb_scene_linear_info() const
243{
244 if (is_info_cached_) {
245 return;
246 }
247 color_space_is_builtin(ocio_config_, ocio_color_space_, is_scene_linear_, is_srgb_);
248 is_info_cached_ = true;
249}
250
251void LibOCIOColorSpace::clear_caches()
252{
253 from_scene_linear_cpu_processor_ = CPUProcessorCache();
254 to_scene_linear_cpu_processor_ = CPUProcessorCache();
255 is_info_cached_ = false;
256}
257
258} // namespace blender::ocio
259
260#endif
float srgb_to_linearrgb(float c)
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_TRACE(clg_ref,...)
Definition CLG_log.h:192
ATTR_WARN_UNUSED_RESULT const BMVert * v
#define abs
#define LOG(level)
Definition log.h:97
ccl_device_inline bool compare_floats(const float a, const float b, float abs_diff, const int ulp_diff)
Definition math_base.h:769
std::string cleanup_description(const StringRef description)
const char * name
#define fabsf
i
Definition text_draw.cc:230