Blender V5.0
MOD_color_balance.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_math_base.h"
10
11#include "BLT_translation.hh"
12
13#include "DNA_sequence_types.h"
14
15#include "SEQ_modifier.hh"
16#include "SEQ_modifiertypes.hh"
17
18#include "UI_interface.hh"
20
21#include "RNA_access.hh"
22
23#include "modifier.hh"
24
25namespace blender::seq {
26
27/* Lift-Gamma-Gain math. NOTE: lift is actually (2-lift). */
28static float color_balance_lgg(
29 float in, const float lift, const float gain, const float gamma, const float mul)
30{
31 float x = (((in - 1.0f) * lift) + 1.0f) * gain;
32
33 /* prevent NaN */
34 x = std::max(x, 0.0f);
35
36 x = powf(x, gamma) * mul;
37 CLAMP(x, FLT_MIN, FLT_MAX);
38 return x;
39}
40
41/* Slope-Offset-Power (ASC CDL) math, see https://en.wikipedia.org/wiki/ASC_CDL */
42static float color_balance_sop(
43 float in, const float slope, const float offset, const float power, float mul)
44{
45 float x = in * slope + offset;
46
47 /* prevent NaN */
48 x = std::max(x, 0.0f);
49
50 x = powf(x, power);
51 x *= mul;
52 CLAMP(x, FLT_MIN, FLT_MAX);
53 return x;
54}
55
60static constexpr int CB_TABLE_SIZE = 1024;
61
63 float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
64{
65 for (int i = 0; i < CB_TABLE_SIZE; i++) {
66 float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
67 r_table[i] = color_balance_lgg(x, lift, gain, gamma, mul);
68 }
69}
70
72 float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
73{
74 for (int i = 0; i < CB_TABLE_SIZE; i++) {
75 float x = float(i) * (1.0f / (CB_TABLE_SIZE - 1.0f));
76 r_table[i] = color_balance_sop(x, slope, offset, power, mul);
77 }
78}
79
81 int method;
85 float lut[3][CB_TABLE_SIZE];
86
87 /* Apply on a byte image via a table lookup. */
88 template<typename MaskSampler>
89 void apply(uchar *image, MaskSampler &mask, int image_x, IndexRange y_range)
90 {
91 image += y_range.first() * image_x * 4;
92 for (int64_t y : y_range) {
93 mask.begin_row(y);
94 for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
96
98 int p0 = int(input.x * (CB_TABLE_SIZE - 1.0f) + 0.5f);
99 int p1 = int(input.y * (CB_TABLE_SIZE - 1.0f) + 0.5f);
100 int p2 = int(input.z * (CB_TABLE_SIZE - 1.0f) + 0.5f);
101 result.x = this->lut[0][p0];
102 result.y = this->lut[1][p1];
103 result.z = this->lut[2][p2];
104 result.w = input.w;
105
106 mask.apply_mask(input, result);
108 image += 4;
109 }
110 }
111 }
112
113 /* Apply on a float image by doing full math. */
114 template<typename MaskSampler>
115 void apply(float *image, MaskSampler &mask, int image_x, IndexRange y_range)
116 {
117 image += y_range.first() * image_x * 4;
118 for (int64_t y : y_range) {
119 mask.begin_row(y);
120 if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
121 /* Lift/Gamma/Gain */
122 for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
124
127 input.x, this->lift.x, this->gain.x, this->gamma.x, this->multiplier);
129 input.y, this->lift.y, this->gain.y, this->gamma.y, this->multiplier);
131 input.z, this->lift.z, this->gain.z, this->gamma.z, this->multiplier);
132 result.w = input.w;
133
134 mask.apply_mask(input, result);
136 image += 4;
137 }
138 }
139 else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
140 /* Slope/Offset/Power */
141 for ([[maybe_unused]] int64_t x : IndexRange(image_x)) {
143
146 input.x, this->slope.x, this->offset.x, this->power.x, this->multiplier);
148 input.y, this->slope.y, this->offset.y, this->power.y, this->multiplier);
150 input.z, this->slope.z, this->offset.z, this->power.z, this->multiplier);
151 result.w = input.w;
152
153 mask.apply_mask(input, result);
155 image += 4;
156 }
157 }
158 else {
160 }
161 }
162 }
163
165 {
167
168 this->lift = 2.0f - float3(data.lift);
170 for (int c = 0; c < 3; c++) {
171 /* tweak to give more subtle results
172 * values above 1.0 are scaled */
173 if (this->lift[c] > 1.0f) {
174 this->lift[c] = powf(this->lift[c] - 1.0f, 2.0f) + 1.0f;
175 }
176 this->lift[c] = 2.0f - this->lift[c];
177 }
178 }
179
180 this->gain = float3(data.gain);
182 this->gain = math::rcp(math::max(this->gain, float3(1.0e-6f)));
183 }
184
185 this->gamma = float3(data.gamma);
187 this->gamma = math::rcp(math::max(this->gamma, float3(1.0e-6f)));
188 }
189 }
190
192 {
194
195 this->slope = float3(data.slope);
197 this->slope = math::rcp(math::max(this->slope, float3(1.0e-6f)));
198 }
199
200 this->offset = float3(data.offset) - 1.0f;
202 this->offset = -this->offset;
203 }
204
205 this->power = float3(data.power);
207 this->power = math::rcp(math::max(this->power, float3(1.0e-6f)));
208 }
209 }
210
211 void init(const ColorBalanceModifierData &data, bool byte_image)
212 {
213 this->multiplier = data.color_multiply;
214 this->method = data.color_balance.method;
215
216 if (this->method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
217 init_lgg(data.color_balance);
218 if (byte_image) {
219 for (int c = 0; c < 3; c++) {
221 this->lift[c], this->gain[c], this->gamma[c], this->multiplier, this->lut[c]);
222 }
223 }
224 }
225 else if (this->method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
226 init_sop(data.color_balance);
227 if (byte_image) {
228 for (int c = 0; c < 3; c++) {
230 this->slope[c], this->offset[c], this->power[c], this->multiplier, this->lut[c]);
231 }
232 }
233 }
234 else {
236 }
237 }
238};
239
241{
243
244 cbmd->color_multiply = 1.0f;
245 cbmd->color_balance.method = 0;
246
247 for (int c = 0; c < 3; c++) {
248 cbmd->color_balance.lift[c] = 1.0f;
249 cbmd->color_balance.gamma[c] = 1.0f;
250 cbmd->color_balance.gain[c] = 1.0f;
251 cbmd->color_balance.slope[c] = 1.0f;
252 cbmd->color_balance.offset[c] = 1.0f;
253 cbmd->color_balance.power[c] = 1.0f;
254 }
255}
256
258{
259 const ColorBalanceModifierData *cbmd = (const ColorBalanceModifierData *)smd;
260
262 op.init(*cbmd, context.image->byte_buffer.data != nullptr);
263 apply_modifier_op(op, context.image, mask, context.transform);
264}
265
266static void colorBalance_panel_draw(const bContext *C, Panel *panel)
267{
268 uiLayout *layout = panel->layout;
270
271 PointerRNA color_balance = RNA_pointer_get(ptr, "color_balance");
272 const int correction_method = RNA_enum_get(&color_balance, "correction_method");
273
274 layout->use_property_split_set(true);
275
276 layout->prop(ptr, "color_multiply", UI_ITEM_NONE, std::nullopt, ICON_NONE);
277 layout->prop(&color_balance, "correction_method", UI_ITEM_NONE, std::nullopt, ICON_NONE);
278
279 uiLayout &flow = layout->grid_flow(true, 0, true, false, false);
280 flow.use_property_split_set(false);
281 if (correction_method == SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN) {
282 /* Split into separate scopes to be able to reuse "split" and "col" variable names. */
283 {
284 uiLayout &split = flow.column(false).split(0.35f, false);
285 uiLayout &col = split.column(true);
286 col.label(IFACE_("Lift"), ICON_NONE);
287 col.separator();
288 col.separator();
289 col.prop(&color_balance, "lift", UI_ITEM_NONE, "", ICON_NONE);
290 col.prop(
291 &color_balance, "invert_lift", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
292 uiTemplateColorPicker(&split, &color_balance, "lift", true, false, false, true);
293 col.separator();
294 }
295 {
296 uiLayout &split = flow.column(false).split(0.35f, false);
297 uiLayout &col = split.column(true);
298 col.label(IFACE_("Gamma"), ICON_NONE);
299 col.separator();
300 col.separator();
301 col.prop(&color_balance, "gamma", UI_ITEM_NONE, "", ICON_NONE);
302 col.prop(
303 &color_balance, "invert_gamma", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
304 uiTemplateColorPicker(&split, &color_balance, "gamma", true, false, true, true);
305 col.separator();
306 }
307 {
308 uiLayout &split = flow.column(false).split(0.35f, false);
309 uiLayout &col = split.column(true);
310 col.label(IFACE_("Gain"), ICON_NONE);
311 col.separator();
312 col.separator();
313 col.prop(&color_balance, "gain", UI_ITEM_NONE, "", ICON_NONE);
314 col.prop(
315 &color_balance, "invert_gain", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
316 uiTemplateColorPicker(&split, &color_balance, "gain", true, false, true, true);
317 }
318 }
319 else if (correction_method == SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER) {
320 {
321 uiLayout &split = flow.column(false).split(0.35f, false);
322 uiLayout &col = split.column(true);
323 col.label(IFACE_("Offset"), ICON_NONE);
324 col.separator();
325 col.separator();
326 col.prop(&color_balance, "offset", UI_ITEM_NONE, "", ICON_NONE);
327 col.prop(
328 &color_balance, "invert_offset", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
329 uiTemplateColorPicker(&split, &color_balance, "offset", true, false, false, true);
330 col.separator();
331 }
332 {
333 uiLayout &split = flow.column(false).split(0.35f, false);
334 uiLayout &col = split.column(true);
335 col.label(IFACE_("Power"), ICON_NONE);
336 col.separator();
337 col.separator();
338 col.prop(&color_balance, "power", UI_ITEM_NONE, "", ICON_NONE);
339 col.prop(
340 &color_balance, "invert_power", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
341 uiTemplateColorPicker(&split, &color_balance, "power", true, false, false, true);
342 col.separator();
343 }
344 {
345 uiLayout &split = flow.column(false).split(0.35f, false);
346 uiLayout &col = split.column(true);
347 col.label(IFACE_("Slope"), ICON_NONE);
348 col.separator();
349 col.separator();
350 col.prop(&color_balance, "slope", UI_ITEM_NONE, "", ICON_NONE);
351 col.prop(
352 &color_balance, "invert_slope", UI_ITEM_NONE, IFACE_("Invert"), ICON_ARROW_LEFTRIGHT);
353 uiTemplateColorPicker(&split, &color_balance, "slope", true, false, false, true);
354 }
355 }
356 else {
358 }
359
360 if (uiLayout *mask_input_layout = layout->panel_prop(
361 C, ptr, "open_mask_input_panel", IFACE_("Mask Input")))
362 {
363 draw_mask_input_type_settings(C, mask_input_layout, ptr);
364 }
365}
366
371
373 /*idname*/ "ColorBalance",
374 /*name*/ CTX_N_(BLT_I18NCONTEXT_ID_SEQUENCE, "Color Balance"),
375 /*struct_name*/ "ColorBalanceModifierData",
376 /*struct_size*/ sizeof(ColorBalanceModifierData),
377 /*init_data*/ colorBalance_init_data,
378 /*free_data*/ nullptr,
379 /*copy_data*/ nullptr,
380 /*apply*/ colorBalance_apply,
381 /*panel_register*/ colorBalance_register,
382 /*blend_write*/ nullptr,
383 /*blend_read*/ nullptr,
384};
385
386}; // namespace blender::seq
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
unsigned char uchar
#define CLAMP(a, b, c)
#define CTX_N_(context, msgid)
#define BLT_I18NCONTEXT_ID_SEQUENCE
#define IFACE_(msgid)
@ SEQ_COLOR_BALANCE_INVERSE_GAIN
@ SEQ_COLOR_BALANCE_INVERSE_LIFT
@ SEQ_COLOR_BALANCE_INVERSE_SLOPE
@ SEQ_COLOR_BALANCE_INVERSE_POWER
@ SEQ_COLOR_BALANCE_INVERSE_OFFSET
@ SEQ_COLOR_BALANCE_INVERSE_GAMMA
@ eSeqModifierType_ColorBalance
@ SEQ_COLOR_BALANCE_METHOD_LIFTGAMMAGAIN
@ SEQ_COLOR_BALANCE_METHOD_SLOPEOFFSETPOWER
static void split(const char *text, const char *seps, char ***str, int *count)
#define C
Definition RandGen.cpp:29
void uiTemplateColorPicker(uiLayout *layout, PointerRNA *ptr, blender::StringRefNull propname, bool value_slider, bool lock, bool lock_luminosity, bool cubic)
PointerRNA * UI_panel_custom_data_get(const Panel *panel)
#define UI_ITEM_NONE
BMesh const char void * data
long long int int64_t
static void mul(btAlignedObjectArray< T > &items, const Q &value)
constexpr int64_t first() const
nullptr float
#define powf(x, y)
uint col
#define input
#define in
ccl_device_inline float2 power(const float2 v, const float e)
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
T rcp(const T &a)
T max(const T &a, const T &b)
static void colorBalance_init_data(StripModifierData *smd)
static constexpr int CB_TABLE_SIZE
void draw_mask_input_type_settings(const bContext *C, uiLayout *layout, PointerRNA *ptr)
static float color_balance_lgg(float in, const float lift, const float gain, const float gamma, const float mul)
void apply_modifier_op(T &op, ImBuf *ibuf, const ImBuf *mask, const float3x3 &mask_transform)
Definition modifier.hh:263
static float color_balance_sop(float in, const float slope, const float offset, const float power, float mul)
void store_pixel_premul(float4 pix, uchar *ptr)
static void make_cb_table_sop(float slope, float offset, float power, float mul, float r_table[CB_TABLE_SIZE])
static void make_cb_table_lgg(float lift, float gain, float gamma, float mul, float r_table[CB_TABLE_SIZE])
static void colorBalance_panel_draw(const bContext *C, Panel *panel)
StripModifierTypeInfo seqModifierType_ColorBalance
float4 load_pixel_premul(const uchar *ptr)
static void colorBalance_apply(ModifierApplyContext &context, StripModifierData *smd, ImBuf *mask)
static void colorBalance_register(ARegionType *region_type)
PanelType * modifier_panel_register(ARegionType *region_type, const eStripModifierType type, PanelDrawFn draw)
VecBase< float, 4 > float4
VecBase< float, 3 > float3
PointerRNA RNA_pointer_get(PointerRNA *ptr, const char *name)
int RNA_enum_get(PointerRNA *ptr, const char *name)
#define FLT_MAX
Definition stdcycles.h:14
StripColorBalance color_balance
struct uiLayout * layout
void init_sop(const StripColorBalance &data)
void apply(uchar *image, MaskSampler &mask, int image_x, IndexRange y_range)
void init_lgg(const StripColorBalance &data)
void apply(float *image, MaskSampler &mask, int image_x, IndexRange y_range)
void init(const ColorBalanceModifierData &data, bool byte_image)
PanelLayout panel_prop(const bContext *C, PointerRNA *open_prop_owner, blender::StringRefNull open_prop_name)
uiLayout & column(bool align)
uiLayout & grid_flow(bool row_major, int columns_len, bool even_columns, bool even_rows, bool align)
uiLayout & split(float percentage, bool align)
void use_property_split_set(bool value)
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:4238