diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index b355900ef70..cf039ac46d5 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -780,15 +780,6 @@ void IMB_colormanagement_display_settings_from_ctx( } } -static const ColorSpace *get_untonemapped_display_colorspace( - const ColorManagedDisplaySettings *display_settings) -{ - ColorManagedViewSettings view_settings = {}; - IMB_colormanagement_init_untonemapped_view_settings(&view_settings, display_settings); - return g_config->get_display_view_color_space(display_settings->display_device, - view_settings.view_transform); -} - static std::shared_ptr get_display_buffer_processor( const ColorManagedDisplaySettings &display_settings, const char *look, @@ -822,22 +813,11 @@ static std::shared_ptr get_display_buffer_processor( } void IMB_colormanagement_init_untonemapped_view_settings( - ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings) + ColorManagedViewSettings *view_settings, + const ColorManagedDisplaySettings * /*display_settings*/) { - /* Get untonemapped view from the display. */ - const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device); - const ocio::View *default_view = (display) ? display->get_untonemapped_view() : nullptr; - /* If that fails, we fall back to the default view transform of the display - * as per OCIO configuration. */ - if (default_view == nullptr) { - default_view = (display) ? display->get_default_view() : nullptr; - } - if (default_view != nullptr) { - STRNCPY_UTF8(view_settings->view_transform, default_view->name().c_str()); - } - else { - view_settings->view_transform[0] = '\0'; - } + /* Empty view transform name means skip tone mapping. */ + view_settings->view_transform[0] = '\0'; /* TODO(sergey): Find a way to safely/reliable un-hardcode this. */ STRNCPY_UTF8(view_settings->look, "None"); /* Initialize rest of the settings. */ @@ -3036,16 +3016,22 @@ const ColorSpace *IMB_colormangement_display_get_color_space( const ColorManagedDisplaySettings *display_settings) { /* Get the colorspace that the image is in after applying this view and display - * transform. If we are going to a display referred colorspace we can use that. - * However this is not always available, especially in v1 configs. We then rely - * on guessing what the untonemapped view transform is. */ - const ColorSpace *colorspace = g_config->get_display_view_color_space( - display_settings->display_device, view_settings->view_transform); + * transform. If we are going to a display referred colorspace we can use that. */ + const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device); + const ocio::View *view = (display) ? display->get_view_by_name(view_settings->view_transform) : + nullptr; + const ColorSpace *colorspace = (view) ? view->display_colorspace() : nullptr; if (colorspace && colorspace->is_display_referred()) { return colorspace; } - const ColorSpace *untonemapped_colorspace = get_untonemapped_display_colorspace( - display_settings); + /* If not available, try to guess what the untonemapped view is and use its colorspace. + * This is especially needed for v1 configs. */ + const ocio::View *untonemapped_view = (display) ? display->get_untonemapped_view() : nullptr; + const ocio::ColorSpace *untonemapped_colorspace = (untonemapped_view) ? + g_config->get_display_view_color_space( + display_settings->display_device, + untonemapped_view->name()) : + nullptr; return (untonemapped_colorspace) ? untonemapped_colorspace : colorspace; } @@ -4146,7 +4132,8 @@ ColormanageProcessor *IMB_colormanagement_display_processor_new( applied_view_settings = &untonemapped_view_settings; } - display_colorspace = IMB_colormangement_display_get_color_space(view_settings, display_settings); + display_colorspace = IMB_colormangement_display_get_color_space(applied_view_settings, + display_settings); if (display_colorspace) { cm_processor->is_data_result = display_colorspace->is_data(); } diff --git a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display_processor.cc b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display_processor.cc index 4161e7c063e..81a6b10537b 100644 --- a/source/blender/imbuf/opencolorio/intern/libocio/libocio_display_processor.cc +++ b/source/blender/imbuf/opencolorio/intern/libocio/libocio_display_processor.cc @@ -8,6 +8,7 @@ # include # include +# include "BLI_colorspace.hh" # include "BLI_math_matrix.hh" # include "OCIO_config.hh" @@ -27,6 +28,74 @@ static CLG_LogRef LOG = {"color_management"}; namespace blender::ocio { +static TransferFunction system_extended_srgb_transfer_function(const LibOCIOView *view, + const bool use_hdr_buffer) +{ +# ifdef __APPLE__ + /* The Metal backend always uses sRGB or extended sRGB buffer. + * + * How this will be decoded depends on the macOS display preset, but from testing + * on a MacBook P3 M3 it appears: + * - Apple XDR Display (P3 - 1600 nits): Decode with gamma 2.2 + * - HDR Video (P3-ST 2084): Decode with sRGB. As we encode with the sRGB transfer + * function, this will be cancelled out, and linear values will be passed on + * effectively unmodified. + */ + UNUSED_VARS(use_hdr_buffer, view); + return TransferFunction::sRGB; +# elif defined(_WIN32) + /* The Vulkan backend uses either sRGB for SDR, or linear extended sRGB for HDR. + * + * - Windows HDR mode off: use_hdr_buffer will be false, and we encode with sRGB. + * By default Windows will decode with gamma 2.2. + * - Windows HDR mode on: use_hdr_buffer will be true, and we encode with sRGB. + * The Vulkan HDR swapchain blitting will decode with sRGB to cancel this out + * exactly, meaning we effectively pass on linear values unmodified. + * + * Note this means that both the user interface and SDR content will not be + * displayed the same in HDR mode off and on. However it is consistent with other + * software. To match, gamma 2.2 would have to be used. + */ + UNUSED_VARS(use_hdr_buffer, view); + return TransferFunction::sRGB; +# else + /* The Vulkan backend uses either sRGB for SDR, or linear extended sRGB for HDR. + * + * - When using a HDR swapchain and the display + view is HDR, ensure we pass on + * values linearly by doing gamma 2.2 encode here + gamma 2.2 decode in the + * Vulkan HDR swapchain blitting. + * - When using HDR swapain and the display + view is SDR, use sRGB encode to + * emulate what happens on a typical SDR monitor. + * - When using an SDR swapchain, the buffer is always sRGB. + */ + return (use_hdr_buffer && view && view->is_hdr()) ? TransferFunction::Gamma22 : + TransferFunction::sRGB; +# endif +} + +static OCIO_NAMESPACE::TransformRcPtr create_extended_srgb_transform( + const TransferFunction transfer_function) +{ + if (transfer_function == TransferFunction::sRGB) { + /* Piecewise sRGB transfer function. */ + auto to_ui = OCIO_NAMESPACE::ExponentWithLinearTransform::Create(); + to_ui->setGamma({2.4, 2.4, 2.4, 1.0}); + to_ui->setOffset({0.055, 0.055, 0.055, 0.0}); + /* Mirrored for negative as specified by scRGB and extended sRGB. */ + to_ui->setNegativeStyle(OCIO_NAMESPACE::NEGATIVE_MIRROR); + to_ui->setDirection(OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE); + return to_ui; + } + + /* Pure gamma 2.2 function. */ + auto to_ui = OCIO_NAMESPACE::ExponentTransform::Create(); + to_ui->setValue({2.2, 2.2, 2.2, 1.0}); + /* Mirrored for negative as specified by scRGB and extended sRGB. */ + to_ui->setNegativeStyle(OCIO_NAMESPACE::NEGATIVE_MIRROR); + to_ui->setDirection(OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE); + return to_ui; +} + static void display_as_extended_srgb(const LibOCIOConfig &config, OCIO_NAMESPACE::GroupTransformRcPtr &group, StringRefNull display_name, @@ -58,47 +127,8 @@ static void display_as_extended_srgb(const LibOCIOConfig &config, return; } -# ifdef __APPLE__ - /* The Metal backend always uses sRGB or extended sRGB buffer. - * - * How this will be decoded depends on the macOS display preset, but from testing - * on a MacBook P3 M3 it appears: - * - Apple XDR Display (P3 - 1600 nits): Decode with gamma 2.2 - * - HDR Video (P3-ST 2084): Decode with sRGB. As we encode with the sRGB transfer - * function, this will be cancelled out, and linear values will be passed on - * effectively unmodified. - */ - const TransferFunction target_transfer_function = TransferFunction::sRGB; - UNUSED_VARS(use_hdr_buffer); -# elif defined(_WIN32) - /* The Vulkan backend uses either sRGB for SDR, or linear extended sRGB for HDR. - * - * - Windows HDR mode off: use_hdr_buffer will be false, and we encode with sRGB. - * By default Windows will decode with gamma 2.2. - * - Windows HDR mode on: use_hdr_buffer will be true, and we encode with sRGB. - * The Vulkan HDR swapchain blitting will decode with sRGB to cancel this out - * exactly, meaning we effectively pass on linear values unmodified. - * - * Note this means that both the user interface and SDR content will not be - * displayed the same in HDR mode off and on. However it is consistent with other - * software. To match, gamma 2.2 would have to be used. - */ - const TransferFunction target_transfer_function = TransferFunction::sRGB; - UNUSED_VARS(use_hdr_buffer); -# else - /* The Vulkan backend uses either sRGB for SDR, or linear extended sRGB for HDR. - * - * - When using a HDR swapchain and the display + view is HDR, ensure we pass on - * values linearly by doing gamma 2.2 encode here + gamma 2.2 decode in the - * Vulkan HDR swapchain blitting. - * - When using HDR swapain and the display + view is SDR, use sRGB encode to - * emulate what happens on a typical SDR monitor. - * - When using an SDR swapchain, the buffer is always sRGB. - */ - const TransferFunction target_transfer_function = (use_hdr_buffer && view->is_hdr()) ? - TransferFunction::Gamma22 : - TransferFunction::sRGB; -# endif + const TransferFunction target_transfer_function = system_extended_srgb_transfer_function( + view, use_hdr_buffer); /* If we are already in the desired display colorspace, all we have to do is clamp. */ if ((view->transfer_function() == target_transfer_function || @@ -252,25 +282,7 @@ static void display_as_extended_srgb(const LibOCIOConfig &config, group->appendTransform(to_rec709); } - if (target_transfer_function == TransferFunction::sRGB) { - /* Piecewise sRGB transfer function. */ - auto to_ui = OCIO_NAMESPACE::ExponentWithLinearTransform::Create(); - to_ui->setGamma({2.4, 2.4, 2.4, 1.0}); - to_ui->setOffset({0.055, 0.055, 0.055, 0.0}); - /* Mirrored for negative as specified by scRGB and extended sRGB. */ - to_ui->setNegativeStyle(OCIO_NAMESPACE::NEGATIVE_MIRROR); - to_ui->setDirection(OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE); - group->appendTransform(to_ui); - } - else { - /* Pure gamma 2.2 function. */ - auto to_ui = OCIO_NAMESPACE::ExponentTransform::Create(); - to_ui->setValue({2.2, 2.2, 2.2, 1.0}); - /* Mirrored for negative as specified by scRGB and extended sRGB. */ - to_ui->setNegativeStyle(OCIO_NAMESPACE::NEGATIVE_MIRROR); - to_ui->setDirection(OCIO_NAMESPACE::TRANSFORM_DIR_INVERSE); - group->appendTransform(to_ui); - } + group->appendTransform(create_extended_srgb_transform(target_transfer_function)); } OCIO_NAMESPACE::TransformRcPtr create_ocio_display_transform( @@ -323,6 +335,34 @@ OCIO_NAMESPACE::TransformRcPtr create_ocio_display_transform( return group; } +static OCIO_NAMESPACE::TransformRcPtr create_untonemapped_ocio_display_transform( + const LibOCIOConfig &config, + StringRefNull display_name, + StringRefNull from_colorspace, + bool use_hdr_buffer) +{ + /* Convert to extended sRGB without any tone mapping. */ + const auto group = OCIO_NAMESPACE::GroupTransform::Create(); + + const auto to_scene_linear = OCIO_NAMESPACE::ColorSpaceTransform::Create(); + to_scene_linear->setSrc(from_colorspace.c_str()); + to_scene_linear->setDst(OCIO_NAMESPACE::ROLE_SCENE_LINEAR); + group->appendTransform(to_scene_linear); + + const auto to_rec709 = OCIO_NAMESPACE::MatrixTransform::Create(); + to_rec709->setMatrix(math::transpose(double4x4(colorspace::scene_linear_to_rec709)).base_ptr()); + group->appendTransform(to_rec709); + + const LibOCIODisplay *display = static_cast( + config.get_display_by_name(display_name)); + const LibOCIOView *view = (display) ? static_cast( + display->get_untonemapped_view()) : + nullptr; + group->appendTransform(create_extended_srgb_transform( + system_extended_srgb_transfer_function(view, use_hdr_buffer))); + return group; +} + OCIO_NAMESPACE::ConstProcessorRcPtr create_ocio_display_processor( const LibOCIOConfig &config, const DisplayParameters &display_parameters) { @@ -359,30 +399,37 @@ OCIO_NAMESPACE::ConstProcessorRcPtr create_ocio_display_processor( group->appendTransform(mt); } - /* Core display processor. */ - group->appendTransform(create_ocio_display_transform(ocio_config, - display_parameters.display, - display_parameters.view, - display_parameters.look, - from_colorspace)); + if (!display_parameters.view.is_empty()) { + /* Core display processor. */ + group->appendTransform(create_ocio_display_transform(ocio_config, + display_parameters.display, + display_parameters.view, + display_parameters.look, + from_colorspace)); + /* Gamma. */ + if (display_parameters.exponent != 1.0f) { + ExponentTransformRcPtr et = ExponentTransform::Create(); + const double value[4] = {display_parameters.exponent, + display_parameters.exponent, + display_parameters.exponent, + 1.0}; + et->setValue(value); + group->appendTransform(et); + } - /* Gamma. */ - if (display_parameters.exponent != 1.0f) { - ExponentTransformRcPtr et = ExponentTransform::Create(); - const double value[4] = {display_parameters.exponent, - display_parameters.exponent, - display_parameters.exponent, - 1.0}; - et->setValue(value); - group->appendTransform(et); + /* Convert to extended sRGB to match the system graphics buffer. */ + if (display_parameters.use_display_emulation) { + display_as_extended_srgb(config, + group, + display_parameters.display, + display_parameters.view, + display_parameters.use_hdr_buffer); + } } - - if (display_parameters.use_display_emulation) { - display_as_extended_srgb(config, - group, - display_parameters.display, - display_parameters.view, - display_parameters.use_hdr_buffer); + else { + /* Untonemapped case, directly to extended sRGB. */ + group->appendTransform(create_untonemapped_ocio_display_transform( + config, display_parameters.display, from_colorspace, display_parameters.use_hdr_buffer)); } if (display_parameters.inverse) {