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