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, ustringHash> cached_colorspaces;
32static unordered_map<ustring, OCIO::ConstProcessorRcPtr, ustringHash> 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{
248 util_image_cast_to_float(data[3]));
249}
250
251template<typename T> inline void cast_from_float4(T *data, float4 value)
252{
253 data[0] = util_image_cast_from_float<T>(value.x);
254 data[1] = util_image_cast_from_float<T>(value.y);
255 data[2] = util_image_cast_from_float<T>(value.z);
256 data[3] = util_image_cast_from_float<T>(value.w);
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
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)
VecBase< float, 4 > float4
float x
Definition sky_float3.h:27
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