Blender V5.0
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/image.h"
9#include "util/log.h"
10#include "util/map.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 unordered_map<ustring, ustring> cached_colorspaces;
31
32static thread_mutex cache_processors_mutex;
33static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cached_processors;
34
35static thread_mutex cache_scene_linear_mutex;
36static string cache_scene_linear_name;
37
38static void check_invalidate_caches()
39{
40 /* Invalidate cached processors and colorspace, in case Blender changed it.
41 * Note this should not happen during rendering, all render should be stopped
42 * before it is changed. */
43 const thread_scoped_lock cache_scene_linear_lock(cache_scene_linear_mutex);
44 OCIO::ConstConfigRcPtr config = nullptr;
45 try {
46 config = OCIO::GetCurrentConfig();
47 }
48 catch (const OCIO::Exception &exception) {
49 LOG_ERROR << "OCIO config error: " << exception.what();
50 return;
51 }
52
53 const OCIO::ConstColorSpaceRcPtr scene_linear_colorspace = config->getColorSpace("scene_linear");
54 if (scene_linear_colorspace && cache_scene_linear_name != scene_linear_colorspace->getName()) {
55 cache_scene_linear_name = scene_linear_colorspace->getName();
56 {
57 const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
58 cached_processors.clear();
59 }
60 {
61 const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
62 cached_colorspaces.clear();
63 }
64 }
65}
66#endif
67
68ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
69{
70#ifdef WITH_OCIO
71 /* Only use this for OpenColorIO color spaces, not the builtin ones. */
72 assert(colorspace != u_colorspace_srgb && colorspace != u_colorspace_auto);
73
74 if (colorspace == u_colorspace_raw) {
75 return nullptr;
76 }
77
78 OCIO::ConstConfigRcPtr config = nullptr;
79 try {
80 config = OCIO::GetCurrentConfig();
81 }
82 catch (const OCIO::Exception &exception) {
83 LOG_ERROR << "OCIO config error: " << exception.what();
84 return nullptr;
85 }
86
87 if (!config) {
88 return nullptr;
89 }
90
91 check_invalidate_caches();
92
93 /* Cache processor until free_memory(), memory overhead is expected to be
94 * small and the processor is likely to be reused. */
95 const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
96
97 if (cached_processors.find(colorspace) == cached_processors.end()) {
98 try {
99 cached_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
100 }
101 catch (const OCIO::Exception &exception) {
102 cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
103 LOG_WARNING << "Colorspace " << colorspace.c_str()
104 << " cannot be converted to scene_linear: " << exception.what();
105 }
106 }
107
108 const OCIO::Processor *processor = cached_processors[colorspace].get();
109 return (ColorSpaceProcessor *)processor;
110#else
111 /* No OpenColorIO. */
112 (void)colorspace;
113 return nullptr;
114#endif
115}
116
118{
119 if (colorspace == u_colorspace_auto || colorspace == u_colorspace_raw ||
120 colorspace == u_colorspace_srgb)
121 {
122 return false;
123 }
124
125#ifdef WITH_OCIO
126 OCIO::ConstConfigRcPtr config = nullptr;
127 try {
128 config = OCIO::GetCurrentConfig();
129 }
130 catch (const OCIO::Exception &exception) {
131 LOG_ERROR << "OCIO config error: " << exception.what();
132 return false;
133 }
134
135 if (!config) {
136 return false;
137 }
138
139 try {
140 const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
141 return space && space->isData();
142 }
143 catch (const OCIO::Exception &) {
144 return false;
145 }
146#else
147 return false;
148#endif
149}
150
152 const char *file_colorspace,
153 const char *file_format,
154 bool is_float)
155{
156 if (colorspace == u_colorspace_auto) {
157 /* Auto detect sRGB or raw if none specified. */
158 if (is_float) {
159 const bool srgb = (strcmp(file_colorspace, "sRGB") == 0 ||
160 strcmp(file_colorspace, "GammaCorrected") == 0 ||
161 (file_colorspace[0] == '\0' &&
162 (strcmp(file_format, "png") == 0 || strcmp(file_format, "jpeg") == 0 ||
163 strcmp(file_format, "tiff") == 0 || strcmp(file_format, "dpx") == 0 ||
164 strcmp(file_format, "jpeg2000") == 0)));
165 return srgb ? u_colorspace_srgb : u_colorspace_raw;
166 }
167 return u_colorspace_srgb;
168 }
169
170 /* Builtin colorspaces. */
171 if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
172 return colorspace;
173 }
174
175 /* Use OpenColorIO. */
176#ifdef WITH_OCIO
177 check_invalidate_caches();
178
179 {
180 const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
181 /* Cached lookup. */
182 if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
183 return cached_colorspaces[colorspace];
184 }
185 }
186
187 /* Detect if it matches a simple builtin colorspace. */
188 bool is_scene_linear;
189 bool is_srgb;
190 is_builtin_colorspace(colorspace, is_scene_linear, is_srgb);
191
192 const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
193 if (is_scene_linear) {
194 LOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
195 cached_colorspaces[colorspace] = u_colorspace_raw;
196 return u_colorspace_raw;
197 }
198 if (is_srgb) {
199 LOG_INFO << "Colorspace " << colorspace.string() << " is sRGB";
200 cached_colorspaces[colorspace] = u_colorspace_srgb;
201 return u_colorspace_srgb;
202 }
203
204 /* Verify if we can convert from the requested color space. */
205 if (!get_processor(colorspace)) {
206 OCIO::ConstConfigRcPtr config = nullptr;
207 try {
208 config = OCIO::GetCurrentConfig();
209 }
210 catch (const OCIO::Exception &exception) {
211 LOG_ERROR << "OCIO config error: " << exception.what();
212 return u_colorspace_raw;
213 }
214
215 if (!config || !config->getColorSpace(colorspace.c_str())) {
216 LOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
217 }
218 else {
219 LOG_WARNING << "Colorspace " << colorspace.c_str()
220 << " can't be converted to scene_linear, using raw instead";
221 }
222 cached_colorspaces[colorspace] = u_colorspace_raw;
223 return u_colorspace_raw;
224 }
225
226 /* Convert to/from colorspace with OpenColorIO. */
227 LOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
228 cached_colorspaces[colorspace] = colorspace;
229 return colorspace;
230#else
231 LOG_WARNING << "Colorspace " << colorspace.c_str()
232 << " not available, built without OpenColorIO";
233 return u_colorspace_raw;
234#endif
235}
236
237void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
238 bool &is_scene_linear,
239 bool &is_srgb)
240{
241#ifdef WITH_OCIO
242 const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
243 if (!processor) {
244 is_scene_linear = false;
245 is_srgb = false;
246 return;
247 }
248
249 const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
250 is_scene_linear = true;
251 is_srgb = true;
252 for (int i = 0; i < 256; i++) {
253 const float v = i / 255.0f;
254
255 float cR[3] = {v, 0, 0};
256 float cG[3] = {0, v, 0};
257 float cB[3] = {0, 0, v};
258 float cW[3] = {v, v, v};
259 device_processor->applyRGB(cR);
260 device_processor->applyRGB(cG);
261 device_processor->applyRGB(cB);
262 device_processor->applyRGB(cW);
263
264 /* Make sure that there is no channel crosstalk. */
265 if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
266 fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f)
267 {
268 is_scene_linear = false;
269 is_srgb = false;
270 break;
271 }
272 /* Make sure that the three primaries combine linearly. */
273 if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
274 !compare_floats(cB[2], cW[2], 1e-6f, 64))
275 {
276 is_scene_linear = false;
277 is_srgb = false;
278 break;
279 }
280 /* Make sure that the three channels behave identically. */
281 if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
282 is_scene_linear = false;
283 is_srgb = false;
284 break;
285 }
286
287 const float out_v = average(make_float3(cW[0], cW[1], cW[2]));
288 if (!compare_floats(v, out_v, 1e-6f, 64)) {
289 is_scene_linear = false;
290 }
291 if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-4f, 64)) {
292 is_srgb = false;
293 }
294 }
295#else
296 (void)colorspace;
297 is_scene_linear = false;
298 is_srgb = false;
299#endif
300}
301
302#ifdef WITH_OCIO
303
304template<typename T> inline float4 cast_to_float4(T *data)
305{
310}
311
312template<typename T> inline void cast_from_float4(T *data, const float4 value)
313{
318}
319
320/* Slower versions for other all data types, which needs to convert to float and back. */
321template<typename T, bool compress_as_srgb = false>
322inline void processor_apply_pixels_rgba(const OCIO::Processor *processor,
323 T *pixels,
324 const size_t num_pixels,
325 const bool ignore_alpha)
326{
327 /* TODO: implement faster version for when we know the conversion
328 * is a simple matrix transform between linear spaces. In that case
329 * un-premultiply is not needed. */
330 const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
331
332 /* Process large images in chunks to keep temporary memory requirement down. */
333 const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
334 vector<float4> float_pixels(chunk_size);
335
336 for (size_t j = 0; j < num_pixels; j += chunk_size) {
337 const size_t width = std::min(chunk_size, num_pixels - j);
338
339 for (size_t i = 0; i < width; i++) {
340 float4 value = cast_to_float4(pixels + 4 * (j + i));
341
342 if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
343 const float inv_alpha = 1.0f / value.w;
344 value.x *= inv_alpha;
345 value.y *= inv_alpha;
346 value.z *= inv_alpha;
347 }
348
349 float_pixels[i] = value;
350 }
351
352 const OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 4);
353 device_processor->apply(desc);
354
355 for (size_t i = 0; i < width; i++) {
356 float4 value = float_pixels[i];
357
358 if (compress_as_srgb) {
359 value = color_linear_to_srgb_v4(value);
360 }
361
362 if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
363 value.x *= value.w;
364 value.y *= value.w;
365 value.z *= value.w;
366 }
367
368 cast_from_float4(pixels + 4 * (j + i), value);
369 }
370 }
371}
372
373template<typename T, bool compress_as_srgb = false>
374inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
375 T *pixels,
376 const size_t num_pixels)
377{
378 const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
379
380 /* Process large images in chunks to keep temporary memory requirement down. */
381 const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
382 vector<float> float_pixels(chunk_size * 3);
383
384 for (size_t j = 0; j < num_pixels; j += chunk_size) {
385 const size_t width = std::min(chunk_size, num_pixels - j);
386
387 /* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
388 {
389 const T *pixel = pixels + j;
390 float *fpixel = float_pixels.data();
391 for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
392 const float f = util_image_cast_to_float<T>(*pixel);
393 fpixel[0] = f;
394 fpixel[1] = f;
395 fpixel[2] = f;
396 }
397 }
398
399 const OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 3);
400 device_processor->apply(desc);
401
402 {
403 T *pixel = pixels + j;
404 const float *fpixel = float_pixels.data();
405 for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
406 float f = average(make_float3(fpixel[0], fpixel[1], fpixel[2]));
407 if (compress_as_srgb) {
409 }
411 }
412 }
413 }
414}
415
416#endif
417
418template<typename T>
420 T *pixels,
421 const size_t num_pixels,
422 bool is_rgba,
423 bool compress_as_srgb,
424 bool ignore_alpha)
425{
426#ifdef WITH_OCIO
427 const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
428
429 if (processor) {
430 if (is_rgba) {
431 if (compress_as_srgb) {
432 /* Compress output as sRGB. */
433 processor_apply_pixels_rgba<T, true>(processor, pixels, num_pixels, ignore_alpha);
434 }
435 else {
436 /* Write output as scene linear directly. */
437 processor_apply_pixels_rgba<T>(processor, pixels, num_pixels, ignore_alpha);
438 }
439 }
440 else {
441 if (compress_as_srgb) {
442 /* Compress output as sRGB. */
443 processor_apply_pixels_grayscale<T, true>(processor, pixels, num_pixels);
444 }
445 else {
446 /* Write output as scene linear directly. */
447 processor_apply_pixels_grayscale<T>(processor, pixels, num_pixels);
448 }
449 }
450 }
451#else
452 (void)colorspace;
453 (void)pixels;
454 (void)num_pixels;
455 (void)is_rgba;
456 (void)compress_as_srgb;
457#endif
458}
459
460void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
461 float *pixel,
462 const int channels)
463{
464#ifdef WITH_OCIO
465 const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
466
467 if (processor) {
468 const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
469 if (channels == 1) {
470 float3 rgb = make_float3(pixel[0], pixel[0], pixel[0]);
471 device_processor->applyRGB(&rgb.x);
472 pixel[0] = average(rgb);
473 }
474 if (channels == 3) {
475 device_processor->applyRGB(pixel);
476 }
477 else if (channels == 4) {
478 if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
479 /* Fast path for RGBA. */
480 device_processor->applyRGB(pixel);
481 }
482 else {
483 /* Un-associate and associate alpha since color management should not
484 * be affected by transparency. */
485 const float alpha = pixel[3];
486 const float inv_alpha = 1.0f / alpha;
487
488 pixel[0] *= inv_alpha;
489 pixel[1] *= inv_alpha;
490 pixel[2] *= inv_alpha;
491
492 device_processor->applyRGB(pixel);
493
494 pixel[0] *= alpha;
495 pixel[1] *= alpha;
496 pixel[2] *= alpha;
497 }
498 }
499 }
500#else
501 (void)processor_;
502 (void)pixel;
503 (void)channels;
504#endif
505}
506
508{
509#ifdef WITH_OCIO
510 map_free_memory(cached_colorspaces);
511 map_free_memory(cached_processors);
512#endif
513}
514
516{
517#ifdef WITH_OCIO
518 OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
519#endif
520}
521
522/* Template instantiations so we don't have to inline functions. */
523template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool, bool, bool);
524template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool, bool, bool);
525template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool, bool, bool);
526template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool, bool, bool);
527
unsigned char uchar
unsigned short ushort
BMesh const char void * data
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, const size_t num_pixels, bool is_rgba, bool compress_as_srgb, bool ignore_alpha)
static void free_memory()
static ColorSpaceProcessor * get_processor(ustring colorspace)
static void init_fallback_config()
Definition half.h:41
ccl_device float color_srgb_to_linear(const float c)
Definition color.h:63
ccl_device float4 color_linear_to_srgb_v4(const float4 c)
Definition color.h:341
ccl_device float color_linear_to_srgb(const float c)
Definition color.h:71
CCL_NAMESPACE_BEGIN ustring u_colorspace_auto
T util_image_cast_from_float(const float value)
float util_image_cast_to_float(T value)
static void map_free_memory(T &data)
#define CCL_NAMESPACE_END
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
#define assert(assertion)
#define LOG_ERROR
Definition log.h:101
#define LOG_WARNING
Definition log.h:103
#define LOG_INFO
Definition log.h:106
ccl_device_inline bool compare_floats(const float a, const float b, float abs_diff, const int ulp_diff)
Definition math_base.h:769
#define T
float average(point a)
Definition node_math.h:144
#define fabsf
#define make_float4
ustring u_colorspace_raw
ustring u_colorspace_srgb
CCL_NAMESPACE_BEGIN ustring u_colorspace_auto
float x
Definition sky_math.h:136
float y
Definition sky_math.h:225
float z
Definition sky_math.h:225
float x
Definition sky_math.h:225
float w
Definition sky_math.h:225
i
Definition text_draw.cc:230
std::mutex thread_mutex
Definition thread.h:27
std::unique_lock< std::mutex > thread_scoped_lock
Definition thread.h:28