Blender V4.5
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
6
7#if defined(WITH_OPENCOLORIO)
8
9# include <cmath>
10
11# include "BLI_math_color.h"
12
13# include "../description.hh"
14# include "error_handling.hh"
16# include "libocio_processor.hh"
17
18namespace blender::ocio {
19
20static bool compare_floats(float a, float b, float abs_diff, int ulp_diff)
21{
22 /* Returns true if the absolute difference is smaller than abs_diff (for numbers near zero)
23 * or their relative difference is less than ulp_diff ULPs. Based on:
24 * https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
25 */
26 if (fabsf(a - b) < abs_diff) {
27 return true;
28 }
29
30 if ((a < 0.0f) != (b < 0.0f)) {
31 return false;
32 }
33
34 return (abs((*(int *)&a) - (*(int *)&b)) < ulp_diff);
35}
36
37static bool color_space_is_invertible(const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space)
38{
39 const StringRefNull family = ocio_color_space->getFamily();
40
41 if (ELEM(family, "rrt", "display")) {
42 /* assume display and rrt transformations are not invertible in fact some of them could be,
43 * but it doesn't make much sense to allow use them as invertible. */
44 return false;
45 }
46
47 if (ocio_color_space->isData()) {
48 /* Data color spaces don't have transformation at all. */
49 return true;
50 }
51
52 if (ocio_color_space->getTransform(OCIO_NAMESPACE::COLORSPACE_DIR_TO_REFERENCE)) {
53 /* if there's defined transform to reference space, color space could be converted to scene
54 * linear. */
55 return true;
56 }
57
58 return true;
59}
60
61static void color_space_is_builtin(const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config,
62 const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space,
63 bool &is_scene_linear,
64 bool &is_srgb)
65{
66 OCIO_NAMESPACE::ConstProcessorRcPtr processor = create_ocio_processor_silent(
67 ocio_config, ocio_color_space->getName(), OCIO_NAMESPACE::ROLE_SCENE_LINEAR);
68 if (!processor) {
69 /* Silently ignore if no conversion possible, then it's not scene linear or sRGB. */
70 is_scene_linear = false;
71 is_srgb = false;
72 return;
73 }
74
75 OCIO_NAMESPACE::ConstCPUProcessorRcPtr cpu_processor = processor->getDefaultCPUProcessor();
76
77 is_scene_linear = true;
78 is_srgb = true;
79 for (int i = 0; i < 256; i++) {
80 float v = i / 255.0f;
81
82 float cR[3] = {v, 0, 0};
83 float cG[3] = {0, v, 0};
84 float cB[3] = {0, 0, v};
85 float cW[3] = {v, v, v};
86 cpu_processor->applyRGB(cR);
87 cpu_processor->applyRGB(cG);
88 cpu_processor->applyRGB(cB);
89 cpu_processor->applyRGB(cW);
90
91 /* Make sure that there is no channel crosstalk. */
92 if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
93 fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
94 {
95 is_scene_linear = false;
96 is_srgb = false;
97 break;
98 }
99 /* Make sure that the three primaries combine linearly. */
100 if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
101 !compare_floats(cB[2], cW[2], 1e-6f, 64))
102 {
103 is_scene_linear = false;
104 is_srgb = false;
105 break;
106 }
107 /* Make sure that the three channels behave identically. */
108 if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
109 is_scene_linear = false;
110 is_srgb = false;
111 break;
112 }
113
114 float out_v = (cW[0] + cW[1] + cW[2]) * (1.0f / 3.0f);
115 if (!compare_floats(v, out_v, 1e-6f, 64)) {
116 is_scene_linear = false;
117 }
118 if (!compare_floats(srgb_to_linearrgb(v), out_v, 1e-4f, 64)) {
119 is_srgb = false;
120 }
121 }
122}
123
124LibOCIOColorSpace::LibOCIOColorSpace(const int index,
125 const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config,
126 const OCIO_NAMESPACE::ConstColorSpaceRcPtr &ocio_color_space)
127 : ocio_config_(ocio_config),
128 ocio_color_space_(ocio_color_space),
129 clean_description_(cleanup_description(ocio_color_space->getDescription()))
130{
131 this->index = index;
132
133 is_inveetible_ = color_space_is_invertible(ocio_color_space);
134}
135
136bool LibOCIOColorSpace::is_scene_linear() const
137{
138 ensure_srgb_scene_linear_info();
139 return is_scene_linear_;
140}
141
142bool LibOCIOColorSpace::is_srgb() const
143{
144 ensure_srgb_scene_linear_info();
145 return is_srgb_;
146}
147
148const CPUProcessor *LibOCIOColorSpace::get_to_scene_linear_cpu_processor() const
149{
150 return to_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
151 OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_processor(
152 ocio_config_, ocio_color_space_->getName(), OCIO_NAMESPACE::ROLE_SCENE_LINEAR);
153 if (!ocio_processor) {
154 return nullptr;
155 }
156 return std::make_unique<LibOCIOCPUProcessor>(ocio_processor->getDefaultCPUProcessor());
157 });
158}
159
160const CPUProcessor *LibOCIOColorSpace::get_from_scene_linear_cpu_processor() const
161{
162 return from_scene_linear_cpu_processor_.get([&]() -> std::unique_ptr<CPUProcessor> {
163 OCIO_NAMESPACE::ConstProcessorRcPtr ocio_processor = create_ocio_processor(
164 ocio_config_, OCIO_NAMESPACE::ROLE_SCENE_LINEAR, ocio_color_space_->getName());
165 if (!ocio_processor) {
166 return nullptr;
167 }
168 return std::make_unique<LibOCIOCPUProcessor>(ocio_processor->getDefaultCPUProcessor());
169 });
170}
171
172void LibOCIOColorSpace::ensure_srgb_scene_linear_info() const
173{
174 if (is_info_cached_) {
175 return;
176 }
177 color_space_is_builtin(ocio_config_, ocio_color_space_, is_scene_linear_, is_srgb_);
178 is_info_cached_ = true;
179}
180
181} // namespace blender::ocio
182
183#endif
float srgb_to_linearrgb(float c)
#define ELEM(...)
ATTR_WARN_UNUSED_RESULT const BMVert * v
#define fabsf(x)
#define abs
ccl_device_inline bool compare_floats(const float a, const float b, float abs_diff, const int ulp_diff)
Definition math_base.h:754
std::string cleanup_description(const StringRef description)
i
Definition text_draw.cc:230