Blender V4.5
libocio_config.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
5#include "libocio_config.hh"
6
7#if defined(WITH_OPENCOLORIO)
8
9# include <algorithm>
10# include <numeric>
11
12# include <fmt/format.h>
13
14# include "BLI_array.hh"
15# include "BLI_assert.h"
16# include "BLI_index_range.hh"
17# include "BLI_math_matrix.hh"
18
19# include "OCIO_matrix.hh"
20# include "OCIO_role_names.hh"
21
22# include "error_handling.hh"
23# include "libocio_colorspace.hh"
26# include "libocio_processor.hh"
27
28namespace blender::ocio {
29
30/* -------------------------------------------------------------------- */
33
34std::unique_ptr<Config> LibOCIOConfig::create_from_environment()
35{
36 try {
37 OCIO_NAMESPACE::ConstConfigRcPtr ocio_config = OCIO_NAMESPACE::Config::CreateFromEnv();
38 if (!ocio_config) {
39 return nullptr;
40 }
41
42 return std::unique_ptr<LibOCIOConfig>(new LibOCIOConfig(ocio_config));
43 }
44 catch (OCIO_NAMESPACE::Exception &exception) {
45 report_exception(exception);
46 }
47
48 return nullptr;
49}
50
51std::unique_ptr<Config> LibOCIOConfig::create_from_file(const StringRefNull filename)
52{
53 try {
54 OCIO_NAMESPACE::ConstConfigRcPtr ocio_config = OCIO_NAMESPACE::Config::CreateFromFile(
55 filename.c_str());
56 if (!ocio_config) {
57 return nullptr;
58 }
59
60 return std::unique_ptr<LibOCIOConfig>(new LibOCIOConfig(ocio_config));
61 }
62 catch (OCIO_NAMESPACE::Exception &exception) {
63 report_exception(exception);
64 }
65
66 return nullptr;
67}
68
69LibOCIOConfig::LibOCIOConfig(const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config)
70{
71 BLI_assert(ocio_config);
72
73 /* Set the global OpenColorIO configuration so that other parts of Blender can access it. For
74 * example, Cycles uses for own color management of textures.
75 *
76 * Acquire a pointer to the configuration and pass it around explicitly to avoid unneeded shared
77 * pointer acquisition. */
78 OCIO_NAMESPACE::SetCurrentConfig(ocio_config);
79 ocio_config_ = OCIO_NAMESPACE::GetCurrentConfig();
80
81 initialize_active_color_spaces();
82 initialize_inactive_color_spaces();
83 initialize_looks();
84 initialize_displays();
85}
86
87LibOCIOConfig::~LibOCIOConfig() {}
88
89void LibOCIOConfig::initialize_active_color_spaces()
90{
91 OCIO_NAMESPACE::ColorSpaceSetRcPtr ocio_color_spaces;
92
93 try {
94 ocio_color_spaces = ocio_config_->getColorSpaces(nullptr);
95 }
96 catch (OCIO_NAMESPACE::Exception &exception) {
97 report_exception(exception);
98 return;
99 }
100
101 if (!ocio_color_spaces) {
102 report_error("Invalid OpenColorIO configuration: color spaces set is nullptr");
103 return;
104 }
105
106 const int num_color_spaces = ocio_color_spaces->getNumColorSpaces();
107 if (num_color_spaces < 0) {
108 report_error(fmt::format(
109 "Invalid OpenColorIO configuration: invalid number of color spaces {}", num_color_spaces));
110 return;
111 }
112
113 color_spaces_.reserve(num_color_spaces);
114
115 for (const int i : IndexRange(num_color_spaces)) {
116 const OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_color_space =
117 ocio_color_spaces->getColorSpaceByIndex(i);
118 color_spaces_.append_as(i, ocio_config_, ocio_color_space);
119 }
120
121 /* Create index array for access to the color space in alphabetic order. */
122 sorted_color_space_index_.resize(num_color_spaces);
123 std::iota(sorted_color_space_index_.begin(), sorted_color_space_index_.end(), 0);
124 std::sort(sorted_color_space_index_.begin(), sorted_color_space_index_.end(), [&](int a, int b) {
125 return color_spaces_[a].name() < color_spaces_[b].name();
126 });
127}
128
129void LibOCIOConfig::initialize_inactive_color_spaces()
130{
131 const int num_inactive_color_spaces = ocio_config_->getNumColorSpaces(
132 OCIO_NAMESPACE::SEARCH_REFERENCE_SPACE_ALL, OCIO_NAMESPACE::COLORSPACE_INACTIVE);
133 if (num_inactive_color_spaces < 0) {
134 report_error(fmt::format(
135 "Invalid OpenColorIO configuration: invalid number of inactive color spaces {}",
136 num_inactive_color_spaces));
137 return;
138 }
139
140 for (const int i : IndexRange(num_inactive_color_spaces)) {
141 const char *colorspace_name = ocio_config_->getColorSpaceNameByIndex(
142 OCIO_NAMESPACE::SEARCH_REFERENCE_SPACE_ALL, OCIO_NAMESPACE::COLORSPACE_INACTIVE, i);
143
144 OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_color_space;
145 try {
146 ocio_color_space = ocio_config_->getColorSpace(colorspace_name);
147 }
148 catch (OCIO_NAMESPACE::Exception &exception) {
149 report_exception(exception);
150 continue;
151 }
152
153 inactive_color_spaces_.append_as(i, ocio_config_, ocio_color_space);
154 }
155}
156
157void LibOCIOConfig::initialize_looks()
158{
159 const int num_looks = ocio_config_->getNumLooks();
160
161 looks_.reserve(num_looks + 1);
162
163 /* Add entry for look None. */
164 looks_.append_as(0, nullptr);
165
166 for (const int i : IndexRange(num_looks)) {
167 const StringRefNull view_name = ocio_config_->getLookNameByIndex(i);
168
169 /* Look None is built-in and always exists. Skip it from the configuration. */
170 if (view_name == "None") {
171 continue;
172 }
173
174 const OCIO_NAMESPACE::ConstLookRcPtr ocio_look = ocio_config_->getLook(view_name.c_str());
175 looks_.append_as(i + 1, ocio_look);
176 }
177}
178
179void LibOCIOConfig::initialize_displays()
180{
181 const int num_displays = ocio_config_->getNumDisplays();
182 if (num_displays < 0) {
183 report_error(fmt::format("Invalid OpenColorIO configuration: invalid number of displays {}",
184 num_displays));
185 return;
186 }
187
188 displays_.reserve(num_displays);
189
190 for (const int i : IndexRange(num_displays)) {
191 displays_.append_as(i, *this);
192 }
193}
194
196
197/* -------------------------------------------------------------------- */
200
201float3 LibOCIOConfig::get_default_luma_coefs() const
202{
203 try {
204 double rgb_double[3];
205 ocio_config_->getDefaultLumaCoefs(rgb_double);
206
207 return float3(rgb_double[0], rgb_double[1], rgb_double[2]);
208 }
209 catch (OCIO_NAMESPACE::Exception &exception) {
210 report_exception(exception);
211 }
212
213 /* Fallback to the older Blender assumed primaries of ITU-BT.709 / sRGB, matching the
214 * coefficients used in the fallback implementation. */
215 return float3(0.2126f, 0.7152f, 0.0722f);
216}
217
218static bool to_scene_linear_matrix(const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config,
219 const StringRefNull colorspace,
220 float3x3 &to_scene_linear)
221{
222 const OCIO_NAMESPACE::ConstProcessorRcPtr processor = create_ocio_processor(
223 ocio_config, colorspace.c_str(), OCIO_NAMESPACE::ROLE_SCENE_LINEAR);
224 if (!processor) {
225 return false;
226 }
227
228 const OCIO_NAMESPACE::ConstCPUProcessorRcPtr cpu_processor = processor->getDefaultCPUProcessor();
229 to_scene_linear = float3x3::identity();
230 cpu_processor->applyRGB(to_scene_linear[0]);
231 cpu_processor->applyRGB(to_scene_linear[1]);
232 cpu_processor->applyRGB(to_scene_linear[2]);
233
234 return true;
235}
236
237float3x3 LibOCIOConfig::get_xyz_to_scene_linear_matrix() const
238{
239 /* Default to ITU-BT.709 in case no appropriate transform found.
240 * Note XYZ is defined here as having a D65 white point. */
241 float3x3 xyz_to_scene_linear = XYZ_TO_REC709;
242
243 /* Get from OpenColorO config if it has the required roles. */
244 if (!ocio_config_->hasRole(OCIO_NAMESPACE::ROLE_SCENE_LINEAR)) {
245 return xyz_to_scene_linear;
246 }
247
248 if (ocio_config_->hasRole("aces_interchange")) {
249 /* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
250 float3x3 aces_to_scene_linear;
251 if (to_scene_linear_matrix(ocio_config_, "aces_interchange", aces_to_scene_linear)) {
252 float3x3 xyz_to_aces = math::invert(ACES_TO_XYZ);
253 xyz_to_scene_linear = aces_to_scene_linear * xyz_to_aces;
254 }
255 }
256 else if (ocio_config_->hasRole("XYZ")) {
257 /* Custom role used before the standard existed. */
258 to_scene_linear_matrix(ocio_config_, "XYZ", xyz_to_scene_linear);
259 }
260
261 return xyz_to_scene_linear;
262}
263
264const char *LibOCIOConfig::get_color_space_from_filepath(const char *filepath) const
265{
266 /* Ignore the default rule, same behavior as for example OpenImageIO and xStudio.
267 * The ACES studio config has only a default rule set to ACES2065-1, which works
268 * poorly if we assign it to every file as default.
269 *
270 * It's unclear if the default rule should be used for anything, and if not why
271 * it even exists. */
272 if (ocio_config_->filepathOnlyMatchesDefaultRule(filepath)) {
273 return nullptr;
274 }
275
276 return ocio_config_->getColorSpaceFromFilepath(filepath);
277}
278
280
281/* -------------------------------------------------------------------- */
284
285const ColorSpace *LibOCIOConfig::get_color_space(const StringRefNull name) const
286{
287 OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_color_space;
288
289 try {
290 /* Lookup color space in the OpenColorIO, letting it resolve role name or an alias. */
291 ocio_color_space = ocio_config_->getColorSpace(name.c_str());
292 }
293 catch (OCIO_NAMESPACE::Exception &exception) {
294 report_exception(exception);
295 return nullptr;
296 }
297
298 if (!ocio_color_space) {
299 return nullptr;
300 }
301
302 /* TODO(sergey): Is there faster way to lookup Blender-side color space?
303 * It does not seem that pointer in ConstColorSpaceRcPtr is unique enough to use for
304 * comparison. */
305 for (const LibOCIOColorSpace &color_space : color_spaces_) {
306 if (color_space.name() == ocio_color_space->getName()) {
307 return &color_space;
308 }
309 }
310
311 /* Also lookup in the inactive color space, as the requested space might be coming from the
312 * display and marked as inactive to prevent it from showing up in the application menu. */
313 for (const LibOCIOColorSpace &color_space : inactive_color_spaces_) {
314 if (color_space.name() == ocio_color_space->getName()) {
315 return &color_space;
316 }
317 }
318
319 report_error(
320 fmt::format("Invalid OpenColorIO configuration: color space {} not found on Blender side",
321 ocio_color_space->getName()));
322
323 return nullptr;
324}
325
326int LibOCIOConfig::get_num_color_spaces() const
327{
328 return color_spaces_.size();
329}
330
331const ColorSpace *LibOCIOConfig::get_color_space_by_index(int const index) const
332{
333 if (index < 0 || index >= color_spaces_.size()) {
334 return nullptr;
335 }
336 return &color_spaces_[index];
337}
338
339const ColorSpace *LibOCIOConfig::get_sorted_color_space_by_index(const int index) const
340{
341 BLI_assert(color_spaces_.size() == sorted_color_space_index_.size());
342 if (index < 0 || index >= color_spaces_.size()) {
343 return nullptr;
344 }
345 return get_color_space_by_index(sorted_color_space_index_[index]);
346}
347
349
350/* -------------------------------------------------------------------- */
353
354const Display *LibOCIOConfig::get_default_display() const
355{
356 if (displays_.is_empty()) {
357 return nullptr;
358 }
359 /* Matches the behavior of OpenColorIO, but avoids using API which potentially throws exception
360 * and requires string lookups. */
361 return &displays_[0];
362}
363
364const Display *LibOCIOConfig::get_display_by_name(const StringRefNull name) const
365{
366 //* TODO(sergey): Is there faster way to lookup Blender-side display?
367 for (const LibOCIODisplay &display : displays_) {
368 if (display.name() == name) {
369 return &display;
370 }
371 }
372 return nullptr;
373}
374
375int LibOCIOConfig::get_num_displays() const
376{
377 return displays_.size();
378}
379
380const Display *LibOCIOConfig::get_display_by_index(int index) const
381{
382 if (index < 0 || index >= displays_.size()) {
383 return nullptr;
384 }
385 return &displays_[index];
386}
387
389
390/* -------------------------------------------------------------------- */
393
394const ColorSpace *LibOCIOConfig::get_display_view_color_space(const StringRefNull display,
395 const StringRefNull view) const
396{
397 StringRefNull display_color_space;
398
399 try {
400 display_color_space = ocio_config_->getDisplayViewColorSpaceName(display.c_str(),
401 view.c_str());
402 /* OpenColorIO does not resolve this token for us, so do it ourselves. */
403 if (strcasecmp(display_color_space.c_str(), "<USE_DISPLAY_NAME>") == 0) {
404 display_color_space = display;
405 }
406 }
407 catch (OCIO_NAMESPACE::Exception &exception) {
408 report_exception(exception);
409 display_color_space = display;
410 }
411
412 return get_color_space(display_color_space);
413}
414
416
417/* -------------------------------------------------------------------- */
420
421const Look *LibOCIOConfig::get_look_by_name(const StringRefNull name) const
422{
423 /* TODO(sergey): Is there faster way to lookup Blender-side look? */
424 for (const LibOCIOLook &look : looks_) {
425 if (look.name() == name) {
426 return &look;
427 }
428 }
429 return nullptr;
430}
431
432int LibOCIOConfig::get_num_looks() const
433{
434 return looks_.size();
435}
436
437const Look *LibOCIOConfig::get_look_by_index(const int index) const
438{
439 if (index < 0 || index >= looks_.size()) {
440 return nullptr;
441 }
442 return &looks_[index];
443}
444
446
447/* -------------------------------------------------------------------- */
450
451std::shared_ptr<const CPUProcessor> LibOCIOConfig::get_display_cpu_processor(
452 const DisplayParameters &display_parameters) const
453{
454 OCIO_NAMESPACE::ConstProcessorRcPtr processor = create_ocio_display_processor(
455 *this, display_parameters);
456 if (!processor) {
457 return nullptr;
458 }
459 return std::make_shared<LibOCIOCPUProcessor>(processor->getDefaultCPUProcessor());
460}
461
462std::shared_ptr<const CPUProcessor> LibOCIOConfig::get_cpu_processor(
463 const StringRefNull from_colorspace, const StringRefNull to_colorspace) const
464{
465 const OCIO_NAMESPACE::ConstProcessorRcPtr processor = create_ocio_processor(
466 ocio_config_, from_colorspace.c_str(), to_colorspace.c_str());
467 if (!processor) {
468 return nullptr;
469 }
470 return std::make_shared<LibOCIOCPUProcessor>(processor->getDefaultCPUProcessor());
471}
472
474
475/* -------------------------------------------------------------------- */
478
479const GPUShaderBinder &LibOCIOConfig::get_gpu_shader_binder() const
480{
481 return gpu_shader_binder_;
482}
483
485
486} // namespace blender::ocio
487
488#endif
#define BLI_assert(a)
Definition BLI_assert.h:46
static AppView * view
constexpr const char * c_str() const
CartesianBasis invert(const CartesianBasis &basis)
static const float3x3 ACES_TO_XYZ
static const float3x3 XYZ_TO_REC709
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
i
Definition text_draw.cc:230