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