Color Management: Base color space interpretation on interop ID
For display and image saving, we need to know certain properties of displays and view transforms that OpenColorIO does not explicitly provide. In the upcoming OpenColorIO 2.5 there will be an interop ID for color spaces that adds more information. This moves towards that design, with heuristics for configs that don't have the interop IDs. * Try to use the first colorspace alias as the interop ID. This is used in some existing configs, and now the Blender config as well. * Improve ACES 2.0 config compatibility by interpreting some aliases like srgb_display as interop IDs. * Detect is_srgb and is_wide_gamut through display colorspace interop ID. These are now per view rather than per display. * Detect is_extended where we can't clamp to 0..1 through interop ID. * Detect untonemapped view as config wide default view transform for converting between reference and display space, if "Standard" or "Un-tone-mapped" can not be found. * Detect which display and view transform combination is HDR by checking for "hdr-video" encoding on the display colorspace in the OpenColorIO config. Ref #144911 Pull Request: https://projects.blender.org/blender/blender/pulls/144565
This commit is contained in:
@@ -326,6 +326,13 @@ const char *IMB_colormanagement_display_get_none_name();
|
||||
const char *IMB_colormanagement_display_get_default_view_transform_name(
|
||||
const ColorManagedDisplay *display);
|
||||
|
||||
const ColorSpace *IMB_colormangement_display_get_color_space(
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
bool IMB_colormanagement_display_is_hdr(const ColorManagedDisplaySettings *display_settings,
|
||||
const char *view_name);
|
||||
bool IMB_colormanagement_display_is_wide_gamut(const ColorManagedDisplaySettings *display_settings,
|
||||
const char *view_name);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -776,16 +776,12 @@ static std::shared_ptr<const ocio::CPUProcessor> get_display_buffer_processor(
|
||||
void IMB_colormanagement_init_untonemapped_view_settings(
|
||||
ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings)
|
||||
{
|
||||
/* First, try use "Un-tone-mapped" (ACES configs) or "Standard" (Blender config) view transform
|
||||
* of the requested device. */
|
||||
const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device);
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
const ocio::View *default_view = display->get_view_by_name("Un-tone-mapped");
|
||||
if (default_view == nullptr) {
|
||||
default_view = display->get_view_by_name("Standard");
|
||||
}
|
||||
/* Try to guess what the untonemapped view is. */
|
||||
const ocio::View *default_view = display->get_untonemapped_view();
|
||||
/* If that fails, we fall back to the default view transform of the display
|
||||
* as per OCIO configuration. */
|
||||
if (default_view == nullptr) {
|
||||
@@ -2678,6 +2674,34 @@ const char *IMB_colormanagement_display_get_default_view_transform_name(
|
||||
return default_view->name().c_str();
|
||||
}
|
||||
|
||||
const ColorSpace *IMB_colormangement_display_get_color_space(
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
{
|
||||
return get_untonemapped_display_colorspace(display_settings);
|
||||
}
|
||||
|
||||
bool IMB_colormanagement_display_is_hdr(const ColorManagedDisplaySettings *display_settings,
|
||||
const char *view_name)
|
||||
{
|
||||
const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device);
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const ocio::View *view = display->get_view_by_name(view_name);
|
||||
return (view) ? view->is_hdr() : false;
|
||||
}
|
||||
|
||||
bool IMB_colormanagement_display_is_wide_gamut(const ColorManagedDisplaySettings *display_settings,
|
||||
const char *view_name)
|
||||
{
|
||||
const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device);
|
||||
if (display == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const ocio::View *view = display->get_view_by_name(view_name);
|
||||
return (view) ? view->is_wide_gamut() : false;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
@@ -49,6 +49,12 @@ class ColorSpace {
|
||||
*/
|
||||
virtual bool is_data() const = 0;
|
||||
|
||||
/*
|
||||
* Identifier for colorspaces that works with multiple OpenColorIO configurations,
|
||||
* as defined by the ASWF Color Interop Forum.
|
||||
*/
|
||||
virtual StringRefNull interop_id() const = 0;
|
||||
|
||||
/**
|
||||
* Quick access to CPU processors that convert color space from the current one to scene linear
|
||||
* and vice versa.
|
||||
|
||||
@@ -19,15 +19,18 @@ class CPUProcessor;
|
||||
class GPUShaderBinder;
|
||||
|
||||
struct DisplayParameters {
|
||||
/* Convert from a colorspace to a display, using the view transform and look. */
|
||||
StringRefNull from_colorspace;
|
||||
StringRefNull view;
|
||||
StringRefNull display;
|
||||
StringRefNull look;
|
||||
/* Artistic controls. */
|
||||
float scale = 1.0f;
|
||||
float exponent = 1.0f;
|
||||
float temperature = 6500.0f;
|
||||
float tint = 10.0f;
|
||||
bool use_white_balance = false;
|
||||
/* Invert the entire transform. */
|
||||
bool inverse = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,10 @@ class Display {
|
||||
* Get default view of this display. */
|
||||
virtual const View *get_default_view() const = 0;
|
||||
|
||||
/**
|
||||
* Get the view without tonemapping. */
|
||||
virtual const View *get_untonemapped_view() const = 0;
|
||||
|
||||
/**
|
||||
* Get view with the given name for this display.
|
||||
* If the view does not exist nullptr is returned.
|
||||
@@ -54,6 +58,11 @@ class Display {
|
||||
*/
|
||||
const virtual CPUProcessor *get_to_scene_linear_cpu_processor() const = 0;
|
||||
const virtual CPUProcessor *get_from_scene_linear_cpu_processor() const = 0;
|
||||
|
||||
/**
|
||||
* Determine if the display supports HDR.
|
||||
*/
|
||||
virtual bool is_hdr() const = 0;
|
||||
};
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -36,10 +36,12 @@ class GPUShaderCache;
|
||||
} // namespace internal
|
||||
|
||||
struct GPUDisplayParameters {
|
||||
/* Convert from a colorspace to a display, using the view transform and look. */
|
||||
StringRefNull from_colorspace;
|
||||
StringRefNull view;
|
||||
StringRefNull display;
|
||||
StringRefNull look;
|
||||
/* Artistic controls. */
|
||||
CurveMapping *curve_mapping = nullptr;
|
||||
float scale = 1.0f;
|
||||
float exponent = 1.0f;
|
||||
@@ -47,8 +49,11 @@ struct GPUDisplayParameters {
|
||||
float temperature = 6500.0f;
|
||||
float tint = 10.0f;
|
||||
bool use_white_balance = false;
|
||||
/* Divide RGB by alpha before performing the transform. */
|
||||
bool use_predivide = false;
|
||||
/* Composite an overlay buffer on top of the image. */
|
||||
bool do_overlay_merge = false;
|
||||
/* Allow HDR colors (above 1.0) in the result. */
|
||||
bool use_hdr = false;
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,16 @@ class View {
|
||||
* The name is used to address to this view from various places of the configuration.
|
||||
*/
|
||||
virtual StringRefNull name() const = 0;
|
||||
|
||||
/**
|
||||
* Does this view transform output HDR colors?
|
||||
*/
|
||||
virtual bool is_hdr() const = 0;
|
||||
|
||||
/**
|
||||
* Does this view transform output wide gamut colors?
|
||||
*/
|
||||
virtual bool is_wide_gamut() const = 0;
|
||||
};
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -36,6 +36,19 @@ class FallbackColorSpace : public ColorSpace {
|
||||
{
|
||||
return "";
|
||||
}
|
||||
StringRefNull interop_id() const override
|
||||
{
|
||||
switch (type_) {
|
||||
case Type::LINEAR:
|
||||
return "lin_rec709_scene";
|
||||
case Type::SRGB:
|
||||
return "srgb_rec709_display";
|
||||
case Type::DATA:
|
||||
return "data";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool is_invertible() const override
|
||||
{
|
||||
|
||||
@@ -34,6 +34,11 @@ class FallbackDefaultDisplay : public Display {
|
||||
return &default_view_;
|
||||
}
|
||||
|
||||
const View *get_untonemapped_view() const override
|
||||
{
|
||||
return &default_view_;
|
||||
}
|
||||
|
||||
const View *get_view_by_name(const StringRefNull name) const override
|
||||
{
|
||||
if (name == default_view_.name()) {
|
||||
@@ -66,6 +71,11 @@ class FallbackDefaultDisplay : public Display {
|
||||
static FallbackLinearRGBToSRGBCPUProcessor processor;
|
||||
return &processor;
|
||||
}
|
||||
|
||||
bool is_hdr() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -19,6 +19,16 @@ class FallbackDefaultView : public View {
|
||||
{
|
||||
return "Standard";
|
||||
}
|
||||
|
||||
bool is_hdr() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_wide_gamut() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace blender::ocio
|
||||
|
||||
@@ -131,6 +131,42 @@ LibOCIOColorSpace::LibOCIOColorSpace(const int index,
|
||||
this->index = index;
|
||||
|
||||
is_invertible_ = color_space_is_invertible(ocio_color_space);
|
||||
|
||||
/* In OpenColorIO 2.5 there will be native support for this. For older configs and
|
||||
* older OpenColorIO versions, check the first alias. This a convention used in the
|
||||
* Blender and ACES 2.0 configs. */
|
||||
if (ocio_color_space->getNumAliases() > 0) {
|
||||
StringRefNull first_alias = ocio_color_space->getAlias(0);
|
||||
if (first_alias == "srgb_display") {
|
||||
interop_id_ = "srgb_rec709_display";
|
||||
}
|
||||
else if (first_alias == "displayp3_display") {
|
||||
interop_id_ = "srgb_p3d65_display";
|
||||
}
|
||||
else if (first_alias == "displayp3_hdr_display") {
|
||||
interop_id_ = "srgbx_p3d65_display";
|
||||
}
|
||||
else if (first_alias == "p3d65_display") {
|
||||
interop_id_ = "g26_p3d65_display";
|
||||
}
|
||||
else if (first_alias == "rec1886_rec709_display") {
|
||||
interop_id_ = "g24_rec709_display";
|
||||
}
|
||||
else if (first_alias == "rec2100_pq_display") {
|
||||
interop_id_ = "pq_rec2020_display";
|
||||
}
|
||||
else if (first_alias == "rec2100_hlg_display") {
|
||||
interop_id_ = "hlg_rec2020_display";
|
||||
}
|
||||
else if ((first_alias.startswith("lin_") || first_alias.startswith("srgb_") ||
|
||||
first_alias.startswith("g18_") || first_alias.startswith("g22_") ||
|
||||
first_alias.startswith("g24_") || first_alias.startswith("g26_") ||
|
||||
first_alias.startswith("pq_") || first_alias.startswith("hlg_")) &&
|
||||
(first_alias.endswith("_scene") || first_alias.endswith("_display")))
|
||||
{
|
||||
interop_id_ = first_alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LibOCIOColorSpace::is_scene_linear() const
|
||||
|
||||
@@ -22,6 +22,7 @@ class LibOCIOColorSpace : public ColorSpace {
|
||||
OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_color_space_;
|
||||
|
||||
std::string clean_description_;
|
||||
StringRefNull interop_id_;
|
||||
bool is_invertible_ = false;
|
||||
|
||||
/* Mutable because they are lazily initialized and cached from the is_scene_linear() and
|
||||
@@ -48,6 +49,11 @@ class LibOCIOColorSpace : public ColorSpace {
|
||||
return clean_description_;
|
||||
}
|
||||
|
||||
StringRefNull interop_id() const override
|
||||
{
|
||||
return interop_id_;
|
||||
}
|
||||
|
||||
bool is_invertible() const override
|
||||
{
|
||||
return is_invertible_;
|
||||
|
||||
@@ -315,9 +315,11 @@ const ColorSpace *LibOCIOConfig::get_color_space(const StringRefNull name) const
|
||||
}
|
||||
}
|
||||
|
||||
report_error(
|
||||
fmt::format("Invalid OpenColorIO configuration: color space {} not found on Blender side",
|
||||
ocio_color_space->getName()));
|
||||
if (!ocio_config_->isInactiveColorSpace(ocio_color_space->getName())) {
|
||||
report_error(
|
||||
fmt::format("Invalid OpenColorIO configuration: color space {} not found on Blender side",
|
||||
ocio_color_space->getName()));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,23 @@
|
||||
|
||||
namespace blender::ocio {
|
||||
|
||||
static OCIO_NAMESPACE::ConstColorSpaceRcPtr get_display_view_colorspace(
|
||||
const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config, const char *display, const char *view)
|
||||
{
|
||||
const char *display_colorspace = ocio_config->getDisplayViewColorSpaceName(display, view);
|
||||
if (display_colorspace == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Shared view transforms can use this special display name to indicate
|
||||
* the display colorspace name is the same as the display name. */
|
||||
if (STREQ(display_colorspace, "<USE_DISPLAY_NAME>")) {
|
||||
return ocio_config->getColorSpace(display);
|
||||
}
|
||||
|
||||
return ocio_config->getColorSpace(display_colorspace);
|
||||
}
|
||||
|
||||
LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : config_(&config)
|
||||
{
|
||||
const OCIO_NAMESPACE::ConstConfigRcPtr &ocio_config = config.get_ocio_config();
|
||||
@@ -36,8 +53,70 @@ LibOCIODisplay::LibOCIODisplay(const int index, const LibOCIOConfig &config) : c
|
||||
views_.reserve(num_views);
|
||||
for (const int view_index : IndexRange(num_views)) {
|
||||
const char *view_name = ocio_config->getView(name_.c_str(), view_index);
|
||||
views_.append_as(view_index, view_name);
|
||||
|
||||
OCIO_NAMESPACE::ConstColorSpaceRcPtr ocio_display_colorspace = get_display_view_colorspace(
|
||||
ocio_config, name_.c_str(), view_name);
|
||||
|
||||
/* Detect if view is HDR, through encoding of display colorspace. */
|
||||
bool view_is_hdr = false;
|
||||
if (ocio_display_colorspace) {
|
||||
StringRefNull encoding = ocio_display_colorspace->getEncoding();
|
||||
view_is_hdr = encoding == "hdr-video";
|
||||
is_hdr_ |= view_is_hdr;
|
||||
}
|
||||
|
||||
/* Detect sRGB and wide gamut through interop ID. These are not entirely reliable,
|
||||
* and are currently only used as optimization. */
|
||||
bool is_wide_gamut = true;
|
||||
bool is_srgb = false;
|
||||
bool is_extended = false;
|
||||
|
||||
StringRefNull display_interop_id;
|
||||
if (ocio_display_colorspace) {
|
||||
const ColorSpace *display_colorspace = config.get_color_space(
|
||||
ocio_display_colorspace->getName());
|
||||
if (display_colorspace) {
|
||||
display_interop_id = display_colorspace->interop_id();
|
||||
}
|
||||
}
|
||||
|
||||
if (!display_interop_id.is_empty()) {
|
||||
is_srgb = display_interop_id == "srgb_rec709_display" ||
|
||||
display_interop_id == "srgb_rec709_scene";
|
||||
is_wide_gamut = !(display_interop_id.endswith("_rec709_display") ||
|
||||
display_interop_id.endswith("_rec709_scene"));
|
||||
is_extended = display_interop_id.startswith("srgbx_");
|
||||
}
|
||||
|
||||
views_.append_as(view_index, view_name, view_is_hdr, is_wide_gamut, is_srgb, is_extended);
|
||||
}
|
||||
|
||||
/* Detect untonemppaed view transform. */
|
||||
if (untonemapped_view_ == nullptr) {
|
||||
/* Use Blender config and ACES config naming conventions. */
|
||||
for (const LibOCIOView &view : views_) {
|
||||
if (view.name() == "Un-tone-mapped" || view.name() == "Standard") {
|
||||
untonemapped_view_ = &view;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (untonemapped_view_ == nullptr) {
|
||||
/* Use config wide default view transform between reference and display spaces.
|
||||
* Note this is not always the same as the default view transform of the display. */
|
||||
const char *default_view_transform = ocio_config->getDefaultViewTransformName();
|
||||
for (const LibOCIOView &view : views_) {
|
||||
if (view.name() == default_view_transform) {
|
||||
untonemapped_view_ = &view;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const View *LibOCIODisplay::get_untonemapped_view() const
|
||||
{
|
||||
return untonemapped_view_;
|
||||
}
|
||||
|
||||
const View *LibOCIODisplay::get_view_by_name(const StringRefNull name) const
|
||||
|
||||
@@ -29,6 +29,8 @@ class LibOCIODisplay : public Display {
|
||||
|
||||
StringRefNull name_;
|
||||
Vector<LibOCIOView> views_;
|
||||
const LibOCIOView *untonemapped_view_ = nullptr;
|
||||
bool is_hdr_ = false;
|
||||
|
||||
CPUProcessorCache to_scene_linear_cpu_processor_;
|
||||
CPUProcessorCache from_scene_linear_cpu_processor_;
|
||||
@@ -38,7 +40,7 @@ class LibOCIODisplay : public Display {
|
||||
LibOCIODisplay(const LibOCIODisplay &other) = delete;
|
||||
LibOCIODisplay(LibOCIODisplay &&other) noexcept = default;
|
||||
|
||||
~LibOCIODisplay() = default;
|
||||
~LibOCIODisplay() override = default;
|
||||
|
||||
LibOCIODisplay &operator=(const LibOCIODisplay &other) = delete;
|
||||
LibOCIODisplay &operator=(LibOCIODisplay &&other) = default;
|
||||
@@ -55,6 +57,8 @@ class LibOCIODisplay : public Display {
|
||||
return get_view_by_index(0);
|
||||
}
|
||||
|
||||
const View *get_untonemapped_view() const override;
|
||||
|
||||
const View *get_view_by_name(StringRefNull name) const override;
|
||||
int get_num_views() const override;
|
||||
const View *get_view_by_index(int index) const override;
|
||||
@@ -62,6 +66,11 @@ class LibOCIODisplay : public Display {
|
||||
const CPUProcessor *get_to_scene_linear_cpu_processor() const override;
|
||||
const CPUProcessor *get_from_scene_linear_cpu_processor() const override;
|
||||
|
||||
bool is_hdr() const override
|
||||
{
|
||||
return is_hdr_;
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("LibOCIOConfig");
|
||||
};
|
||||
|
||||
|
||||
@@ -16,9 +16,23 @@ namespace blender::ocio {
|
||||
|
||||
class LibOCIOView : public View {
|
||||
StringRefNull name_;
|
||||
bool is_hdr_ = false;
|
||||
bool is_wide_gamut_ = false;
|
||||
bool is_srgb_ = false;
|
||||
bool is_extended_ = false;
|
||||
|
||||
public:
|
||||
LibOCIOView(const int index, const StringRefNull name) : name_(name)
|
||||
LibOCIOView(const int index,
|
||||
const StringRefNull name,
|
||||
const bool is_hdr,
|
||||
const bool is_wide_gamut,
|
||||
const bool is_srgb,
|
||||
const bool is_extended)
|
||||
: name_(name),
|
||||
is_hdr_(is_hdr),
|
||||
is_wide_gamut_(is_wide_gamut),
|
||||
is_srgb_(is_srgb),
|
||||
is_extended_(is_extended)
|
||||
{
|
||||
this->index = index;
|
||||
}
|
||||
@@ -28,6 +42,28 @@ class LibOCIOView : public View {
|
||||
return name_;
|
||||
}
|
||||
|
||||
bool is_hdr() const override
|
||||
{
|
||||
return is_hdr_;
|
||||
}
|
||||
|
||||
bool is_wide_gamut() const override
|
||||
{
|
||||
return is_wide_gamut_;
|
||||
}
|
||||
|
||||
/* Display space is exactly Rec.709 + sRGB piecewise transfer function. */
|
||||
bool is_srgb() const
|
||||
{
|
||||
return is_srgb_;
|
||||
}
|
||||
|
||||
/* Display space has values outside of 0..1 range. */
|
||||
bool is_extended() const
|
||||
{
|
||||
return is_extended_;
|
||||
}
|
||||
|
||||
MEM_CXX_CLASS_ALLOC_FUNCS("LibOCIOView");
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user