Color Management: Add working color space for blend files
* Store scene linear to XYZ conversion matrix in each blend file, along with the colorspace name. The matrix is the source of truth. The name is currently only used for error logging about unknown color spaces. * Add Working Space option in color management panel, to change the working space for the entire blend file. Changing this will pop up a dialog, with a default enabled option to convert all colors in the blend file to the new working space. Note this is necessarily only an approximation. * Link and append automatically converts to the color space of the main open blend file. * There is builtin support for Rec.709, Rec.2020 and ACEScg working spaces, in addition to the working space of custom OpenColorIO configs. * Undo of working space for linked datablocks isn't quite correct when going to a smaller gamut working space. This can be fixed by reloading the file so the linked datablocks are reloaded. Compatibility with blend files saved with a custom OpenColorIO config is tricky, as we can not detect this. * We assume that if the blend file has no information about the scene linear color space, it is the default one from the active OCIO config. And the same for any blend files linked or appended. This is effectively the same behavior as before. * Now that there is a warning when color spaces are missing, it is more likely that a user will notice something is wrong and only save the blend file with the correct config active. * As no automatic working space conversion happens on file load, there is an opportunity to correct things by changing the working space with "Convert Colors" disabled. This can also be scripted for all blend files in a project. Ref #144911 Pull Request: https://projects.blender.org/blender/blender/pulls/145476
This commit is contained in:
@@ -85,7 +85,34 @@ class RENDER_PT_color_management(RenderButtonsPanel, Panel):
|
||||
col.prop(view, "exposure")
|
||||
col.prop(view, "gamma")
|
||||
|
||||
col.separator()
|
||||
|
||||
class RENDER_PT_color_management_working_space(RenderButtonsPanel, Panel):
|
||||
bl_label = "Working Space"
|
||||
bl_parent_id = "RENDER_PT_color_management"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
COMPAT_ENGINES = {
|
||||
'BLENDER_RENDER',
|
||||
'BLENDER_EEVEE',
|
||||
'BLENDER_WORKBENCH',
|
||||
}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False # No animation.
|
||||
|
||||
scene = context.scene
|
||||
blend_colorspace = context.blend_data.colorspace
|
||||
|
||||
flow = layout.grid_flow(row_major=True, columns=0, even_columns=False, even_rows=False, align=True)
|
||||
|
||||
col = flow.column()
|
||||
|
||||
split = col.split(factor=0.4)
|
||||
row = split.row()
|
||||
row.label(text="File")
|
||||
row.alignment = 'RIGHT'
|
||||
split.operator_menu_enum("wm.set_working_color_space", "working_space", text=blend_colorspace.working_space)
|
||||
|
||||
col.prop(scene.sequencer_colorspace_settings, "name", text="Sequencer")
|
||||
|
||||
@@ -1112,6 +1139,7 @@ classes = (
|
||||
RENDER_PT_opengl_film,
|
||||
RENDER_PT_hydra_debug,
|
||||
RENDER_PT_color_management,
|
||||
RENDER_PT_color_management_working_space,
|
||||
RENDER_PT_color_management_curves,
|
||||
RENDER_PT_color_management_white_balance_presets,
|
||||
RENDER_PT_color_management_white_balance,
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#include "BLI_string_ref.hh"
|
||||
|
||||
#include "BKE_main.hh"
|
||||
|
||||
struct FileData;
|
||||
struct Library;
|
||||
struct ListBase;
|
||||
@@ -55,6 +57,9 @@ struct LibraryRuntime {
|
||||
/** See BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION, needed for do_versions. */
|
||||
short versionfile = 0;
|
||||
short subversionfile = 0;
|
||||
|
||||
/* Colorspace information. */
|
||||
MainColorspace colorspace;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "DNA_listBase.h"
|
||||
|
||||
#include "BLI_compiler_attrs.h"
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_sys_types.h"
|
||||
#include "BLI_utility_mixins.hh"
|
||||
#include "BLI_vector_set.hh"
|
||||
@@ -141,6 +142,14 @@ enum {
|
||||
};
|
||||
|
||||
struct MainColorspace {
|
||||
/*
|
||||
* File working colorspace for all scene linear colors.
|
||||
* The name is only for the user interface and is not a unique identifier, the matrix is
|
||||
* the XYZ colorspace is the source of truth.
|
||||
* */
|
||||
char scene_linear_name[64 /*MAX_COLORSPACE_NAME*/] = "";
|
||||
blender::float3x3 scene_linear_to_xyz = blender::float3x3::zero();
|
||||
|
||||
/*
|
||||
* A colorspace, view or display was not found, which likely means the OpenColorIO config
|
||||
* used to create this blend file is missing.
|
||||
|
||||
@@ -982,6 +982,9 @@ static void setup_app_data(bContext *C,
|
||||
reuse_data.old_bmain = bmain;
|
||||
reuse_data.wm_setup_data = wm_setup_data;
|
||||
|
||||
const bool reuse_editable_assets = mode != LOAD_UNDO && !params->is_factory_settings &&
|
||||
reuse_editable_asset_needed(&reuse_data);
|
||||
|
||||
if (mode != LOAD_UNDO) {
|
||||
const short ui_id_codes[]{ID_WS, ID_SCR};
|
||||
|
||||
@@ -1008,7 +1011,7 @@ static void setup_app_data(bContext *C,
|
||||
|
||||
BKE_main_idmap_destroy(reuse_data.id_map);
|
||||
|
||||
if (!params->is_factory_settings && reuse_editable_asset_needed(&reuse_data)) {
|
||||
if (reuse_editable_assets) {
|
||||
unpin_file_local_grease_pencil_brush_materials(&reuse_data);
|
||||
/* Keep linked brush asset data, similar to UI data. Only does a known
|
||||
* subset know. Could do everything, but that risks dragging along more
|
||||
@@ -1229,9 +1232,8 @@ static void setup_app_data(bContext *C,
|
||||
/* Setting scene might require having a dependency graph, with copy-on-eval
|
||||
* we need to make sure we ensure scene has correct color management before
|
||||
* constructing dependency graph. */
|
||||
if (mode != LOAD_UNDO) {
|
||||
IMB_colormanagement_check_file_config(bmain);
|
||||
}
|
||||
IMB_colormanagement_working_space_check(bmain, mode == LOAD_UNDO, reuse_editable_assets);
|
||||
IMB_colormanagement_check_file_config(bmain);
|
||||
|
||||
BKE_scene_set_background(bmain, curscene);
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "BKE_main_namemap.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
@@ -88,6 +89,7 @@ Main::~Main()
|
||||
Main *BKE_main_new()
|
||||
{
|
||||
Main *bmain = MEM_new<Main>(__func__);
|
||||
IMB_colormanagement_working_space_init(bmain);
|
||||
return bmain;
|
||||
}
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_utils.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "readfile.hh"
|
||||
#include "versioning_common.hh"
|
||||
|
||||
@@ -444,6 +446,7 @@ void blo_split_main(Main *bmain)
|
||||
libmain->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL(
|
||||
libmain, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
|
||||
libmain->is_asset_edit_file = (lib->runtime->tag & LIBRARY_IS_ASSET_EDIT_FILE) != 0;
|
||||
libmain->colorspace = lib->runtime->colorspace;
|
||||
bmain->split_mains->add_new(libmain);
|
||||
libmain->split_mains = bmain->split_mains;
|
||||
lib->runtime->temp_index = i;
|
||||
@@ -464,7 +467,7 @@ void blo_split_main(Main *bmain)
|
||||
MEM_freeN(lib_main_array);
|
||||
}
|
||||
|
||||
static void read_file_version(FileData *fd, Main *main)
|
||||
static void read_file_version_and_colorspace(FileData *fd, Main *main)
|
||||
{
|
||||
BHead *bhead;
|
||||
|
||||
@@ -485,6 +488,9 @@ static void read_file_version(FileData *fd, Main *main)
|
||||
main->has_forward_compatibility_issues = !MAIN_VERSION_FILE_OLDER_OR_EQUAL(
|
||||
main, BLENDER_FILE_VERSION, BLENDER_FILE_SUBVERSION);
|
||||
main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0;
|
||||
STRNCPY(main->colorspace.scene_linear_name, fg->colorspace_scene_linear_name);
|
||||
main->colorspace.scene_linear_to_xyz = blender::float3x3(
|
||||
fg->colorspace_scene_linear_to_xyz);
|
||||
MEM_freeN(fg);
|
||||
}
|
||||
else if (bhead->code == BLO_CODE_ENDB) {
|
||||
@@ -497,6 +503,7 @@ static void read_file_version(FileData *fd, Main *main)
|
||||
main->curlib->runtime->subversionfile = main->subversionfile;
|
||||
SET_FLAG_FROM_TEST(
|
||||
main->curlib->runtime->tag, main->is_asset_edit_file, LIBRARY_IS_ASSET_EDIT_FILE);
|
||||
main->curlib->runtime->colorspace = main->colorspace;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -588,7 +595,7 @@ static Main *blo_find_main(FileData *fd, const char *filepath, const char *relab
|
||||
|
||||
m->curlib = lib;
|
||||
|
||||
read_file_version(fd, m);
|
||||
read_file_version_and_colorspace(fd, m);
|
||||
|
||||
if (G.debug & G_DEBUG) {
|
||||
CLOG_DEBUG(&LOG, "Added new lib %s", filepath);
|
||||
@@ -3228,6 +3235,10 @@ static BHead *read_global(BlendFileData *bfd, FileData *fd, BHead *bhead)
|
||||
STRNCPY(bfd->main->build_hash, fg->build_hash);
|
||||
bfd->main->is_asset_edit_file = (fg->fileflags & G_FILE_ASSET_EDIT_FILE) != 0;
|
||||
|
||||
STRNCPY(bfd->main->colorspace.scene_linear_name, fg->colorspace_scene_linear_name);
|
||||
bfd->main->colorspace.scene_linear_to_xyz = blender::float3x3(
|
||||
fg->colorspace_scene_linear_to_xyz);
|
||||
|
||||
bfd->fileflags = fg->fileflags;
|
||||
bfd->globalf = fg->globalf;
|
||||
|
||||
@@ -4004,6 +4015,7 @@ BlendFileData *blo_read_file_internal(FileData *fd, const char *filepath)
|
||||
mainvar->curlib->runtime->filedata :
|
||||
fd,
|
||||
mainvar);
|
||||
IMB_colormanagement_working_space_convert(mainvar, bfd->main);
|
||||
}
|
||||
blo_join_main(bfd->main);
|
||||
|
||||
@@ -4597,7 +4609,7 @@ static Main *library_link_begin(Main *mainvar,
|
||||
|
||||
/* needed for do_version */
|
||||
mainl->versionfile = short(fd->fileversion);
|
||||
read_file_version(fd, mainl);
|
||||
read_file_version_and_colorspace(fd, mainl);
|
||||
read_file_bhead_idname_map_create(fd);
|
||||
|
||||
return mainl;
|
||||
@@ -4646,6 +4658,7 @@ static void split_main_newid(Main *mainptr, Main *main_newid)
|
||||
main_newid->subversionfile = mainptr->subversionfile;
|
||||
STRNCPY(main_newid->filepath, mainptr->filepath);
|
||||
main_newid->curlib = mainptr->curlib;
|
||||
main_newid->colorspace = mainptr->colorspace;
|
||||
|
||||
MainListsArray lbarray = BKE_main_lists_get(*mainptr);
|
||||
MainListsArray lbarray_newid = BKE_main_lists_get(*main_newid);
|
||||
@@ -4728,6 +4741,7 @@ static void library_link_end(Main *mainl, FileData **fd, const int flag, ReportL
|
||||
main_newid->curlib->runtime->filedata :
|
||||
*fd,
|
||||
main_newid);
|
||||
IMB_colormanagement_working_space_convert(main_newid, mainvar);
|
||||
|
||||
add_main_to_main(mainlib, main_newid);
|
||||
|
||||
@@ -5037,7 +5051,7 @@ static FileData *read_library_file_data(FileData *basefd, Main *bmain, Main *lib
|
||||
lib_bmain->versionfile = fd->fileversion;
|
||||
|
||||
/* subversion */
|
||||
read_file_version(fd, lib_bmain);
|
||||
read_file_version_and_colorspace(fd, lib_bmain);
|
||||
read_file_bhead_idname_map_create(fd);
|
||||
}
|
||||
else {
|
||||
@@ -5047,6 +5061,7 @@ static FileData *read_library_file_data(FileData *basefd, Main *bmain, Main *lib
|
||||
/* Set lib version to current main one... Makes assert later happy. */
|
||||
lib_bmain->versionfile = lib_bmain->curlib->runtime->versionfile = bmain->versionfile;
|
||||
lib_bmain->subversionfile = lib_bmain->curlib->runtime->subversionfile = bmain->subversionfile;
|
||||
lib_bmain->colorspace = lib_bmain->curlib->runtime->colorspace = bmain->colorspace;
|
||||
}
|
||||
|
||||
if (fd == nullptr) {
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_implicit_sharing.hh"
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_multi_value_map.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_set.hh"
|
||||
@@ -1241,6 +1242,9 @@ static void write_global(WriteData *wd, const int fileflags, Main *mainvar)
|
||||
fg.curscene = scene;
|
||||
fg.cur_view_layer = view_layer;
|
||||
|
||||
STRNCPY(fg.colorspace_scene_linear_name, mainvar->colorspace.scene_linear_name);
|
||||
copy_m3_m3(fg.colorspace_scene_linear_to_xyz, mainvar->colorspace.scene_linear_to_xyz.ptr());
|
||||
|
||||
/* Prevent to save this, is not good convention, and feature with concerns. */
|
||||
fg.fileflags = (fileflags & ~G_FILE_FLAG_ALL_RUNTIME);
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ set(LIB
|
||||
PRIVATE bf::blenkernel
|
||||
PRIVATE bf::blenlib
|
||||
PRIVATE bf::blenloader
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::gpu
|
||||
bf_imbuf_openimageio
|
||||
|
||||
@@ -19,6 +19,7 @@ struct ColorManagedColorspaceSettings;
|
||||
struct ColorManagedDisplaySettings;
|
||||
struct ColorManagedViewSettings;
|
||||
struct ColormanageProcessor;
|
||||
struct ID;
|
||||
struct EnumPropertyItem;
|
||||
struct ImBuf;
|
||||
struct ImageFormatData;
|
||||
@@ -392,6 +393,37 @@ void IMB_colormanagement_colorspace_from_ibuf_ftype(
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Working Space Functions
|
||||
* \{ */
|
||||
|
||||
const char *IMB_colormanagement_working_space_get_default();
|
||||
const char *IMB_colormanagement_working_space_get();
|
||||
|
||||
bool IMB_colormanagement_working_space_set_from_name(const char *name);
|
||||
bool IMB_colormanagement_working_space_set_from_matrix(
|
||||
const char *name, const blender::float3x3 &scene_linear_to_xyz);
|
||||
|
||||
void IMB_colormanagement_working_space_check(Main *bmain,
|
||||
const bool for_undo,
|
||||
const bool have_editable_assets);
|
||||
|
||||
void IMB_colormanagement_working_space_init(Main *bmain);
|
||||
void IMB_colormanagement_working_space_convert(
|
||||
Main *bmain,
|
||||
const blender::float3x3 ¤t_scene_linear_to_xyz,
|
||||
const blender::float3x3 &new_xyz_to_scene_linear,
|
||||
const bool depsgraph_tag = false,
|
||||
const bool linked_only = false,
|
||||
const bool editable_assets_only = false);
|
||||
void IMB_colormanagement_working_space_convert(Main *bmain, const Main *reference_bmain);
|
||||
|
||||
int IMB_colormanagement_working_space_get_named_index(const char *name);
|
||||
const char *IMB_colormanagement_working_space_get_indexed_name(int index);
|
||||
void IMB_colormanagement_working_space_items_add(EnumPropertyItem **items, int *totitem);
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name RNA Helper Functions
|
||||
* \{ */
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "DNA_ID.h"
|
||||
#include "DNA_color_types.h"
|
||||
#include "DNA_image_types.h"
|
||||
#include "DNA_movieclip_types.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_colorspace.hh"
|
||||
#include "BLI_fileops.hh"
|
||||
#include "BLI_listbase.h"
|
||||
@@ -49,8 +51,11 @@
|
||||
#include "BKE_colortools.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_idtype.hh"
|
||||
#include "BKE_image_format.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_node.hh"
|
||||
#include "BKE_node_legacy_types.hh"
|
||||
|
||||
#include "GPU_capabilities.hh"
|
||||
@@ -59,6 +64,8 @@
|
||||
|
||||
#include "SEQ_iterator.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include "OCIO_api.hh"
|
||||
@@ -92,6 +99,12 @@ static char global_role_default_float[MAX_COLORSPACE_NAME];
|
||||
static char global_role_default_sequencer[MAX_COLORSPACE_NAME];
|
||||
static char global_role_aces_interchange[MAX_COLORSPACE_NAME];
|
||||
|
||||
/* Defaults from the config that never change with working space. */
|
||||
static char global_role_scene_linear_default[MAX_COLORSPACE_NAME];
|
||||
static char global_role_default_float_default[MAX_COLORSPACE_NAME];
|
||||
|
||||
float3x3 global_scene_linear_to_xyz_default = float3x3::zero();
|
||||
|
||||
/* lock used by pre-cached processors getters, so processor wouldn't
|
||||
* be created several times
|
||||
* LOCK_COLORMANAGE can not be used since this mutex could be needed to
|
||||
@@ -587,6 +600,11 @@ static bool colormanage_load_config(ocio::Config &config)
|
||||
|
||||
colormanage_update_matrices();
|
||||
|
||||
/* Defaults that don't change with file working space. */
|
||||
STRNCPY(global_role_scene_linear_default, global_role_scene_linear);
|
||||
STRNCPY(global_role_default_float_default, global_role_default_float);
|
||||
global_scene_linear_to_xyz_default = blender::colorspace::scene_linear_to_xyz;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -3069,6 +3087,391 @@ const char *IMB_colormanagement_look_validate_for_view(const char *view_name,
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Working Space Functions
|
||||
* \{ */
|
||||
|
||||
/* Should have enough bits of precision, and this can be reasonably high assuming
|
||||
* that if colorspaces are really this close, no point converting anyway. */
|
||||
static const float imb_working_space_compare_threshold = 0.001f;
|
||||
|
||||
const char *IMB_colormanagement_working_space_get_default()
|
||||
{
|
||||
return global_role_scene_linear_default;
|
||||
}
|
||||
|
||||
int IMB_colormanagement_working_space_get_named_index(const char *name)
|
||||
{
|
||||
return IMB_colormanagement_colorspace_get_named_index(name);
|
||||
}
|
||||
|
||||
const char *IMB_colormanagement_working_space_get_indexed_name(int index)
|
||||
{
|
||||
return IMB_colormanagement_colorspace_get_indexed_name(index);
|
||||
}
|
||||
|
||||
void IMB_colormanagement_working_space_items_add(EnumPropertyItem **items, int *totitem)
|
||||
{
|
||||
const ColorSpace *scene_linear = g_config->get_color_space(OCIO_ROLE_SCENE_LINEAR);
|
||||
|
||||
blender::Vector<const ColorSpace *> working_spaces = {
|
||||
IMB_colormanagement_space_from_interop_id("lin_rec709_scene"),
|
||||
IMB_colormanagement_space_from_interop_id("lin_rec2020_scene"),
|
||||
IMB_colormanagement_space_from_interop_id("lin_ap1_scene")};
|
||||
|
||||
if (!working_spaces.contains(scene_linear)) {
|
||||
working_spaces.prepend(scene_linear);
|
||||
}
|
||||
|
||||
for (const ColorSpace *colorspace : working_spaces) {
|
||||
if (colorspace == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
EnumPropertyItem item;
|
||||
|
||||
item.value = colorspace->index;
|
||||
item.name = colorspace->name().c_str();
|
||||
item.identifier = colorspace->name().c_str();
|
||||
item.icon = 0;
|
||||
item.description = colorspace->description().c_str();
|
||||
|
||||
RNA_enum_item_add(items, totitem, &item);
|
||||
}
|
||||
}
|
||||
|
||||
const char *IMB_colormanagement_working_space_get()
|
||||
{
|
||||
return global_role_scene_linear;
|
||||
}
|
||||
|
||||
bool IMB_colormanagement_working_space_set_from_name(const char *name)
|
||||
{
|
||||
if (STREQ(global_role_scene_linear, name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CLOG_DEBUG(&LOG, "Setting blend file working color space to '%s'", name);
|
||||
|
||||
/* Change default float along with working space for convenience, if it was the same. */
|
||||
if (STREQ(global_role_default_float_default, global_role_scene_linear_default)) {
|
||||
STRNCPY(global_role_default_float, name);
|
||||
}
|
||||
else {
|
||||
STRNCPY(global_role_default_float, global_role_default_float_default);
|
||||
}
|
||||
|
||||
STRNCPY(global_role_scene_linear, name);
|
||||
g_config->set_scene_linear_role(name);
|
||||
|
||||
colormanage_update_matrices();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IMB_colormanagement_working_space_set_from_matrix(
|
||||
const char *name, const blender::float3x3 &scene_linear_to_xyz)
|
||||
{
|
||||
StringRefNull interop_id;
|
||||
|
||||
/* Check if we match the working space defined by the config. */
|
||||
if (blender::math::is_equal(scene_linear_to_xyz,
|
||||
global_scene_linear_to_xyz_default,
|
||||
imb_working_space_compare_threshold))
|
||||
{
|
||||
return IMB_colormanagement_working_space_set_from_name(global_role_scene_linear_default);
|
||||
}
|
||||
|
||||
/* Check if we match a common known working space, that hopefully exists in the config. */
|
||||
if (blender::math::is_equal(
|
||||
scene_linear_to_xyz, ocio::ACES_TO_XYZ, imb_working_space_compare_threshold))
|
||||
{
|
||||
interop_id = "lin_ap0_scene";
|
||||
}
|
||||
else if (blender::math::is_equal(
|
||||
scene_linear_to_xyz, ocio::ACESCG_TO_XYZ, imb_working_space_compare_threshold))
|
||||
{
|
||||
interop_id = "lin_ap1_scene";
|
||||
}
|
||||
else if (blender::math::is_equal(scene_linear_to_xyz,
|
||||
blender::math::invert(ocio::XYZ_TO_REC709),
|
||||
imb_working_space_compare_threshold))
|
||||
{
|
||||
interop_id = "lin_rec709_scene";
|
||||
}
|
||||
else if (blender::math::is_equal(scene_linear_to_xyz,
|
||||
blender::math::invert(ocio::XYZ_TO_REC2020),
|
||||
imb_working_space_compare_threshold))
|
||||
{
|
||||
interop_id = "lin_rec2020_scene";
|
||||
}
|
||||
|
||||
const ColorSpace *colorspace = g_config->get_color_space_by_interop_id(interop_id);
|
||||
if (colorspace) {
|
||||
return IMB_colormanagement_working_space_set_from_name(colorspace->name().c_str());
|
||||
}
|
||||
|
||||
CLOG_ERROR(
|
||||
&LOG, "Unknown scene linear working space '%s'. Missing OpenColorIO configuration?", name);
|
||||
return IMB_colormanagement_working_space_set_from_name(global_role_scene_linear_default);
|
||||
}
|
||||
|
||||
void IMB_colormanagement_working_space_check(Main *bmain,
|
||||
const bool for_undo,
|
||||
const bool have_editable_assets)
|
||||
{
|
||||
/* For old files without info, assume current OpenColorIO config. */
|
||||
if (blender::math::is_zero(bmain->colorspace.scene_linear_to_xyz)) {
|
||||
STRNCPY(bmain->colorspace.scene_linear_name, global_role_scene_linear_default);
|
||||
bmain->colorspace.scene_linear_to_xyz = global_scene_linear_to_xyz_default;
|
||||
CLOG_DEBUG(&LOG,
|
||||
"Blend file has unknown scene linear working color space, setting to default");
|
||||
}
|
||||
|
||||
const blender::float3x3 current_scene_linear_to_xyz = blender::colorspace::scene_linear_to_xyz;
|
||||
|
||||
/* Change the working space to the one from the blend file. */
|
||||
const bool working_space_changed = IMB_colormanagement_working_space_set_from_matrix(
|
||||
bmain->colorspace.scene_linear_name, bmain->colorspace.scene_linear_to_xyz);
|
||||
if (!working_space_changed) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* For undo, we need to convert the linked datablocks as they were left unchanged by undo.
|
||||
* For file load, we need to convert editable assets that came from the previous main. */
|
||||
if (!(for_undo || have_editable_assets)) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMB_colormanagement_working_space_convert(
|
||||
bmain,
|
||||
current_scene_linear_to_xyz,
|
||||
blender::math::invert(bmain->colorspace.scene_linear_to_xyz),
|
||||
for_undo,
|
||||
for_undo,
|
||||
!for_undo && have_editable_assets);
|
||||
}
|
||||
|
||||
static blender::float3 imb_working_space_convert(const blender::float3x3 &m,
|
||||
const bool is_smaller_gamut,
|
||||
const blender::float3 in_rgb)
|
||||
{
|
||||
blender::float3 rgb = m * in_rgb;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
/* Round to nicer fractions. */
|
||||
rgb[i] = 1e-5f * roundf(rgb[i] * 1e5f);
|
||||
/* Snap to 0 and 1. */
|
||||
if (fabsf(rgb[i]) < 5e-5) {
|
||||
rgb[i] = 0.0f;
|
||||
}
|
||||
else if (fabsf(1.0f - rgb[i]) < 5e-5) {
|
||||
rgb[i] = 1.0f;
|
||||
}
|
||||
/* Clamp when goig to smaller gamut. We can't really distinguish
|
||||
* between HDR and out of gamut colors. */
|
||||
if (is_smaller_gamut) {
|
||||
rgb[i] = blender::math::clamp(rgb[i], 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
return rgb;
|
||||
}
|
||||
|
||||
static blender::ColorGeometry4f imb_working_space_convert(const blender::float3x3 &m,
|
||||
const bool is_smaller_gamut,
|
||||
const blender::ColorGeometry4f color)
|
||||
{
|
||||
using namespace blender;
|
||||
const float3 in_rgb = float3(color::unpremultiply_alpha(color));
|
||||
const float3 rgb = imb_working_space_convert(m, is_smaller_gamut, in_rgb);
|
||||
return color::premultiply_alpha(ColorGeometry4f(rgb[0], rgb[1], rgb[2], color[3]));
|
||||
}
|
||||
|
||||
void IMB_colormanagement_working_space_convert(
|
||||
Main *bmain,
|
||||
const blender::float3x3 ¤t_scene_linear_to_xyz,
|
||||
const blender::float3x3 &new_xyz_to_scene_linear,
|
||||
const bool depsgraph_tag,
|
||||
const bool linked_only,
|
||||
const bool editable_assets_only)
|
||||
{
|
||||
using namespace blender;
|
||||
/* If unknown, assume it's the OpenColorIO config scene linear space. */
|
||||
float3x3 bmain_scene_linear_to_xyz = (math::is_zero(current_scene_linear_to_xyz)) ?
|
||||
global_scene_linear_to_xyz_default :
|
||||
current_scene_linear_to_xyz;
|
||||
|
||||
float3x3 M = new_xyz_to_scene_linear * bmain_scene_linear_to_xyz;
|
||||
|
||||
/* Already in the same space? */
|
||||
if (math::is_equal(M, float3x3::identity(), imb_working_space_compare_threshold)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (math::determinant(M) == 0.0f) {
|
||||
CLOG_ERROR(&LOG, "Working space conversion matrix is not invertible");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Determine if we are going to a smaller gamut and need to clamp. We prefer not to,
|
||||
* to preserve HDR colors, although they should not be common in properties. */
|
||||
bool is_smaller_gamut = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
if (M[i][j] < 0.0f) {
|
||||
is_smaller_gamut = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Single color. */
|
||||
const auto single = [&M, is_smaller_gamut](float rgb[3]) {
|
||||
copy_v3_v3(rgb, imb_working_space_convert(M, is_smaller_gamut, float3(rgb)));
|
||||
};
|
||||
|
||||
/* Array with implicit sharing.
|
||||
*
|
||||
* We store references to all color arrays, so we can efficiently preserve implicit
|
||||
* sharing and write in place when possible. */
|
||||
struct ColorArrayInfo {
|
||||
Vector<ColorGeometry4f **> data_ptrs;
|
||||
Vector<ImplicitSharingPtr<> *> sharing_info_ptrs;
|
||||
/* Though it is unlikely, the same data array could be used among multiple geometries with
|
||||
* different domain sizes, so keep track of the maximum size among all users. */
|
||||
size_t max_size;
|
||||
};
|
||||
Map<const ImplicitSharingInfo *, ColorArrayInfo> color_array_map;
|
||||
|
||||
const auto implicit_sharing_array =
|
||||
[&](ImplicitSharingPtr<> &sharing_info, ColorGeometry4f *&data, size_t size) {
|
||||
/* No data? */
|
||||
if (!sharing_info) {
|
||||
BLI_assert(size == 0);
|
||||
return;
|
||||
}
|
||||
color_array_map.add_or_modify(
|
||||
sharing_info.get(),
|
||||
[&](ColorArrayInfo *value) {
|
||||
new (value) ColorArrayInfo();
|
||||
value->data_ptrs.append(&data);
|
||||
value->sharing_info_ptrs.append(&sharing_info);
|
||||
value->max_size = size;
|
||||
},
|
||||
[&](ColorArrayInfo *value) {
|
||||
BLI_assert(data == *value->data_ptrs.last());
|
||||
value->data_ptrs.append(&data);
|
||||
value->sharing_info_ptrs.append(&sharing_info);
|
||||
value->max_size = std::max(value->max_size, size);
|
||||
});
|
||||
};
|
||||
|
||||
IDTypeForeachColorFunctionCallback fn = {single, implicit_sharing_array};
|
||||
|
||||
/* Iterate over IDs and embedded IDs. No need to do it for master collections
|
||||
* though, they don't have colors. */
|
||||
/* TODO: Multithreading over IDs? */
|
||||
ID *id_iter;
|
||||
FOREACH_MAIN_ID_BEGIN (bmain, id_iter) {
|
||||
if (linked_only) {
|
||||
if (!id_iter->lib) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (editable_assets_only) {
|
||||
if (!(id_iter->lib && (id_iter->lib->runtime->tag & LIBRARY_ASSET_EDITABLE))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(id_iter);
|
||||
if (id_type->foreach_working_space_color) {
|
||||
id_type->foreach_working_space_color(id_iter, fn);
|
||||
if (depsgraph_tag) {
|
||||
DEG_id_tag_update(id_iter, ID_RECALC_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
if (bNodeTree *node_tree = bke::node_tree_from_id(id_iter)) {
|
||||
const IDTypeInfo *id_type = BKE_idtype_get_info_from_id(&node_tree->id);
|
||||
if (id_type->foreach_working_space_color) {
|
||||
id_type->foreach_working_space_color(&node_tree->id, fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
FOREACH_MAIN_ID_END;
|
||||
|
||||
/* Handle implicit sharing arrays. */
|
||||
Vector<Map<const ImplicitSharingInfo *, ColorArrayInfo>::Item> color_array_items(
|
||||
color_array_map.items().begin(), color_array_map.items().end());
|
||||
|
||||
threading::parallel_for(color_array_items.index_range(), 64, [&](const IndexRange range) {
|
||||
for (const int item_index : range) {
|
||||
const auto &item = color_array_items[item_index];
|
||||
|
||||
if (item.value.data_ptrs.size() == item.key->strong_users()) {
|
||||
/* All of the users of the array data are from the main we're converting, so we can change
|
||||
* the data array in place without allocating a new version. */
|
||||
item.key->tag_ensured_mutable();
|
||||
MutableSpan<ColorGeometry4f> data(*item.value.data_ptrs.first(), item.value.max_size);
|
||||
threading::parallel_for(data.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
data[i] = imb_working_space_convert(M, is_smaller_gamut, data[i]);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
/* Somehow the data is used by something outside of the Main we're currently converting, it
|
||||
* has to be duplicated before being converted to avoid changing the original. */
|
||||
const Span<ColorGeometry4f> src_data(*item.value.data_ptrs.first(), item.value.max_size);
|
||||
|
||||
auto *dst_data = MEM_malloc_arrayN<ColorGeometry4f>(
|
||||
src_data.size(), "IMB_colormanagement_working_space_convert");
|
||||
const ImplicitSharingPtr<> sharing_ptr(implicit_sharing::info_for_mem_free(dst_data));
|
||||
|
||||
threading::parallel_for(src_data.index_range(), 1024, [&](const IndexRange range) {
|
||||
for (const int64_t i : range) {
|
||||
dst_data[i] = imb_working_space_convert(M, is_smaller_gamut, src_data[i]);
|
||||
}
|
||||
});
|
||||
|
||||
/* Replace the data pointer and the sharing info pointer with the new data in all of the
|
||||
* users from the main data-base. The sharing pointer assignment adds a user. */
|
||||
for (ColorGeometry4f **pointer : item.value.data_ptrs) {
|
||||
*pointer = dst_data;
|
||||
}
|
||||
for (ImplicitSharingPtr<> *pointer : item.value.sharing_info_ptrs) {
|
||||
*pointer = sharing_ptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void IMB_colormanagement_working_space_convert(Main *bmain, const Main *reference_bmain)
|
||||
{
|
||||
/* If unknown, assume it's the OpenColorIO config scene linear space. */
|
||||
float3x3 reference_scene_linear_to_xyz = blender::math::is_zero(
|
||||
reference_bmain->colorspace.scene_linear_to_xyz) ?
|
||||
global_scene_linear_to_xyz_default :
|
||||
reference_bmain->colorspace.scene_linear_to_xyz;
|
||||
|
||||
IMB_colormanagement_working_space_convert(bmain,
|
||||
bmain->colorspace.scene_linear_to_xyz,
|
||||
blender::math::invert(reference_scene_linear_to_xyz),
|
||||
false);
|
||||
|
||||
STRNCPY(bmain->colorspace.scene_linear_name, reference_bmain->colorspace.scene_linear_name);
|
||||
bmain->colorspace.scene_linear_to_xyz = reference_bmain->colorspace.scene_linear_to_xyz;
|
||||
}
|
||||
|
||||
void IMB_colormanagement_working_space_init(Main *bmain)
|
||||
{
|
||||
STRNCPY(bmain->colorspace.scene_linear_name, global_role_scene_linear_default);
|
||||
bmain->colorspace.scene_linear_to_xyz = global_scene_linear_to_xyz_default;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name RNA Helper Functions
|
||||
* \{ */
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "BLI_math_matrix_types.hh"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_string_ref.hh"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
|
||||
namespace blender::ocio {
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "libocio_colorspace.hh"
|
||||
#include "OCIO_cpu_processor.hh"
|
||||
#include "intern/cpu_processor_cache.hh"
|
||||
|
||||
#if defined(WITH_OPENCOLORIO)
|
||||
|
||||
|
||||
@@ -34,6 +34,12 @@ typedef struct FileGlobal {
|
||||
char build_hash[16];
|
||||
/** File path where this was saved, for recover. */
|
||||
char filepath[/*FILE_MAX*/ 1024];
|
||||
|
||||
/* Working colorspace, for automatic conversion. Note the matrix is
|
||||
* the source of truth, the name is only for user interface and diagnosis. */
|
||||
char colorspace_scene_linear_name[/*MAX_COLORSPACE_NAME*/ 64];
|
||||
float colorspace_scene_linear_to_xyz[3][3];
|
||||
int _pad2[3];
|
||||
} FileGlobal;
|
||||
|
||||
/* minversion: in file, the oldest past blender version you can use compliant */
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
#include "BLI_path_utils.hh"
|
||||
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "rna_internal.hh"
|
||||
|
||||
#ifdef RNA_RUNTIME
|
||||
|
||||
# include "IMB_colormanagement.hh"
|
||||
|
||||
# include "DNA_windowmanager_types.h"
|
||||
|
||||
# include "BKE_global.hh"
|
||||
@@ -88,6 +91,28 @@ static PointerRNA rna_Main_colorspace_get(PointerRNA *ptr)
|
||||
return PointerRNA(nullptr, &RNA_BlendFileColorspace, &bmain->colorspace);
|
||||
}
|
||||
|
||||
static int rna_MainColorspace_working_space_get(PointerRNA *ptr)
|
||||
{
|
||||
MainColorspace *colorspace = ptr->data_as<MainColorspace>();
|
||||
return IMB_colormanagement_working_space_get_named_index(colorspace->scene_linear_name);
|
||||
}
|
||||
|
||||
static const EnumPropertyItem *rna_MainColorspace_working_space_itemf(bContext * /*C*/,
|
||||
PointerRNA * /*ptr*/,
|
||||
PropertyRNA * /*prop*/,
|
||||
bool *r_free)
|
||||
{
|
||||
EnumPropertyItem *items = nullptr;
|
||||
int totitem = 0;
|
||||
|
||||
IMB_colormanagement_working_space_items_add(&items, &totitem);
|
||||
RNA_enum_item_end(&items, &totitem);
|
||||
|
||||
*r_free = true;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
static bool rna_MainColorspace_is_missing_opencolorio_config_get(PointerRNA *ptr)
|
||||
{
|
||||
MainColorspace *colorspace = ptr->data_as<MainColorspace>();
|
||||
@@ -185,7 +210,20 @@ static void rna_def_main_colorspace(BlenderRNA *brna)
|
||||
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");
|
||||
"Information about the color space used for data-blocks in a blend file");
|
||||
|
||||
prop = RNA_def_property(srna, "working_space", PROP_ENUM, PROP_NONE);
|
||||
RNA_def_property_flag(prop, PROP_ENUM_NO_CONTEXT);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
RNA_def_property_enum_items(prop, rna_enum_dummy_NULL_items);
|
||||
RNA_def_property_ui_text(prop,
|
||||
"Working Space",
|
||||
"Color space used for all scene linear colors in this file, and "
|
||||
"for compositing, shader and geometry nodes processing");
|
||||
RNA_def_property_enum_funcs(prop,
|
||||
"rna_MainColorspace_working_space_get",
|
||||
nullptr,
|
||||
"rna_MainColorspace_working_space_itemf");
|
||||
|
||||
prop = RNA_def_property(srna, "is_missing_opencolorio_config", PROP_BOOLEAN, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
@@ -511,7 +549,7 @@ void RNA_def_main(BlenderRNA *brna)
|
||||
RNA_def_property_ui_text(
|
||||
prop,
|
||||
"Color Space",
|
||||
"Information about the color space used for datablocks in a blend file");
|
||||
"Information about the color space used for data-blocks in a blend file");
|
||||
|
||||
RNA_api_main(srna);
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ set(SRC
|
||||
intern/wm_event_system.cc
|
||||
intern/wm_files.cc
|
||||
intern/wm_files_link.cc
|
||||
intern/wm_files_colorspace.cc
|
||||
intern/wm_gesture.cc
|
||||
intern/wm_gesture_ops.cc
|
||||
intern/wm_init_exit.cc
|
||||
|
||||
206
source/blender/windowmanager/intern/wm_files_colorspace.cc
Normal file
206
source/blender/windowmanager/intern/wm_files_colorspace.cc
Normal file
@@ -0,0 +1,206 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup wm
|
||||
*
|
||||
* Functions for handling file colorspaces.
|
||||
*/
|
||||
|
||||
#include "BLI_colorspace.hh"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_image.hh"
|
||||
#include "BKE_main.hh"
|
||||
#include "BKE_movieclip.h"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "DNA_windowmanager_enums.h"
|
||||
#include "DNA_windowmanager_types.h"
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
#include "RNA_enum_types.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
|
||||
#include "UI_interface_c.hh"
|
||||
#include "UI_interface_icons.hh"
|
||||
#include "UI_interface_layout.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "ED_image.hh"
|
||||
#include "ED_render.hh"
|
||||
|
||||
#include "RE_pipeline.h"
|
||||
|
||||
#include "SEQ_prefetch.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "wm_files.hh"
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Set Working Color Space Operator
|
||||
* \{ */
|
||||
|
||||
static const EnumPropertyItem *working_space_itemf(bContext * /*C*/,
|
||||
PointerRNA * /*ptr*/,
|
||||
PropertyRNA * /*prop*/,
|
||||
bool *r_free)
|
||||
{
|
||||
EnumPropertyItem *item = nullptr;
|
||||
int totitem = 0;
|
||||
IMB_colormanagement_working_space_items_add(&item, &totitem);
|
||||
RNA_enum_item_end(&item, &totitem);
|
||||
*r_free = true;
|
||||
return item;
|
||||
}
|
||||
|
||||
static bool wm_set_working_space_check_safe(bContext *C, wmOperator *op)
|
||||
{
|
||||
const wmWindowManager *wm = CTX_wm_manager(C);
|
||||
const Main *bmain = CTX_data_main(C);
|
||||
const Scene *scene = CTX_data_scene(C);
|
||||
|
||||
if (WM_jobs_test(wm, scene, WM_JOB_TYPE_ANY)) {
|
||||
BKE_report(
|
||||
op->reports, RPT_WARNING, RPT_("Can't change working space while josb are running"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ED_image_should_save_modified(bmain)) {
|
||||
BKE_report(op->reports,
|
||||
RPT_WARNING,
|
||||
RPT_("Can't change working space with modified images, save them first"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static wmOperatorStatus wm_set_working_color_space_exec(bContext *C, wmOperator *op)
|
||||
{
|
||||
Main *bmain = CTX_data_main(C);
|
||||
const bool convert_colors = RNA_boolean_get(op->ptr, "convert_colors");
|
||||
const int working_space_index = RNA_enum_get(op->ptr, "working_space");
|
||||
const char *working_space = IMB_colormanagement_working_space_get_indexed_name(
|
||||
working_space_index);
|
||||
|
||||
if (!wm_set_working_space_check_safe(C, op)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (working_space[0] == '\0' || STREQ(working_space, bmain->colorspace.scene_linear_name)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
/* Stop all viewport renders. */
|
||||
ED_render_engine_changed(bmain, true);
|
||||
RE_FreeAllPersistentData();
|
||||
|
||||
/* Change working space. */
|
||||
IMB_colormanagement_working_space_set_from_name(working_space);
|
||||
|
||||
if (convert_colors) {
|
||||
const bool depsgraph_tag = true;
|
||||
IMB_colormanagement_working_space_convert(bmain,
|
||||
bmain->colorspace.scene_linear_to_xyz,
|
||||
blender::colorspace::xyz_to_scene_linear,
|
||||
depsgraph_tag);
|
||||
}
|
||||
|
||||
STRNCPY(bmain->colorspace.scene_linear_name, working_space);
|
||||
bmain->colorspace.scene_linear_to_xyz = blender::colorspace::scene_linear_to_xyz;
|
||||
|
||||
/* Free all render, compositor and sequencer caches. */
|
||||
RE_FreeAllRenderResults();
|
||||
RE_FreeInteractiveCompositorRenders();
|
||||
blender::seq::prefetch_stop_all();
|
||||
LISTBASE_FOREACH (Scene *, scene, &bmain->scenes) {
|
||||
blender::seq::cache_cleanup(scene);
|
||||
}
|
||||
|
||||
/* Free all images, they may have scene linear float buffers. */
|
||||
LISTBASE_FOREACH (Image *, image, &bmain->images) {
|
||||
DEG_id_tag_update(&image->id, ID_RECALC_SOURCE);
|
||||
BKE_image_signal(bmain, image, nullptr, IMA_SIGNAL_COLORMANAGE);
|
||||
BKE_image_partial_update_mark_full_update(image);
|
||||
}
|
||||
LISTBASE_FOREACH (MovieClip *, clip, &bmain->movieclips) {
|
||||
BKE_movieclip_clear_cache(clip);
|
||||
BKE_movieclip_free_gputexture(clip);
|
||||
DEG_id_tag_update(&clip->id, ID_RECALC_SOURCE);
|
||||
}
|
||||
|
||||
/* Redraw everything. */
|
||||
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, nullptr);
|
||||
WM_main_add_notifier(NC_SCENE | ND_RENDER_OPTIONS, nullptr);
|
||||
WM_main_add_notifier(NC_SCENE | ND_NODES, nullptr);
|
||||
WM_main_add_notifier(NC_WINDOW, nullptr);
|
||||
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
static wmOperatorStatus wm_set_working_color_space_invoke(bContext *C,
|
||||
wmOperator *op,
|
||||
const wmEvent *event)
|
||||
{
|
||||
if (!wm_set_working_space_check_safe(C, op)) {
|
||||
return OPERATOR_CANCELLED;
|
||||
}
|
||||
|
||||
if (RNA_enum_get(op->ptr, "working_space") == -1) {
|
||||
RNA_enum_set(op->ptr,
|
||||
"working_space",
|
||||
IMB_colormanagement_working_space_get_named_index(
|
||||
IMB_colormanagement_working_space_get_default()));
|
||||
}
|
||||
|
||||
return WM_operator_props_popup_confirm_ex(
|
||||
C,
|
||||
op,
|
||||
event,
|
||||
std::nullopt,
|
||||
IFACE_("Apply"),
|
||||
false,
|
||||
IFACE_("To match renders with the previous working space as closely as possible,\n"
|
||||
"colors in all materials, lights and geometry must be converted.\n\n"
|
||||
"Some nodes graphs cannot be converted accurately and may need manual fix-ups."));
|
||||
}
|
||||
|
||||
void WM_OT_set_working_color_space(wmOperatorType *ot)
|
||||
{
|
||||
ot->name = "Set Blend File Working Color Space";
|
||||
ot->idname = "WM_OT_set_working_color_space";
|
||||
ot->description = "Change the working color space of all colors in this blend file";
|
||||
|
||||
ot->exec = wm_set_working_color_space_exec;
|
||||
ot->invoke = wm_set_working_color_space_invoke;
|
||||
|
||||
ot->flag = OPTYPE_UNDO | OPTYPE_REGISTER;
|
||||
|
||||
RNA_def_boolean(ot->srna,
|
||||
"convert_colors",
|
||||
true,
|
||||
"Convert Colors in All Data-blocks",
|
||||
"Change colors in all data-blocks to the new working space");
|
||||
PropertyRNA *prop = RNA_def_enum(ot->srna,
|
||||
"working_space",
|
||||
rna_enum_dummy_NULL_items,
|
||||
-1,
|
||||
"Working Space",
|
||||
"Color space to set");
|
||||
RNA_def_enum_funcs(prop, working_space_itemf);
|
||||
|
||||
ot->prop = prop;
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -4242,6 +4242,7 @@ void wm_operatortypes_register()
|
||||
WM_operatortype_append(WM_OT_previews_ensure);
|
||||
WM_operatortype_append(WM_OT_previews_clear);
|
||||
WM_operatortype_append(WM_OT_doc_view_manual_ui_context);
|
||||
WM_operatortype_append(WM_OT_set_working_color_space);
|
||||
|
||||
#ifdef WITH_XR_OPENXR
|
||||
wm_xr_operatortypes_register();
|
||||
|
||||
@@ -131,3 +131,7 @@ void WM_OT_id_linked_relocate(wmOperatorType *ot);
|
||||
|
||||
void WM_OT_lib_relocate(wmOperatorType *ot);
|
||||
void WM_OT_lib_reload(wmOperatorType *ot);
|
||||
|
||||
/* `wm_files_colorspace.cc` */
|
||||
|
||||
void WM_OT_set_working_color_space(wmOperatorType *ot);
|
||||
|
||||
BIN
tests/files/render/colorspace/acescg_blackbody.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/acescg_blackbody.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/cycles_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/cycles_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/cycles_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/cycles_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/eevee_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/eevee_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/eevee_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/eevee_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/rec2020_lights.blend
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/rec2020_lights.blend
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/storm_hydra_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/storm_hydra_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/storm_hydra_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/storm_hydra_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/storm_usd_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/storm_usd_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/storm_usd_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/storm_usd_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/workbench_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/workbench_renders/acescg_blackbody.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
tests/files/render/colorspace/workbench_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
BIN
tests/files/render/colorspace/workbench_renders/rec2020_lights.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@@ -669,6 +669,7 @@ if((WITH_CYCLES OR WITH_GPU_RENDER_TESTS) AND TEST_SRC_DIR_EXISTS)
|
||||
set(render_tests
|
||||
attributes
|
||||
camera
|
||||
colorspace
|
||||
bsdf
|
||||
hair
|
||||
image_colorspace
|
||||
|
||||
@@ -280,6 +280,9 @@ def main():
|
||||
if (test_dir_name in {'volume', 'openvdb'}):
|
||||
report.set_fail_threshold(0.048)
|
||||
report.set_fail_percent(3)
|
||||
# OSL blackbody output is a little different.
|
||||
if (test_dir_name in {'colorspace'}):
|
||||
report.set_fail_threshold(0.05)
|
||||
|
||||
ok = report.run(args.testdir, args.blender, get_arguments, batch=args.batch)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user