diff --git a/source/blender/blenkernel/BKE_blender_version.h b/source/blender/blenkernel/BKE_blender_version.h index 978c1be896d..98af4cb4056 100644 --- a/source/blender/blenkernel/BKE_blender_version.h +++ b/source/blender/blenkernel/BKE_blender_version.h @@ -27,7 +27,7 @@ /* Blender file format version. */ #define BLENDER_FILE_VERSION BLENDER_VERSION -#define BLENDER_FILE_SUBVERSION 76 +#define BLENDER_FILE_SUBVERSION 77 /* Minimum Blender version that supports reading file written with the current * version. Older Blender versions will test this and cancel loading the file, showing a warning to diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index aecd2afd044..d53be8ed57e 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1049,6 +1049,80 @@ static void write_compositor_legacy_properties(bNodeTree &node_tree) storage->y1 = y_input->default_value_typed()->value + height_input->default_value_typed()->value; } + + if (node->type_legacy == CMP_NODE_COLORBALANCE) { + NodeColorBalance *storage = static_cast(node->storage); + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Lift"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Lift"); + const float3 value = base_input->default_value_typed()->value + + float3( + color_input->default_value_typed()->value); + copy_v3_v3(storage->lift, value); + } + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Gamma"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Gamma"); + const float3 value = base_input->default_value_typed()->value * + float3( + color_input->default_value_typed()->value); + copy_v3_v3(storage->gamma, value); + } + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Gain"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Gain"); + const float3 value = base_input->default_value_typed()->value * + float3( + color_input->default_value_typed()->value); + copy_v3_v3(storage->gain, value); + } + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Power"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Power"); + const float3 value = base_input->default_value_typed()->value * + float3( + color_input->default_value_typed()->value); + copy_v3_v3(storage->power, value); + } + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Slope"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Slope"); + const float3 value = base_input->default_value_typed()->value * + float3( + color_input->default_value_typed()->value); + copy_v3_v3(storage->slope, value); + } + + { + const bNodeSocket *base_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Base Offset"); + const bNodeSocket *color_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Color Offset"); + storage->offset_basis = base_input->default_value_typed()->value; + copy_v3_v3(storage->offset, + color_input->default_value_typed()->value); + } + + write_input_to_property_float("Input Temperature", storage->input_temperature); + write_input_to_property_float("Input Tint", storage->input_tint); + write_input_to_property_float("Output Temperature", storage->output_temperature); + write_input_to_property_float("Output Tint", storage->output_tint); + } } } diff --git a/source/blender/blenloader/intern/versioning_260.cc b/source/blender/blenloader/intern/versioning_260.cc index 990e35114af..96fa44fd672 100644 --- a/source/blender/blenloader/intern/versioning_260.cc +++ b/source/blender/blenloader/intern/versioning_260.cc @@ -999,6 +999,33 @@ static void do_versions_nodetree_customnodes(bNodeTree *ntree, int /*is_group*/) } } +/* Sync functions update formula parameters for other modes, such that the result is comparable. + * Note that the results are not exactly the same due to differences in color handling + * (sRGB conversion happens for LGG), + * but this keeps settings comparable. */ +static void color_balance_node_cdl_from_lgg(bNode *node) +{ + NodeColorBalance *n = (NodeColorBalance *)node->storage; + + for (int c = 0; c < 3; c++) { + n->slope[c] = (2.0f - n->lift[c]) * n->gain[c]; + n->offset[c] = (n->lift[c] - 1.0f) * n->gain[c]; + n->power[c] = (n->gamma[c] != 0.0f) ? 1.0f / n->gamma[c] : 1000000.0f; + } +} + +static void color_balance_node_lgg_from_cdl(bNode *node) +{ + NodeColorBalance *n = (NodeColorBalance *)node->storage; + + for (int c = 0; c < 3; c++) { + float d = n->slope[c] + n->offset[c]; + n->lift[c] = (d != 0.0f ? n->slope[c] + 2.0f * n->offset[c] / d : 0.0f); + n->gain[c] = d; + n->gamma[c] = (n->power[c] != 0.0f) ? 1.0f / n->power[c] : 1000000.0f; + } +} + static bool strip_colorbalance_update_cb(Strip *strip, void * /*user_data*/) { StripData *data = strip->data; @@ -2677,7 +2704,7 @@ void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain) NodeColorBalance *n = static_cast(node->storage); if (node->custom1 == 0) { /* LGG mode stays the same, just init CDL settings */ - ntreeCompositColorBalanceSyncFromLGG(ntree, node); + color_balance_node_cdl_from_lgg(node); } else if (node->custom1 == 1) { /* CDL previously used same variables as LGG, copy them over @@ -2686,7 +2713,7 @@ void blo_do_versions_260(FileData *fd, Library * /*lib*/, Main *bmain) copy_v3_v3(n->offset, n->lift); copy_v3_v3(n->power, n->gamma); copy_v3_v3(n->slope, n->gain); - ntreeCompositColorBalanceSyncFromCDL(ntree, node); + color_balance_node_lgg_from_cdl(node); } } } diff --git a/source/blender/blenloader/intern/versioning_450.cc b/source/blender/blenloader/intern/versioning_450.cc index 923356c0f49..7257dbb349a 100644 --- a/source/blender/blenloader/intern/versioning_450.cc +++ b/source/blender/blenloader/intern/versioning_450.cc @@ -3831,6 +3831,151 @@ static void do_version_crop_node_options_to_inputs_animation(bNodeTree *node_tre }); } +/* The options were converted into inputs. */ +static void do_version_color_balance_node_options_to_inputs(bNodeTree *node_tree, bNode *node) +{ + NodeColorBalance *storage = static_cast(node->storage); + if (!storage) { + return; + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Lift")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Lift", "Lift"); + copy_v3_v3(input->default_value_typed()->value, storage->lift); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Gamma")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Gamma", "Gamma"); + copy_v3_v3(input->default_value_typed()->value, storage->gamma); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Gain")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Gain", "Gain"); + copy_v3_v3(input->default_value_typed()->value, storage->gain); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Offset")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Offset", "Offset"); + copy_v3_v3(input->default_value_typed()->value, storage->offset); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Power")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Power", "Power"); + copy_v3_v3(input->default_value_typed()->value, storage->power); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Color Slope")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_RGBA, PROP_NONE, "Color Slope", "Slope"); + copy_v3_v3(input->default_value_typed()->value, storage->slope); + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Base Offset")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Base Offset", "Offset"); + input->default_value_typed()->value = storage->offset_basis; + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Input Temperature")) { + bNodeSocket *input = blender::bke::node_add_static_socket(*node_tree, + *node, + SOCK_IN, + SOCK_FLOAT, + PROP_COLOR_TEMPERATURE, + "Input Temperature", + "Temperature"); + input->default_value_typed()->value = storage->input_temperature; + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Input Tint")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Input Tint", "Tint"); + input->default_value_typed()->value = storage->input_tint; + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Output Temperature")) { + bNodeSocket *input = blender::bke::node_add_static_socket(*node_tree, + *node, + SOCK_IN, + SOCK_FLOAT, + PROP_COLOR_TEMPERATURE, + "Output Temperature", + "Temperature"); + input->default_value_typed()->value = storage->output_temperature; + } + + if (!blender::bke::node_find_socket(*node, SOCK_IN, "Output Tint")) { + bNodeSocket *input = blender::bke::node_add_static_socket( + *node_tree, *node, SOCK_IN, SOCK_FLOAT, PROP_NONE, "Output Tint", "Tint"); + input->default_value_typed()->value = storage->output_tint; + } +} + +/* The options were converted into inputs. */ +static void do_version_color_balance_node_options_to_inputs_animation(bNodeTree *node_tree, + bNode *node) +{ + /* Compute the RNA path of the node. */ + char escaped_node_name[sizeof(node->name) * 2 + 1]; + BLI_str_escape(escaped_node_name, node->name, sizeof(escaped_node_name)); + const std::string node_rna_path = fmt::format("nodes[\"{}\"]", escaped_node_name); + + BKE_fcurves_id_cb(&node_tree->id, [&](ID * /*id*/, FCurve *fcurve) { + /* The FCurve does not belong to the node since its RNA path doesn't start with the node's RNA + * path. */ + if (!blender::StringRef(fcurve->rna_path).startswith(node_rna_path)) { + return; + } + + /* Change the RNA path of the FCurve from the old properties to the new inputs, adjusting the + * values of the FCurves frames when needed. */ + char *old_rna_path = fcurve->rna_path; + if (BLI_str_endswith(fcurve->rna_path, "lift")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[3].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "gamma")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[5].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "gain")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[7].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "offset_basis")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[8].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "offset")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[9].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "power")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[11].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "slope")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[13].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "input_temperature")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[14].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "input_tint")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[15].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "output_temperature")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[16].default_value"); + } + else if (BLI_str_endswith(fcurve->rna_path, "output_tint")) { + fcurve->rna_path = BLI_sprintfN("%s.%s", node_rna_path.c_str(), "inputs[17].default_value"); + } + + /* The RNA path was changed, free the old path. */ + if (fcurve->rna_path != old_rna_path) { + MEM_freeN(old_rna_path); + } + }); +} + void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) { if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 8)) { @@ -4421,6 +4566,19 @@ void do_versions_after_linking_450(FileData * /*fd*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 77)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_COLORBALANCE) { + do_version_color_balance_node_options_to_inputs_animation(node_tree, node); + } + } + } + } + FOREACH_NODETREE_END; + } + /** * Always bump subversion in BKE_blender_version.h when adding versioning * code here, and wrap it inside a MAIN_VERSION_FILE_ATLEAST check. @@ -5566,6 +5724,19 @@ void blo_do_versions_450(FileData * /*fd*/, Library * /*lib*/, Main *bmain) } } + if (!MAIN_VERSION_FILE_ATLEAST(bmain, 405, 77)) { + FOREACH_NODETREE_BEGIN (bmain, node_tree, id) { + if (node_tree->type == NTREE_COMPOSIT) { + LISTBASE_FOREACH (bNode *, node, &node_tree->nodes) { + if (node->type_legacy == CMP_NODE_COLORBALANCE) { + do_version_color_balance_node_options_to_inputs(node_tree, node); + } + } + } + } + FOREACH_NODETREE_END; + } + /* Always run this versioning (keep at the bottom of the function). Meshes are written with the * legacy format which always needs to be converted to the new format on file load. To be moved * to a subversion check in 5.0. */ diff --git a/source/blender/compositor/shaders/library/gpu_shader_compositor_color_balance.glsl b/source/blender/compositor/shaders/library/gpu_shader_compositor_color_balance.glsl index bf97c9b5cec..cb3d444b46c 100644 --- a/source/blender/compositor/shaders/library/gpu_shader_compositor_color_balance.glsl +++ b/source/blender/compositor/shaders/library/gpu_shader_compositor_color_balance.glsl @@ -3,35 +3,291 @@ * SPDX-License-Identifier: GPL-2.0-or-later */ #include "gpu_shader_common_color_utils.glsl" +#include "gpu_shader_math_matrix_lib.glsl" -void node_composite_color_balance_lgg( - float factor, float4 color, float3 lift, float3 gamma, float3 gain, out float4 result) +void node_composite_color_balance_lgg(float factor, + float4 color, + float base_lift, + float4 color_lift, + float base_gamma, + float4 color_gamma, + float base_gain, + float4 color_gain, + float base_offset, + float4 color_offset, + float base_power, + float4 color_power, + float base_slope, + float4 color_slope, + float input_temperature, + float input_tint, + float output_temperature, + float output_tint, + out float4 result) { - float3 inverse_lift = 2.0f - lift; - float3 srgb_color = linear_rgb_to_srgb(color.rgb); - float3 lift_balanced = ((srgb_color - 1.0f) * inverse_lift) + 1.0f; + const float3 srgb_color = linear_rgb_to_srgb(color.rgb); + const float3 lift = base_lift + color_lift.xyz(); + const float3 lift_balanced = ((srgb_color - 1.0f) * (2.0f - lift)) + 1.0f; + + const float3 gain = base_gain * color_gain.xyz(); float3 gain_balanced = lift_balanced * gain; gain_balanced = max(gain_balanced, float3(0.0f)); float3 linear_color = srgb_to_linear_rgb(gain_balanced); - float3 gamma_balanced = pow(linear_color, 1.0f / gamma); - result = float4(mix(color.rgb, gamma_balanced, min(factor, 1.0f)), color.a); + const float3 gamma = base_gamma * color_gamma.xyz(); + float3 gamma_balanced = pow(linear_color, 1.0f / max(gamma, float3(1e-6))); + + result = float4(mix(color.xyz(), gamma_balanced, min(factor, 1.0f)), color.w); } -void node_composite_color_balance_asc_cdl( - float factor, float4 color, float3 offset, float3 power, float3 slope, out float4 result) +void node_composite_color_balance_asc_cdl(float factor, + float4 color, + float base_lift, + float4 color_lift, + float base_gamma, + float4 color_gamma, + float base_gain, + float4 color_gain, + float base_offset, + float4 color_offset, + float base_power, + float4 color_power, + float base_slope, + float4 color_slope, + float input_temperature, + float input_tint, + float output_temperature, + float output_tint, + out float4 result) { - float3 balanced = color.rgb * slope + offset; - balanced = pow(max(balanced, float3(0.0f)), power); - result = float4(mix(color.rgb, balanced, min(factor, 1.0f)), color.a); + const float3 slope = base_slope * color_slope.xyz(); + const float3 slope_balanced = color.xyz() * slope; + + const float3 offset = base_offset + color_offset.xyz(); + const float3 offset_balanced = slope_balanced + offset; + + const float3 power = base_power * color_power.xyz(); + const float3 power_balanced = pow(max(offset_balanced, float3(0.0f)), power); + + result = float4(mix(color.xyz(), power_balanced, min(factor, 1.0f)), color.w); } -void node_composite_color_balance_whitepoint(float factor, - float4 color, - float4x4 matrix, - out float4 result) +void node_composite_color_balance_white_point_constant(float factor, + float4 color, + float base_lift, + float4 color_lift, + float base_gamma, + float4 color_gamma, + float base_gain, + float4 color_gain, + float base_offset, + float4 color_offset, + float base_power, + float4 color_power, + float base_slope, + float4 color_slope, + float input_temperature, + float input_tint, + float output_temperature, + float output_tint, + float4x4 white_point_matrix, + out float4 result) { - result = mix(color, matrix * color, min(factor, 1.0f)); + const float3 balanced = to_float3x3(white_point_matrix) * color.xyz; + result = float4(mix(color.xyz, balanced, min(factor, 1.0f)), color.w); +} + +float3 whitepoint_from_temp_tint(const float temperature, const float tint) +{ + /* Tabulated approximation of the Planckian locus. Based on: + * + * http://www.brucelindbloom.com/Eqn_XYZ_to_T.html. + * + * Original source: + * + * "Color Science: Concepts and Methods, Quantitative Data and Formulae", Second Edition, + * Gunter Wyszecki and W. S. Stiles, John Wiley & Sons, 1982, pp. 227, 228. + * + * Note that the inverse temperature table is multiplied by 10^6 compared to the reference + * table. */ + constexpr float inverse_temperatures[31] = float_array(0.0f, + 10.0f, + 20.0f, + 30.0f, + 40.0f, + 50.0f, + 60.0f, + 70.0f, + 80.0f, + 90.0f, + 100.0f, + 125.0f, + 150.0f, + 175.0f, + 200.0f, + 225.0f, + 250.0f, + 275.0f, + 300.0f, + 325.0f, + 350.0f, + 375.0f, + 400.0f, + 425.0f, + 450.0f, + 475.0f, + 500.0f, + 525.0f, + 550.0f, + 575.0f, + 600.0f); + + constexpr float2 uv_coordinates[31] = float2_array(float2(0.18006f, 0.26352f), + float2(0.18066f, 0.26589f), + float2(0.18133f, 0.26846f), + float2(0.18208f, 0.27119f), + float2(0.18293f, 0.27407f), + float2(0.18388f, 0.27709f), + float2(0.18494f, 0.28021f), + float2(0.18611f, 0.28342f), + float2(0.18740f, 0.28668f), + float2(0.18880f, 0.28997f), + float2(0.19032f, 0.29326f), + float2(0.19462f, 0.30141f), + float2(0.19962f, 0.30921f), + float2(0.20525f, 0.31647f), + float2(0.21142f, 0.32312f), + float2(0.21807f, 0.32909f), + float2(0.22511f, 0.33439f), + float2(0.23247f, 0.33904f), + float2(0.24010f, 0.34308f), + float2(0.24792f, 0.34655f), + float2(0.25591f, 0.34951f), + float2(0.26400f, 0.35200f), + float2(0.27218f, 0.35407f), + float2(0.28039f, 0.35577f), + float2(0.28863f, 0.35714f), + float2(0.29685f, 0.35823f), + float2(0.30505f, 0.35907f), + float2(0.31320f, 0.35968f), + float2(0.32129f, 0.36011f), + float2(0.32931f, 0.36038f), + float2(0.33724f, 0.36051f)); + + constexpr float isotherm_parameters[31] = float_array(-0.24341f, + -0.25479f, + -0.26876f, + -0.28539f, + -0.30470f, + -0.32675f, + -0.35156f, + -0.37915f, + -0.40955f, + -0.44278f, + -0.47888f, + -0.58204f, + -0.70471f, + -0.84901f, + -1.0182f, + -1.2168f, + -1.4512f, + -1.7298f, + -2.0637f, + -2.4681f, + -2.9641f, + -3.5814f, + -4.3633f, + -5.3762f, + -6.7262f, + -8.5955f, + -11.324f, + -15.628f, + -23.325f, + -40.770f, + -116.45f); + + /* Compute the inverse temperature, multiplying by 10^6 since the reference table is scaled by + * that factor. We also make sure we don't divide by zero and are less than 600 for a simpler + * algorithm as will be seen. */ + const float inverse_temperature = clamp(1e6f / max(1e-6f, temperature), 0.0f, 600.0f - 1e-6f); + + /* Find the index of the table entry that is less than or equal the inverse temperature. Note + * that the table is two arithmetic sequences concatenated, [0, 10, 20, ...] followed by [100, + * 125, 150, ...]. So the index in the first sequence is simply the floor division by 10, and the + * second is simply the floor division by 25, while of course adjusting for the start value of + * the sequence. */ + const int i = inverse_temperature < 100.0f ? (int(inverse_temperature) / 10) : + (int(inverse_temperature - 100.0f) / 25 + 10); + + /* Find interpolation factor. */ + const float interpolation_factor = (inverse_temperature - inverse_temperatures[i]) / + (inverse_temperatures[i + 1] - inverse_temperatures[i]); + + /* Interpolate point along Planckian locus. */ + const float2 uv = mix(uv_coordinates[i], uv_coordinates[i + 1], interpolation_factor); + + /* Compute and interpolate isotherm. */ + const float2 lower_isotherm = normalize(float2(1.0f, isotherm_parameters[i])); + const float2 higher_isotherm = normalize(float2(1.0f, isotherm_parameters[i + 1])); + const float2 isotherm = normalize(mix(lower_isotherm, higher_isotherm, interpolation_factor)); + + /* Offset away from the Planckian locus according to the tint. + * Tint is parameterized such that +-3000 tint corresponds to +-1 delta UV. */ + const float2 tinted_uv = uv - (isotherm * tint / 3000.0f); + + /* Convert CIE 1960 uv -> xyY. */ + const float x = 3.0f * tinted_uv.x / (2.0f * tinted_uv.x - 8.0f * tinted_uv.y + 4.0f); + const float y = 2.0f * tinted_uv.y / (2.0f * tinted_uv.x - 8.0f * tinted_uv.y + 4.0f); + + /* Convert xyY -> XYZ (assuming Y=1). */ + return float3(x / y, 1.0f, (1.0f - x - y) / y); +} + +float3x3 chromatic_adaption_matrix(const float3 from_XYZ, const float3 to_XYZ) +{ + /* Bradford transformation matrix (XYZ -> LMS). */ + const float3x3 bradford = float3x3(float3(0.8951f, -0.7502f, 0.0389f), + float3(0.2664f, 1.7135f, -0.0685f), + float3(-0.1614f, 0.0367f, 1.0296f)); + + /* Compute white points in LMS space. */ + const float3 from_LMS = bradford * from_XYZ / from_XYZ.y; + const float3 to_LMS = bradford * to_XYZ / to_XYZ.y; + + /* Assemble full transform: XYZ -> LMS -> adapted LMS -> adapted XYZ. */ + return inverse(bradford) * from_scale(to_LMS / from_LMS) * bradford; +} + +void node_composite_color_balance_white_point_variable(float factor, + float4 color, + float base_lift, + float4 color_lift, + float base_gamma, + float4 color_gamma, + float base_gain, + float4 color_gain, + float base_offset, + float4 color_offset, + float base_power, + float4 color_power, + float base_slope, + float4 color_slope, + float input_temperature, + float input_tint, + float output_temperature, + float output_tint, + float4x4 scene_to_xyz, + float4x4 xyz_to_scene, + out float4 result) +{ + const float3 input_white_point = whitepoint_from_temp_tint(input_temperature, input_tint); + const float3 output_white_point = whitepoint_from_temp_tint(output_temperature, output_tint); + const float3x3 adaption = chromatic_adaption_matrix(input_white_point, output_white_point); + const float3x3 white_point_matrix = to_float3x3(xyz_to_scene) * adaption * + to_float3x3(scene_to_xyz); + + const float3 balanced = white_point_matrix * color.xyz; + result = float4(mix(color.xyz, balanced, min(factor, 1.0f)), color.w); } diff --git a/source/blender/functions/FN_multi_function_builder.hh b/source/blender/functions/FN_multi_function_builder.hh index 84acbb0c2ac..fdad301aae6 100644 --- a/source/blender/functions/FN_multi_function_builder.hh +++ b/source/blender/functions/FN_multi_function_builder.hh @@ -648,6 +648,26 @@ inline auto SI6_SO(const char *name, name, element_fn, exec_preset, TypeSequence()); } +/** Build multi-function with 8 single-input and 1 single-output parameter. */ +template +inline auto SI8_SO(const char *name, + const ElementFn element_fn, + const ExecPreset exec_preset = exec_presets::Materialized()) +{ + return detail::build_multi_function_with_n_inputs_one_output( + name, element_fn, exec_preset, TypeSequence()); +} + /** Build multi-function with 1 single-mutable parameter. */ template inline auto SM(const char *name, diff --git a/source/blender/makesdna/DNA_node_types.h b/source/blender/makesdna/DNA_node_types.h index c16861760a9..a2e10f9d229 100644 --- a/source/blender/makesdna/DNA_node_types.h +++ b/source/blender/makesdna/DNA_node_types.h @@ -1346,22 +1346,22 @@ typedef struct NodeLensDist { typedef struct NodeColorBalance { /* ASC CDL parameters. */ - float slope[3]; - float offset[3]; - float power[3]; - float offset_basis; + float slope[3] DNA_DEPRECATED; + float offset[3] DNA_DEPRECATED; + float power[3] DNA_DEPRECATED; + float offset_basis DNA_DEPRECATED; char _pad[4]; /* LGG parameters. */ - float lift[3]; - float gamma[3]; - float gain[3]; + float lift[3] DNA_DEPRECATED; + float gamma[3] DNA_DEPRECATED; + float gain[3] DNA_DEPRECATED; /* White-point parameters. */ - float input_temperature; - float input_tint; - float output_temperature; - float output_tint; + float input_temperature DNA_DEPRECATED; + float input_tint DNA_DEPRECATED; + float output_temperature DNA_DEPRECATED; + float output_tint DNA_DEPRECATED; } NodeColorBalance; typedef struct NodeColorspill { diff --git a/source/blender/makesrna/intern/rna_nodetree.cc b/source/blender/makesrna/intern/rna_nodetree.cc index 657fc64a343..1c75b3b2b98 100644 --- a/source/blender/makesrna/intern/rna_nodetree.cc +++ b/source/blender/makesrna/intern/rna_nodetree.cc @@ -3509,20 +3509,6 @@ static PointerRNA rna_NodeOutputFile_slot_file_get(CollectionPropertyIterator *i return ptr; } -static void rna_NodeColorBalance_update_lgg(Main *bmain, Scene *scene, PointerRNA *ptr) -{ - ntreeCompositColorBalanceSyncFromLGG(reinterpret_cast(ptr->owner_id), - ptr->data_as()); - rna_Node_update(bmain, scene, ptr); -} - -static void rna_NodeColorBalance_update_cdl(Main *bmain, Scene *scene, PointerRNA *ptr) -{ - ntreeCompositColorBalanceSyncFromCDL(reinterpret_cast(ptr->owner_id), - ptr->data_as()); - rna_Node_update(bmain, scene, ptr); -} - /* -------------------------------------------------------------------- * Glare Node Compatibility Setters/Getters. * @@ -4242,6 +4228,19 @@ static const char node_input_extend_bounds[] = "Extend Bounds"; static const char node_input_x[] = "X"; static const char node_input_y[] = "Y"; +/* Color Balance node. */ +static const char node_input_color_lift[] = "Color Lift"; +static const char node_input_color_gamma[] = "Color Gamma"; +static const char node_input_color_gain[] = "Color Gain"; +static const char node_input_color_offset[] = "Color Offset"; +static const char node_input_color_power[] = "Color Power"; +static const char node_input_color_slope[] = "Color Slope"; +static const char node_input_base_offset[] = "Base Offset"; +static const char node_input_input_temperature[] = "Input Temperature"; +static const char node_input_input_tint[] = "Input Tint"; +static const char node_input_output_temperature[] = "Output Temperature"; +static const char node_input_output_tint[] = "Output Tint"; + /* -------------------------------------------------------------------- * White Balance Node. */ @@ -4249,29 +4248,49 @@ static const char node_input_y[] = "Y"; static void rna_NodeColorBalance_input_whitepoint_get(PointerRNA *ptr, float value[3]) { bNode *node = ptr->data_as(); - NodeColorBalance *n = static_cast(node->storage); - IMB_colormanagement_get_whitepoint(n->input_temperature, n->input_tint, value); + bNodeSocket *temperature_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Input Temperature"); + bNodeSocket *tint_input = blender::bke::node_find_socket(*node, SOCK_IN, "Input Tint"); + IMB_colormanagement_get_whitepoint( + temperature_input->default_value_typed()->value, + tint_input->default_value_typed()->value, + value); } static void rna_NodeColorBalance_input_whitepoint_set(PointerRNA *ptr, const float value[3]) { bNode *node = ptr->data_as(); - NodeColorBalance *n = static_cast(node->storage); - IMB_colormanagement_set_whitepoint(value, n->input_temperature, n->input_tint); + bNodeSocket *temperature_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Input Temperature"); + bNodeSocket *tint_input = blender::bke::node_find_socket(*node, SOCK_IN, "Input Tint"); + IMB_colormanagement_set_whitepoint( + value, + temperature_input->default_value_typed()->value, + tint_input->default_value_typed()->value); } static void rna_NodeColorBalance_output_whitepoint_get(PointerRNA *ptr, float value[3]) { bNode *node = ptr->data_as(); - NodeColorBalance *n = static_cast(node->storage); - IMB_colormanagement_get_whitepoint(n->output_temperature, n->output_tint, value); + bNodeSocket *temperature_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Output Temperature"); + bNodeSocket *tint_input = blender::bke::node_find_socket(*node, SOCK_IN, "Output Tint"); + IMB_colormanagement_get_whitepoint( + temperature_input->default_value_typed()->value, + tint_input->default_value_typed()->value, + value); } static void rna_NodeColorBalance_output_whitepoint_set(PointerRNA *ptr, const float value[3]) { bNode *node = ptr->data_as(); - NodeColorBalance *n = static_cast(node->storage); - IMB_colormanagement_set_whitepoint(value, n->output_temperature, n->output_tint); + bNodeSocket *temperature_input = blender::bke::node_find_socket( + *node, SOCK_IN, "Output Temperature"); + bNodeSocket *tint_input = blender::bke::node_find_socket(*node, SOCK_IN, "Output Tint"); + IMB_colormanagement_set_whitepoint( + value, + temperature_input->default_value_typed()->value, + tint_input->default_value_typed()->value); } static void rna_NodeCryptomatte_source_set(PointerRNA *ptr, int value) @@ -9200,79 +9219,127 @@ static void def_cmp_colorbalance(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_struct_sdna_from(srna, "NodeColorBalance", "storage"); prop = RNA_def_property(srna, "lift", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "lift"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_float_array_default(prop, default_1); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); - RNA_def_property_ui_text(prop, "Lift", "Correction for shadows"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_lgg"); + RNA_def_property_ui_text( + prop, "Lift", "Correction for shadows. (Deprecated: Use Lift input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "gamma", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "gamma"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_float_array_default(prop, default_1); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); - RNA_def_property_ui_text(prop, "Gamma", "Correction for midtones"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_lgg"); + RNA_def_property_ui_text( + prop, "Gamma", "Correction for midtones. (Deprecated: Use Gamma input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "gain", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "gain"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_float_array_default(prop, default_1); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); - RNA_def_property_ui_text(prop, "Gain", "Correction for highlights"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_lgg"); + RNA_def_property_ui_text( + prop, "Gain", "Correction for highlights. (Deprecated: Use Gain input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "offset", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "offset"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_ui_range(prop, 0, 1, 0.1, 3); - RNA_def_property_ui_text(prop, "Offset", "Correction for entire tonal range"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_cdl"); + RNA_def_property_ui_text( + prop, + "Offset", + "Correction for entire tonal range. (Deprecated: Use Offset input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "power", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "power"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_float_array_default(prop, default_1); RNA_def_property_range(prop, 0.0f, FLT_MAX); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); - RNA_def_property_ui_text(prop, "Power", "Correction for midtones"); + RNA_def_property_ui_text( + prop, "Power", "Correction for midtones. (Deprecated: Use Power input instead.)"); RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_MOVIECLIP); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_cdl"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "slope", PROP_FLOAT, PROP_COLOR_GAMMA); - RNA_def_property_float_sdna(prop, nullptr, "slope"); + RNA_def_property_float_funcs( + prop, + "rna_node_array_property_to_input_getter", + "rna_node_array_property_to_input_setter", + nullptr); RNA_def_property_array(prop, 3); RNA_def_property_float_array_default(prop, default_1); RNA_def_property_range(prop, 0.0f, FLT_MAX); RNA_def_property_ui_range(prop, 0, 2, 0.1, 3); - RNA_def_property_ui_text(prop, "Slope", "Correction for highlights"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_cdl"); + RNA_def_property_ui_text( + prop, "Slope", "Correction for highlights. (Deprecated: Use Slope input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "offset_basis", PROP_FLOAT, PROP_NONE); + RNA_def_property_float_funcs(prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter", + nullptr); RNA_def_property_range(prop, -FLT_MAX, FLT_MAX); RNA_def_property_ui_range(prop, -1.0, 1.0, 1.0, 2); - RNA_def_property_ui_text(prop, "Basis", "Support negative color by using this as the RGB basis"); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_NodeColorBalance_update_cdl"); + RNA_def_property_ui_text(prop, + "Basis", + "Support negative color by using this as the RGB basis. (Deprecated: " + "Use Offset Basis input instead.)"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "input_temperature", PROP_FLOAT, PROP_COLOR_TEMPERATURE); - RNA_def_property_float_sdna(prop, nullptr, "input_temperature"); + RNA_def_property_float_funcs( + prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter", + nullptr); RNA_def_property_float_default(prop, 6500.0f); RNA_def_property_range(prop, 1800.0f, 100000.0f); RNA_def_property_ui_range(prop, 2000.0f, 11000.0f, 100, 0); - RNA_def_property_ui_text( - prop, "Input Temperature", "Color temperature of the input's white point"); + RNA_def_property_ui_text(prop, + "Input Temperature", + "Color temperature of the input's white point. (Deprecated: Use Input " + "Temperature input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "input_tint", PROP_FLOAT, PROP_FACTOR); - RNA_def_property_float_sdna(prop, nullptr, "input_tint"); + RNA_def_property_float_funcs(prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter", + nullptr); RNA_def_property_float_default(prop, 10.0f); RNA_def_property_range(prop, -500.0f, 500.0f); RNA_def_property_ui_range(prop, -150.0f, 150.0f, 1, 1); - RNA_def_property_ui_text( - prop, - "Input Tint", - "Color tint of the input's white point (the default of 10 matches daylight)"); + RNA_def_property_ui_text(prop, + "Input Tint", + "Color tint of the input's white point (the default of 10 matches " + "daylight). (Deprecated: Use Input Tint input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "input_whitepoint", PROP_FLOAT, PROP_COLOR); @@ -9288,23 +9355,32 @@ static void def_cmp_colorbalance(BlenderRNA * /*brna*/, StructRNA *srna) RNA_def_property_update(prop, NC_WINDOW, "rna_Node_update"); prop = RNA_def_property(srna, "output_temperature", PROP_FLOAT, PROP_COLOR_TEMPERATURE); - RNA_def_property_float_sdna(prop, nullptr, "output_temperature"); + RNA_def_property_float_funcs( + prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter", + nullptr); RNA_def_property_float_default(prop, 6500.0f); RNA_def_property_range(prop, 1800.0f, 100000.0f); RNA_def_property_ui_range(prop, 2000.0f, 11000.0f, 100, 0); - RNA_def_property_ui_text( - prop, "Output Temperature", "Color temperature of the output's white point"); + RNA_def_property_ui_text(prop, + "Output Temperature", + "Color temperature of the output's white point. (Deprecated: Use " + "Output Temperature input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "output_tint", PROP_FLOAT, PROP_FACTOR); - RNA_def_property_float_sdna(prop, nullptr, "output_tint"); + RNA_def_property_float_funcs(prop, + "rna_node_property_to_input_getter", + "rna_node_property_to_input_setter", + nullptr); RNA_def_property_float_default(prop, 10.0f); RNA_def_property_range(prop, -500.0f, 500.0f); RNA_def_property_ui_range(prop, -150.0f, 150.0f, 1, 1); - RNA_def_property_ui_text( - prop, - "Output Tint", - "Color tint of the output's white point (the default of 10 matches daylight)"); + RNA_def_property_ui_text(prop, + "Output Tint", + "Color tint of the output's white point (the default of 10 matches " + "daylight). (Deprecated: Use Output Tint input instead.)"); RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); prop = RNA_def_property(srna, "output_whitepoint", PROP_FLOAT, PROP_COLOR); diff --git a/source/blender/nodes/NOD_composite.hh b/source/blender/nodes/NOD_composite.hh index 72dd64951fe..24dfcdca2a5 100644 --- a/source/blender/nodes/NOD_composite.hh +++ b/source/blender/nodes/NOD_composite.hh @@ -77,9 +77,6 @@ void ntreeCompositOutputFileUniqueLayer(ListBase *list, const char defname[], char delim); -void ntreeCompositColorBalanceSyncFromLGG(bNodeTree *ntree, bNode *node); -void ntreeCompositColorBalanceSyncFromCDL(bNodeTree *ntree, bNode *node); - void ntreeCompositCryptomatteSyncFromAdd(bNode *node); void ntreeCompositCryptomatteSyncFromRemove(bNode *node); bNodeSocket *ntreeCompositCryptomatteAddSocket(bNodeTree *ntree, bNode *node); diff --git a/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc index 6ce28e8f4f0..1c9af627098 100644 --- a/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc +++ b/source/blender/nodes/composite/nodes/node_composite_colorbalance.cc @@ -32,40 +32,20 @@ /* ******************* Color Balance ********************************* */ -/* Sync functions update formula parameters for other modes, such that the result is comparable. - * Note that the results are not exactly the same due to differences in color handling - * (sRGB conversion happens for LGG), - * but this keeps settings comparable. */ - -void ntreeCompositColorBalanceSyncFromLGG(bNodeTree * /*ntree*/, bNode *node) -{ - NodeColorBalance *n = (NodeColorBalance *)node->storage; - - for (int c = 0; c < 3; c++) { - n->slope[c] = (2.0f - n->lift[c]) * n->gain[c]; - n->offset[c] = (n->lift[c] - 1.0f) * n->gain[c]; - n->power[c] = (n->gamma[c] != 0.0f) ? 1.0f / n->gamma[c] : 1000000.0f; - } -} - -void ntreeCompositColorBalanceSyncFromCDL(bNodeTree * /*ntree*/, bNode *node) -{ - NodeColorBalance *n = (NodeColorBalance *)node->storage; - - for (int c = 0; c < 3; c++) { - float d = n->slope[c] + n->offset[c]; - n->lift[c] = (d != 0.0f ? n->slope[c] + 2.0f * n->offset[c] / d : 0.0f); - n->gain[c] = d; - n->gamma[c] = (n->power[c] != 0.0f) ? 1.0f / n->power[c] : 1000000.0f; - } -} - namespace blender::nodes::node_composite_colorbalance_cc { NODE_STORAGE_FUNCS(NodeColorBalance) static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b) { + b.use_custom_socket_order(); + + b.add_output("Image"); + + b.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { + layout->prop(ptr, "correction_method", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE); + }); + b.add_input("Fac") .default_value(1.0f) .min(0.0f) @@ -75,153 +55,165 @@ static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b) b.add_input("Image") .default_value({1.0f, 1.0f, 1.0f, 1.0f}) .compositor_domain_priority(0); - b.add_output("Image"); + + b.add_input("Lift", "Base Lift") + .default_value(0.0f) + .min(-1.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description("Correction for shadows"); + b.add_input("Lift", "Color Lift") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .description("Correction for shadows"); + b.add_input("Gamma", "Base Gamma") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR) + .description("Correction for midtones"); + b.add_input("Gamma", "Color Gamma") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .description("Correction for midtones"); + b.add_input("Gain", "Base Gain") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR) + .description("Correction for highlights"); + b.add_input("Gain", "Color Gain") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .description("Correction for highlights"); + + b.add_input("Offset", "Base Offset") + .default_value(0.0f) + .min(-1.0f) + .max(1.0f) + .subtype(PROP_FACTOR) + .description("Correction for shadows"); + b.add_input("Offset", "Color Offset") + .default_value({0.0f, 0.0f, 0.0f, 1.0f}) + .description("Correction for shadows"); + b.add_input("Power", "Base Power") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR) + .description("Correction for midtones"); + b.add_input("Power", "Color Power") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .description("Correction for midtones"); + b.add_input("Slope", "Base Slope") + .default_value(1.0f) + .min(0.0f) + .max(2.0f) + .subtype(PROP_FACTOR) + .description("Correction for highlights"); + b.add_input("Slope", "Color Slope") + .default_value({1.0f, 1.0f, 1.0f, 1.0f}) + .description("Correction for highlights"); + + PanelDeclarationBuilder &input_panel = b.add_panel("Input"); + input_panel.add_input("Temperature", "Input Temperature") + .default_value(6500.0f) + .subtype(PROP_COLOR_TEMPERATURE) + .min(1800.0f) + .max(100000.0f) + .description("Color temperature of the input's white point"); + input_panel.add_input("Tint", "Input Tint") + .default_value(10.0f) + .subtype(PROP_FACTOR) + .min(-150.0f) + .max(150.0f) + .description("Color tint of the input's white point (the default of 10 matches daylight)"); + input_panel.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { + uiLayout *split = &layout->split(0.2f, false); + uiTemplateCryptoPicker(split, ptr, "input_whitepoint", ICON_EYEDROPPER); + }); + + PanelDeclarationBuilder &output_panel = b.add_panel("Output"); + output_panel.add_input("Temperature", "Output Temperature") + .default_value(6500.0f) + .subtype(PROP_COLOR_TEMPERATURE) + .min(1800.0f) + .max(100000.0f) + .description("Color temperature of the output's white point"); + output_panel.add_input("Tint", "Output Tint") + .default_value(10.0f) + .subtype(PROP_FACTOR) + .min(-150.0f) + .max(150.0f) + .description("Color tint of the output's white point (the default of 10 matches daylight)"); + output_panel.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) { + uiLayout *split = &layout->split(0.2f, false); + uiTemplateCryptoPicker(split, ptr, "output_whitepoint", ICON_EYEDROPPER); + }); } static void node_composit_init_colorbalance(bNodeTree * /*ntree*/, bNode *node) { + /* All members are deprecated and needn't be set, but the data is still allocated for forward + * compatibility. */ NodeColorBalance *n = MEM_callocN(__func__); - - n->lift[0] = n->lift[1] = n->lift[2] = 1.0f; - n->gamma[0] = n->gamma[1] = n->gamma[2] = 1.0f; - n->gain[0] = n->gain[1] = n->gain[2] = 1.0f; - - n->slope[0] = n->slope[1] = n->slope[2] = 1.0f; - n->offset[0] = n->offset[1] = n->offset[2] = 0.0f; - n->power[0] = n->power[1] = n->power[2] = 1.0f; - - n->input_temperature = n->output_temperature = 6500.0f; - n->input_tint = n->output_tint = 10.0f; node->storage = n; } -static void node_composit_buts_colorbalance(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - uiLayout *split, *col, *row; - - layout->prop(ptr, "correction_method", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - const int method = RNA_enum_get(ptr, "correction_method"); - - if (method == CMP_NODE_COLOR_BALANCE_LGG) { - split = &layout->split(0.0f, false); - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "lift", true, true, false, true); - row = &col->row(false); - row->prop(ptr, "lift", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "gamma", true, true, true, true); - row = &col->row(false); - row->prop(ptr, "gamma", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "gain", true, true, true, true); - row = &col->row(false); - row->prop(ptr, "gain", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - else if (method == CMP_NODE_COLOR_BALANCE_ASC_CDL) { - split = &layout->split(0.0f, false); - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "offset", true, true, false, true); - row = &col->row(false); - row->prop(ptr, "offset", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - col->prop(ptr, "offset_basis", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "power", true, true, false, true); - row = &col->row(false); - row->prop(ptr, "power", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - col = &split->column(false); - uiTemplateColorPicker(col, ptr, "slope", true, true, false, true); - row = &col->row(false); - row->prop(ptr, "slope", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - else if (method == CMP_NODE_COLOR_BALANCE_WHITEPOINT) { - split = &layout->split(0.0f, false); - - col = &split->column(false); - row = &col->row(true); - row->label(IFACE_("Input"), ICON_NONE); - uiTemplateCryptoPicker(row, ptr, "input_whitepoint", ICON_EYEDROPPER); - col->prop( - ptr, "input_temperature", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Temperature"), ICON_NONE); - col->prop(ptr, "input_tint", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Tint"), ICON_NONE); - - col = &split->column(false); - row = &col->row(true); - row->label(IFACE_("Output"), ICON_NONE); - uiTemplateCryptoPicker(row, ptr, "output_whitepoint", ICON_EYEDROPPER); - col->prop( - ptr, "output_temperature", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Temperature"), ICON_NONE); - col->prop(ptr, "output_tint", UI_ITEM_R_SPLIT_EMPTY_NAME, IFACE_("Tint"), ICON_NONE); - } - else { - BLI_assert(false); - } -} - -static void node_composit_buts_colorbalance_ex(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) -{ - layout->prop(ptr, "correction_method", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - const int method = RNA_enum_get(ptr, "correction_method"); - - if (method == CMP_NODE_COLOR_BALANCE_LGG) { - uiTemplateColorPicker(layout, ptr, "lift", true, true, false, true); - layout->prop(ptr, "lift", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - uiTemplateColorPicker(layout, ptr, "gamma", true, true, true, true); - layout->prop(ptr, "gamma", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - uiTemplateColorPicker(layout, ptr, "gain", true, true, true, true); - layout->prop(ptr, "gain", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - else if (method == CMP_NODE_COLOR_BALANCE_ASC_CDL) { - uiTemplateColorPicker(layout, ptr, "offset", true, true, false, true); - layout->prop(ptr, "offset", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - uiTemplateColorPicker(layout, ptr, "power", true, true, false, true); - layout->prop(ptr, "power", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - - uiTemplateColorPicker(layout, ptr, "slope", true, true, false, true); - layout->prop(ptr, "slope", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - else if (method == CMP_NODE_COLOR_BALANCE_WHITEPOINT) { - layout->prop(ptr, "input_temperature", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - layout->prop(ptr, "input_tint", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - layout->prop(ptr, "output_temperature", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - layout->prop(ptr, "output_tint", UI_ITEM_R_SPLIT_EMPTY_NAME, std::nullopt, ICON_NONE); - } - else { - BLI_assert(false); - } -} - -using namespace blender::compositor; - static CMPNodeColorBalanceMethod get_color_balance_method(const bNode &node) { return static_cast(node.custom1); } -static float3 get_sanitized_gamma(const float3 gamma) +static void node_update(bNodeTree *ntree, bNode *node) { - return float3(gamma.x == 0.0f ? 1e-6f : gamma.x, - gamma.y == 0.0f ? 1e-6f : gamma.y, - gamma.z == 0.0f ? 1e-6f : gamma.z); + const bool is_lgg = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_LGG; + bNodeSocket *base_lift_input = bke::node_find_socket(*node, SOCK_IN, "Base Lift"); + bNodeSocket *base_gamma_input = bke::node_find_socket(*node, SOCK_IN, "Base Gamma"); + bNodeSocket *base_gain_input = bke::node_find_socket(*node, SOCK_IN, "Base Gain"); + bNodeSocket *color_lift_input = bke::node_find_socket(*node, SOCK_IN, "Color Lift"); + bNodeSocket *color_gamma_input = bke::node_find_socket(*node, SOCK_IN, "Color Gamma"); + bNodeSocket *color_gain_input = bke::node_find_socket(*node, SOCK_IN, "Color Gain"); + blender::bke::node_set_socket_availability(*ntree, *base_lift_input, is_lgg); + blender::bke::node_set_socket_availability(*ntree, *base_gamma_input, is_lgg); + blender::bke::node_set_socket_availability(*ntree, *base_gain_input, is_lgg); + blender::bke::node_set_socket_availability(*ntree, *color_lift_input, is_lgg); + blender::bke::node_set_socket_availability(*ntree, *color_gamma_input, is_lgg); + blender::bke::node_set_socket_availability(*ntree, *color_gain_input, is_lgg); + + const bool is_cdl = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_ASC_CDL; + bNodeSocket *base_offset_input = bke::node_find_socket(*node, SOCK_IN, "Base Offset"); + bNodeSocket *base_power_input = bke::node_find_socket(*node, SOCK_IN, "Base Power"); + bNodeSocket *base_slope_input = bke::node_find_socket(*node, SOCK_IN, "Base Slope"); + bNodeSocket *color_offset_input = bke::node_find_socket(*node, SOCK_IN, "Color Offset"); + bNodeSocket *color_power_input = bke::node_find_socket(*node, SOCK_IN, "Color Power"); + bNodeSocket *color_slope_input = bke::node_find_socket(*node, SOCK_IN, "Color Slope"); + blender::bke::node_set_socket_availability(*ntree, *base_offset_input, is_cdl); + blender::bke::node_set_socket_availability(*ntree, *base_power_input, is_cdl); + blender::bke::node_set_socket_availability(*ntree, *base_slope_input, is_cdl); + blender::bke::node_set_socket_availability(*ntree, *color_offset_input, is_cdl); + blender::bke::node_set_socket_availability(*ntree, *color_power_input, is_cdl); + blender::bke::node_set_socket_availability(*ntree, *color_slope_input, is_cdl); + + const bool is_white_point = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_WHITEPOINT; + bNodeSocket *input_temperature_input = bke::node_find_socket( + *node, SOCK_IN, "Input Temperature"); + bNodeSocket *input_tint_input = bke::node_find_socket(*node, SOCK_IN, "Input Tint"); + bNodeSocket *output_temperature_input = bke::node_find_socket( + *node, SOCK_IN, "Output Temperature"); + bNodeSocket *output_tint_input = bke::node_find_socket(*node, SOCK_IN, "Output Tint"); + blender::bke::node_set_socket_availability(*ntree, *input_temperature_input, is_white_point); + blender::bke::node_set_socket_availability(*ntree, *input_tint_input, is_white_point); + blender::bke::node_set_socket_availability(*ntree, *output_temperature_input, is_white_point); + blender::bke::node_set_socket_availability(*ntree, *output_tint_input, is_white_point); } -static float3x3 get_white_point_matrix(const bNode &node) +static float3x3 get_white_point_matrix(const float input_temperature, + const float input_tint, + const float output_temperature, + const float output_tint) { - const NodeColorBalance &node_color_balance = node_storage(node); const float3x3 scene_to_xyz = IMB_colormanagement_get_scene_linear_to_xyz(); const float3x3 xyz_to_scene = IMB_colormanagement_get_xyz_to_scene_linear(); - const float3 input = blender::math::whitepoint_from_temp_tint( - node_color_balance.input_temperature, node_color_balance.input_tint); - const float3 output = blender::math::whitepoint_from_temp_tint( - node_color_balance.output_temperature, node_color_balance.output_tint); + const float3 input = blender::math::whitepoint_from_temp_tint(input_temperature, input_tint); + const float3 output = blender::math::whitepoint_from_temp_tint(output_temperature, output_tint); const float3x3 adaption = blender::math::chromatic_adaption_matrix(input, output); return xyz_to_scene * adaption * scene_to_xyz; } @@ -232,47 +224,47 @@ static int node_gpu_material(GPUMaterial *material, GPUNodeStack *inputs, GPUNodeStack *outputs) { - const NodeColorBalance &node_color_balance = node_storage(*node); - switch (get_color_balance_method(*node)) { case CMP_NODE_COLOR_BALANCE_LGG: { - const float3 lift = node_color_balance.lift; - const float3 gamma = node_color_balance.gamma; - const float3 gain = node_color_balance.gain; - const float3 sanitized_gamma = get_sanitized_gamma(gamma); - - return GPU_stack_link(material, - node, - "node_composite_color_balance_lgg", - inputs, - outputs, - GPU_uniform(lift), - GPU_uniform(sanitized_gamma), - GPU_uniform(gain)); + return GPU_stack_link(material, node, "node_composite_color_balance_lgg", inputs, outputs); } case CMP_NODE_COLOR_BALANCE_ASC_CDL: { - const float3 offset = node_color_balance.offset; - const float3 power = node_color_balance.power; - const float3 slope = node_color_balance.slope; - const float3 full_offset = node_color_balance.offset_basis + offset; - - return GPU_stack_link(material, - node, - "node_composite_color_balance_asc_cdl", - inputs, - outputs, - GPU_uniform(full_offset), - GPU_uniform(power), - GPU_uniform(slope)); + return GPU_stack_link( + material, node, "node_composite_color_balance_asc_cdl", inputs, outputs); } case CMP_NODE_COLOR_BALANCE_WHITEPOINT: { - const float3x3 matrix = get_white_point_matrix(*node); + const bNodeSocket &input_temperature = node->input_by_identifier("Input Temperature"); + const bNodeSocket &input_tint = node->input_by_identifier("Input Tint"); + const bNodeSocket &output_temperature = node->input_by_identifier("Output Temperature"); + const bNodeSocket &output_tint = node->input_by_identifier("Output Tint"); + + /* If all inputs are not linked, compute the white point matrix on the host and pass it to + * the shader. */ + if (input_temperature.is_directly_linked() && input_tint.is_directly_linked() && + output_temperature.is_directly_linked() && output_tint.is_directly_linked()) + { + const float3x3 white_point_matrix = get_white_point_matrix( + input_temperature.default_value_typed()->value, + input_tint.default_value_typed()->value, + output_temperature.default_value_typed()->value, + output_tint.default_value_typed()->value); + return GPU_stack_link(material, + node, + "node_composite_color_balance_white_point_constant", + inputs, + outputs, + GPU_uniform(blender::float4x4(white_point_matrix).base_ptr())); + } + + const float3x3 scene_to_xyz = IMB_colormanagement_get_scene_linear_to_xyz(); + const float3x3 xyz_to_scene = IMB_colormanagement_get_xyz_to_scene_linear(); return GPU_stack_link(material, node, - "node_composite_color_balance_whitepoint", + "node_composite_color_balance_white_point_variable", inputs, outputs, - GPU_uniform(blender::float4x4(matrix).base_ptr())); + GPU_uniform(blender::float4x4(scene_to_xyz).base_ptr()), + GPU_uniform(blender::float4x4(xyz_to_scene).base_ptr())); } } @@ -281,83 +273,205 @@ static int node_gpu_material(GPUMaterial *material, static float4 color_balance_lgg(const float factor, const float4 &color, - const float3 &lift, - const float3 &gamma, - const float3 &gain) + const float &base_lift, + const float4 &color_lift, + const float &base_gamma, + const float4 &color_gamma, + const float &base_gain, + const float4 &color_gain) { - float3 inverse_lift = 2.0f - lift; float3 srgb_color; linearrgb_to_srgb_v3_v3(srgb_color, color); - float3 lift_balanced = ((srgb_color - 1.0f) * inverse_lift) + 1.0f; + const float3 lift = base_lift + color_lift.xyz(); + const float3 lift_balanced = ((srgb_color - 1.0f) * (2.0f - lift)) + 1.0f; + + const float3 gain = base_gain * color_gain.xyz(); float3 gain_balanced = lift_balanced * gain; gain_balanced = math::max(gain_balanced, float3(0.0f)); float3 linear_color; srgb_to_linearrgb_v3_v3(linear_color, gain_balanced); - float3 gamma_balanced = math::pow(linear_color, 1.0f / gamma); + + const float3 gamma = base_gamma * color_gamma.xyz(); + float3 gamma_balanced = math::pow(linear_color, 1.0f / math::max(gamma, float3(1e-6f))); return float4(math::interpolate(color.xyz(), gamma_balanced, math::min(factor, 1.0f)), color.w); } static float4 color_balance_asc_cdl(const float factor, const float4 &color, - const float3 &offset, - const float3 &power, - const float3 &slope) + const float &base_offset, + const float4 &color_offset, + const float &base_power, + const float4 &color_power, + const float &base_slope, + const float4 &color_slope) { - float3 balanced = color.xyz() * slope + offset; - balanced = math::pow(math::max(balanced, float3(0.0f)), power); + const float3 slope = base_slope * color_slope.xyz(); + const float3 slope_balanced = color.xyz() * slope; + + const float3 offset = base_offset + color_offset.xyz(); + const float3 offset_balanced = slope_balanced + offset; + + const float3 power = base_power * color_power.xyz(); + const float3 power_balanced = math::pow(math::max(offset_balanced, float3(0.0f)), power); + + return float4(math::interpolate(color.xyz(), power_balanced, math::min(factor, 1.0f)), color.w); +} + +static float4 color_balance_white_point_constant(const float factor, + const float4 &color, + const float3x3 &white_point_matrix) +{ + const float3 balanced = white_point_matrix * color.xyz(); return float4(math::interpolate(color.xyz(), balanced, math::min(factor, 1.0f)), color.w); } +static float4 color_balance_white_point_variable(const float factor, + const float4 &color, + const float input_temperature, + const float input_tint, + const float output_temperature, + const float output_tint, + const float3x3 &scene_to_xyz, + const float3x3 &xyz_to_scene) +{ + const float3 input = blender::math::whitepoint_from_temp_tint(input_temperature, input_tint); + const float3 output = blender::math::whitepoint_from_temp_tint(output_temperature, output_tint); + const float3x3 adaption = blender::math::chromatic_adaption_matrix(input, output); + const float3x3 white_point_matrix = xyz_to_scene * adaption * scene_to_xyz; + + const float3 balanced = white_point_matrix * color.xyz(); + return float4(math::interpolate(color.xyz(), balanced, math::min(factor, 1.0f)), color.w); +} + +class ColorBalanceWhitePointFunction : public mf::MultiFunction { + public: + ColorBalanceWhitePointFunction() + { + static const mf::Signature signature = []() { + mf::Signature signature; + mf::SignatureBuilder builder{"Color Balance White Point", signature}; + builder.single_input("Factor"); + builder.single_input("Color"); + builder.single_input("Input Temperature"); + builder.single_input("Input Tint"); + builder.single_input("Output Temperature"); + builder.single_input("Output Tint"); + builder.single_output("Result"); + return signature; + }(); + this->set_signature(&signature); + } + + void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override + { + const VArray factor_array = params.readonly_single_input(0, "Factor"); + const VArray color_array = params.readonly_single_input(1, "Color"); + const VArray input_temperature_array = params.readonly_single_input( + 2, "Input Temperature"); + const VArray input_tint_array = params.readonly_single_input(3, "Input Tint"); + const VArray output_temperature_array = params.readonly_single_input( + 4, "Output Temperature"); + const VArray output_tint_array = params.readonly_single_input(5, "Output Tint"); + + MutableSpan result = params.uninitialized_single_output(6, "Result"); + + const std::optional input_temperature_single = input_temperature_array.get_if_single(); + const std::optional input_tint_single = input_tint_array.get_if_single(); + const std::optional output_temperature_single = + output_temperature_array.get_if_single(); + const std::optional output_tint_single = output_tint_array.get_if_single(); + + if (input_temperature_single.has_value() && input_tint_single.has_value() && + output_temperature_single.has_value() && output_tint_single.has_value()) + { + const float3x3 white_point_matrix = get_white_point_matrix(input_temperature_single.value(), + input_tint_single.value(), + output_temperature_single.value(), + output_tint_single.value()); + mask.foreach_index([&](const int64_t i) { + result[i] = color_balance_white_point_constant( + factor_array[i], color_array[i], white_point_matrix); + }); + } + else { + const float3x3 scene_to_xyz = IMB_colormanagement_get_scene_linear_to_xyz(); + const float3x3 xyz_to_scene = IMB_colormanagement_get_xyz_to_scene_linear(); + + mask.foreach_index([&](const int64_t i) { + result[i] = color_balance_white_point_variable(factor_array[i], + color_array[i], + input_temperature_array[i], + input_tint_array[i], + output_temperature_array[i], + output_tint_array[i], + scene_to_xyz, + xyz_to_scene); + }); + } + } +}; + static void node_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder) { - const NodeColorBalance &node_color_balance = node_storage(builder.node()); - switch (get_color_balance_method(builder.node())) { case CMP_NODE_COLOR_BALANCE_LGG: { - const float3 lift = node_color_balance.lift; - const float3 gamma = node_color_balance.gamma; - const float3 gain = node_color_balance.gain; - const float3 sanitized_gamma = get_sanitized_gamma(gamma); - builder.construct_and_set_matching_fn_cb([=]() { - return mf::build::SI2_SO( - "Color Balance LGG", - [=](const float factor, const float4 &color) -> float4 { - return color_balance_lgg(factor, color, lift, sanitized_gamma, gain); - }, - mf::build::exec_presets::SomeSpanOrSingle<1>()); + return mf::build:: + SI8_SO( + "Color Balance LGG", + [=](const float factor, + const float4 &color, + const float base_lift, + const float4 &color_lift, + const float base_gamma, + const float4 &color_gamma, + const float base_gain, + const float4 &color_gain) -> float4 { + return color_balance_lgg(factor, + color, + base_lift, + color_lift, + base_gamma, + color_gamma, + base_gain, + color_gain); + }, + mf::build::exec_presets::SomeSpanOrSingle<1>()); }); break; } case CMP_NODE_COLOR_BALANCE_ASC_CDL: { - const float3 offset = node_color_balance.offset; - const float3 power = node_color_balance.power; - const float3 slope = node_color_balance.slope; - const float3 full_offset = node_color_balance.offset_basis + offset; - builder.construct_and_set_matching_fn_cb([=]() { - return mf::build::SI2_SO( - "Color Balance ASC CDL", - [=](const float factor, const float4 &color) -> float4 { - return color_balance_asc_cdl(factor, color, full_offset, power, slope); - }, - mf::build::exec_presets::SomeSpanOrSingle<1>()); + return mf::build:: + SI8_SO( + "Color Balance ASC CDL", + [=](const float factor, + const float4 &color, + const float base_offset, + const float4 &color_offset, + const float base_power, + const float4 &color_power, + const float base_slope, + const float4 &color_slope) -> float4 { + return color_balance_asc_cdl(factor, + color, + base_offset, + color_offset, + base_power, + color_power, + base_slope, + color_slope); + }, + mf::build::exec_presets::SomeSpanOrSingle<1>()); }); break; } case CMP_NODE_COLOR_BALANCE_WHITEPOINT: { - const float4x4 matrix = float4x4(get_white_point_matrix(builder.node())); - builder.construct_and_set_matching_fn_cb([=]() { - return mf::build::SI2_SO( - "Color Balance White Point", - [=](const float factor, const float4 &color) -> float4 { - return math::interpolate(color, matrix * color, math::min(factor, 1.0f)); - }, - mf::build::exec_presets::SomeSpanOrSingle<1>()); - }); + const static ColorBalanceWhitePointFunction function; + builder.set_matching_fn(function); break; } } @@ -377,9 +491,7 @@ static void register_node_type_cmp_colorbalance() ntype.enum_name_legacy = "COLORBALANCE"; ntype.nclass = NODE_CLASS_OP_COLOR; ntype.declare = file_ns::cmp_node_colorbalance_declare; - ntype.draw_buttons = file_ns::node_composit_buts_colorbalance; - ntype.draw_buttons_ex = file_ns::node_composit_buts_colorbalance_ex; - blender::bke::node_type_size(ntype, 400, 200, 400); + ntype.updatefunc = file_ns::node_update; ntype.initfunc = file_ns::node_composit_init_colorbalance; blender::bke::node_type_storage( ntype, "NodeColorBalance", node_free_standard_storage, node_copy_standard_storage);