Color Management: Warn when OpenColorIO config for blend file is likely missing

If any display, view or colorspace is not found, these would be changed to an
existing one and show warnings in the console. Now also warn about this in the
user interface using the same mechanism as loading new blend files in older Blender
versions, with a warning in the status bar and on file save.

Ref #145476

Pull Request: https://projects.blender.org/blender/blender/pulls/145756
This commit is contained in:
Brecht Van Lommel
2025-08-31 03:15:13 +02:00
parent 7d829e58d3
commit 9d2058eb1e
6 changed files with 154 additions and 33 deletions

View File

@@ -140,6 +140,14 @@ enum {
MAINIDRELATIONS_INCLUDE_UI = 1 << 0,
};
struct MainColorspace {
/*
* A colorspace, view or display was not found, which likely means the OpenColorIO config
* used to create this blend file is missing.
*/
bool is_missing_opencolorio_config = false;
};
struct Main : blender::NonCopyable, blender::NonMovable {
/**
* Runtime vector storing all split Mains (one Main for each library data), during readfile or
@@ -251,6 +259,11 @@ struct Main : blender::NonCopyable, blender::NonMovable {
*/
Library *curlib = nullptr;
/*
* Colorspace information for this file.
*/
MainColorspace colorspace;
/* List bases for all ID types, containing all IDs for the current #Main. */
ListBase scenes = {};

View File

@@ -472,12 +472,14 @@ bool BKE_main_is_empty(Main *bmain)
bool BKE_main_has_issues(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file;
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file ||
bmain->colorspace.is_missing_opencolorio_config;
}
bool BKE_main_needs_overwrite_confirm(const Main *bmain)
{
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file;
return bmain->has_forward_compatibility_issues || bmain->is_asset_edit_file ||
bmain->colorspace.is_missing_opencolorio_config;
}
void BKE_main_lock(Main *bmain)

View File

@@ -389,6 +389,13 @@ static std::string ui_template_status_tooltip(bContext *C,
tooltip_message += RPT_(
"This file is managed by the Blender asset system and cannot be overridden");
}
if (bmain->colorspace.is_missing_opencolorio_config) {
if (!tooltip_message.empty()) {
tooltip_message += "\n\n";
}
tooltip_message += RPT_(
"Displays, views or color spaces in this file were missing and have been changed");
}
return tooltip_message;
}
@@ -503,7 +510,7 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
blender::StringRefNull version_string = ED_info_statusbar_string_ex(
bmain, scene, view_layer, STATUSBAR_SHOW_VERSION);
blender::StringRefNull warning_message;
std::string warning_message;
/* Blender version part is shown as warning area when there are forward compatibility issues with
* currently loaded .blend file. */
@@ -517,6 +524,14 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
}
}
/* Color space warning. */
if (bmain->colorspace.is_missing_opencolorio_config) {
if (!warning_message.empty()) {
warning_message = warning_message + " ";
}
warning_message = warning_message + RPT_("Color Management");
}
const uiStyle *style = UI_style_get();
uiLayout *ui_abs = &layout->absolute(false);
uiBlock *block = ui_abs->block();
@@ -545,7 +560,7 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
/*# ButType::Roundbox's background color is set in `but->col`. */
UI_GetThemeColor4ubv(TH_WARNING, but->col);
if (!warning_message.is_empty()) {
if (!warning_message.empty()) {
/* Background for the rest of the message. */
but = uiDefBut(block,
ButType::Roundbox,
@@ -586,7 +601,7 @@ void uiTemplateStatusInfo(uiLayout *layout, bContext *C)
but->col[3] = 255; /* This theme color is RBG only, so have to set alpha here. */
/* The warning message, if any. */
if (!warning_message.is_empty()) {
if (!warning_message.empty()) {
but = uiDefBut(block,
ButType::But,
0,

View File

@@ -894,7 +894,7 @@ void colormanage_imbuf_make_linear(ImBuf *ibuf, const char *from_colorspace)
/** \name Generic Functions
* \{ */
static void colormanage_check_display_settings(ColorManagedDisplaySettings *display_settings,
static bool colormanage_check_display_settings(ColorManagedDisplaySettings *display_settings,
const char *what,
const ocio::Display *default_display)
{
@@ -902,12 +902,12 @@ static void colormanage_check_display_settings(ColorManagedDisplaySettings *disp
if (display_name.is_empty()) {
STRNCPY_UTF8(display_settings->display_device, default_display->name().c_str());
return;
return true;
}
const ocio::Display *display = g_config->get_display_by_name(display_name);
if (display) {
return;
return true;
}
StringRefNull new_display_name = default_display->name();
@@ -929,6 +929,7 @@ static void colormanage_check_display_settings(ColorManagedDisplaySettings *disp
new_display_name.c_str());
STRNCPY_UTF8(display_settings->display_device, new_display_name.c_str());
return false;
}
static StringRefNull colormanage_find_matching_view_name(const ocio::Display *display,
@@ -958,14 +959,17 @@ static StringRefNull colormanage_find_matching_view_name(const ocio::Display *di
return "";
}
static void colormanage_check_view_settings(ColorManagedDisplaySettings *display_settings,
static bool colormanage_check_view_settings(ColorManagedDisplaySettings *display_settings,
ColorManagedViewSettings *view_settings,
const char *what)
{
const ocio::Display *display = g_config->get_display_by_name(display_settings->display_device);
if (!display) {
return;
return false;
}
bool ok = true;
const char *default_look_name = IMB_colormanagement_look_get_default_name();
StringRefNull view_name = view_settings->view_transform;
@@ -986,6 +990,7 @@ static void colormanage_check_view_settings(ColorManagedDisplaySettings *display
view_settings->view_transform,
new_view_name.c_str());
STRNCPY_UTF8(view_settings->view_transform, new_view_name.c_str());
ok = false;
}
}
}
@@ -1003,6 +1008,7 @@ static void colormanage_check_view_settings(ColorManagedDisplaySettings *display
default_look_name);
STRNCPY_UTF8(view_settings->look, default_look_name);
ok = false;
}
else if (!colormanage_compatible_look(look, view_settings->view_transform)) {
CLOG_INFO(&LOG,
@@ -1015,6 +1021,7 @@ static void colormanage_check_view_settings(ColorManagedDisplaySettings *display
default_look_name);
STRNCPY_UTF8(view_settings->look, default_look_name);
ok = false;
}
}
@@ -1023,10 +1030,13 @@ static void colormanage_check_view_settings(ColorManagedDisplaySettings *display
view_settings->exposure = 0.0f;
view_settings->gamma = 1.0f;
}
return ok;
}
static void colormanage_check_colorspace_name(char *name, const char *what)
static bool colormanage_check_colorspace_name(char *name, const char *what)
{
bool ok = true;
if (name[0] == '\0') {
/* pass */
}
@@ -1036,24 +1046,18 @@ static void colormanage_check_colorspace_name(char *name, const char *what)
if (!colorspace) {
CLOG_WARN(&LOG, "%s colorspace \"%s\" not found, will use default instead.", what, name);
name[0] = '\0';
ok = false;
}
}
(void)what;
return ok;
}
static void colormanage_check_colorspace_settings(ColorManagedColorspaceSettings *settings,
static bool colormanage_check_colorspace_settings(ColorManagedColorspaceSettings *settings,
const char *what)
{
colormanage_check_colorspace_name(settings->name, what);
}
static bool strip_callback(Strip *strip, void * /*user_data*/)
{
if (strip->data) {
colormanage_check_colorspace_name(strip->data->colorspace_settings.name, "sequencer strip");
}
return true;
return colormanage_check_colorspace_name(settings->name, what);
}
void IMB_colormanagement_check_file_config(Main *bmain)
@@ -1064,17 +1068,22 @@ void IMB_colormanagement_check_file_config(Main *bmain)
return;
}
/* Check display, view and colorspace names in datablocks. */
bool is_missing_opencolorio_config = false;
/* Check scenes. */
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
ColorManagedColorspaceSettings *sequencer_colorspace_settings;
bool ok = true;
/* check scene color management settings */
colormanage_check_display_settings(&scene->display_settings, "scene", default_display);
colormanage_check_view_settings(&scene->display_settings, &scene->view_settings, "scene");
ok &= colormanage_check_display_settings(&scene->display_settings, "scene", default_display);
ok &= colormanage_check_view_settings(
&scene->display_settings, &scene->view_settings, "scene");
sequencer_colorspace_settings = &scene->sequencer_colorspace_settings;
colormanage_check_colorspace_settings(sequencer_colorspace_settings, "sequencer");
ok &= colormanage_check_colorspace_settings(sequencer_colorspace_settings, "sequencer");
if (sequencer_colorspace_settings->name[0] == '\0') {
STRNCPY_UTF8(sequencer_colorspace_settings->name, global_role_default_sequencer);
@@ -1082,36 +1091,57 @@ void IMB_colormanagement_check_file_config(Main *bmain)
/* Check sequencer strip input colorspace. */
if (scene->ed != nullptr) {
blender::seq::for_each_callback(&scene->ed->seqbase, strip_callback, nullptr);
blender::seq::for_each_callback(&scene->ed->seqbase, [&](Strip *strip) {
if (strip->data) {
ok &= colormanage_check_colorspace_settings(&strip->data->colorspace_settings,
"sequencer strip");
}
return true;
});
}
is_missing_opencolorio_config |= (!ok && !ID_IS_LINKED(&scene->id));
}
/* Check image and movie input colorspace. */
LISTBASE_FOREACH (Image *, image, &bmain->images) {
colormanage_check_colorspace_settings(&image->colorspace_settings, "image");
const bool ok = colormanage_check_colorspace_settings(&image->colorspace_settings, "image");
is_missing_opencolorio_config |= (!ok && !ID_IS_LINKED(&image->id));
}
LISTBASE_FOREACH (MovieClip *, clip, &bmain->movieclips) {
colormanage_check_colorspace_settings(&clip->colorspace_settings, "clip");
const bool ok = colormanage_check_colorspace_settings(&clip->colorspace_settings, "clip");
is_missing_opencolorio_config |= (!ok && !ID_IS_LINKED(&clip->id));
}
/* Check compositing nodes. */
LISTBASE_FOREACH (bNodeTree *, ntree, &bmain->nodetrees) {
if (ntree->type == NTREE_COMPOSIT) {
bool ok = true;
LISTBASE_FOREACH (bNode *, node, &ntree->nodes) {
if (node->type_legacy == CMP_NODE_CONVERT_TO_DISPLAY) {
NodeConvertToDisplay *nctd = static_cast<NodeConvertToDisplay *>(node->storage);
colormanage_check_display_settings(&nctd->display_settings, "node", default_display);
colormanage_check_view_settings(&nctd->display_settings, &nctd->view_settings, "node");
ok &= colormanage_check_display_settings(
&nctd->display_settings, "node", default_display);
ok &= colormanage_check_view_settings(
&nctd->display_settings, &nctd->view_settings, "node");
}
else if (node->type_legacy == CMP_NODE_CONVERT_COLOR_SPACE) {
NodeConvertColorSpace *ncs = static_cast<NodeConvertColorSpace *>(node->storage);
colormanage_check_colorspace_name(ncs->from_color_space, "node");
colormanage_check_colorspace_name(ncs->to_color_space, "node");
ok &= colormanage_check_colorspace_name(ncs->from_color_space, "node");
ok &= colormanage_check_colorspace_name(ncs->to_color_space, "node");
}
}
is_missing_opencolorio_config |= (!ok && !ID_IS_LINKED(&ntree->id));
}
}
/* Inform users about mismatch, but not for new files. Linked datablocks are also ignored,
* as we are not overwriting them on blend file save which is the main purpose of this
* warning. */
bmain->colorspace.is_missing_opencolorio_config = (bmain->filepath[0] == '\0') ?
false :
is_missing_opencolorio_config;
}
void IMB_colormanagement_validate_settings(const ColorManagedDisplaySettings *display_settings,

View File

@@ -82,6 +82,18 @@ static void rna_Main_filepath_set(PointerRNA *ptr, const char *value)
}
# endif
static PointerRNA rna_Main_colorspace_get(PointerRNA *ptr)
{
Main *bmain = (Main *)ptr->data;
return PointerRNA(nullptr, &RNA_BlendFileColorspace, &bmain->colorspace);
}
static bool rna_MainColorspace_is_missing_opencolorio_config_get(PointerRNA *ptr)
{
MainColorspace *colorspace = ptr->data_as<MainColorspace>();
return colorspace->is_missing_opencolorio_config;
}
# define RNA_MAIN_LISTBASE_FUNCS_DEF(_listbase_name) \
static void rna_Main_##_listbase_name##_begin(CollectionPropertyIterator *iter, \
PointerRNA *ptr) \
@@ -165,6 +177,26 @@ struct MainCollectionDef {
CollectionDefFunc *func;
};
static void rna_def_main_colorspace(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "BlendFileColorspace", nullptr);
RNA_def_struct_ui_text(srna,
"Blend-File Color Space",
"Information about the color space used for datablocks in a blend file");
prop = RNA_def_property(srna, "is_missing_opencolorio_config", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_funcs(
prop, "rna_MainColorspace_is_missing_opencolorio_config_get", nullptr);
RNA_def_property_ui_text(prop,
"Missing OpenColorIO Configuration",
"A color space, view or display was not found, which likely means the "
"OpenColorIO config used to create this blend file is missing");
}
void RNA_def_main(BlenderRNA *brna)
{
StructRNA *srna;
@@ -470,6 +502,17 @@ void RNA_def_main(BlenderRNA *brna)
}
}
rna_def_main_colorspace(brna);
prop = RNA_def_property(srna, "colorspace", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_struct_type(prop, "BlendFileColorspace");
RNA_def_property_pointer_funcs(prop, "rna_Main_colorspace_get", nullptr, nullptr, nullptr);
RNA_def_property_ui_text(
prop,
"Color Space",
"Information about the color space used for datablocks in a blend file");
RNA_api_main(srna);
# ifdef UNIT_TEST

View File

@@ -3791,6 +3791,7 @@ static wmOperatorStatus wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
/* If saved file is the active one, there are technically no more compatibility issues, the
* file on disk now matches the currently opened data version-wise. */
bmain->has_forward_compatibility_issues = false;
bmain->colorspace.is_missing_opencolorio_config = false;
/* If saved file is the active one, notify WM so that saved status and window title can be
* updated. */
@@ -4382,6 +4383,14 @@ static void file_overwrite_detailed_info_show(uiLayout *parent_layout, Main *bma
ICON_NONE);
layout->label(RPT_("saved as a new, regular file."), ICON_NONE);
}
if (bmain->colorspace.is_missing_opencolorio_config) {
layout->label(
RPT_("Displays, views or color spaces in this file were missing and have been changed."),
ICON_NONE);
layout->label(RPT_("Saving it with this OpenColorIO configuration may cause loss of data."),
ICON_NONE);
}
}
static void save_file_overwrite_cancel(bContext *C, void *arg_block, void * /*arg_data*/)
@@ -4487,10 +4496,18 @@ static uiBlock *block_create_save_file_overwrite_dialog(bContext *C, ARegion *re
true,
false);
}
else {
else if (!bmain->colorspace.is_missing_opencolorio_config) {
BLI_assert_unreachable();
}
if (bmain->colorspace.is_missing_opencolorio_config) {
uiItemL_ex(layout,
RPT_("Overwrite file with current OpenColorIO configuration?"),
ICON_NONE,
true,
false);
}
/* Filename. */
const char *blendfile_path = BKE_main_blendfile_path(CTX_data_main(C));
char filename[FILE_MAX];
@@ -4604,7 +4621,8 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat
bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
if (file_has_been_saved_before) {
if (bmain->has_forward_compatibility_issues) {
if (bmain->has_forward_compatibility_issues || bmain->colorspace.is_missing_opencolorio_config)
{
/* Need to invoke to get the file-browser and choose where to save the new file.
* This also makes it impossible to keep on going with current operation, which is why
* callback cannot be executed anymore.