Blender V4.3
colorspace.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "scene/colorspace.h"
6
7#include "util/color.h"
8#include "util/half.h"
9#include "util/image.h"
10#include "util/log.h"
11#include "util/math.h"
12#include "util/thread.h"
13#include "util/vector.h"
14
15#ifdef WITH_OCIO
16# include <OpenColorIO/OpenColorIO.h>
17namespace OCIO = OCIO_NAMESPACE;
18#endif
19
21
22/* Builtin colorspaces. */
24ustring u_colorspace_raw("__builtin_raw");
25ustring u_colorspace_srgb("__builtin_srgb");
26
27/* Cached data. */
28#ifdef WITH_OCIO
29static thread_mutex cache_colorspaces_mutex;
30static thread_mutex cache_processors_mutex;
31static unordered_map<ustring, ustring> cached_colorspaces;
32static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cached_processors;
33#endif
34
35ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
36{
37#ifdef WITH_OCIO
38 /* Only use this for OpenColorIO color spaces, not the builtin ones. */
39 assert(colorspace != u_colorspace_srgb && colorspace != u_colorspace_auto);
40
41 if (colorspace == u_colorspace_raw) {
42 return NULL;
43 }
44
45 OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
46 if (!config) {
47 return NULL;
48 }
49
50 /* Cache processor until free_memory(), memory overhead is expected to be
51 * small and the processor is likely to be reused. */
52 thread_scoped_lock cache_processors_lock(cache_processors_mutex);
53 if (cached_processors.find(colorspace) == cached_processors.end()) {
54 try {
55 cached_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
56 }
57 catch (OCIO::Exception &exception) {
58 cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
59 VLOG_WARNING << "Colorspace " << colorspace.c_str()
60 << " can't be converted to scene_linear: " << exception.what();
61 }
62 }
63
64 const OCIO::Processor *processor = cached_processors[colorspace].get();
65 return (ColorSpaceProcessor *)processor;
66#else
67 /* No OpenColorIO. */
68 (void)colorspace;
69 return NULL;
70#endif
71}
72
74{
75 if (colorspace == u_colorspace_auto || colorspace == u_colorspace_raw ||
76 colorspace == u_colorspace_srgb)
77 {
78 return false;
79 }
80
81#ifdef WITH_OCIO
82 OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
83 if (!config) {
84 return false;
85 }
86
87 try {
88 OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
89 return space && space->isData();
90 }
91 catch (OCIO::Exception &) {
92 return false;
93 }
94#else
95 return false;
96#endif
97}
98
100 const char *file_colorspace,
101 const char *file_format,
102 bool is_float)
103{
104 if (colorspace == u_colorspace_auto) {
105 /* Auto detect sRGB or raw if none specified. */
106 if (is_float) {
107 bool srgb = (strcmp(file_colorspace, "sRGB") == 0 ||
108 strcmp(file_colorspace, "GammaCorrected") == 0 ||
109 (file_colorspace[0] == '\0' &&
110 (strcmp(file_format, "png") == 0 || strcmp(file_format, "jpeg") == 0 ||
111 strcmp(file_format, "tiff") == 0 || strcmp(file_format, "dpx") == 0 ||
112 strcmp(file_format, "jpeg2000") == 0)));
113 return srgb ? u_colorspace_srgb : u_colorspace_raw;
114 }
115 else {
116 return u_colorspace_srgb;
117 }
118 }
119 else if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
120 /* Builtin colorspaces. */
121 return colorspace;
122 }
123 else {
124 /* Use OpenColorIO. */
125#ifdef WITH_OCIO
126 {
127 thread_scoped_lock cache_lock(cache_colorspaces_mutex);
128 /* Cached lookup. */
129 if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
130 return cached_colorspaces[colorspace];
131 }
132 }
133
134 /* Detect if it matches a simple builtin colorspace. */
135 bool is_scene_linear, is_srgb;
136 is_builtin_colorspace(colorspace, is_scene_linear, is_srgb);
137
138 thread_scoped_lock cache_lock(cache_colorspaces_mutex);
139 if (is_scene_linear) {
140 VLOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
141 cached_colorspaces[colorspace] = u_colorspace_raw;
142 return u_colorspace_raw;
143 }
144 else if (is_srgb) {
145 VLOG_INFO << "Colorspace " << colorspace.string() << " is sRGB";
146 cached_colorspaces[colorspace] = u_colorspace_srgb;
147 return u_colorspace_srgb;
148 }
149
150 /* Verify if we can convert from the requested color space. */
151 if (!get_processor(colorspace)) {
152 OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
153 if (!config || !config->getColorSpace(colorspace.c_str())) {
154 VLOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
155 }
156 else {
157 VLOG_WARNING << "Colorspace " << colorspace.c_str()
158 << " can't be converted to scene_linear, using raw instead";
159 }
160 cached_colorspaces[colorspace] = u_colorspace_raw;
161 return u_colorspace_raw;
162 }
163
164 /* Convert to/from colorspace with OpenColorIO. */
165 VLOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
166 cached_colorspaces[colorspace] = colorspace;
167 return colorspace;
168#else
169 VLOG_WARNING << "Colorspace " << colorspace.c_str()
170 << " not available, built without OpenColorIO";
171 return u_colorspace_raw;
172#endif
173 }
174}
175
176void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
177 bool &is_scene_linear,
178 bool &is_srgb)
179{
180#ifdef WITH_OCIO
181 const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
182 if (!processor) {
183 is_scene_linear = false;
184 is_srgb = false;
185 return;
186 }
187
188 OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
189 is_scene_linear = true;
190 is_srgb = true;
191 for (int i = 0; i < 256; i++) {
192 float v = i / 255.0f;
193
194 float cR[3] = {v, 0, 0};
195 float cG[3] = {0, v, 0};
196 float cB[3] = {0, 0, v};
197 float cW[3] = {v, v, v};
198 device_processor->applyRGB(cR);
199 device_processor->applyRGB(cG);
200 device_processor->applyRGB(cB);
201 device_processor->applyRGB(cW);
202
203 /* Make sure that there is no channel crosstalk. */
204 if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
205 fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
206 {
207 is_scene_linear = false;
208 is_srgb = false;
209 break;
210 }
211 /* Make sure that the three primaries combine linearly. */
212 if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
213 !compare_floats(cB[2], cW[2], 1e-6f, 64))
214 {
215 is_scene_linear = false;
216 is_srgb = false;
217 break;
218 }
219 /* Make sure that the three channels behave identically. */
220 if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
221 is_scene_linear = false;
222 is_srgb = false;
223 break;
224 }
225
226 float out_v = average(make_float3(cW[0], cW[1], cW[2]));
227 if (!compare_floats(v, out_v, 1e-6f, 64)) {
228 is_scene_linear = false;
229 }
230 if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-4f, 64)) {
231 is_srgb = false;
232 }
233 }
234#else
235 (void)colorspace;
236 is_scene_linear = false;
237 is_srgb = false;
238#endif
239}
240
241#ifdef WITH_OCIO
242
243template<typename T> inline float4 cast_to_float4(T *data)
244{
249}
250
251template<typename T> inline void cast_from_float4(T *data, float4 value)
252{
257}
258
259/* Slower versions for other all data types, which needs to convert to float and back. */
260template<typename T, bool compress_as_srgb = false>
261inline void processor_apply_pixels_rgba(const OCIO::Processor *processor,
262 T *pixels,
263 size_t num_pixels)
264{
265 /* TODO: implement faster version for when we know the conversion
266 * is a simple matrix transform between linear spaces. In that case
267 * un-premultiply is not needed. */
268 OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
269
270 /* Process large images in chunks to keep temporary memory requirement down. */
271 const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
272 vector<float4> float_pixels(chunk_size);
273
274 for (size_t j = 0; j < num_pixels; j += chunk_size) {
275 size_t width = std::min(chunk_size, num_pixels - j);
276
277 for (size_t i = 0; i < width; i++) {
278 float4 value = cast_to_float4(pixels + 4 * (j + i));
279
280 if (!(value.w <= 0.0f || value.w == 1.0f)) {
281 float inv_alpha = 1.0f / value.w;
282 value.x *= inv_alpha;
283 value.y *= inv_alpha;
284 value.z *= inv_alpha;
285 }
286
287 float_pixels[i] = value;
288 }
289
290 OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 4);
291 device_processor->apply(desc);
292
293 for (size_t i = 0; i < width; i++) {
294 float4 value = float_pixels[i];
295
296 if (compress_as_srgb) {
297 value = color_linear_to_srgb_v4(value);
298 }
299
300 if (!(value.w <= 0.0f || value.w == 1.0f)) {
301 value.x *= value.w;
302 value.y *= value.w;
303 value.z *= value.w;
304 }
305
306 cast_from_float4(pixels + 4 * (j + i), value);
307 }
308 }
309}
310
311template<typename T, bool compress_as_srgb = false>
312inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
313 T *pixels,
314 size_t num_pixels)
315{
316 OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
317
318 /* Process large images in chunks to keep temporary memory requirement down. */
319 const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
320 vector<float> float_pixels(chunk_size * 3);
321
322 for (size_t j = 0; j < num_pixels; j += chunk_size) {
323 size_t width = std::min(chunk_size, num_pixels - j);
324
325 /* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
326 {
327 const T *pixel = pixels + j;
328 float *fpixel = float_pixels.data();
329 for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
330 const float f = util_image_cast_to_float<T>(*pixel);
331 fpixel[0] = f;
332 fpixel[1] = f;
333 fpixel[2] = f;
334 }
335 }
336
337 OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 3);
338 device_processor->apply(desc);
339
340 {
341 T *pixel = pixels + j;
342 const float *fpixel = float_pixels.data();
343 for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
344 float f = average(make_float3(fpixel[0], fpixel[1], fpixel[2]));
345 if (compress_as_srgb) {
347 }
349 }
350 }
351 }
352}
353
354#endif
355
356template<typename T>
358 ustring colorspace, T *pixels, size_t num_pixels, bool is_rgba, bool compress_as_srgb)
359{
360#ifdef WITH_OCIO
361 const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
362
363 if (processor) {
364 if (is_rgba) {
365 if (compress_as_srgb) {
366 /* Compress output as sRGB. */
367 processor_apply_pixels_rgba<T, true>(processor, pixels, num_pixels);
368 }
369 else {
370 /* Write output as scene linear directly. */
371 processor_apply_pixels_rgba<T>(processor, pixels, num_pixels);
372 }
373 }
374 else {
375 if (compress_as_srgb) {
376 /* Compress output as sRGB. */
377 processor_apply_pixels_grayscale<T, true>(processor, pixels, num_pixels);
378 }
379 else {
380 /* Write output as scene linear directly. */
381 processor_apply_pixels_grayscale<T>(processor, pixels, num_pixels);
382 }
383 }
384 }
385#else
386 (void)colorspace;
387 (void)pixels;
388 (void)num_pixels;
389 (void)is_rgba;
390 (void)compress_as_srgb;
391#endif
392}
393
394void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
395 float *pixel,
396 int channels)
397{
398#ifdef WITH_OCIO
399 const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
400
401 if (processor) {
402 OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
403 if (channels == 1) {
404 float3 rgb = make_float3(pixel[0], pixel[0], pixel[0]);
405 device_processor->applyRGB(&rgb.x);
406 pixel[0] = average(rgb);
407 }
408 if (channels == 3) {
409 device_processor->applyRGB(pixel);
410 }
411 else if (channels == 4) {
412 if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
413 /* Fast path for RGBA. */
414 device_processor->applyRGB(pixel);
415 }
416 else {
417 /* Un-associate and associate alpha since color management should not
418 * be affected by transparency. */
419 float alpha = pixel[3];
420 float inv_alpha = 1.0f / alpha;
421
422 pixel[0] *= inv_alpha;
423 pixel[1] *= inv_alpha;
424 pixel[2] *= inv_alpha;
425
426 device_processor->applyRGB(pixel);
427
428 pixel[0] *= alpha;
429 pixel[1] *= alpha;
430 pixel[2] *= alpha;
431 }
432 }
433 }
434#else
435 (void)processor_;
436 (void)pixel;
437 (void)channels;
438#endif
439}
440
442{
443#ifdef WITH_OCIO
444 map_free_memory(cached_colorspaces);
445 map_free_memory(cached_processors);
446#endif
447}
448
450{
451#ifdef WITH_OCIO
452 OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
453#endif
454}
455
456/* Template instantiations so we don't have to inline functions. */
457template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool, bool);
458template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool, bool);
459template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool, bool);
460template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool, bool);
461
unsigned char uchar
unsigned short ushort
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a producing a negative Combine Generate a color from its and blue channels(Deprecated)") DefNode(ShaderNode
ATTR_WARN_UNUSED_RESULT const BMVert * v
static bool colorspace_is_data(ustring colorspace)
static ustring detect_known_colorspace(ustring colorspace, const char *file_colorspace, const char *file_format, bool is_float)
static void to_scene_linear(ustring colorspace, T *pixels, size_t num_pixels, bool is_rgba, bool compress_as_srgb)
static void free_memory()
static ColorSpaceProcessor * get_processor(ustring colorspace)
static void init_fallback_config()
Definition half.h:42
CCL_NAMESPACE_BEGIN ustring u_colorspace_auto
ustring u_colorspace_raw
ustring u_colorspace_srgb
CCL_NAMESPACE_BEGIN ustring u_colorspace_auto
float util_image_cast_to_float(T value)
T util_image_cast_from_float(float value)
static void map_free_memory(T &data)
#define CCL_NAMESPACE_END
ccl_device_forceinline float4 make_float4(const float x, const float y, const float z, const float w)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
#define NULL
#define fabsf(x)
#define VLOG_INFO
Definition log.h:72
#define VLOG_WARNING
Definition log.h:70
ccl_device_inline float average(const float2 a)
#define T
float x
Definition sky_float3.h:27
VecBase< float, 4 > float4
std::unique_lock< std::mutex > thread_scoped_lock
Definition thread.h:30
CCL_NAMESPACE_BEGIN typedef std::mutex thread_mutex
Definition thread.h:29
ccl_device float color_linear_to_srgb(float c)
Definition util/color.h:72
ccl_device float4 color_linear_to_srgb_v4(float4 c)
Definition util/color.h:321
ccl_device float color_srgb_to_linear(float c)
Definition util/color.h:62
ccl_device_inline bool compare_floats(float a, float b, float abs_diff, int ulp_diff)
Definition util/math.h:970