This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/intern/cycles/scene/colorspace.cpp
Brecht Van Lommel ec04870091 Fix Cycles standalone float textures not taking into account colorspace metadata
This got lost in colorspace refactoring at some point. It probably does not
affect many files in practice, but implementation was wrong regardless.
2022-12-07 18:34:57 +01:00

452 lines
14 KiB
C++

/* SPDX-License-Identifier: Apache-2.0
* Copyright 2011-2022 Blender Foundation */
#include "scene/colorspace.h"
#include "util/color.h"
#include "util/half.h"
#include "util/image.h"
#include "util/log.h"
#include "util/math.h"
#include "util/thread.h"
#include "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_WARNING << "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_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 = (strcmp(file_colorspace, "sRGB") == 0 ||
strcmp(file_colorspace, "GammaCorrected") == 0 ||
(file_colorspace[0] == '\0' &&
(strcmp(file_format, "png") == 0 || strcmp(file_format, "jpeg") == 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_INFO << "Colorspace " << colorspace.string() << " is no-op";
cached_colorspaces[colorspace] = u_colorspace_raw;
return u_colorspace_raw;
}
else if (is_srgb) {
VLOG_INFO << "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_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
}
else {
VLOG_WARNING << "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_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
cached_colorspaces[colorspace] = colorspace;
return colorspace;
#else
VLOG_WARNING << "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_rgba(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);
}
}
}
template<typename T, bool compress_as_srgb = false>
inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
T *pixels,
size_t num_pixels)
{
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<float> float_pixels(chunk_size * 3);
for (size_t j = 0; j < num_pixels; j += chunk_size) {
size_t width = std::min(chunk_size, num_pixels - j);
/* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
{
const T *pixel = pixels + j;
float *fpixel = float_pixels.data();
for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
const float f = util_image_cast_to_float<T>(*pixel);
fpixel[0] = f;
fpixel[1] = f;
fpixel[2] = f;
}
}
OCIO::PackedImageDesc desc((float *)float_pixels.data(), width, 1, 3);
device_processor->apply(desc);
{
T *pixel = pixels + j;
const float *fpixel = float_pixels.data();
for (size_t i = 0; i < width; i++, pixel++, fpixel += 3) {
float f = average(make_float3(fpixel[0], fpixel[1], fpixel[2]));
if (compress_as_srgb) {
f = color_linear_to_srgb(f);
}
*pixel = util_image_cast_from_float<T>(f);
}
}
}
}
#endif
template<typename T>
void ColorSpaceManager::to_scene_linear(
ustring colorspace, T *pixels, size_t num_pixels, bool is_rgba, bool compress_as_srgb)
{
#ifdef WITH_OCIO
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
if (processor) {
if (is_rgba) {
if (compress_as_srgb) {
/* Compress output as sRGB. */
processor_apply_pixels_rgba<T, true>(processor, pixels, num_pixels);
}
else {
/* Write output as scene linear directly. */
processor_apply_pixels_rgba<T>(processor, pixels, num_pixels);
}
}
else {
if (compress_as_srgb) {
/* Compress output as sRGB. */
processor_apply_pixels_grayscale<T, true>(processor, pixels, num_pixels);
}
else {
/* Write output as scene linear directly. */
processor_apply_pixels_grayscale<T>(processor, pixels, num_pixels);
}
}
}
#else
(void)colorspace;
(void)pixels;
(void)num_pixels;
(void)is_rgba;
(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 == 1) {
float3 rgb = make_float3(pixel[0], pixel[0], pixel[0]);
device_processor->applyRGB(&rgb.x);
pixel[0] = average(rgb);
}
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, bool);
template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool, bool);
template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool, bool);
template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool, bool);
CCL_NAMESPACE_END