diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index 2cf05c0f4c0..f5ee4ff986d 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -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); + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index ba23b097208..be59a5c3055 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -776,16 +776,12 @@ static std::shared_ptr 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; +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/imbuf/opencolorio/OCIO_colorspace.hh b/source/blender/imbuf/opencolorio/OCIO_colorspace.hh index 5ac142aa5b6..a9d3c8c3cb6 100644 --- a/source/blender/imbuf/opencolorio/OCIO_colorspace.hh +++ b/source/blender/imbuf/opencolorio/OCIO_colorspace.hh @@ -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. diff --git a/source/blender/imbuf/opencolorio/OCIO_config.hh b/source/blender/imbuf/opencolorio/OCIO_config.hh index cfdd2da1a7f..517115fdc2c 100644 --- a/source/blender/imbuf/opencolorio/OCIO_config.hh +++ b/source/blender/imbuf/opencolorio/OCIO_config.hh @@ -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; }; diff --git a/source/blender/imbuf/opencolorio/OCIO_display.hh b/source/blender/imbuf/opencolorio/OCIO_display.hh index 286ab488b66..2ad2aad2306 100644 --- a/source/blender/imbuf/opencolorio/OCIO_display.hh +++ b/source/blender/imbuf/opencolorio/OCIO_display.hh @@ -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 diff --git a/source/blender/imbuf/opencolorio/OCIO_gpu_shader_binder.hh b/source/blender/imbuf/opencolorio/OCIO_gpu_shader_binder.hh index ed5a9881324..20e2b205d1e 100644 --- a/source/blender/imbuf/opencolorio/OCIO_gpu_shader_binder.hh +++ b/source/blender/imbuf/opencolorio/OCIO_gpu_shader_binder.hh @@ -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; }; diff --git a/source/blender/imbuf/opencolorio/OCIO_view.hh b/source/blender/imbuf/opencolorio/OCIO_view.hh index 3db10d61514..dccc7b77431 100644 --- a/source/blender/imbuf/opencolorio/OCIO_view.hh +++ b/source/blender/imbuf/opencolorio/OCIO_view.hh @@ -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 diff --git a/source/blender/imbuf/opencolorio/intern/fallback/fallback_colorspace.hh b/source/blender/imbuf/opencolorio/intern/fallback/fallback_colorspace.hh index 156a8282573..f3512341837 100644 --- a/source/blender/imbuf/opencolorio/intern/fallback/fallback_colorspace.hh +++ b/source/blender/imbuf/opencolorio/intern/fallback/fallback_colorspace.hh @@ -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 { diff --git a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_display.hh b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_display.hh index bc431bb4ed6..8773d2f7e1a 100644 --- a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_display.hh +++ b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_display.hh @@ -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 diff --git a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh index 820f50951ec..c4075a7b212 100644 --- a/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh +++ b/source/blender/imbuf/opencolorio/intern/fallback/fallback_default_view.hh @@ -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 diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc index 377dd19b29c..d56f2e3d089 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.cc @@ -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 diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.hh b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.hh index da520ae1433..fa4f600185a 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.hh +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_colorspace.hh @@ -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_; diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_config.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_config.cc index 2855525a63b..74757805de1 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_config.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_config.cc @@ -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; } diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc index cc888d24912..b1cab050ed0 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.cc @@ -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, "")) { + 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 diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.hh b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.hh index 20ecaebc58d..f332413ba1f 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.hh +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display.hh @@ -29,6 +29,8 @@ class LibOCIODisplay : public Display { StringRefNull name_; Vector 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"); }; diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh b/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh index 8c690a02b93..ae4cf79a79f 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_view.hh @@ -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"); };