Blender V4.5
node_composite_colorbalance.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2006 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_node.hh"
10#include "BLI_math_base.hh"
11#include "BLI_math_color.h"
13#include "BLI_math_vector.hh"
15
17
18#include "BKE_node_runtime.hh"
19
20#include "NOD_multi_function.hh"
21
22#include "RNA_access.hh"
23
24#include "UI_interface.hh"
25#include "UI_resources.hh"
26
27#include "GPU_material.hh"
28
30
31#include "BLI_math_color.hh"
32
34
35/* ******************* Color Balance ********************************* */
36
38
40{
41 b.use_custom_socket_order();
42
43 b.add_output<decl::Color>("Image");
44
45 b.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
46 layout->prop(ptr, "correction_method", UI_ITEM_R_SPLIT_EMPTY_NAME, "", ICON_NONE);
47 });
48
49 b.add_input<decl::Float>("Fac")
50 .default_value(1.0f)
51 .min(0.0f)
52 .max(1.0f)
54 .compositor_domain_priority(1);
55 b.add_input<decl::Color>("Image")
56 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
57 .compositor_domain_priority(0);
58
59 b.add_input<decl::Float>("Lift", "Base Lift")
60 .default_value(0.0f)
61 .min(-1.0f)
62 .max(1.0f)
64 .description("Correction for shadows");
65 b.add_input<decl::Color>("Lift", "Color Lift")
66 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
67 .description("Correction for shadows");
68 b.add_input<decl::Float>("Gamma", "Base Gamma")
69 .default_value(1.0f)
70 .min(0.0f)
71 .max(2.0f)
73 .description("Correction for midtones");
74 b.add_input<decl::Color>("Gamma", "Color Gamma")
75 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
76 .description("Correction for midtones");
77 b.add_input<decl::Float>("Gain", "Base Gain")
78 .default_value(1.0f)
79 .min(0.0f)
80 .max(2.0f)
82 .description("Correction for highlights");
83 b.add_input<decl::Color>("Gain", "Color Gain")
84 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
85 .description("Correction for highlights");
86
87 b.add_input<decl::Float>("Offset", "Base Offset")
88 .default_value(0.0f)
89 .min(-1.0f)
90 .max(1.0f)
92 .description("Correction for shadows");
93 b.add_input<decl::Color>("Offset", "Color Offset")
94 .default_value({0.0f, 0.0f, 0.0f, 1.0f})
95 .description("Correction for shadows");
96 b.add_input<decl::Float>("Power", "Base Power")
97 .default_value(1.0f)
98 .min(0.0f)
99 .max(2.0f)
101 .description("Correction for midtones");
102 b.add_input<decl::Color>("Power", "Color Power")
103 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
104 .description("Correction for midtones");
105 b.add_input<decl::Float>("Slope", "Base Slope")
106 .default_value(1.0f)
107 .min(0.0f)
108 .max(2.0f)
110 .description("Correction for highlights");
111 b.add_input<decl::Color>("Slope", "Color Slope")
112 .default_value({1.0f, 1.0f, 1.0f, 1.0f})
113 .description("Correction for highlights");
114
115 PanelDeclarationBuilder &input_panel = b.add_panel("Input");
116 input_panel.add_input<decl::Float>("Temperature", "Input Temperature")
117 .default_value(6500.0f)
119 .min(1800.0f)
120 .max(100000.0f)
121 .description("Color temperature of the input's white point");
122 input_panel.add_input<decl::Float>("Tint", "Input Tint")
123 .default_value(10.0f)
125 .min(-150.0f)
126 .max(150.0f)
127 .description("Color tint of the input's white point (the default of 10 matches daylight)");
128 input_panel.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
129 uiLayout *split = &layout->split(0.2f, false);
130 uiTemplateCryptoPicker(split, ptr, "input_whitepoint", ICON_EYEDROPPER);
131 });
132
133 PanelDeclarationBuilder &output_panel = b.add_panel("Output");
134 output_panel.add_input<decl::Float>("Temperature", "Output Temperature")
135 .default_value(6500.0f)
137 .min(1800.0f)
138 .max(100000.0f)
139 .description("Color temperature of the output's white point");
140 output_panel.add_input<decl::Float>("Tint", "Output Tint")
141 .default_value(10.0f)
143 .min(-150.0f)
144 .max(150.0f)
145 .description("Color tint of the output's white point (the default of 10 matches daylight)");
146 output_panel.add_layout([](uiLayout *layout, bContext * /*C*/, PointerRNA *ptr) {
147 uiLayout *split = &layout->split(0.2f, false);
148 uiTemplateCryptoPicker(split, ptr, "output_whitepoint", ICON_EYEDROPPER);
149 });
150}
151
153{
154 return static_cast<CMPNodeColorBalanceMethod>(node.custom1);
155}
156
157static void node_update(bNodeTree *ntree, bNode *node)
158{
159 const bool is_lgg = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_LGG;
160 bNodeSocket *base_lift_input = bke::node_find_socket(*node, SOCK_IN, "Base Lift");
161 bNodeSocket *base_gamma_input = bke::node_find_socket(*node, SOCK_IN, "Base Gamma");
162 bNodeSocket *base_gain_input = bke::node_find_socket(*node, SOCK_IN, "Base Gain");
163 bNodeSocket *color_lift_input = bke::node_find_socket(*node, SOCK_IN, "Color Lift");
164 bNodeSocket *color_gamma_input = bke::node_find_socket(*node, SOCK_IN, "Color Gamma");
165 bNodeSocket *color_gain_input = bke::node_find_socket(*node, SOCK_IN, "Color Gain");
166 blender::bke::node_set_socket_availability(*ntree, *base_lift_input, is_lgg);
167 blender::bke::node_set_socket_availability(*ntree, *base_gamma_input, is_lgg);
168 blender::bke::node_set_socket_availability(*ntree, *base_gain_input, is_lgg);
169 blender::bke::node_set_socket_availability(*ntree, *color_lift_input, is_lgg);
170 blender::bke::node_set_socket_availability(*ntree, *color_gamma_input, is_lgg);
171 blender::bke::node_set_socket_availability(*ntree, *color_gain_input, is_lgg);
172
173 const bool is_cdl = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_ASC_CDL;
174 bNodeSocket *base_offset_input = bke::node_find_socket(*node, SOCK_IN, "Base Offset");
175 bNodeSocket *base_power_input = bke::node_find_socket(*node, SOCK_IN, "Base Power");
176 bNodeSocket *base_slope_input = bke::node_find_socket(*node, SOCK_IN, "Base Slope");
177 bNodeSocket *color_offset_input = bke::node_find_socket(*node, SOCK_IN, "Color Offset");
178 bNodeSocket *color_power_input = bke::node_find_socket(*node, SOCK_IN, "Color Power");
179 bNodeSocket *color_slope_input = bke::node_find_socket(*node, SOCK_IN, "Color Slope");
180 blender::bke::node_set_socket_availability(*ntree, *base_offset_input, is_cdl);
181 blender::bke::node_set_socket_availability(*ntree, *base_power_input, is_cdl);
182 blender::bke::node_set_socket_availability(*ntree, *base_slope_input, is_cdl);
183 blender::bke::node_set_socket_availability(*ntree, *color_offset_input, is_cdl);
184 blender::bke::node_set_socket_availability(*ntree, *color_power_input, is_cdl);
185 blender::bke::node_set_socket_availability(*ntree, *color_slope_input, is_cdl);
186
187 const bool is_white_point = get_color_balance_method(*node) == CMP_NODE_COLOR_BALANCE_WHITEPOINT;
188 bNodeSocket *input_temperature_input = bke::node_find_socket(
189 *node, SOCK_IN, "Input Temperature");
190 bNodeSocket *input_tint_input = bke::node_find_socket(*node, SOCK_IN, "Input Tint");
191 bNodeSocket *output_temperature_input = bke::node_find_socket(
192 *node, SOCK_IN, "Output Temperature");
193 bNodeSocket *output_tint_input = bke::node_find_socket(*node, SOCK_IN, "Output Tint");
194 blender::bke::node_set_socket_availability(*ntree, *input_temperature_input, is_white_point);
195 blender::bke::node_set_socket_availability(*ntree, *input_tint_input, is_white_point);
196 blender::bke::node_set_socket_availability(*ntree, *output_temperature_input, is_white_point);
197 blender::bke::node_set_socket_availability(*ntree, *output_tint_input, is_white_point);
198}
199
200static float3x3 get_white_point_matrix(const float input_temperature,
201 const float input_tint,
202 const float output_temperature,
203 const float output_tint)
204{
207 const float3 input = blender::math::whitepoint_from_temp_tint(input_temperature, input_tint);
208 const float3 output = blender::math::whitepoint_from_temp_tint(output_temperature, output_tint);
210 return xyz_to_scene * adaption * scene_to_xyz;
211}
212
213static int node_gpu_material(GPUMaterial *material,
214 bNode *node,
215 bNodeExecData * /*execdata*/,
218{
219 switch (get_color_balance_method(*node)) {
221 return GPU_stack_link(material, node, "node_composite_color_balance_lgg", inputs, outputs);
222 }
224 return GPU_stack_link(
225 material, node, "node_composite_color_balance_asc_cdl", inputs, outputs);
226 }
228 const bNodeSocket &input_temperature = node->input_by_identifier("Input Temperature");
229 const bNodeSocket &input_tint = node->input_by_identifier("Input Tint");
230 const bNodeSocket &output_temperature = node->input_by_identifier("Output Temperature");
231 const bNodeSocket &output_tint = node->input_by_identifier("Output Tint");
232
233 /* If all inputs are not linked, compute the white point matrix on the host and pass it to
234 * the shader. */
235 if (input_temperature.is_directly_linked() && input_tint.is_directly_linked() &&
236 output_temperature.is_directly_linked() && output_tint.is_directly_linked())
237 {
238 const float3x3 white_point_matrix = get_white_point_matrix(
239 input_temperature.default_value_typed<bNodeSocketValueFloat>()->value,
240 input_tint.default_value_typed<bNodeSocketValueFloat>()->value,
241 output_temperature.default_value_typed<bNodeSocketValueFloat>()->value,
242 output_tint.default_value_typed<bNodeSocketValueFloat>()->value);
243 return GPU_stack_link(material,
244 node,
245 "node_composite_color_balance_white_point_constant",
246 inputs,
247 outputs,
248 GPU_uniform(blender::float4x4(white_point_matrix).base_ptr()));
249 }
250
253 return GPU_stack_link(material,
254 node,
255 "node_composite_color_balance_white_point_variable",
256 inputs,
257 outputs,
258 GPU_uniform(blender::float4x4(scene_to_xyz).base_ptr()),
259 GPU_uniform(blender::float4x4(xyz_to_scene).base_ptr()));
260 }
261 }
262
263 return false;
264}
265
266static float4 color_balance_lgg(const float factor,
267 const float4 &color,
268 const float &base_lift,
269 const float4 &color_lift,
270 const float &base_gamma,
271 const float4 &color_gamma,
272 const float &base_gain,
273 const float4 &color_gain)
274{
275 float3 srgb_color;
276 linearrgb_to_srgb_v3_v3(srgb_color, color);
277
278 const float3 lift = base_lift + color_lift.xyz();
279 const float3 lift_balanced = ((srgb_color - 1.0f) * (2.0f - lift)) + 1.0f;
280
281 const float3 gain = base_gain * color_gain.xyz();
282 float3 gain_balanced = lift_balanced * gain;
283 gain_balanced = math::max(gain_balanced, float3(0.0f));
284
285 float3 linear_color;
286 srgb_to_linearrgb_v3_v3(linear_color, gain_balanced);
287
288 const float3 gamma = base_gamma * color_gamma.xyz();
289 float3 gamma_balanced = math::pow(linear_color, 1.0f / math::max(gamma, float3(1e-6f)));
290
291 return float4(math::interpolate(color.xyz(), gamma_balanced, math::min(factor, 1.0f)), color.w);
292}
293
294static float4 color_balance_asc_cdl(const float factor,
295 const float4 &color,
296 const float &base_offset,
297 const float4 &color_offset,
298 const float &base_power,
299 const float4 &color_power,
300 const float &base_slope,
301 const float4 &color_slope)
302{
303 const float3 slope = base_slope * color_slope.xyz();
304 const float3 slope_balanced = color.xyz() * slope;
305
306 const float3 offset = base_offset + color_offset.xyz();
307 const float3 offset_balanced = slope_balanced + offset;
308
309 const float3 power = base_power * color_power.xyz();
310 const float3 power_balanced = math::pow(math::max(offset_balanced, float3(0.0f)), power);
311
312 return float4(math::interpolate(color.xyz(), power_balanced, math::min(factor, 1.0f)), color.w);
313}
314
316 const float4 &color,
317 const float3x3 &white_point_matrix)
318{
319 const float3 balanced = white_point_matrix * color.xyz();
320 return float4(math::interpolate(color.xyz(), balanced, math::min(factor, 1.0f)), color.w);
321}
322
324 const float4 &color,
325 const float input_temperature,
326 const float input_tint,
327 const float output_temperature,
328 const float output_tint,
329 const float3x3 &scene_to_xyz,
330 const float3x3 &xyz_to_scene)
331{
332 const float3 input = blender::math::whitepoint_from_temp_tint(input_temperature, input_tint);
333 const float3 output = blender::math::whitepoint_from_temp_tint(output_temperature, output_tint);
335 const float3x3 white_point_matrix = xyz_to_scene * adaption * scene_to_xyz;
336
337 const float3 balanced = white_point_matrix * color.xyz();
338 return float4(math::interpolate(color.xyz(), balanced, math::min(factor, 1.0f)), color.w);
339}
340
341class ColorBalanceWhitePointFunction : public mf::MultiFunction {
342 public:
344 {
345 static const mf::Signature signature = []() {
346 mf::Signature signature;
347 mf::SignatureBuilder builder{"Color Balance White Point", signature};
348 builder.single_input<float>("Factor");
349 builder.single_input<float4>("Color");
350 builder.single_input<float>("Input Temperature");
351 builder.single_input<float>("Input Tint");
352 builder.single_input<float>("Output Temperature");
353 builder.single_input<float>("Output Tint");
354 builder.single_output<float4>("Result");
355 return signature;
356 }();
357 this->set_signature(&signature);
358 }
359
360 void call(const IndexMask &mask, mf::Params params, mf::Context /*context*/) const override
361 {
362 const VArray<float> factor_array = params.readonly_single_input<float>(0, "Factor");
363 const VArray<float4> color_array = params.readonly_single_input<float4>(1, "Color");
364 const VArray<float> input_temperature_array = params.readonly_single_input<float>(
365 2, "Input Temperature");
366 const VArray<float> input_tint_array = params.readonly_single_input<float>(3, "Input Tint");
367 const VArray<float> output_temperature_array = params.readonly_single_input<float>(
368 4, "Output Temperature");
369 const VArray<float> output_tint_array = params.readonly_single_input<float>(5, "Output Tint");
370
371 MutableSpan<float4> result = params.uninitialized_single_output<float4>(6, "Result");
372
373 const std::optional<float> input_temperature_single = input_temperature_array.get_if_single();
374 const std::optional<float> input_tint_single = input_tint_array.get_if_single();
375 const std::optional<float> output_temperature_single =
376 output_temperature_array.get_if_single();
377 const std::optional<float> output_tint_single = output_tint_array.get_if_single();
378
379 if (input_temperature_single.has_value() && input_tint_single.has_value() &&
380 output_temperature_single.has_value() && output_tint_single.has_value())
381 {
382 const float3x3 white_point_matrix = get_white_point_matrix(input_temperature_single.value(),
383 input_tint_single.value(),
384 output_temperature_single.value(),
385 output_tint_single.value());
386 mask.foreach_index([&](const int64_t i) {
388 factor_array[i], color_array[i], white_point_matrix);
389 });
390 }
391 else {
394
395 mask.foreach_index([&](const int64_t i) {
397 color_array[i],
398 input_temperature_array[i],
399 input_tint_array[i],
400 output_temperature_array[i],
401 output_tint_array[i],
402 scene_to_xyz,
403 xyz_to_scene);
404 });
405 }
406 }
407};
408
410{
411 switch (get_color_balance_method(builder.node())) {
414 return mf::build::
415 SI8_SO<float, float4, float, float4, float, float4, float, float4, float4>(
416 "Color Balance LGG",
417 [=](const float factor,
418 const float4 &color,
419 const float base_lift,
420 const float4 &color_lift,
421 const float base_gamma,
422 const float4 &color_gamma,
423 const float base_gain,
424 const float4 &color_gain) -> float4 {
425 return color_balance_lgg(factor,
426 color,
427 base_lift,
428 color_lift,
429 base_gamma,
430 color_gamma,
431 base_gain,
432 color_gain);
433 },
434 mf::build::exec_presets::SomeSpanOrSingle<1>());
435 });
436 break;
437 }
440 return mf::build::
441 SI8_SO<float, float4, float, float4, float, float4, float, float4, float4>(
442 "Color Balance ASC CDL",
443 [=](const float factor,
444 const float4 &color,
445 const float base_offset,
446 const float4 &color_offset,
447 const float base_power,
448 const float4 &color_power,
449 const float base_slope,
450 const float4 &color_slope) -> float4 {
451 return color_balance_asc_cdl(factor,
452 color,
453 base_offset,
454 color_offset,
455 base_power,
456 color_power,
457 base_slope,
458 color_slope);
459 },
460 mf::build::exec_presets::SomeSpanOrSingle<1>());
461 });
462 break;
463 }
465 const static ColorBalanceWhitePointFunction function;
466 builder.set_matching_fn(function);
467 break;
468 }
469 }
470}
471
472} // namespace blender::nodes::node_composite_colorbalance_cc
473
475{
477
478 static blender::bke::bNodeType ntype;
479
480 cmp_node_type_base(&ntype, "CompositorNodeColorBalance", CMP_NODE_COLORBALANCE);
481 ntype.ui_name = "Color Balance";
482 ntype.ui_description = "Adjust color and values";
483 ntype.enum_name_legacy = "COLORBALANCE";
485 ntype.declare = file_ns::cmp_node_colorbalance_declare;
486 ntype.updatefunc = file_ns::node_update;
487 ntype.gpu_fn = file_ns::node_gpu_material;
488 ntype.build_multi_function = file_ns::node_build_multi_function;
489
491}
#define NODE_CLASS_OP_COLOR
Definition BKE_node.hh:435
#define CMP_NODE_COLORBALANCE
void linearrgb_to_srgb_v3_v3(float srgb[3], const float linear[3])
void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
@ SOCK_IN
CMPNodeColorBalanceMethod
@ CMP_NODE_COLOR_BALANCE_LGG
@ CMP_NODE_COLOR_BALANCE_ASC_CDL
@ CMP_NODE_COLOR_BALANCE_WHITEPOINT
static void split(const char *text, const char *seps, char ***str, int *count)
bool GPU_stack_link(GPUMaterial *mat, const bNode *node, const char *name, GPUNodeStack *in, GPUNodeStack *out,...)
GPUNodeLink * GPU_uniform(const float *num)
blender::float3x3 IMB_colormanagement_get_scene_linear_to_xyz()
blender::float3x3 IMB_colormanagement_get_xyz_to_scene_linear()
#define NOD_REGISTER_NODE(REGISTER_FUNC)
@ PROP_COLOR_TEMPERATURE
Definition RNA_types.hh:278
@ PROP_FACTOR
Definition RNA_types.hh:239
void uiTemplateCryptoPicker(uiLayout *layout, PointerRNA *ptr, blender::StringRefNull propname, int icon)
@ UI_ITEM_R_SPLIT_EMPTY_NAME
long long int int64_t
void set_signature(const Signature *signature)
void add_layout(std::function< void(uiLayout *, bContext *, PointerRNA *)> draw)
DeclType::Builder & add_input(StringRef name, StringRef identifier="")
void set_matching_fn(const mf::MultiFunction *fn)
void construct_and_set_matching_fn_cb(Fn &&create_multi_function)
void call(const IndexMask &mask, mf::Params params, mf::Context) const override
#define input
#define output
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
ccl_device_inline float2 power(const float2 v, const float e)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2864
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void node_set_socket_availability(bNodeTree &ntree, bNodeSocket &sock, bool is_available)
Definition node.cc:5011
T pow(const T &x, const T &power)
float3 whitepoint_from_temp_tint(float temperature, float tint)
T min(const T &a, const T &b)
T interpolate(const T &a, const T &b, const FactorT &t)
float3x3 chromatic_adaption_matrix(const float3 &from_XYZ, const float3 &to_XYZ)
T max(const T &a, const T &b)
static void cmp_node_colorbalance_declare(NodeDeclarationBuilder &b)
static void node_update(bNodeTree *ntree, bNode *node)
static float4 color_balance_white_point_constant(const float factor, const float4 &color, const float3x3 &white_point_matrix)
static 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)
static 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)
static CMPNodeColorBalanceMethod get_color_balance_method(const bNode &node)
static void node_build_multi_function(blender::nodes::NodeMultiFunctionBuilder &builder)
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)
static float3x3 get_white_point_matrix(const float input_temperature, const float input_tint, const float output_temperature, const float output_tint)
static int node_gpu_material(GPUMaterial *material, bNode *node, bNodeExecData *, GPUNodeStack *inputs, GPUNodeStack *outputs)
MatBase< float, 4, 4 > float4x4
VecBase< float, 4 > float4
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
static void register_node_type_cmp_colorbalance()
void cmp_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
static blender::bke::bNodeSocketTemplate outputs[]
static blender::bke::bNodeSocketTemplate inputs[]
int16_t custom1
VecBase< T, 3 > xyz() const
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGPUExecFunction gpu_fn
Definition BKE_node.hh:330
NodeMultiFunctionBuildFunction build_multi_function
Definition BKE_node.hh:344
const char * enum_name_legacy
Definition BKE_node.hh:235
NodeDeclareFunction declare
Definition BKE_node.hh:355
void(* updatefunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:269
uiLayout & split(float percentage, bool align)
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4226