mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Refactor: Cycles: Colorspace changes to prepare for texture cache
* Move code into util/ module to be reused by tx generator. * Move logic from ShaderManager into colorspace.cpp. * Improve naming distinction between scene linear and data, and sRGB and scene linear compressed as sRGB. * Use interop ID instead of enum for known scene linear color spaces. * Support colorspace conversion on image buffers with arbitrary y stride. Pull Request: https://projects.blender.org/blender/blender/pulls/152665
This commit is contained in:
parent
78efebec1e
commit
ca4458f6ea
16 changed files with 946 additions and 744 deletions
|
|
@ -47,12 +47,14 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
|
||||||
ImageMetaData &metadata)
|
ImageMetaData &metadata)
|
||||||
{
|
{
|
||||||
bool is_float = false;
|
bool is_float = false;
|
||||||
|
bool is_data = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
void *lock;
|
void *lock;
|
||||||
blender::ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
|
blender::ImBuf *ibuf = BKE_image_acquire_ibuf(b_image, &b_iuser, &lock);
|
||||||
if (ibuf) {
|
if (ibuf) {
|
||||||
is_float = ibuf->float_buffer.data != nullptr;
|
is_float = ibuf->float_buffer.data != nullptr;
|
||||||
|
is_data = ibuf->colormanage_flag & blender::IMB_COLORMANAGE_IS_DATA;
|
||||||
metadata.width = ibuf->x;
|
metadata.width = ibuf->x;
|
||||||
metadata.height = ibuf->y;
|
metadata.height = ibuf->y;
|
||||||
metadata.channels = (is_float) ? ibuf->channels : 4;
|
metadata.channels = (is_float) ? ibuf->channels : 4;
|
||||||
|
|
@ -76,12 +78,13 @@ bool BlenderImageLoader::load_metadata(const ImageDeviceFeatures & /*features*/,
|
||||||
|
|
||||||
/* Float images are already converted on the Blender side,
|
/* Float images are already converted on the Blender side,
|
||||||
* no need to do anything in Cycles. */
|
* no need to do anything in Cycles. */
|
||||||
metadata.colorspace = u_colorspace_raw;
|
metadata.colorspace = (is_data) ? u_colorspace_data : u_colorspace_scene_linear;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the
|
/* In some cases (e.g. #94135), the colorspace setting in Blender gets updated as part of the
|
||||||
* metadata queries in this function, so update the colorspace setting here. */
|
* metadata queries in this function, so update the colorspace setting here. */
|
||||||
metadata.colorspace = b_image->colorspace_settings.name;
|
metadata.colorspace = (is_data) ? u_colorspace_data :
|
||||||
|
ustring(b_image->colorspace_settings.name);
|
||||||
metadata.type = IMAGE_DATA_TYPE_BYTE4;
|
metadata.type = IMAGE_DATA_TYPE_BYTE4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,9 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "scene/colorspace.h"
|
|
||||||
#include "scene/object.h"
|
#include "scene/object.h"
|
||||||
|
|
||||||
|
#include "util/colorspace.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ set(SRC
|
||||||
background.cpp
|
background.cpp
|
||||||
bake.cpp
|
bake.cpp
|
||||||
camera.cpp
|
camera.cpp
|
||||||
colorspace.cpp
|
|
||||||
constant_fold.cpp
|
constant_fold.cpp
|
||||||
devicescene.cpp
|
devicescene.cpp
|
||||||
film.cpp
|
film.cpp
|
||||||
|
|
@ -56,7 +55,6 @@ set(SRC_HEADERS
|
||||||
bake.h
|
bake.h
|
||||||
background.h
|
background.h
|
||||||
camera.h
|
camera.h
|
||||||
colorspace.h
|
|
||||||
constant_fold.h
|
constant_fold.h
|
||||||
devicescene.h
|
devicescene.h
|
||||||
film.h
|
film.h
|
||||||
|
|
@ -110,16 +108,6 @@ if(WITH_CYCLES_OSL)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WITH_OPENCOLORIO)
|
|
||||||
add_definitions(-DWITH_OCIO)
|
|
||||||
list(APPEND LIB
|
|
||||||
bf::dependencies::optional::opencolorio
|
|
||||||
)
|
|
||||||
if(WIN32 AND NOT USD_OVERRIDE_OPENCOLORIO)
|
|
||||||
add_definitions(-DOpenColorIO_SKIP_IMPORTS)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(WITH_OPENVDB)
|
if(WITH_OPENVDB)
|
||||||
list(APPEND LIB
|
list(APPEND LIB
|
||||||
bf::dependencies::optional::openvdb
|
bf::dependencies::optional::openvdb
|
||||||
|
|
|
||||||
|
|
@ -900,7 +900,7 @@ void Camera::set_osl_camera(Scene *scene,
|
||||||
{
|
{
|
||||||
#ifdef WITH_OSL
|
#ifdef WITH_OSL
|
||||||
/* Ensure shading system exists before we try to load a shader. */
|
/* Ensure shading system exists before we try to load a shader. */
|
||||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_space());
|
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||||
|
|
||||||
/* Load the shader. */
|
/* Load the shader. */
|
||||||
const char *hash;
|
const char *hash;
|
||||||
|
|
|
||||||
|
|
@ -1,528 +0,0 @@
|
||||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0 */
|
|
||||||
|
|
||||||
#include "scene/colorspace.h"
|
|
||||||
|
|
||||||
#include "util/color.h"
|
|
||||||
#include "util/image.h"
|
|
||||||
#include "util/log.h"
|
|
||||||
#include "util/map.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 unordered_map<ustring, ustring> cached_colorspaces;
|
|
||||||
|
|
||||||
static thread_mutex cache_processors_mutex;
|
|
||||||
static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cached_processors;
|
|
||||||
|
|
||||||
static thread_mutex cache_scene_linear_mutex;
|
|
||||||
static string cache_scene_linear_name;
|
|
||||||
|
|
||||||
static void check_invalidate_caches()
|
|
||||||
{
|
|
||||||
/* Invalidate cached processors and colorspace, in case Blender changed it.
|
|
||||||
* Note this should not happen during rendering, all render should be stopped
|
|
||||||
* before it is changed. */
|
|
||||||
const thread_scoped_lock cache_scene_linear_lock(cache_scene_linear_mutex);
|
|
||||||
OCIO::ConstConfigRcPtr config = nullptr;
|
|
||||||
try {
|
|
||||||
config = OCIO::GetCurrentConfig();
|
|
||||||
}
|
|
||||||
catch (const OCIO::Exception &exception) {
|
|
||||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OCIO::ConstColorSpaceRcPtr scene_linear_colorspace = config->getColorSpace("scene_linear");
|
|
||||||
if (scene_linear_colorspace && cache_scene_linear_name != scene_linear_colorspace->getName()) {
|
|
||||||
cache_scene_linear_name = scene_linear_colorspace->getName();
|
|
||||||
{
|
|
||||||
const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
|
|
||||||
cached_processors.clear();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
|
||||||
cached_colorspaces.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#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 nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
OCIO::ConstConfigRcPtr config = nullptr;
|
|
||||||
try {
|
|
||||||
config = OCIO::GetCurrentConfig();
|
|
||||||
}
|
|
||||||
catch (const OCIO::Exception &exception) {
|
|
||||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
check_invalidate_caches();
|
|
||||||
|
|
||||||
/* Cache processor until free_memory(), memory overhead is expected to be
|
|
||||||
* small and the processor is likely to be reused. */
|
|
||||||
const 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 (const OCIO::Exception &exception) {
|
|
||||||
cached_processors[colorspace] = OCIO::ConstProcessorRcPtr();
|
|
||||||
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
|
||||||
<< " cannot be converted to scene_linear: " << exception.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const OCIO::Processor *processor = cached_processors[colorspace].get();
|
|
||||||
return (ColorSpaceProcessor *)processor;
|
|
||||||
#else
|
|
||||||
/* No OpenColorIO. */
|
|
||||||
(void)colorspace;
|
|
||||||
return nullptr;
|
|
||||||
#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 = nullptr;
|
|
||||||
try {
|
|
||||||
config = OCIO::GetCurrentConfig();
|
|
||||||
}
|
|
||||||
catch (const OCIO::Exception &exception) {
|
|
||||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
|
||||||
return space && space->isData();
|
|
||||||
}
|
|
||||||
catch (const 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) {
|
|
||||||
const 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;
|
|
||||||
}
|
|
||||||
return u_colorspace_srgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Builtin colorspaces. */
|
|
||||||
if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_raw) {
|
|
||||||
return colorspace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Use OpenColorIO. */
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
check_invalidate_caches();
|
|
||||||
|
|
||||||
{
|
|
||||||
const 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;
|
|
||||||
bool is_srgb;
|
|
||||||
is_builtin_colorspace(colorspace, is_scene_linear, is_srgb);
|
|
||||||
|
|
||||||
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
|
||||||
if (is_scene_linear) {
|
|
||||||
LOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
|
|
||||||
cached_colorspaces[colorspace] = u_colorspace_raw;
|
|
||||||
return u_colorspace_raw;
|
|
||||||
}
|
|
||||||
if (is_srgb) {
|
|
||||||
LOG_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 = nullptr;
|
|
||||||
try {
|
|
||||||
config = OCIO::GetCurrentConfig();
|
|
||||||
}
|
|
||||||
catch (const OCIO::Exception &exception) {
|
|
||||||
LOG_ERROR << "OCIO config error: " << exception.what();
|
|
||||||
return u_colorspace_raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config || !config->getColorSpace(colorspace.c_str())) {
|
|
||||||
LOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
LOG_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. */
|
|
||||||
LOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
|
|
||||||
cached_colorspaces[colorspace] = colorspace;
|
|
||||||
return colorspace;
|
|
||||||
#else
|
|
||||||
LOG_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
|
||||||
is_scene_linear = true;
|
|
||||||
is_srgb = true;
|
|
||||||
for (int i = 0; i < 256; i++) {
|
|
||||||
const 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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-4f, 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, const 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,
|
|
||||||
const size_t num_pixels,
|
|
||||||
const bool ignore_alpha)
|
|
||||||
{
|
|
||||||
/* 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. */
|
|
||||||
const 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) {
|
|
||||||
const 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 (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
|
||||||
const float inv_alpha = 1.0f / value.w;
|
|
||||||
value.x *= inv_alpha;
|
|
||||||
value.y *= inv_alpha;
|
|
||||||
value.z *= inv_alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
float_pixels[i] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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 (!ignore_alpha && !(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,
|
|
||||||
const size_t num_pixels)
|
|
||||||
{
|
|
||||||
const 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) {
|
|
||||||
const 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const 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,
|
|
||||||
const size_t num_pixels,
|
|
||||||
bool is_rgba,
|
|
||||||
bool compress_as_srgb,
|
|
||||||
bool ignore_alpha)
|
|
||||||
{
|
|
||||||
#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, ignore_alpha);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
/* Write output as scene linear directly. */
|
|
||||||
processor_apply_pixels_rgba<T>(processor, pixels, num_pixels, ignore_alpha);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
const int channels)
|
|
||||||
{
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
|
|
||||||
|
|
||||||
if (processor) {
|
|
||||||
const 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. */
|
|
||||||
const float alpha = pixel[3];
|
|
||||||
const 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
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColorSpaceManager::init_fallback_config()
|
|
||||||
{
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Template instantiations so we don't have to inline functions. */
|
|
||||||
template void ColorSpaceManager::to_scene_linear(ustring, uchar *, size_t, bool, bool, bool);
|
|
||||||
template void ColorSpaceManager::to_scene_linear(ustring, ushort *, size_t, bool, bool, bool);
|
|
||||||
template void ColorSpaceManager::to_scene_linear(ustring, half *, size_t, bool, bool, bool);
|
|
||||||
template void ColorSpaceManager::to_scene_linear(ustring, float *, size_t, bool, bool, bool);
|
|
||||||
|
|
||||||
CCL_NAMESPACE_END
|
|
||||||
|
|
@ -2,14 +2,15 @@
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0 */
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
|
||||||
#include "scene/image.h"
|
|
||||||
#include "device/device.h"
|
#include "device/device.h"
|
||||||
#include "scene/colorspace.h"
|
|
||||||
|
#include "scene/image.h"
|
||||||
#include "scene/image_oiio.h"
|
#include "scene/image_oiio.h"
|
||||||
#include "scene/image_vdb.h"
|
#include "scene/image_vdb.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "scene/stats.h"
|
#include "scene/stats.h"
|
||||||
|
|
||||||
|
#include "util/colorspace.h"
|
||||||
#include "util/image.h"
|
#include "util/image.h"
|
||||||
#include "util/image_impl.h"
|
#include "util/image_impl.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
|
|
@ -232,7 +233,7 @@ ImageMetaData::ImageMetaData()
|
||||||
height(0),
|
height(0),
|
||||||
byte_size(0),
|
byte_size(0),
|
||||||
type(IMAGE_DATA_NUM_TYPES),
|
type(IMAGE_DATA_NUM_TYPES),
|
||||||
colorspace(u_colorspace_raw),
|
colorspace(u_colorspace_scene_linear),
|
||||||
colorspace_file_format(""),
|
colorspace_file_format(""),
|
||||||
use_transform_3d(false),
|
use_transform_3d(false),
|
||||||
compress_as_srgb(false)
|
compress_as_srgb(false)
|
||||||
|
|
@ -259,10 +260,10 @@ void ImageMetaData::detect_colorspace()
|
||||||
colorspace = ColorSpaceManager::detect_known_colorspace(
|
colorspace = ColorSpaceManager::detect_known_colorspace(
|
||||||
colorspace, colorspace_file_hint.c_str(), colorspace_file_format, is_float());
|
colorspace, colorspace_file_hint.c_str(), colorspace_file_format, is_float());
|
||||||
|
|
||||||
if (colorspace == u_colorspace_raw) {
|
if (colorspace == u_colorspace_scene_linear || colorspace == u_colorspace_data) {
|
||||||
/* Nothing to do. */
|
/* Nothing to do. */
|
||||||
}
|
}
|
||||||
else if (colorspace == u_colorspace_srgb) {
|
else if (colorspace == u_colorspace_scene_linear_srgb) {
|
||||||
/* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time
|
/* Keep sRGB colorspace stored as sRGB, to save memory and/or loading time
|
||||||
* for the common case of 8bit sRGB images like PNG. */
|
* for the common case of 8bit sRGB images like PNG. */
|
||||||
compress_as_srgb = true;
|
compress_as_srgb = true;
|
||||||
|
|
@ -620,15 +621,18 @@ bool ImageManager::file_load_image(Image *img, const int texture_limit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (img->metadata.colorspace != u_colorspace_raw &&
|
if (img->metadata.colorspace != u_colorspace_scene_linear &&
|
||||||
img->metadata.colorspace != u_colorspace_srgb)
|
img->metadata.colorspace != u_colorspace_scene_linear_srgb &&
|
||||||
|
img->metadata.colorspace != u_colorspace_data)
|
||||||
{
|
{
|
||||||
/* Convert to scene linear. */
|
/* Convert to scene linear. */
|
||||||
const bool ignore_alpha = img->params.alpha_type == IMAGE_ALPHA_IGNORE ||
|
const bool ignore_alpha = img->params.alpha_type == IMAGE_ALPHA_IGNORE ||
|
||||||
img->params.alpha_type == IMAGE_ALPHA_CHANNEL_PACKED;
|
img->params.alpha_type == IMAGE_ALPHA_CHANNEL_PACKED;
|
||||||
ColorSpaceManager::to_scene_linear(img->metadata.colorspace,
|
ColorSpaceManager::to_scene_linear(img->metadata.colorspace,
|
||||||
pixels,
|
pixels,
|
||||||
num_pixels,
|
width,
|
||||||
|
height,
|
||||||
|
width * (is_rgba ? 4 : 1),
|
||||||
is_rgba,
|
is_rgba,
|
||||||
img->metadata.compress_as_srgb,
|
img->metadata.compress_as_srgb,
|
||||||
ignore_alpha);
|
ignore_alpha);
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@
|
||||||
|
|
||||||
#include "device/memory.h"
|
#include "device/memory.h"
|
||||||
|
|
||||||
#include "scene/colorspace.h"
|
#include "util/colorspace.h"
|
||||||
|
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "util/thread.h"
|
#include "util/thread.h"
|
||||||
#include "util/transform.h"
|
#include "util/transform.h"
|
||||||
|
|
@ -38,7 +37,7 @@ class ImageParams {
|
||||||
ustring colorspace;
|
ustring colorspace;
|
||||||
float frame = 0.0f;
|
float frame = 0.0f;
|
||||||
|
|
||||||
ImageParams() : colorspace(u_colorspace_raw) {}
|
ImageParams() : colorspace(u_colorspace_scene_linear) {}
|
||||||
|
|
||||||
bool operator==(const ImageParams &other) const
|
bool operator==(const ImageParams &other) const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include "scene/background.h"
|
#include "scene/background.h"
|
||||||
#include "scene/camera.h"
|
#include "scene/camera.h"
|
||||||
#include "scene/colorspace.h"
|
|
||||||
#include "scene/light.h"
|
#include "scene/light.h"
|
||||||
#include "scene/osl.h"
|
#include "scene/osl.h"
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
|
|
@ -124,7 +123,7 @@ bool OSLManager::need_update() const
|
||||||
void OSLManager::device_update_pre(Device *device, Scene *scene)
|
void OSLManager::device_update_pre(Device *device, Scene *scene)
|
||||||
{
|
{
|
||||||
if (scene->shader_manager->use_osl() || !scene->camera->script_name.empty()) {
|
if (scene->shader_manager->use_osl() || !scene->camera->script_name.empty()) {
|
||||||
shading_system_init(scene->shader_manager->get_scene_linear_space());
|
shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!need_update()) {
|
if (!need_update()) {
|
||||||
|
|
@ -343,7 +342,7 @@ void OSLManager::texture_system_free()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
void OSLManager::shading_system_init(const string &colorspace_interop_id)
|
||||||
{
|
{
|
||||||
/* No need to do anything if we already have shading systems. */
|
/* No need to do anything if we already have shading systems. */
|
||||||
if (!ss_map.empty()) {
|
if (!ss_map.empty()) {
|
||||||
|
|
@ -353,7 +352,7 @@ void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
||||||
/* create shading system, shared between different renders to reduce memory usage */
|
/* create shading system, shared between different renders to reduce memory usage */
|
||||||
const thread_scoped_lock lock(ss_shared_mutex);
|
const thread_scoped_lock lock(ss_shared_mutex);
|
||||||
|
|
||||||
foreach_osl_device(device_, [this, colorspace](Device *sub_device, OSLGlobals *) {
|
foreach_osl_device(device_, [this, colorspace_interop_id](Device *sub_device, OSLGlobals *) {
|
||||||
const DeviceType device_type = sub_device->info.type;
|
const DeviceType device_type = sub_device->info.type;
|
||||||
|
|
||||||
if (!ss_shared[device_type]) {
|
if (!ss_shared[device_type]) {
|
||||||
|
|
@ -385,18 +384,14 @@ void OSLManager::shading_system_init(ShaderManager::SceneLinearSpace colorspace)
|
||||||
ss->attribute("greedyjit", 1);
|
ss->attribute("greedyjit", 1);
|
||||||
|
|
||||||
/* OSL doesn't accept an arbitrary space, so support a few specific spaces. */
|
/* OSL doesn't accept an arbitrary space, so support a few specific spaces. */
|
||||||
switch (colorspace) {
|
if (colorspace_interop_id == "lin_rec709_scene") {
|
||||||
case ShaderManager::SceneLinearSpace::Rec709:
|
ss->attribute("colorspace", OSL::Strings::Rec709);
|
||||||
ss->attribute("colorspace", OSL::Strings::Rec709);
|
}
|
||||||
break;
|
else if (colorspace_interop_id == "lin_rec2020_scene") {
|
||||||
case ShaderManager::SceneLinearSpace::Rec2020:
|
ss->attribute("colorspace", OSL::Strings::HDTV);
|
||||||
ss->attribute("colorspace", OSL::Strings::HDTV);
|
}
|
||||||
break;
|
else if (colorspace_interop_id == "lin_ap1_scene") {
|
||||||
case ShaderManager::SceneLinearSpace::ACEScg:
|
ss->attribute("colorspace", OSL::Strings::ACEScg);
|
||||||
ss->attribute("colorspace", OSL::Strings::ACEScg);
|
|
||||||
break;
|
|
||||||
case ShaderManager::SceneLinearSpace::Unknown:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *groupdata_alloc_str = getenv("CYCLES_OSL_GROUPDATA_ALLOC");
|
const char *groupdata_alloc_str = getenv("CYCLES_OSL_GROUPDATA_ALLOC");
|
||||||
|
|
@ -758,7 +753,7 @@ OSLNode *OSLShaderManager::osl_node(ShaderGraph *graph,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure shading system exists before we try to load a shader. */
|
/* Ensure shading system exists before we try to load a shader. */
|
||||||
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_space());
|
scene->osl_manager->shading_system_init(scene->shader_manager->get_scene_linear_interop_id());
|
||||||
|
|
||||||
/* Load shader code. */
|
/* Load shader code. */
|
||||||
const char *hash;
|
const char *hash;
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ class OSLManager {
|
||||||
const char *shader_load_filepath(string filepath);
|
const char *shader_load_filepath(string filepath);
|
||||||
OSLShaderInfo *shader_loaded_info(const string &hash);
|
OSLShaderInfo *shader_loaded_info(const string &hash);
|
||||||
|
|
||||||
void shading_system_init(ShaderManager::SceneLinearSpace colorspace);
|
void shading_system_init(const string &colorspace_interop_id);
|
||||||
|
|
||||||
OSL::ShadingSystem *get_shading_system(Device *sub_device);
|
OSL::ShadingSystem *get_shading_system(Device *sub_device);
|
||||||
OSL::TextureSystem *get_texture_system();
|
OSL::TextureSystem *get_texture_system();
|
||||||
|
|
|
||||||
|
|
@ -20,15 +20,11 @@
|
||||||
#include "scene/tables.h"
|
#include "scene/tables.h"
|
||||||
#include "scene/volume.h"
|
#include "scene/volume.h"
|
||||||
|
|
||||||
|
#include "util/colorspace.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/murmurhash.h"
|
#include "util/murmurhash.h"
|
||||||
#include "util/transform.h"
|
#include "util/transform.h"
|
||||||
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
# include <OpenColorIO/OpenColorIO.h>
|
|
||||||
namespace OCIO = OCIO_NAMESPACE;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "scene/shader.tables"
|
#include "scene/shader.tables"
|
||||||
|
|
||||||
CCL_NAMESPACE_BEGIN
|
CCL_NAMESPACE_BEGIN
|
||||||
|
|
@ -734,7 +730,7 @@ void ShaderManager::device_update_common(Device * /*device*/,
|
||||||
kfilm->rec709_to_r = make_float4(rec709_to_r);
|
kfilm->rec709_to_r = make_float4(rec709_to_r);
|
||||||
kfilm->rec709_to_g = make_float4(rec709_to_g);
|
kfilm->rec709_to_g = make_float4(rec709_to_g);
|
||||||
kfilm->rec709_to_b = make_float4(rec709_to_b);
|
kfilm->rec709_to_b = make_float4(rec709_to_b);
|
||||||
kfilm->is_rec709 = scene_linear_space == SceneLinearSpace::Rec709;
|
kfilm->is_rec709 = scene_linear_interop_id == "lin_rec709_scene";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderManager::device_free_common(Device * /*device*/, DeviceScene *dscene, Scene *scene)
|
void ShaderManager::device_free_common(Device * /*device*/, DeviceScene *dscene, Scene *scene)
|
||||||
|
|
@ -919,37 +915,6 @@ bool ShaderManager::need_update() const
|
||||||
return update_flags != UPDATE_NONE;
|
return update_flags != UPDATE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
static bool to_scene_linear_transform(OCIO::ConstConfigRcPtr &config,
|
|
||||||
const char *colorspace,
|
|
||||||
Transform &to_scene_linear)
|
|
||||||
{
|
|
||||||
OCIO::ConstProcessorRcPtr processor;
|
|
||||||
try {
|
|
||||||
processor = config->getProcessor("scene_linear", colorspace);
|
|
||||||
}
|
|
||||||
catch (OCIO::Exception &) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!processor) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
|
||||||
if (!device_processor) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
to_scene_linear = transform_identity();
|
|
||||||
device_processor->applyRGB(&to_scene_linear.x.x);
|
|
||||||
device_processor->applyRGB(&to_scene_linear.y.x);
|
|
||||||
device_processor->applyRGB(&to_scene_linear.z.x);
|
|
||||||
to_scene_linear = transform_transposed_inverse(to_scene_linear);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void ShaderManager::compute_thin_film_table(const Transform &xyz_to_rgb)
|
void ShaderManager::compute_thin_film_table(const Transform &xyz_to_rgb)
|
||||||
{
|
{
|
||||||
/* Our implementation of Thin Film Fresnel is based on
|
/* Our implementation of Thin Film Fresnel is based on
|
||||||
|
|
@ -1015,84 +980,21 @@ void ShaderManager::compute_thin_film_table(const Transform &xyz_to_rgb)
|
||||||
|
|
||||||
void ShaderManager::init_xyz_transforms()
|
void ShaderManager::init_xyz_transforms()
|
||||||
{
|
{
|
||||||
/* Default to ITU-BT.709 in case no appropriate transform found.
|
const Transform xyz_to_rgb = ColorSpaceManager::get_xyz_to_scene_linear_rgb();
|
||||||
* Note XYZ here is defined as having a D65 white point. */
|
|
||||||
const Transform xyz_to_rec709 = make_transform(3.2404542f,
|
|
||||||
-1.5371385f,
|
|
||||||
-0.4985314f,
|
|
||||||
0.0f,
|
|
||||||
-0.9692660f,
|
|
||||||
1.8760108f,
|
|
||||||
0.0415560f,
|
|
||||||
0.0f,
|
|
||||||
0.0556434f,
|
|
||||||
-0.2040259f,
|
|
||||||
1.0572252f,
|
|
||||||
0.0f);
|
|
||||||
|
|
||||||
xyz_to_r = make_float3(xyz_to_rec709.x);
|
scene_linear_interop_id = ColorSpaceManager::get_scene_linear_interop_id();
|
||||||
xyz_to_g = make_float3(xyz_to_rec709.y);
|
|
||||||
xyz_to_b = make_float3(xyz_to_rec709.z);
|
|
||||||
rgb_to_y = make_float3(0.2126729f, 0.7151522f, 0.0721750f);
|
|
||||||
white_xyz = make_float3(0.95047f, 1.0f, 1.08883f);
|
|
||||||
|
|
||||||
rec709_to_r = make_float3(1.0f, 0.0f, 0.0f);
|
if (scene_linear_interop_id == "lin_rec709_scene") {
|
||||||
rec709_to_g = make_float3(0.0f, 1.0f, 0.0f);
|
rec709_to_r = make_float3(1.0f, 0.0f, 0.0f);
|
||||||
rec709_to_b = make_float3(0.0f, 0.0f, 1.0f);
|
rec709_to_g = make_float3(0.0f, 1.0f, 0.0f);
|
||||||
scene_linear_space = SceneLinearSpace::Rec709;
|
rec709_to_b = make_float3(0.0f, 0.0f, 1.0f);
|
||||||
|
|
||||||
compute_thin_film_table(xyz_to_rec709);
|
|
||||||
|
|
||||||
#ifdef WITH_OCIO
|
|
||||||
/* Get from OpenColorO config if it has the required roles. */
|
|
||||||
OCIO::ConstConfigRcPtr config = nullptr;
|
|
||||||
try {
|
|
||||||
config = OCIO::GetCurrentConfig();
|
|
||||||
}
|
|
||||||
catch (OCIO::Exception &exception) {
|
|
||||||
LOG_WARNING << "OCIO config error: " << exception.what();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(config && config->hasRole("scene_linear"))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Transform xyz_to_rgb;
|
|
||||||
|
|
||||||
if (config->hasRole("aces_interchange")) {
|
|
||||||
/* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
|
|
||||||
Transform aces_to_rgb;
|
|
||||||
if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is the OpenColorIO builtin transform:
|
|
||||||
* UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */
|
|
||||||
const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f,
|
|
||||||
-0.004451f,
|
|
||||||
0.016628f,
|
|
||||||
0.000000f,
|
|
||||||
0.337369f,
|
|
||||||
0.729522f,
|
|
||||||
-0.066890f,
|
|
||||||
0.000000f,
|
|
||||||
0.001174f,
|
|
||||||
-0.003711f,
|
|
||||||
1.091595f,
|
|
||||||
0.000000f);
|
|
||||||
const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65);
|
|
||||||
xyz_to_rgb = aces_to_rgb * xyz_to_aces;
|
|
||||||
}
|
|
||||||
else if (config->hasRole("XYZ")) {
|
|
||||||
/* Custom role used before the standard existed. */
|
|
||||||
if (!to_scene_linear_transform(config, "XYZ", xyz_to_rgb)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* No reference role found to determine XYZ. */
|
const Transform xyz_to_rec709 = ColorSpaceManager::get_xyz_to_rec709();
|
||||||
return;
|
const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709);
|
||||||
|
rec709_to_r = make_float3(rec709_to_rgb.x);
|
||||||
|
rec709_to_g = make_float3(rec709_to_rgb.y);
|
||||||
|
rec709_to_b = make_float3(rec709_to_rgb.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
xyz_to_r = make_float3(xyz_to_rgb.x);
|
xyz_to_r = make_float3(xyz_to_rgb.x);
|
||||||
|
|
@ -1103,51 +1005,7 @@ void ShaderManager::init_xyz_transforms()
|
||||||
rgb_to_y = make_float3(rgb_to_xyz.y);
|
rgb_to_y = make_float3(rgb_to_xyz.y);
|
||||||
white_xyz = transform_direction(&rgb_to_xyz, one_float3());
|
white_xyz = transform_direction(&rgb_to_xyz, one_float3());
|
||||||
|
|
||||||
const Transform rec709_to_rgb = xyz_to_rgb * transform_inverse(xyz_to_rec709);
|
|
||||||
rec709_to_r = make_float3(rec709_to_rgb.x);
|
|
||||||
rec709_to_g = make_float3(rec709_to_rgb.y);
|
|
||||||
rec709_to_b = make_float3(rec709_to_rgb.z);
|
|
||||||
|
|
||||||
compute_thin_film_table(xyz_to_rgb);
|
compute_thin_film_table(xyz_to_rgb);
|
||||||
|
|
||||||
const Transform xyz_to_rec2020 = make_transform(1.7166512f,
|
|
||||||
-0.3556708f,
|
|
||||||
-0.2533663f,
|
|
||||||
0.0f,
|
|
||||||
-0.6666844,
|
|
||||||
1.6164812f,
|
|
||||||
0.0157685f,
|
|
||||||
0.0f,
|
|
||||||
0.0176399f,
|
|
||||||
-0.0427706f,
|
|
||||||
0.9421031f,
|
|
||||||
0.0f);
|
|
||||||
const Transform acescg_to_xyz = make_transform(0.652238f,
|
|
||||||
0.128237f,
|
|
||||||
0.169983f,
|
|
||||||
0.0f,
|
|
||||||
0.267672f,
|
|
||||||
0.674340f,
|
|
||||||
0.057988f,
|
|
||||||
0.0f,
|
|
||||||
-0.005382f,
|
|
||||||
0.001369f,
|
|
||||||
1.093071f,
|
|
||||||
0.0f);
|
|
||||||
|
|
||||||
if (transform_equal_threshold(xyz_to_rgb, xyz_to_rec709, 0.001f)) {
|
|
||||||
scene_linear_space = SceneLinearSpace::Rec709;
|
|
||||||
}
|
|
||||||
else if (transform_equal_threshold(xyz_to_rgb, xyz_to_rec2020, 0.001f)) {
|
|
||||||
scene_linear_space = SceneLinearSpace::Rec2020;
|
|
||||||
}
|
|
||||||
else if (transform_equal_threshold(rgb_to_xyz, acescg_to_xyz, 0.001f)) {
|
|
||||||
scene_linear_space = SceneLinearSpace::ACEScg;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
scene_linear_space = SceneLinearSpace::Unknown;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t ShaderManager::ensure_bsdf_table_impl(DeviceScene *dscene,
|
size_t ShaderManager::ensure_bsdf_table_impl(DeviceScene *dscene,
|
||||||
|
|
|
||||||
|
|
@ -224,11 +224,9 @@ class ShaderManager {
|
||||||
|
|
||||||
void init_xyz_transforms();
|
void init_xyz_transforms();
|
||||||
|
|
||||||
enum class SceneLinearSpace { Rec709, Rec2020, ACEScg, Unknown };
|
const string &get_scene_linear_interop_id()
|
||||||
|
|
||||||
SceneLinearSpace get_scene_linear_space()
|
|
||||||
{
|
{
|
||||||
return scene_linear_space;
|
return scene_linear_interop_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -254,7 +252,7 @@ class ShaderManager {
|
||||||
float3 rec709_to_r;
|
float3 rec709_to_r;
|
||||||
float3 rec709_to_g;
|
float3 rec709_to_g;
|
||||||
float3 rec709_to_b;
|
float3 rec709_to_b;
|
||||||
SceneLinearSpace scene_linear_space;
|
string scene_linear_interop_id;
|
||||||
vector<float> thin_film_table;
|
vector<float> thin_film_table;
|
||||||
|
|
||||||
template<std::size_t n>
|
template<std::size_t n>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include "scene/shader_nodes.h"
|
#include "scene/shader_nodes.h"
|
||||||
#include "kernel/svm/types.h"
|
#include "kernel/svm/types.h"
|
||||||
#include "kernel/types.h"
|
#include "kernel/types.h"
|
||||||
#include "scene/colorspace.h"
|
|
||||||
#include "scene/constant_fold.h"
|
#include "scene/constant_fold.h"
|
||||||
#include "scene/film.h"
|
#include "scene/film.h"
|
||||||
#include "scene/image.h"
|
#include "scene/image.h"
|
||||||
|
|
@ -21,7 +20,7 @@
|
||||||
#include "sky_nishita.h"
|
#include "sky_nishita.h"
|
||||||
|
|
||||||
#include "util/color.h"
|
#include "util/color.h"
|
||||||
|
#include "util/colorspace.h"
|
||||||
#include "util/log.h"
|
#include "util/log.h"
|
||||||
#include "util/math_base.h"
|
#include "util/math_base.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|
@ -266,7 +265,7 @@ NODE_DEFINE(ImageTextureNode)
|
||||||
|
|
||||||
ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(get_node_type())
|
ImageTextureNode::ImageTextureNode() : ImageSlotTextureNode(get_node_type())
|
||||||
{
|
{
|
||||||
colorspace = u_colorspace_raw;
|
colorspace = u_colorspace_scene_linear;
|
||||||
animated = false;
|
animated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,7 +465,7 @@ void ImageTextureNode::compile(OSLCompiler &compiler)
|
||||||
|
|
||||||
if (handle.svm_slot() == -1) {
|
if (handle.svm_slot() == -1) {
|
||||||
compiler.parameter_texture(
|
compiler.parameter_texture(
|
||||||
"filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
|
"filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
compiler.parameter_texture("filename", handle);
|
compiler.parameter_texture("filename", handle);
|
||||||
|
|
@ -535,7 +534,7 @@ NODE_DEFINE(EnvironmentTextureNode)
|
||||||
|
|
||||||
EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(get_node_type())
|
EnvironmentTextureNode::EnvironmentTextureNode() : ImageSlotTextureNode(get_node_type())
|
||||||
{
|
{
|
||||||
colorspace = u_colorspace_raw;
|
colorspace = u_colorspace_scene_linear;
|
||||||
animated = false;
|
animated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -618,7 +617,7 @@ void EnvironmentTextureNode::compile(OSLCompiler &compiler)
|
||||||
|
|
||||||
if (handle.svm_slot() == -1) {
|
if (handle.svm_slot() == -1) {
|
||||||
compiler.parameter_texture(
|
compiler.parameter_texture(
|
||||||
"filename", filename, compress_as_srgb ? u_colorspace_raw : known_colorspace);
|
"filename", filename, compress_as_srgb ? u_colorspace_scene_linear : known_colorspace);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
compiler.parameter_texture("filename", handle);
|
compiler.parameter_texture("filename", handle);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
#include "device/device.h"
|
#include "device/device.h"
|
||||||
|
|
||||||
#include "scene/colorspace.h"
|
|
||||||
#include "scene/scene.h"
|
#include "scene/scene.h"
|
||||||
#include "scene/shader_graph.h"
|
#include "scene/shader_graph.h"
|
||||||
#include "scene/shader_nodes.h"
|
#include "scene/shader_nodes.h"
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ set(INC_SYS
|
||||||
|
|
||||||
set(SRC
|
set(SRC
|
||||||
aligned_malloc.cpp
|
aligned_malloc.cpp
|
||||||
|
colorspace.cpp
|
||||||
debug.cpp
|
debug.cpp
|
||||||
ies.cpp
|
ies.cpp
|
||||||
log.cpp
|
log.cpp
|
||||||
|
|
@ -45,6 +46,7 @@ set(SRC_HEADERS
|
||||||
atomic.h
|
atomic.h
|
||||||
boundbox.h
|
boundbox.h
|
||||||
color.h
|
color.h
|
||||||
|
colorspace.h
|
||||||
concurrent_set.h
|
concurrent_set.h
|
||||||
concurrent_vector.h
|
concurrent_vector.h
|
||||||
debug.h
|
debug.h
|
||||||
|
|
@ -150,4 +152,14 @@ if(WITH_OPENVDB)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(WITH_OPENCOLORIO)
|
||||||
|
add_definitions(-DWITH_OCIO)
|
||||||
|
list(APPEND LIB
|
||||||
|
bf::dependencies::optional::opencolorio
|
||||||
|
)
|
||||||
|
if(WIN32 AND NOT USD_OVERRIDE_OPENCOLORIO)
|
||||||
|
add_definitions(-DOpenColorIO_SKIP_IMPORTS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
cycles_add_library(cycles_util "${LIB}" ${SRC} ${SRC_HEADERS})
|
cycles_add_library(cycles_util "${LIB}" ${SRC} ${SRC_HEADERS})
|
||||||
|
|
|
||||||
850
intern/cycles/util/colorspace.cpp
Normal file
850
intern/cycles/util/colorspace.cpp
Normal file
|
|
@ -0,0 +1,850 @@
|
||||||
|
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
|
||||||
|
#include "util/colorspace.h"
|
||||||
|
#include "util/color.h"
|
||||||
|
#include "util/image.h"
|
||||||
|
#include "util/log.h"
|
||||||
|
#include "util/map.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
#include "util/thread.h"
|
||||||
|
#include "util/transform.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_data("data");
|
||||||
|
ustring u_colorspace_scene_linear("scene_linear");
|
||||||
|
ustring u_colorspace_scene_linear_srgb("scene_linear_srgb");
|
||||||
|
ustring u_colorspace_srgb("__builtin_srgb");
|
||||||
|
|
||||||
|
/* Cached data. */
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
static thread_mutex cache_processors_mutex;
|
||||||
|
static unordered_map<ustring, OCIO::ConstProcessorRcPtr> cache_processors;
|
||||||
|
|
||||||
|
static thread_mutex cache_colorspaces_mutex;
|
||||||
|
static unordered_map<ustring, ustring> cached_colorspaces;
|
||||||
|
|
||||||
|
static thread_mutex cache_scene_linear_interop_id_mutex;
|
||||||
|
static bool cache_scene_linear_interop_id_done = false;
|
||||||
|
static const char *cache_scene_linear_interop_id = "";
|
||||||
|
static const char *cache_scene_linear_srgb_interop_id = "";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static thread_mutex cache_xyz_to_scene_linear_mutex;
|
||||||
|
static string cache_xyz_to_scene_linear_hash;
|
||||||
|
|
||||||
|
ColorSpaceProcessor *ColorSpaceManager::get_processor(ustring colorspace)
|
||||||
|
{
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
/* Only use this for OpenColorIO color spaces, not the builtin ones. */
|
||||||
|
assert(colorspace != u_colorspace_scene_linear_srgb && colorspace != u_colorspace_auto);
|
||||||
|
|
||||||
|
if (colorspace == u_colorspace_data || colorspace == u_colorspace_scene_linear) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OCIO::ConstConfigRcPtr config = nullptr;
|
||||||
|
try {
|
||||||
|
config = OCIO::GetCurrentConfig();
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &exception) {
|
||||||
|
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cache processor until free_memory(), memory overhead is expected to be
|
||||||
|
* small and the processor is likely to be reused. */
|
||||||
|
const thread_scoped_lock cache_processors_lock(cache_processors_mutex);
|
||||||
|
if (cache_processors.find(colorspace) == cache_processors.end()) {
|
||||||
|
try {
|
||||||
|
if (colorspace == u_colorspace_srgb) {
|
||||||
|
/* Linear Rec.709 to sRGB is handled separately in to_scene_linear, here
|
||||||
|
* we only need the matrix transform from scene_linear to Linear Rec.709. */
|
||||||
|
if (strcmp(get_scene_linear_interop_id(), "lin_rec709_scene") == 0) {
|
||||||
|
cache_processors[colorspace] = nullptr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const Transform rgb_to_rec709 = transform_inverse(get_xyz_to_scene_linear_rgb()) *
|
||||||
|
get_xyz_to_rec709();
|
||||||
|
const double data[16] = {rgb_to_rec709.x.x,
|
||||||
|
rgb_to_rec709.x.y,
|
||||||
|
rgb_to_rec709.x.z,
|
||||||
|
rgb_to_rec709.x.w,
|
||||||
|
rgb_to_rec709.y.x,
|
||||||
|
rgb_to_rec709.y.y,
|
||||||
|
rgb_to_rec709.y.z,
|
||||||
|
rgb_to_rec709.y.w,
|
||||||
|
rgb_to_rec709.z.x,
|
||||||
|
rgb_to_rec709.z.y,
|
||||||
|
rgb_to_rec709.z.z,
|
||||||
|
rgb_to_rec709.z.w,
|
||||||
|
0.0f,
|
||||||
|
0.0f,
|
||||||
|
0.0f,
|
||||||
|
1.0f};
|
||||||
|
OCIO::MatrixTransformRcPtr tfm = OCIO::MatrixTransform::Create();
|
||||||
|
tfm->setMatrix(data);
|
||||||
|
cache_processors[colorspace] = config->getProcessor(tfm, OCIO::TRANSFORM_DIR_FORWARD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache_processors[colorspace] = config->getProcessor(colorspace.c_str(), "scene_linear");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &exception) {
|
||||||
|
cache_processors[colorspace] = OCIO::ConstProcessorRcPtr();
|
||||||
|
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||||
|
<< " can't be converted to scene_linear: " << exception.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const OCIO::Processor *processor = cache_processors[colorspace].get();
|
||||||
|
return (ColorSpaceProcessor *)processor;
|
||||||
|
#else
|
||||||
|
/* No OpenColorIO. */
|
||||||
|
(void)colorspace;
|
||||||
|
return nullptr;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ColorSpaceManager::colorspace_is_data(ustring colorspace)
|
||||||
|
{
|
||||||
|
if (colorspace == u_colorspace_data) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (colorspace == u_colorspace_auto || colorspace == u_colorspace_scene_linear ||
|
||||||
|
colorspace == u_colorspace_scene_linear_srgb || colorspace == u_colorspace_srgb)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
OCIO::ConstConfigRcPtr config = nullptr;
|
||||||
|
try {
|
||||||
|
config = OCIO::GetCurrentConfig();
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &exception) {
|
||||||
|
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
||||||
|
return space && space->isData();
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ColorSpaceManager::colorspace_interop_id(ustring colorspace)
|
||||||
|
{
|
||||||
|
if (colorspace == u_colorspace_auto) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (colorspace == u_colorspace_data) {
|
||||||
|
return "data";
|
||||||
|
}
|
||||||
|
if (colorspace == u_colorspace_srgb) {
|
||||||
|
return "srgb_rec709_scene";
|
||||||
|
}
|
||||||
|
if (colorspace == u_colorspace_scene_linear) {
|
||||||
|
const char *interop_id = get_scene_linear_interop_id(false);
|
||||||
|
if (!strcmp(interop_id, "unknown")) {
|
||||||
|
return interop_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (colorspace == u_colorspace_scene_linear_srgb) {
|
||||||
|
const char *interop_id = get_scene_linear_interop_id(true);
|
||||||
|
if (!strcmp(interop_id, "unknown")) {
|
||||||
|
return interop_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
OCIO::ConstConfigRcPtr config = nullptr;
|
||||||
|
try {
|
||||||
|
config = OCIO::GetCurrentConfig();
|
||||||
|
if (!config) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const OCIO::ConstColorSpaceRcPtr space = config->getColorSpace(colorspace.c_str());
|
||||||
|
if (space) {
|
||||||
|
if (space->isData()) {
|
||||||
|
return "data";
|
||||||
|
}
|
||||||
|
/* From https://github.com/AcademySoftwareFoundation/ColorInterop. */
|
||||||
|
/* TODO: Use native interop ID support in OpenColorIO 2.5. */
|
||||||
|
const char *interop_ids[] = {"lin_rec709_scene",
|
||||||
|
"lin_p3d65_scene",
|
||||||
|
"lin_rec2020_scene",
|
||||||
|
"lin_adobergb_scene",
|
||||||
|
"lin_ap1_scene",
|
||||||
|
"lin_ap0_scene",
|
||||||
|
"lin_ciexyzd65_scene",
|
||||||
|
"srgb_rec709_scene",
|
||||||
|
"g22_rec709_scene",
|
||||||
|
"g18_rec709_scene",
|
||||||
|
"srgb_ap1_scene",
|
||||||
|
"g22_ap1_scene",
|
||||||
|
"srgb_p3d65_scene",
|
||||||
|
"g22_adobergb_scene"};
|
||||||
|
for (const char *interop_id : interop_ids) {
|
||||||
|
if (space->hasAlias(interop_id)) {
|
||||||
|
return interop_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const 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_scene_linear;
|
||||||
|
}
|
||||||
|
return u_colorspace_srgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Builtin colorspaces. */
|
||||||
|
if (colorspace == u_colorspace_srgb || colorspace == u_colorspace_scene_linear ||
|
||||||
|
colorspace == u_colorspace_scene_linear_srgb || colorspace == u_colorspace_data)
|
||||||
|
{
|
||||||
|
return colorspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorspace_is_data(colorspace)) {
|
||||||
|
return u_colorspace_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Use OpenColorIO. */
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
{
|
||||||
|
const 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;
|
||||||
|
bool is_scene_linear_srgb;
|
||||||
|
is_builtin_colorspace(colorspace, is_scene_linear, is_scene_linear_srgb);
|
||||||
|
|
||||||
|
const thread_scoped_lock cache_lock(cache_colorspaces_mutex);
|
||||||
|
if (is_scene_linear) {
|
||||||
|
LOG_INFO << "Colorspace " << colorspace.string() << " is no-op";
|
||||||
|
cached_colorspaces[colorspace] = u_colorspace_scene_linear;
|
||||||
|
return u_colorspace_scene_linear;
|
||||||
|
}
|
||||||
|
if (is_scene_linear_srgb) {
|
||||||
|
LOG_INFO << "Colorspace " << colorspace.string() << " is scene linear sRGB";
|
||||||
|
cached_colorspaces[colorspace] = u_colorspace_scene_linear_srgb;
|
||||||
|
return u_colorspace_scene_linear_srgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verify if we can convert from the requested color space. */
|
||||||
|
if (!get_processor(colorspace)) {
|
||||||
|
OCIO::ConstConfigRcPtr config = nullptr;
|
||||||
|
try {
|
||||||
|
config = OCIO::GetCurrentConfig();
|
||||||
|
}
|
||||||
|
catch (const OCIO::Exception &exception) {
|
||||||
|
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||||
|
return u_colorspace_scene_linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config || !config->getColorSpace(colorspace.c_str())) {
|
||||||
|
LOG_WARNING << "Colorspace " << colorspace.c_str() << " not found, using raw instead";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||||
|
<< " can't be converted to scene_linear, using raw instead";
|
||||||
|
}
|
||||||
|
cached_colorspaces[colorspace] = u_colorspace_scene_linear;
|
||||||
|
return u_colorspace_scene_linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert to/from colorspace with OpenColorIO. */
|
||||||
|
LOG_INFO << "Colorspace " << colorspace.string() << " handled through OpenColorIO";
|
||||||
|
cached_colorspaces[colorspace] = colorspace;
|
||||||
|
return colorspace;
|
||||||
|
#else
|
||||||
|
LOG_WARNING << "Colorspace " << colorspace.c_str()
|
||||||
|
<< " not available, built without OpenColorIO";
|
||||||
|
return u_colorspace_scene_linear;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorSpaceManager::is_builtin_colorspace(ustring colorspace,
|
||||||
|
bool &is_scene_linear,
|
||||||
|
bool &is_scene_linear_srgb)
|
||||||
|
{
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||||
|
if (!processor) {
|
||||||
|
is_scene_linear = false;
|
||||||
|
is_scene_linear_srgb = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||||
|
is_scene_linear = true;
|
||||||
|
is_scene_linear_srgb = true;
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
const 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_scene_linear_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_scene_linear_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_scene_linear_srgb = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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-4f, 64)) {
|
||||||
|
is_scene_linear_srgb = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)colorspace;
|
||||||
|
is_scene_linear = false;
|
||||||
|
is_scene_linear_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, const 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,
|
||||||
|
const int64_t width,
|
||||||
|
const int64_t height,
|
||||||
|
const int64_t y_stride,
|
||||||
|
const bool ignore_alpha)
|
||||||
|
{
|
||||||
|
/* 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. */
|
||||||
|
const OCIO::ConstCPUProcessorRcPtr device_processor = (processor) ?
|
||||||
|
processor->getDefaultCPUProcessor() :
|
||||||
|
nullptr;
|
||||||
|
|
||||||
|
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||||
|
const int64_t chunk_rows = divide_up(std::min((int64_t)(16 * 1024 * 1024), width * height),
|
||||||
|
width);
|
||||||
|
vector<float4> float_pixels(chunk_rows * width);
|
||||||
|
|
||||||
|
for (int64_t row = 0; row < height; row += chunk_rows) {
|
||||||
|
const int64_t num_rows = std::min(chunk_rows, height - row);
|
||||||
|
|
||||||
|
for (int64_t j = 0; j < num_rows; j++) {
|
||||||
|
T *pixel = pixels + (row + j) * y_stride;
|
||||||
|
float4 *float_pixel = float_pixels.data() + j * width;
|
||||||
|
for (int64_t i = 0; i < width; i++, pixel += 4, float_pixel++) {
|
||||||
|
float4 value = cast_to_float4(pixel);
|
||||||
|
|
||||||
|
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||||
|
const float inv_alpha = 1.0f / value.w;
|
||||||
|
value.x *= inv_alpha;
|
||||||
|
value.y *= inv_alpha;
|
||||||
|
value.z *= inv_alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
*float_pixel = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processor) {
|
||||||
|
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), num_rows * width, 1, 4);
|
||||||
|
device_processor->apply(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int64_t j = 0; j < num_rows; j++) {
|
||||||
|
T *pixel = pixels + (row + j) * y_stride;
|
||||||
|
float4 *float_pixel = float_pixels.data() + j * width;
|
||||||
|
for (int64_t i = 0; i < width; i++, pixel += 4, float_pixel++) {
|
||||||
|
float4 value = *float_pixel;
|
||||||
|
|
||||||
|
if (compress_as_srgb) {
|
||||||
|
value = color_linear_to_srgb_v4(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignore_alpha && !(value.w <= 0.0f || value.w == 1.0f)) {
|
||||||
|
value.x *= value.w;
|
||||||
|
value.y *= value.w;
|
||||||
|
value.z *= value.w;
|
||||||
|
}
|
||||||
|
|
||||||
|
cast_from_float4(pixel, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, bool compress_as_srgb = false>
|
||||||
|
inline void processor_apply_pixels_grayscale(const OCIO::Processor *processor,
|
||||||
|
T *pixels,
|
||||||
|
const int64_t width,
|
||||||
|
const int64_t height,
|
||||||
|
const int64_t y_stride)
|
||||||
|
{
|
||||||
|
const OCIO::ConstCPUProcessorRcPtr device_processor = (processor) ?
|
||||||
|
processor->getDefaultCPUProcessor() :
|
||||||
|
nullptr;
|
||||||
|
|
||||||
|
/* Process large images in chunks to keep temporary memory requirement down. */
|
||||||
|
const int64_t chunk_rows = divide_up(std::min((int64_t)(16 * 1024 * 1024), width * height),
|
||||||
|
width);
|
||||||
|
vector<float> float_pixels(chunk_rows * width * 3);
|
||||||
|
|
||||||
|
for (int64_t row = 0; row < height; row += chunk_rows) {
|
||||||
|
const int64_t num_rows = std::min(chunk_rows, height - row);
|
||||||
|
|
||||||
|
/* Convert to 3 channels, since that's the minimum required by OpenColorIO. */
|
||||||
|
for (int64_t j = 0; j < num_rows; j++) {
|
||||||
|
T *pixel = pixels + (row + j) * y_stride;
|
||||||
|
float *float_pixel = float_pixels.data() + j * width * 3;
|
||||||
|
for (int64_t i = 0; i < width; i++, pixel++, float_pixel += 3) {
|
||||||
|
const float f = util_image_cast_to_float<T>(*pixel);
|
||||||
|
float_pixel[0] = f;
|
||||||
|
float_pixel[1] = f;
|
||||||
|
float_pixel[2] = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processor) {
|
||||||
|
const OCIO::PackedImageDesc desc((float *)float_pixels.data(), num_rows * width, 1, 3);
|
||||||
|
device_processor->apply(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int64_t j = 0; j < num_rows; j++) {
|
||||||
|
T *pixel = pixels + (row + j) * y_stride;
|
||||||
|
float *float_pixel = float_pixels.data() + j * width * 3;
|
||||||
|
for (int64_t i = 0; i < width; i++, pixel++, float_pixel += 3) {
|
||||||
|
float f = average(make_float3(float_pixel[0], float_pixel[1], float_pixel[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,
|
||||||
|
const int64_t width,
|
||||||
|
const int64_t height,
|
||||||
|
const int64_t y_stride,
|
||||||
|
bool is_rgba,
|
||||||
|
bool compress_as_srgb,
|
||||||
|
bool ignore_alpha)
|
||||||
|
{
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
const OCIO::Processor *processor = (const OCIO::Processor *)get_processor(colorspace);
|
||||||
|
|
||||||
|
/* The processor for sRGB does not include the Linear Rec.709 to sRGB transform, that
|
||||||
|
* is handled by compress_as_srgb for better performance when scene_linear is Rec.709 */
|
||||||
|
if (colorspace == u_colorspace_srgb) {
|
||||||
|
assert(!compress_as_srgb);
|
||||||
|
compress_as_srgb = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_rgba) {
|
||||||
|
if (compress_as_srgb) {
|
||||||
|
/* Compress output as sRGB. */
|
||||||
|
processor_apply_pixels_rgba<T, true>(
|
||||||
|
processor, pixels, width, height, y_stride, ignore_alpha);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Write output as scene linear directly. */
|
||||||
|
processor_apply_pixels_rgba<T>(processor, pixels, width, height, y_stride, ignore_alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (compress_as_srgb) {
|
||||||
|
/* Compress output as sRGB. */
|
||||||
|
processor_apply_pixels_grayscale<T, true>(processor, pixels, width, height, y_stride);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Write output as scene linear directly. */
|
||||||
|
processor_apply_pixels_grayscale<T>(processor, pixels, width, height, y_stride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)colorspace;
|
||||||
|
(void)pixels;
|
||||||
|
(void)width;
|
||||||
|
(void)height;
|
||||||
|
(void)y_stride;
|
||||||
|
(void)is_rgba;
|
||||||
|
(void)compress_as_srgb;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorSpaceManager::to_scene_linear(ColorSpaceProcessor *processor_,
|
||||||
|
float *pixel,
|
||||||
|
const int channels)
|
||||||
|
{
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
const OCIO::Processor *processor = (const OCIO::Processor *)processor_;
|
||||||
|
|
||||||
|
if (processor) {
|
||||||
|
const 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. */
|
||||||
|
const float alpha = pixel[3];
|
||||||
|
const 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(cache_processors);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorSpaceManager::init_fallback_config()
|
||||||
|
{
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
OCIO::SetCurrentConfig(OCIO::Config::CreateRaw());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
static bool to_scene_linear_transform(OCIO::ConstConfigRcPtr &config,
|
||||||
|
const char *colorspace,
|
||||||
|
Transform &to_scene_linear)
|
||||||
|
{
|
||||||
|
OCIO::ConstProcessorRcPtr processor;
|
||||||
|
try {
|
||||||
|
processor = config->getProcessor("scene_linear", colorspace);
|
||||||
|
}
|
||||||
|
catch (OCIO::Exception &) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!processor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OCIO::ConstCPUProcessorRcPtr device_processor = processor->getDefaultCPUProcessor();
|
||||||
|
if (!device_processor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
to_scene_linear = transform_identity();
|
||||||
|
device_processor->applyRGB(&to_scene_linear.x.x);
|
||||||
|
device_processor->applyRGB(&to_scene_linear.y.x);
|
||||||
|
device_processor->applyRGB(&to_scene_linear.z.x);
|
||||||
|
to_scene_linear = transform_transposed_inverse(to_scene_linear);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Transform ColorSpaceManager::get_xyz_to_rec709()
|
||||||
|
{
|
||||||
|
/* Default to ITU-BT.709 in case no appropriate transform found.
|
||||||
|
* Note XYZ here is defined as having a D65 white point. */
|
||||||
|
return make_transform(3.2404542f,
|
||||||
|
-1.5371385f,
|
||||||
|
-0.4985314f,
|
||||||
|
0.0f,
|
||||||
|
-0.9692660f,
|
||||||
|
1.8760108f,
|
||||||
|
0.0415560f,
|
||||||
|
0.0f,
|
||||||
|
0.0556434f,
|
||||||
|
-0.2040259f,
|
||||||
|
1.0572252f,
|
||||||
|
0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform ColorSpaceManager::get_xyz_to_rec2020()
|
||||||
|
{
|
||||||
|
return make_transform(1.7166512f,
|
||||||
|
-0.3556708f,
|
||||||
|
-0.2533663f,
|
||||||
|
0.0f,
|
||||||
|
-0.6666844,
|
||||||
|
1.6164812f,
|
||||||
|
0.0157685f,
|
||||||
|
0.0f,
|
||||||
|
0.0176399f,
|
||||||
|
-0.0427706f,
|
||||||
|
0.9421031f,
|
||||||
|
0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform ColorSpaceManager::get_xyz_to_acescg()
|
||||||
|
{
|
||||||
|
return transform_inverse(make_transform(0.652238f,
|
||||||
|
0.128237f,
|
||||||
|
0.169983f,
|
||||||
|
0.0f,
|
||||||
|
0.267672f,
|
||||||
|
0.674340f,
|
||||||
|
0.057988f,
|
||||||
|
0.0f,
|
||||||
|
-0.005382f,
|
||||||
|
0.001369f,
|
||||||
|
1.093071f,
|
||||||
|
0.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform ColorSpaceManager::get_xyz_to_scene_linear_rgb()
|
||||||
|
{
|
||||||
|
Transform xyz_to_rgb = get_xyz_to_rec709();
|
||||||
|
|
||||||
|
#ifdef WITH_OCIO
|
||||||
|
/* Get from OpenColorO config if it has the required roles. */
|
||||||
|
OCIO::ConstConfigRcPtr config = nullptr;
|
||||||
|
try {
|
||||||
|
config = OCIO::GetCurrentConfig();
|
||||||
|
}
|
||||||
|
catch (OCIO::Exception &exception) {
|
||||||
|
LOG_WARNING << "OCIO config error: " << exception.what();
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(config && config->hasRole("scene_linear"))) {
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->hasRole("aces_interchange")) {
|
||||||
|
/* Standard OpenColorIO role, defined as ACES AP0 (ACES2065-1). */
|
||||||
|
Transform aces_to_rgb;
|
||||||
|
if (!to_scene_linear_transform(config, "aces_interchange", aces_to_rgb)) {
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is the OpenColorIO builtin transform:
|
||||||
|
* UTILITY - ACES-AP0_to_CIE-XYZ-D65_BFD. */
|
||||||
|
const Transform ACES_AP0_to_xyz_D65 = make_transform(0.938280f,
|
||||||
|
-0.004451f,
|
||||||
|
0.016628f,
|
||||||
|
0.000000f,
|
||||||
|
0.337369f,
|
||||||
|
0.729522f,
|
||||||
|
-0.066890f,
|
||||||
|
0.000000f,
|
||||||
|
0.001174f,
|
||||||
|
-0.003711f,
|
||||||
|
1.091595f,
|
||||||
|
0.000000f);
|
||||||
|
const Transform xyz_to_aces = transform_inverse(ACES_AP0_to_xyz_D65);
|
||||||
|
xyz_to_rgb = aces_to_rgb * xyz_to_aces;
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config->hasRole("XYZ")) {
|
||||||
|
/* Custom role used before the standard existed. */
|
||||||
|
if (to_scene_linear_transform(config, "XYZ", xyz_to_rgb)) {
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No reference role found to determine XYZ. */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return xyz_to_rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &ColorSpaceManager::get_xyz_to_scene_linear_rgb_string()
|
||||||
|
{
|
||||||
|
/* NOTE: Be careful not to change existing hashes if at all possible, as this
|
||||||
|
* will cause all texture files to be regenerated with significantly increased
|
||||||
|
* disk usage. */
|
||||||
|
const thread_scoped_lock cache_lock(cache_xyz_to_scene_linear_mutex);
|
||||||
|
|
||||||
|
if (cache_xyz_to_scene_linear_hash.empty()) {
|
||||||
|
/* TODO: Verify this results in the same hash across platforms. */
|
||||||
|
const Transform xyz_to_rgb = get_xyz_to_scene_linear_rgb();
|
||||||
|
cache_xyz_to_scene_linear_hash = string_printf(
|
||||||
|
"%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g_%.4g",
|
||||||
|
xyz_to_rgb.x.x,
|
||||||
|
xyz_to_rgb.x.y,
|
||||||
|
xyz_to_rgb.x.z,
|
||||||
|
xyz_to_rgb.x.w,
|
||||||
|
xyz_to_rgb.y.x,
|
||||||
|
xyz_to_rgb.y.y,
|
||||||
|
xyz_to_rgb.y.z,
|
||||||
|
xyz_to_rgb.y.w,
|
||||||
|
xyz_to_rgb.z.x,
|
||||||
|
xyz_to_rgb.z.y,
|
||||||
|
xyz_to_rgb.z.z,
|
||||||
|
xyz_to_rgb.z.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache_xyz_to_scene_linear_hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *ColorSpaceManager::get_scene_linear_interop_id(const bool srgb_encoded)
|
||||||
|
{
|
||||||
|
const thread_scoped_lock cache_lock(cache_scene_linear_interop_id_mutex);
|
||||||
|
|
||||||
|
if (!cache_scene_linear_interop_id_done) {
|
||||||
|
const Transform xyz_to_rgb = get_xyz_to_scene_linear_rgb();
|
||||||
|
|
||||||
|
if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_rec709(), 0.0001f)) {
|
||||||
|
cache_scene_linear_interop_id = "lin_rec709_scene";
|
||||||
|
cache_scene_linear_srgb_interop_id = "srgb_rec709_scene";
|
||||||
|
}
|
||||||
|
else if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_rec2020(), 0.0001f)) {
|
||||||
|
cache_scene_linear_interop_id = "lin_rec2020_scene";
|
||||||
|
cache_scene_linear_srgb_interop_id = "srgb_rec2020_scene";
|
||||||
|
}
|
||||||
|
else if (transform_equal_threshold(xyz_to_rgb, get_xyz_to_acescg(), 0.0001f)) {
|
||||||
|
cache_scene_linear_interop_id = "lin_ap1_scene";
|
||||||
|
cache_scene_linear_srgb_interop_id = "srgb_ap1_scene";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cache_scene_linear_interop_id = "unknown";
|
||||||
|
cache_scene_linear_srgb_interop_id = "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
cache_scene_linear_interop_id_done = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (srgb_encoded) ? cache_scene_linear_srgb_interop_id : cache_scene_linear_interop_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Template instantiations so we don't have to inline functions. */
|
||||||
|
template void ColorSpaceManager::to_scene_linear(
|
||||||
|
ustring, uchar *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||||
|
template void ColorSpaceManager::to_scene_linear(
|
||||||
|
ustring, ushort *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||||
|
template void ColorSpaceManager::to_scene_linear(
|
||||||
|
ustring, half *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||||
|
template void ColorSpaceManager::to_scene_linear(
|
||||||
|
ustring, float *, int64_t, int64_t, int64_t, bool, bool, bool);
|
||||||
|
|
||||||
|
CCL_NAMESPACE_END
|
||||||
|
|
@ -1,18 +1,27 @@
|
||||||
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0 */
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "util/param.h"
|
#include "util/param.h"
|
||||||
|
#include "util/string.h"
|
||||||
|
|
||||||
CCL_NAMESPACE_BEGIN
|
CCL_NAMESPACE_BEGIN
|
||||||
|
|
||||||
|
/* Automatically determine colorspace. */
|
||||||
extern ustring u_colorspace_auto;
|
extern ustring u_colorspace_auto;
|
||||||
extern ustring u_colorspace_raw;
|
/* Non-color data. */
|
||||||
|
extern ustring u_colorspace_data;
|
||||||
|
/* Scene linear colorspace used for rendering. */
|
||||||
|
extern ustring u_colorspace_scene_linear;
|
||||||
|
/* Scene linear + sRGB transfer function . */
|
||||||
|
extern ustring u_colorspace_scene_linear_srgb;
|
||||||
|
/* sRGB. */
|
||||||
extern ustring u_colorspace_srgb;
|
extern ustring u_colorspace_srgb;
|
||||||
|
|
||||||
class ColorSpaceProcessor;
|
class ColorSpaceProcessor;
|
||||||
|
struct Transform;
|
||||||
|
|
||||||
class ColorSpaceManager {
|
class ColorSpaceManager {
|
||||||
public:
|
public:
|
||||||
|
|
@ -26,13 +35,17 @@ class ColorSpaceManager {
|
||||||
|
|
||||||
/* Test if colorspace is for non-color data. */
|
/* Test if colorspace is for non-color data. */
|
||||||
static bool colorspace_is_data(ustring colorspace);
|
static bool colorspace_is_data(ustring colorspace);
|
||||||
|
/* Return color interop forum interop ID. */
|
||||||
|
static const char *colorspace_interop_id(ustring colorspace);
|
||||||
|
|
||||||
/* Convert pixels in the specified colorspace to scene linear color for
|
/* Convert pixels in the specified colorspace to scene linear color for
|
||||||
* rendering. Must be a colorspace returned from detect_known_colorspace. */
|
* rendering. Must be a colorspace returned from detect_known_colorspace. */
|
||||||
template<typename T>
|
template<typename T>
|
||||||
static void to_scene_linear(ustring colorspace,
|
static void to_scene_linear(ustring colorspace,
|
||||||
T *pixels,
|
T *pixels,
|
||||||
const size_t num_pixels,
|
const int64_t width,
|
||||||
|
const int64_t height,
|
||||||
|
const int64_t y_stride,
|
||||||
bool is_rgba,
|
bool is_rgba,
|
||||||
bool compress_as_srgb,
|
bool compress_as_srgb,
|
||||||
bool ignore_alpha);
|
bool ignore_alpha);
|
||||||
|
|
@ -52,8 +65,20 @@ class ColorSpaceManager {
|
||||||
* valid without knowing the actual configuration used by the final application. */
|
* valid without knowing the actual configuration used by the final application. */
|
||||||
static void init_fallback_config();
|
static void init_fallback_config();
|
||||||
|
|
||||||
|
/* Compute matrix to convert from XYZ to scene linear RGB, based on the config. */
|
||||||
|
static Transform get_xyz_to_scene_linear_rgb();
|
||||||
|
static Transform get_xyz_to_rec709();
|
||||||
|
static Transform get_xyz_to_rec2020();
|
||||||
|
static Transform get_xyz_to_acescg();
|
||||||
|
/* Compute unique string for texture cache hashing and metadata. */
|
||||||
|
static const string &get_xyz_to_scene_linear_rgb_string();
|
||||||
|
/* Determine if scene linear is a common known space. */
|
||||||
|
static const char *get_scene_linear_interop_id(const bool srgb_encoded = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void is_builtin_colorspace(ustring colorspace, bool &is_scene_linear, bool &is_srgb);
|
static void is_builtin_colorspace(ustring colorspace,
|
||||||
|
bool &is_scene_linear,
|
||||||
|
bool &is_scene_linear_srgb);
|
||||||
};
|
};
|
||||||
|
|
||||||
CCL_NAMESPACE_END
|
CCL_NAMESPACE_END
|
||||||
Loading…
Add table
Add a link
Reference in a new issue