Blender  V2.93
colorspace.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2011-2013 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "render/colorspace.h"
18 
19 #include "util/util_color.h"
20 #include "util/util_half.h"
21 #include "util/util_image.h"
22 #include "util/util_logging.h"
23 #include "util/util_math.h"
24 #include "util/util_thread.h"
25 #include "util/util_vector.h"
26 
27 #ifdef WITH_OCIO
28 # include <OpenColorIO/OpenColorIO.h>
29 namespace OCIO = OCIO_NAMESPACE;
30 #endif
31 
33 
34 /* Builtin colorspaces. */
36 ustring u_colorspace_raw("__builtin_raw");
37 ustring u_colorspace_srgb("__builtin_srgb");
38 
39 /* Cached data. */
40 #ifdef WITH_OCIO
41 static thread_mutex cache_colorspaces_mutex;
42 static thread_mutex cache_processors_mutex;
43 static unordered_map<ustring, ustring, ustringHash> cached_colorspaces;
44 static unordered_map<ustring, OCIO::ConstProcessorRcPtr, ustringHash> cached_processors;
45 #endif
46 
47 ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
48 {
49 #ifdef WITH_OCIO
50  /* Only use this for OpenColorIO color spaces, not the builtin ones. */
51  assert(colorspace != u_colorspace_srgb && colorspace != u_colorspace_auto);
52 
53  if (colorspace == u_colorspace_raw) {
54  return NULL;
55  }
56 
57  OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
58  if (!config) {
59  return NULL;
60  }
61 
62  /* Cache processor until free_memory(), memory overhead is expected to be
63  * small and the processor is likely to be reused. */
64  thread_scoped_lock cache_processors_lock(cache_processors_mutex);
65  if (cached_processors.find(colorspace) == cached_processors.end()) {
66  try {
67  cached_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
68  }
69  catch (OCIO::Exception &exception) {
70  cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
71  VLOG(1) << "Colorspace " << colorspace.c_str()
72  << " can't be converted to scene_linear: " << exception.what();
73  }
74  }
75 
76  const OCIO::Processor *processor = cached_processors[colorspace].get();
77  return (ColorSpaceProcessor *)processor;
78 #else
79  /* No OpenColorIO. */
80  (void)colorspace;
81  return NULL;
82 #endif
83 }
84 
85 bool ColorSpaceManager::colorspace_is_data(ustring colorspace)
86 {
87  if (colorspace == u_colorspace_auto || colorspace == u_colorspace_raw ||
88  colorspace == u_colorspace_srgb) {
89  return false;
90  }
91 
92 #ifdef WITH_OCIO
93  OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
94  if (!config) {
95  return false;
96  }
97 
98  try {
99  OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
100  return space && space->isData();
101  }
102  catch (OCIO::Exception &) {
103  return false;
104  }
105 #else
106  return false;
107 #endif
108 }
109 
111  const char *file_format,
112  bool is_float)
113 {
114  if (colorspace == u_colorspace_auto) {
115  /* Auto detect sRGB or raw if none specified. */
116  if (is_float) {
117  bool srgb = (colorspace == "sRGB" || colorspace == "GammaCorrected" ||
118  (colorspace.empty() &&
119  (strcmp(file_format, "png") == 0 || strcmp(file_format, "tiff") == 0 ||
120  strcmp(file_format, "dpx") == 0 || strcmp(file_format, "jpeg2000") == 0)));
121  return srgb ? u_colorspace_srgb : u_colorspace_raw;
122  }
123  else {
124  return u_colorspace_srgb;
125  }
126  }
127  else if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
128  /* Builtin colorspaces. */
129  return colorspace;
130  }
131  else {
132  /* Use OpenColorIO. */
133 #ifdef WITH_OCIO
134  {
135  thread_scoped_lock cache_lock(cache_colorspaces_mutex);
136  /* Cached lookup. */
137  if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
138  return cached_colorspaces[colorspace];
139  }
140  }
141 
142  /* Detect if it matches a simple builtin colorspace. */
143  bool is_scene_linear, is_srgb;
144  is_builtin_colorspace(colorspace, is_scene_linear, is_srgb);
145 
146  thread_scoped_lock cache_lock(cache_colorspaces_mutex);
147  if (is_scene_linear) {
148  VLOG(1) << "Colorspace " << colorspace.string() << " is no-op";
149  cached_colorspaces[colorspace] = u_colorspace_raw;
150  return u_colorspace_raw;
151  }
152  else if (is_srgb) {
153  VLOG(1) << "Colorspace " << colorspace.string() << " is sRGB";
154  cached_colorspaces[colorspace] = u_colorspace_srgb;
155  return u_colorspace_srgb;
156  }
157 
158  /* Verify if we can convert from the requested color space. */
159  if (!get_processor(colorspace)) {
160  OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
161  if (!config || !config->getColorSpace(colorspace.c_str())) {
162  VLOG(1) << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
163  }
164  else {
165  VLOG(1) << "Colorspace " << colorspace.c_str()
166  << " can't be converted to scene_linear, using raw instead";
167  }
168  cached_colorspaces[colorspace] = u_colorspace_raw;
169  return u_colorspace_raw;
170  }
171 
172  /* Convert to/from colorspace with OpenColorIO. */
173  VLOG(1) << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
174  cached_colorspaces[colorspace] = colorspace;
175  return colorspace;
176 #else
177  VLOG(1) << "Colorspace " << colorspace.c_str() << " not available, built without OpenColorIO";
178  return u_colorspace_raw;
179 #endif
180  }
181 }
182 
183 void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
184  bool &is_scene_linear,
185  bool &is_srgb)
186 {
187 #ifdef WITH_OCIO
188  const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
189  if (!processor) {
190  is_scene_linear = false;
191  is_srgb = false;
192  return;
193  }
194 
195  OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
196  is_scene_linear = true;
197  is_srgb = true;
198  for (int i = 0; i < 256; i++) {
199  float v = i / 255.0f;
200 
201  float cR[3] = {v, 0, 0};
202  float cG[3] = {0, v, 0};
203  float cB[3] = {0, 0, v};
204  float cW[3] = {v, v, v};
205  device_processor->applyRGB(cR);
206  device_processor->applyRGB(cG);
207  device_processor->applyRGB(cB);
208  device_processor->applyRGB(cW);
209 
210  /* Make sure that there is no channel crosstalk. */
211  if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
212  fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f) {
213  is_scene_linear = false;
214  is_srgb = false;
215  break;
216  }
217  /* Make sure that the three primaries combine linearly. */
218  if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
219  !compare_floats(cB[2], cW[2], 1e-6f, 64)) {
220  is_scene_linear = false;
221  is_srgb = false;
222  break;
223  }
224  /* Make sure that the three channels behave identically. */
225  if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
226  is_scene_linear = false;
227  is_srgb = false;
228  break;
229  }
230 
231  float out_v = average(make_float3(cW[0], cW[1], cW[2]));
232  if (!compare_floats(v, out_v, 1e-6f, 64)) {
233  is_scene_linear = false;
234  }
235  if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-6f, 64)) {
236  is_srgb = false;
237  }
238  }
239 #else
240  (void)colorspace;
241  is_scene_linear = false;
242  is_srgb = false;
243 #endif
244 }
245 
246 #ifdef WITH_OCIO
247 
248 template<typename T> inline float4 cast_to_float4(T *data)
249 {
254 }
255 
256 template<typename T> inline void cast_from_float4(T *data, float4 value)
257 {
258  data[0] = util_image_cast_from_float<T>(value.x);
259  data[1] = util_image_cast_from_float<T>(value.y);
260  data[2] = util_image_cast_from_float<T>(value.z);
261  data[3] = util_image_cast_from_float<T>(value.w);
262 }
263 
264 /* Slower versions for other all data types, which needs to convert to float and back. */
265 template<typename T, bool compress_as_srgb = false>
266 inline void processor_apply_pixels(const OCIO::Processor *processor, T *pixels, size_t num_pixels)
267 {
268  /* TODO: implement faster version for when we know the conversion
269  * is a simple matrix transform between linear spaces. In that case
270  * un-premultiply is not needed. */
271  OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
272 
273  /* Process large images in chunks to keep temporary memory requirement down. */
274  const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels);
275  vector<float4> float_pixels(chunk_size);
276 
277  for (size_t j = 0; j < num_pixels; j += chunk_size) {
278  size_t width = std::min(chunk_size, num_pixels - j);
279 
280  for (size_t i = 0; i < width; i++) {
281  float4 value = cast_to_float4(pixels + 4 * (j + i));
282 
283  if (!(value.w <= 0.0f || value.w == 1.0f)) {
284  float inv_alpha = 1.0f / value.w;
285  value.x *= inv_alpha;
286  value.y *= inv_alpha;
287  value.z *= inv_alpha;
288  }
289 
290  float_pixels[i] = value;
291  }
292 
293  OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 4);
294  device_processor->apply(desc);
295 
296  for (size_t i = 0; i < width; i++) {
297  float4 value = float_pixels[i];
298 
299  if (compress_as_srgb) {
300  value = color_linear_to_srgb_v4(value);
301  }
302 
303  if (!(value.w <= 0.0f || value.w == 1.0f)) {
304  value.x *= value.w;
305  value.y *= value.w;
306  value.z *= value.w;
307  }
308 
309  cast_from_float4(pixels + 4 * (j + i), value);
310  }
311  }
312 }
313 #endif
314 
315 template<typename T>
316 void ColorSpaceManager::to_scene_linear(ustring colorspace,
317  T *pixels,
318  size_t num_pixels,
319  bool compress_as_srgb)
320 {
321 #ifdef WITH_OCIO
322  const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
323 
324  if (processor) {
325  if (compress_as_srgb) {
326  /* Compress output as sRGB. */
327  processor_apply_pixels<T, true>(processor, pixels, num_pixels);
328  }
329  else {
330  /* Write output as scene linear directly. */
331  processor_apply_pixels<T>(processor, pixels, num_pixels);
332  }
333  }
334 #else
335  (void)colorspace;
336  (void)pixels;
337  (void)num_pixels;
338  (void)compress_as_srgb;
339 #endif
340 }
341 
342 void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
343  float *pixel,
344  int channels)
345 {
346 #ifdef WITH_OCIO
347  const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
348 
349  if (processor) {
350  OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
351  if (channels == 3) {
352  device_processor->applyRGB(pixel);
353  }
354  else if (channels == 4) {
355  if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
356  /* Fast path for RGBA. */
357  device_processor->applyRGB(pixel);
358  }
359  else {
360  /* Un-associate and associate alpha since color management should not
361  * be affected by transparency. */
362  float alpha = pixel[3];
363  float inv_alpha = 1.0f / alpha;
364 
365  pixel[0] *= inv_alpha;
366  pixel[1] *= inv_alpha;
367  pixel[2] *= inv_alpha;
368 
369  device_processor->applyRGB(pixel);
370 
371  pixel[0] *= alpha;
372  pixel[1] *= alpha;
373  pixel[2] *= alpha;
374  }
375  }
376  }
377 #else
378  (void)processor_;
379  (void)pixel;
380  (void)channels;
381 #endif
382 }
383 
385 {
386 #ifdef WITH_OCIO
387  map_free_memory(cached_colorspaces);
388  map_free_memory(cached_colorspaces);
389 #endif
390 }
391 
392 /* Template instantiations so we don't have to inline functions. */
393 template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool);
394 template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool);
395 template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool);
396 template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool);
397 
unsigned char uchar
Definition: BLI_sys_types.h:86
unsigned short ushort
Definition: BLI_sys_types.h:84
_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
ATTR_WARN_UNUSED_RESULT const BMVert * v
static bool colorspace_is_data(ustring colorspace)
Definition: colorspace.cpp:85
static void free_memory()
Definition: colorspace.cpp:384
static void to_scene_linear(ustring colorspace, T *pixels, size_t num_pixels, bool compress_as_srgb)
Definition: colorspace.cpp:316
static ColorSpaceProcessor * get_processor(ustring colorspace)
Definition: colorspace.cpp:47
static ustring detect_known_colorspace(ustring colorspace, const char *file_format, bool is_float)
Definition: colorspace.cpp:110
Definition: util_half.h:41
ustring u_colorspace_srgb("__builtin_srgb")
CCL_NAMESPACE_BEGIN ustring u_colorspace_auto
Definition: colorspace.cpp:35
ustring u_colorspace_raw("__builtin_raw")
static CCL_NAMESPACE_BEGIN const double alpha
#define CCL_NAMESPACE_END
#define make_float4(x, y, z, w)
#define fabsf(x)
#define make_float3(x, y, z)
#define T
#define min(a, b)
Definition: sort.c:51
ccl_device float4 color_linear_to_srgb_v4(float4 c)
Definition: util_color.h:257
ccl_device float color_srgb_to_linear(float c)
Definition: util_color.h:69
float util_image_cast_to_float(T value)
#define VLOG(severity)
Definition: util_logging.h:50
static void map_free_memory(T &data)
Definition: util_map.h:30
ccl_device_inline float compare_floats(float a, float b, float abs_diff, int ulp_diff)
Definition: util_math.h:777
ccl_device_inline float average(const float2 &a)
std::unique_lock< std::mutex > thread_scoped_lock
Definition: util_thread.h:41
CCL_NAMESPACE_BEGIN typedef std::mutex thread_mutex
Definition: util_thread.h:40