diff options
Diffstat (limited to 'intern/cycles/scene/colorspace.cpp')
-rw-r--r-- | intern/cycles/scene/colorspace.cpp | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/intern/cycles/scene/colorspace.cpp b/intern/cycles/scene/colorspace.cpp new file mode 100644 index 00000000000..8584a6f5dd7 --- /dev/null +++ b/intern/cycles/scene/colorspace.cpp @@ -0,0 +1,398 @@ +/* + * 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 "scene/colorspace.h" + +#include "util/util_color.h" +#include "util/util_half.h" +#include "util/util_image.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_colorspaces_mutex; +static thread_mutex cache_processors_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_processors_lock(cache_processors_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 +} + +bool ColorSpaceManager::colorspace_is_data(ustring colorspace) +{ + if (colorspace == u_colorspace_auto || colorspace == u_colorspace_raw || + colorspace == u_colorspace_srgb) { + return false; + } + +#ifdef WITH_OCIO + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + if (!config) { + return false; + } + + try { + OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str()); + return space && space->isData(); + } + catch (OCIO::Exception &) { + return false; + } +#else + return false; +#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_colorspaces_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_scene_linear, is_srgb; + is_builtin_colorspace(colorspace, is_scene_linear, is_srgb); + + thread_scoped_lock cache_lock(cache_colorspaces_mutex); + if (is_scene_linear) { + 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_scene_linear, + bool &is_srgb) +{ +#ifdef WITH_OCIO + const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace); + if (!processor) { + is_scene_linear = false; + is_srgb = false; + return; + } + + OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor(); + is_scene_linear = 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}; + device_processor->applyRGB(cR); + device_processor->applyRGB(cG); + device_processor->applyRGB(cB); + device_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_scene_linear = 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_scene_linear = 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_scene_linear = 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_scene_linear = false; + } + if (!compare_floats(color_srgb_to_linear(v), out_v, 1e-6f, 64)) { + is_srgb = false; + } + } +#else + (void)colorspace; + is_scene_linear = 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 num_pixels) +{ + /* TODO: implement faster version for when we know the conversion + * is a simple matrix transform between linear spaces. In that case + * un-premultiply is not needed. */ + OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor(); + + /* Process large images in chunks to keep temporary memory requirement down. */ + const size_t chunk_size = std::min((size_t)(16 * 1024 * 1024), num_pixels); + vector<float4> float_pixels(chunk_size); + + for (size_t j = 0; j < num_pixels; j += chunk_size) { + size_t width = std::min(chunk_size, num_pixels - j); + + for (size_t i = 0; i < width; i++) { + float4 value = cast_to_float4(pixels + 4 * (j + i)); + + if (!(value.w <= 0.0f || value.w == 1.0f)) { + float inv_alpha = 1.0f / value.w; + value.x *= inv_alpha; + value.y *= inv_alpha; + value.z *= inv_alpha; + } + + float_pixels[i] = value; + } + + OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 4); + device_processor->apply(desc); + + for (size_t i = 0; i < width; i++) { + float4 value = float_pixels[i]; + + if (compress_as_srgb) { + value = color_linear_to_srgb_v4(value); + } + + if (!(value.w <= 0.0f || value.w == 1.0f)) { + value.x *= value.w; + value.y *= value.w; + value.z *= value.w; + } + + cast_from_float4(pixels + 4 * (j + i), value); + } + } +} +#endif + +template<typename T> +void ColorSpaceManager::to_scene_linear(ustring colorspace, + T *pixels, + size_t num_pixels, + 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. */ + processor_apply_pixels<T, true>(processor, pixels, num_pixels); + } + else { + /* Write output as scene linear directly. */ + processor_apply_pixels<T>(processor, pixels, num_pixels); + } + } +#else + (void)colorspace; + (void)pixels; + (void)num_pixels; + (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) { + OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor(); + if (channels == 3) { + device_processor->applyRGB(pixel); + } + else if (channels == 4) { + if (pixel[3] == 1.0f || pixel[3] == 0.0f) { + /* Fast path for RGBA. */ + device_processor->applyRGB(pixel); + } + else { + /* Un-associate 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; + + device_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_processors); +#endif +} + +/* Template instantiations so we don't have to inline functions. */ +template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool); +template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool); +template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool); +template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool); + +CCL_NAMESPACE_END |