Compositor: Turn Color Balance options to inputs

This patch turns the options of the Color Balance node into inputs.

In the process, each of the wheels were split into two inputs, a base
float and a color. For instance, Gain is controlled using both a Base
Gain and Color Gain, the former controls the gain for all channels while
the latter controls it per channel.

Reference #137223.

Pull Request: https://projects.blender.org/blender/blender/pulls/138610
This commit is contained in:
Omar Emara
2025-05-23 15:42:54 +02:00
committed by Omar Emara
parent 4f00a470cd
commit 6c62fb5ff6
10 changed files with 1059 additions and 326 deletions

View File

@@ -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

View File

@@ -1049,6 +1049,80 @@ static void write_compositor_legacy_properties(bNodeTree &node_tree)
storage->y1 = y_input->default_value_typed<bNodeSocketValueInt>()->value +
height_input->default_value_typed<bNodeSocketValueInt>()->value;
}
if (node->type_legacy == CMP_NODE_COLORBALANCE) {
NodeColorBalance *storage = static_cast<NodeColorBalance *>(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<bNodeSocketValueFloat>()->value +
float3(
color_input->default_value_typed<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->value *
float3(
color_input->default_value_typed<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->value *
float3(
color_input->default_value_typed<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->value *
float3(
color_input->default_value_typed<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->value *
float3(
color_input->default_value_typed<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->value;
copy_v3_v3(storage->offset,
color_input->default_value_typed<bNodeSocketValueRGBA>()->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);
}
}
}

View File

@@ -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<NodeColorBalance *>(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);
}
}
}

View File

@@ -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<NodeColorBalance *>(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<bNodeSocketValueRGBA>()->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<bNodeSocketValueRGBA>()->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<bNodeSocketValueRGBA>()->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<bNodeSocketValueRGBA>()->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<bNodeSocketValueRGBA>()->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<bNodeSocketValueRGBA>()->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<bNodeSocketValueFloat>()->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<bNodeSocketValueFloat>()->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<bNodeSocketValueFloat>()->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<bNodeSocketValueFloat>()->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<bNodeSocketValueFloat>()->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. */

View File

@@ -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);
}

View File

@@ -648,6 +648,26 @@ inline auto SI6_SO(const char *name,
name, element_fn, exec_preset, TypeSequence<In1, In2, In3, In4, In5, In6>());
}
/** Build multi-function with 8 single-input and 1 single-output parameter. */
template<typename In1,
typename In2,
typename In3,
typename In4,
typename In5,
typename In6,
typename In7,
typename In8,
typename Out1,
typename ElementFn,
typename ExecPreset = exec_presets::Materialized>
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<Out1>(
name, element_fn, exec_preset, TypeSequence<In1, In2, In3, In4, In5, In6, In7, In8>());
}
/** Build multi-function with 1 single-mutable parameter. */
template<typename Mut1, typename ElementFn, typename ExecPreset = exec_presets::AllSpanOrSingle>
inline auto SM(const char *name,

View File

@@ -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 {

View File

@@ -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<bNodeTree *>(ptr->owner_id),
ptr->data_as<bNode>());
rna_Node_update(bmain, scene, ptr);
}
static void rna_NodeColorBalance_update_cdl(Main *bmain, Scene *scene, PointerRNA *ptr)
{
ntreeCompositColorBalanceSyncFromCDL(reinterpret_cast<bNodeTree *>(ptr->owner_id),
ptr->data_as<bNode>());
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<bNode>();
NodeColorBalance *n = static_cast<NodeColorBalance *>(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<bNodeSocketValueFloat>()->value,
tint_input->default_value_typed<bNodeSocketValueFloat>()->value,
value);
}
static void rna_NodeColorBalance_input_whitepoint_set(PointerRNA *ptr, const float value[3])
{
bNode *node = ptr->data_as<bNode>();
NodeColorBalance *n = static_cast<NodeColorBalance *>(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<bNodeSocketValueFloat>()->value,
tint_input->default_value_typed<bNodeSocketValueFloat>()->value);
}
static void rna_NodeColorBalance_output_whitepoint_get(PointerRNA *ptr, float value[3])
{
bNode *node = ptr->data_as<bNode>();
NodeColorBalance *n = static_cast<NodeColorBalance *>(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<bNodeSocketValueFloat>()->value,
tint_input->default_value_typed<bNodeSocketValueFloat>()->value,
value);
}
static void rna_NodeColorBalance_output_whitepoint_set(PointerRNA *ptr, const float value[3])
{
bNode *node = ptr->data_as<bNode>();
NodeColorBalance *n = static_cast<NodeColorBalance *>(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<bNodeSocketValueFloat>()->value,
tint_input->default_value_typed<bNodeSocketValueFloat>()->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<float, node_input_color_lift>",
"rna_node_array_property_to_input_setter<float, node_input_color_lift>",
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<float, node_input_color_gamma>",
"rna_node_array_property_to_input_setter<float, node_input_color_gamma>",
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<float, node_input_color_gain>",
"rna_node_array_property_to_input_setter<float, node_input_color_gain>",
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<float, node_input_color_offset>",
"rna_node_array_property_to_input_setter<float, node_input_color_offset>",
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<float, node_input_color_power>",
"rna_node_array_property_to_input_setter<float, node_input_color_power>",
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<float, node_input_color_slope>",
"rna_node_array_property_to_input_setter<float, node_input_color_slope>",
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<float, node_input_base_offset>",
"rna_node_property_to_input_setter<float, node_input_base_offset>",
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<float, node_input_input_temperature>",
"rna_node_property_to_input_setter<float, node_input_input_temperature>",
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<float, node_input_input_tint>",
"rna_node_property_to_input_setter<float, node_input_input_tint>",
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<float, node_input_output_temperature>",
"rna_node_property_to_input_setter<float, node_input_output_temperature>",
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<float, node_input_output_tint>",
"rna_node_property_to_input_setter<float, node_input_output_tint>",
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);

View File

@@ -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);

View File

@@ -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<decl::Color>("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<decl::Float>("Fac")
.default_value(1.0f)
.min(0.0f)
@@ -75,153 +55,165 @@ static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b)
b.add_input<decl::Color>("Image")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.compositor_domain_priority(0);
b.add_output<decl::Color>("Image");
b.add_input<decl::Float>("Lift", "Base Lift")
.default_value(0.0f)
.min(-1.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("Correction for shadows");
b.add_input<decl::Color>("Lift", "Color Lift")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.description("Correction for shadows");
b.add_input<decl::Float>("Gamma", "Base Gamma")
.default_value(1.0f)
.min(0.0f)
.max(2.0f)
.subtype(PROP_FACTOR)
.description("Correction for midtones");
b.add_input<decl::Color>("Gamma", "Color Gamma")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.description("Correction for midtones");
b.add_input<decl::Float>("Gain", "Base Gain")
.default_value(1.0f)
.min(0.0f)
.max(2.0f)
.subtype(PROP_FACTOR)
.description("Correction for highlights");
b.add_input<decl::Color>("Gain", "Color Gain")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.description("Correction for highlights");
b.add_input<decl::Float>("Offset", "Base Offset")
.default_value(0.0f)
.min(-1.0f)
.max(1.0f)
.subtype(PROP_FACTOR)
.description("Correction for shadows");
b.add_input<decl::Color>("Offset", "Color Offset")
.default_value({0.0f, 0.0f, 0.0f, 1.0f})
.description("Correction for shadows");
b.add_input<decl::Float>("Power", "Base Power")
.default_value(1.0f)
.min(0.0f)
.max(2.0f)
.subtype(PROP_FACTOR)
.description("Correction for midtones");
b.add_input<decl::Color>("Power", "Color Power")
.default_value({1.0f, 1.0f, 1.0f, 1.0f})
.description("Correction for midtones");
b.add_input<decl::Float>("Slope", "Base Slope")
.default_value(1.0f)
.min(0.0f)
.max(2.0f)
.subtype(PROP_FACTOR)
.description("Correction for highlights");
b.add_input<decl::Color>("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<decl::Float>("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<decl::Float>("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<decl::Float>("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<decl::Float>("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<NodeColorBalance>(__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<CMPNodeColorBalanceMethod>(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<bNodeSocketValueFloat>()->value,
input_tint.default_value_typed<bNodeSocketValueFloat>()->value,
output_temperature.default_value_typed<bNodeSocketValueFloat>()->value,
output_tint.default_value_typed<bNodeSocketValueFloat>()->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<float>("Factor");
builder.single_input<float4>("Color");
builder.single_input<float>("Input Temperature");
builder.single_input<float>("Input Tint");
builder.single_input<float>("Output Temperature");
builder.single_input<float>("Output Tint");
builder.single_output<float4>("Result");
return signature;
}();
this->set_signature(&signature);
}
void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
{
const VArray<float> factor_array = params.readonly_single_input<float>(0, "Factor");
const VArray<float4> color_array = params.readonly_single_input<float4>(1, "Color");
const VArray<float> input_temperature_array = params.readonly_single_input<float>(
2, "Input Temperature");
const VArray<float> input_tint_array = params.readonly_single_input<float>(3, "Input Tint");
const VArray<float> output_temperature_array = params.readonly_single_input<float>(
4, "Output Temperature");
const VArray<float> output_tint_array = params.readonly_single_input<float>(5, "Output Tint");
MutableSpan<float4> result = params.uninitialized_single_output<float4>(6, "Result");
const std::optional<float> input_temperature_single = input_temperature_array.get_if_single();
const std::optional<float> input_tint_single = input_tint_array.get_if_single();
const std::optional<float> output_temperature_single =
output_temperature_array.get_if_single();
const std::optional<float> 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<float, float4, float4>(
"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<float, float4, float, float4, float, float4, float, float4, float4>(
"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<float, float4, float4>(
"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<float, float4, float, float4, float, float4, float, float4, float4>(
"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<float, float4, float4>(
"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);