Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLukas Stockner <lukas.stockner@freenet.de>2019-05-03 12:23:16 +0300
committerBrecht Van Lommel <brechtvanlommel@gmail.com>2019-05-03 16:42:49 +0300
commitdba4684f82a179a5c8abdae5940bc7c35e65a1a7 (patch)
tree6bf8add48d7ac5e20a97da89a34db010625750e7
parentfadb6f34662fb60e1a48c2c053c500f017206f27 (diff)
Cycles: add colorspace manager class and utilities based on OpenColorIO
This is the groundwork for supporting loading image textures with arbitrary color spaces through OpenColorIO. Initial patch by Lukas, completed by Brecht. Differential Revision: https://developer.blender.org/D3491
-rw-r--r--intern/cycles/render/CMakeLists.txt2
-rw-r--r--intern/cycles/render/colorspace.cpp374
-rw-r--r--intern/cycles/render/colorspace.h65
-rw-r--r--intern/cycles/util/util_color.h6
-rw-r--r--intern/cycles/util/util_image.h1
-rw-r--r--intern/cycles/util/util_map.h7
-rw-r--r--intern/cycles/util/util_math.h19
7 files changed, 474 insertions, 0 deletions
diff --git a/intern/cycles/render/CMakeLists.txt b/intern/cycles/render/CMakeLists.txt
index 378957d21f4..c79e5a23ea1 100644
--- a/intern/cycles/render/CMakeLists.txt
+++ b/intern/cycles/render/CMakeLists.txt
@@ -14,6 +14,7 @@ set(SRC
bake.cpp
buffers.cpp
camera.cpp
+ colorspace.cpp
constant_fold.cpp
coverage.cpp
denoising.cpp
@@ -48,6 +49,7 @@ set(SRC_HEADERS
background.h
buffers.h
camera.h
+ colorspace.h
constant_fold.h
coverage.h
denoising.h
diff --git a/intern/cycles/render/colorspace.cpp b/intern/cycles/render/colorspace.cpp
new file mode 100644
index 00000000000..cbfcf774a15
--- /dev/null
+++ b/intern/cycles/render/colorspace.cpp
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2011-2013 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "render/colorspace.h"
+
+#include "util/util_color.h"
+#include "util/util_image.h"
+#include "util/util_half.h"
+#include "util/util_logging.h"
+#include "util/util_math.h"
+#include "util/util_thread.h"
+#include "util/util_vector.h"
+
+#ifdef WITH_OCIO
+# include <OpenColorIO/OpenColorIO.h>
+namespace OCIO = OCIO_NAMESPACE;
+#endif
+
+CCL_NAMESPACE_BEGIN
+
+/* Builtin colorspaces. */
+ustring u_colorspace_auto;
+ustring u_colorspace_raw("__builtin_raw");
+ustring u_colorspace_srgb("__builtin_srgb");
+
+/* Cached data. */
+#ifdef WITH_OCIO
+static thread_mutex cache_mutex;
+static unordered_map<ustring, ustring, ustringHash> cached_colorspaces;
+static unordered_map<ustring, OCIO::ConstProcessorRcPtr, ustringHash> cached_processors;
+#endif
+
+ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
+{
+#ifdef WITH_OCIO
+ /* Only use this for OpenColorIO color spaces, not the builtin ones. */
+ assert(colorspace != u_colorspace_srgb && colorspace != u_colorspace_auto);
+
+ if (colorspace == u_colorspace_raw) {
+ return NULL;
+ }
+
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+ if (!config) {
+ return NULL;
+ }
+
+ /* Cache processor until free_memory(), memory overhead is expected to be
+ * small and the processor is likely to be reused. */
+ thread_scoped_lock cache_lock(cache_mutex);
+ if (cached_processors.find(colorspace) == cached_processors.end()) {
+ try {
+ cached_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
+ }
+ catch (OCIO::Exception &exception) {
+ cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
+ VLOG(1) << "Colorspace " << colorspace.c_str()
+ << " can't be converted to scene_linear: " << exception.what();
+ }
+ }
+
+ const OCIO::Processor *processor = cached_processors[colorspace].get();
+ return (ColorSpaceProcessor *)processor;
+#else
+ /* No OpenColorIO. */
+ (void)colorspace;
+ return NULL;
+#endif
+}
+
+ustring ColorSpaceManager::detect_known_colorspace(ustring colorspace,
+ const char *file_format,
+ bool is_float)
+{
+ if (colorspace == u_colorspace_auto) {
+ /* Auto detect sRGB or raw if none specified. */
+ if (is_float) {
+ bool srgb = (colorspace == "sRGB" || colorspace == "GammaCorrected" ||
+ (colorspace.empty() &&
+ (strcmp(file_format, "png") == 0 || strcmp(file_format, "tiff") == 0 ||
+ strcmp(file_format, "dpx") == 0 || strcmp(file_format, "jpeg2000") == 0)));
+ return srgb ? u_colorspace_srgb : u_colorspace_raw;
+ }
+ else {
+ return u_colorspace_srgb;
+ }
+ }
+ else if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
+ /* Builtin colorspaces. */
+ return colorspace;
+ }
+ else {
+ /* Use OpenColorIO. */
+#ifdef WITH_OCIO
+ {
+ thread_scoped_lock cache_lock(cache_mutex);
+ /* Cached lookup. */
+ if (cached_colorspaces.find(colorspace) != cached_colorspaces.end()) {
+ return cached_colorspaces[colorspace];
+ }
+ }
+
+ /* Detect if it matches a simple builtin colorspace. */
+ bool is_no_op, is_srgb;
+ is_builtin_colorspace(colorspace, is_no_op, is_srgb);
+
+ thread_scoped_lock cache_lock(cache_mutex);
+ if (is_no_op) {
+ VLOG(1) << "Colorspace " << colorspace.string() << " is no-op";
+ cached_colorspaces[colorspace] = u_colorspace_raw;
+ return u_colorspace_raw;
+ }
+ else if (is_srgb) {
+ VLOG(1) << "Colorspace " << colorspace.string() << " is sRGB";
+ cached_colorspaces[colorspace] = u_colorspace_srgb;
+ return u_colorspace_srgb;
+ }
+
+ /* Verify if we can convert from the requested color space. */
+ if (!get_processor(colorspace)) {
+ OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
+ if (!config || !config->getColorSpace(colorspace.c_str())) {
+ VLOG(1) << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
+ }
+ else {
+ VLOG(1) << "Colorspace " << colorspace.c_str()
+ << " can't be converted to scene_linear, using raw instead";
+ }
+ cached_colorspaces[colorspace] = u_colorspace_raw;
+ return u_colorspace_raw;
+ }
+
+ /* Convert to/from colorspace with OpenColorIO. */
+ VLOG(1) << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
+ cached_colorspaces[colorspace] = colorspace;
+ return colorspace;
+#else
+ VLOG(1) << "Colorspace " << colorspace.c_str() << " not available, built without OpenColorIO";
+ return u_colorspace_raw;
+#endif
+ }
+}
+
+void ColorSpaceManager::is_builtin_colorspace(ustring colorspace, bool &is_no_op, bool &is_srgb)
+{
+#ifdef WITH_OCIO
+ const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
+ if (!processor) {
+ is_no_op = false;
+ is_srgb = false;
+ return;
+ }
+
+ is_no_op = true;
+ is_srgb = true;
+ for (int i = 0; i < 256; i++) {
+ float v = i / 255.0f;
+
+ float cR[3] = {v, 0, 0};
+ float cG[3] = {0, v, 0};
+ float cB[3] = {0, 0, v};
+ float cW[3] = {v, v, v};
+ processor->applyRGB(cR);
+ processor->applyRGB(cG);
+ processor->applyRGB(cB);
+ processor->applyRGB(cW);
+
+ /* Make sure that there is no channel crosstalk. */
+ if (fabsf(cR[1]) > 1e-5f || fabsf(cR[2]) > 1e-5f || fabsf(cG[0]) > 1e-5f ||
+ fabsf(cG[2]) > 1e-5f || fabsf(cB[0]) > 1e-5f || fabsf(cB[1]) > 1e-5f) {
+ is_no_op = false;
+ is_srgb = false;
+ break;
+ }
+ /* Make sure that the three primaries combine linearly. */
+ if (!compare_floats(cR[0], cW[0], 1e-6f, 64) || !compare_floats(cG[1], cW[1], 1e-6f, 64) ||
+ !compare_floats(cB[2], cW[2], 1e-6f, 64)) {
+ is_no_op = false;
+ is_srgb = false;
+ break;
+ }
+ /* Make sure that the three channels behave identically. */
+ if (!compare_floats(cW[0], cW[1], 1e-6f, 64) || !compare_floats(cW[1], cW[2], 1e-6f, 64)) {
+ is_no_op = false;
+ is_srgb = false;
+ break;
+ }
+
+ float out_v = average(make_float3(cW[0], cW[1], cW[2]));
+ if (!compare_floats(v, out_v, 1e-6f, 64)) {
+ is_no_op = false;
+ }
+ if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-6f, 64)) {
+ is_srgb = false;
+ }
+ }
+#else
+ (void)colorspace;
+ is_no_op = false;
+ is_srgb = false;
+#endif
+}
+
+#ifdef WITH_OCIO
+
+template<typename T> inline float4 cast_to_float4(T *data)
+{
+ return make_float4(util_image_cast_to_float(data[0]),
+ util_image_cast_to_float(data[1]),
+ util_image_cast_to_float(data[2]),
+ util_image_cast_to_float(data[3]));
+}
+
+template<typename T> inline void cast_from_float4(T *data, float4 value)
+{
+ data[0] = util_image_cast_from_float<T>(value.x);
+ data[1] = util_image_cast_from_float<T>(value.y);
+ data[2] = util_image_cast_from_float<T>(value.z);
+ data[3] = util_image_cast_from_float<T>(value.w);
+}
+
+/* Slower versions for other all data types, which needs to convert to float and back. */
+template<typename T, bool compress_as_srgb = false>
+inline void processor_apply_pixels(const OCIO::Processor *processor,
+ T *pixels,
+ size_t width,
+ size_t height)
+{
+ /* Process large images in chunks to keep temporary memory requirement down. */
+ size_t y_chunk_size = max(1, 16 * 1024 * 1024 / (sizeof(float4) * width));
+ vector<float4> float_pixels(y_chunk_size * width);
+
+ for (size_t y0 = 0; y0 < height; y0 += y_chunk_size) {
+ size_t y1 = std::min(y0 + y_chunk_size, height);
+ size_t i = 0;
+
+ for (size_t y = y0; y < y1; y++) {
+ for (size_t x = 0; x < width; x++, i++) {
+ float_pixels[i] = cast_to_float4(pixels + 4 * (y * width + x));
+ }
+ }
+
+ OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, y_chunk_size, 4);
+ processor->apply(desc);
+
+ i = 0;
+ for (size_t y = y0; y < y1; y++) {
+ for (size_t x = 0; x < width; x++, i++) {
+ float4 value = float_pixels[i];
+ if (compress_as_srgb) {
+ value = color_linear_to_srgb_v4(value);
+ }
+ cast_from_float4(pixels + 4 * (y * width + x), value);
+ }
+ }
+ }
+}
+
+/* Fast version for float images, which OpenColorIO can handle natively. */
+template<>
+inline void processor_apply_pixels(const OCIO::Processor *processor,
+ float *pixels,
+ size_t width,
+ size_t height)
+{
+ OCIO::PackedImageDesc desc(pixels, width, height, 4);
+ processor->apply(desc);
+}
+#endif
+
+template<typename T>
+void ColorSpaceManager::to_scene_linear(ustring colorspace,
+ T *pixels,
+ size_t width,
+ size_t height,
+ size_t depth,
+ bool compress_as_srgb)
+{
+#ifdef WITH_OCIO
+ const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
+
+ if (processor) {
+ if (compress_as_srgb) {
+ /* Compress output as sRGB. */
+ for (size_t z = 0; z < depth; z++) {
+ processor_apply_pixels<T, true>(processor, &pixels[z * width * height], width, height);
+ }
+ }
+ else {
+ /* Write output as scene linear directly. */
+ for (size_t z = 0; z < depth; z++) {
+ processor_apply_pixels<T>(processor, &pixels[z * width * height], width, height);
+ }
+ }
+ }
+#else
+ (void)colorspace;
+ (void)pixels;
+ (void)width;
+ (void)height;
+ (void)depth;
+ (void)compress_as_srgb;
+#endif
+}
+
+void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
+ float *pixel,
+ int channels)
+{
+#ifdef WITH_OCIO
+ const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
+
+ if (processor) {
+ if (channels == 3) {
+ processor->applyRGB(pixel);
+ }
+ else if (channels == 4) {
+ if (pixel[3] == 1.0f || pixel[3] == 0.0f) {
+ /* Fast path for RGBA. */
+ processor->applyRGB(pixel);
+ }
+ else {
+ /* Unassociate and associate alpha since color management should not
+ * be affected by transparency. */
+ float alpha = pixel[3];
+ float inv_alpha = 1.0f / alpha;
+
+ pixel[0] *= inv_alpha;
+ pixel[1] *= inv_alpha;
+ pixel[2] *= inv_alpha;
+
+ processor->applyRGB(pixel);
+
+ pixel[0] *= alpha;
+ pixel[1] *= alpha;
+ pixel[2] *= alpha;
+ }
+ }
+ }
+#else
+ (void)processor_;
+ (void)pixel;
+ (void)channels;
+#endif
+}
+
+void ColorSpaceManager::free_memory()
+{
+#ifdef WITH_OCIO
+ map_free_memory(cached_colorspaces);
+ map_free_memory(cached_colorspaces);
+#endif
+}
+
+/* Template instanstations so we don't have to inline functions. */
+template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, size_t, size_t, bool);
+template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, size_t, size_t, bool);
+template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, size_t, size_t, bool);
+template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, size_t, size_t, bool);
+
+CCL_NAMESPACE_END
diff --git a/intern/cycles/render/colorspace.h b/intern/cycles/render/colorspace.h
new file mode 100644
index 00000000000..e7c7f943c2d
--- /dev/null
+++ b/intern/cycles/render/colorspace.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2011-2013 Blender Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __COLORSPACE_H__
+#define __COLORSPACE_H__
+
+#include "util/util_map.h"
+#include "util/util_param.h"
+
+CCL_NAMESPACE_BEGIN
+
+extern ustring u_colorspace_auto;
+extern ustring u_colorspace_raw;
+extern ustring u_colorspace_srgb;
+
+class ColorSpaceProcessor;
+
+class ColorSpaceManager {
+ public:
+ /* Convert used specified colorspace to a colorspace that we are able to
+ * convert to and from. If the colorspace is u_colorspace_auto, we auto
+ * detect a colospace. */
+ static ustring detect_known_colorspace(ustring colorspace,
+ const char *file_format,
+ bool is_float);
+
+ /* Convert pixels in the specified colorspace to scene linear color for
+ * rendering. Must be a colorspace returned from detect_known_colorspace. */
+ template<typename T>
+ static void to_scene_linear(ustring colorspace,
+ T *pixels,
+ size_t width,
+ size_t height,
+ size_t depth,
+ bool compress_as_srgb);
+
+ /* Efficiently convert pixels to scene linear colorspace at render time,
+ * for OSL where the image texture cache contains original pixels. The
+ * handle is valid for the lifetime of the application. */
+ static ColorSpaceProcessor *get_processor(ustring colorspace);
+ static void to_scene_linear(ColorSpaceProcessor *processor, float *pixel, int channels);
+
+ /* Clear memory when the application exits. Invalidates all processors. */
+ static void free_memory();
+
+ private:
+ static void is_builtin_colorspace(ustring colorspace, bool &is_no_op, bool &is_srgb);
+};
+
+CCL_NAMESPACE_END
+
+#endif /* __COLORSPACE_H__ */
diff --git a/intern/cycles/util/util_color.h b/intern/cycles/util/util_color.h
index 85f241c6221..83410db13c6 100644
--- a/intern/cycles/util/util_color.h
+++ b/intern/cycles/util/util_color.h
@@ -236,6 +236,12 @@ ccl_device float3 color_linear_to_srgb_v3(float3 c)
color_linear_to_srgb(c.x), color_linear_to_srgb(c.y), color_linear_to_srgb(c.z));
}
+ccl_device float4 color_linear_to_srgb_v4(float4 c)
+{
+ return make_float4(
+ color_linear_to_srgb(c.x), color_linear_to_srgb(c.y), color_linear_to_srgb(c.z), c.w);
+}
+
ccl_device float4 color_srgb_to_linear_v4(float4 c)
{
#ifdef __KERNEL_SSE2__
diff --git a/intern/cycles/util/util_image.h b/intern/cycles/util/util_image.h
index 8962c09d098..27ec7ffb423 100644
--- a/intern/cycles/util/util_image.h
+++ b/intern/cycles/util/util_image.h
@@ -21,6 +21,7 @@
# include <OpenImageIO/imageio.h>
+# include "util/util_half.h"
# include "util/util_vector.h"
CCL_NAMESPACE_BEGIN
diff --git a/intern/cycles/util/util_map.h b/intern/cycles/util/util_map.h
index 3c9288417cf..8385b08dd5a 100644
--- a/intern/cycles/util/util_map.h
+++ b/intern/cycles/util/util_map.h
@@ -26,6 +26,13 @@ using std::map;
using std::pair;
using std::unordered_map;
+template<typename T> static void map_free_memory(T &data)
+{
+ /* Use swap() trick to actually free all internal memory. */
+ T empty_data;
+ data.swap(empty_data);
+}
+
CCL_NAMESPACE_END
#endif /* __UTIL_MAP_H__ */
diff --git a/intern/cycles/util/util_math.h b/intern/cycles/util/util_math.h
index 92cab29346a..40c5ebbab67 100644
--- a/intern/cycles/util/util_math.h
+++ b/intern/cycles/util/util_math.h
@@ -651,6 +651,25 @@ ccl_device_inline float2 map_to_sphere(const float3 co)
return make_float2(u, v);
}
+/* Compares two floats.
+ * Returns true if their absolute difference is smaller than abs_diff (for numbers near zero)
+ * or their relative difference is less than ulp_diff ULPs.
+ * Based on https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+ */
+
+ccl_device_inline float compare_floats(float a, float b, float abs_diff, int ulp_diff)
+{
+ if (fabsf(a - b) < abs_diff) {
+ return true;
+ }
+
+ if ((a < 0.0f) != (b < 0.0f)) {
+ return false;
+ }
+
+ return (abs(__float_as_int(a) - __float_as_int(b)) < ulp_diff);
+}
+
CCL_NAMESPACE_END
#endif /* __UTIL_MATH_H__ */