Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorbalancergb.c
Go to the documentation of this file.
1/*
2 This file is part of the Ansel project.
3 Copyright (C) 2020-2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2021-2022 Chris Elston.
5 Copyright (C) 2021 Dan Torop.
6 Copyright (C) 2021-2022 Diederik Ter Rahe.
7 Copyright (C) 2021 EdgarLux.
8 Copyright (C) 2021 Marco Carrarini.
9 Copyright (C) 2021 Martin Straeten.
10 Copyright (C) 2021-2022 Pascal Obry.
11 Copyright (C) 2021 Ralf Brown.
12 Copyright (C) 2021 Sakari Kapanen.
13 Copyright (C) 2021 wpferguson.
14 Copyright (C) 2022 Aldric Renaudin.
15 Copyright (C) 2022 Hanno Schwalm.
16 Copyright (C) 2022 Martin Bařinka.
17 Copyright (C) 2022 Philipp Lutz.
18 Copyright (C) 2023 Jehan Singh.
19 Copyright (C) 2023 Luca Zulberti.
20 Copyright (C) 2026 Guillaume Stutin.
21
22 Ansel is free software: you can redistribute it and/or modify
23 it under the terms of the GNU General Public License as published by
24 the Free Software Foundation, either version 3 of the License, or
25 (at your option) any later version.
26
27 Ansel is distributed in the hope that it will be useful,
28 but WITHOUT ANY WARRANTY; without even the implied warranty of
29 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License
33 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
34*/
35
36#ifdef HAVE_CONFIG_H
37#include "config.h"
38#include "develop/iop_profile.h"
40#include "common/conf.h"
41#endif
42// our includes go first:
43#include "widgets/bauhaus.h"
44#include "system/macros.h"
45#include "system/openmp.h"
47#include "system/mem_alloc.h"
48#include "system/simd.h"
49#include "common/logging.h"
53#include "common/opencl.h"
54#include "develop/imageop.h"
55#include "math/openmp_maths.h"
56#include "develop/imageop_gui.h"
57
58#include "widgets/draw.h"
59#include "gui/application.h"
60#include "gui/presets.h"
62#include "iop/iop_api.h"
63
64//#include <gtk/gtk.h>
65#include <stdlib.h>
66#include "widgets/label.h"
67#include "widgets/notebook.h"
68#include "widgets/scroll_wrap.h"
69#include "gui/screen_metrics.h"
70#define LUT_ELEM 360 // gamut LUT number of elements: resolution of 1°
71#define STEPS 92 // so we test 92x92x92 combinations of RGB in [0; 1] to build the gamut LUT
72
73// Filmlight Yrg puts red at 330°, while usual HSL wheels put it at 360/0°
74// so shift in GUI only it to not confuse people. User params are always degrees,
75// pixel params are always radians.
76#define ANGLE_SHIFT -30.f
77#define DEG_TO_RAD(x) ((x + ANGLE_SHIFT) * M_PI / 180.f)
78#define RAD_TO_DEG(x) (x * 180.f / M_PI - ANGLE_SHIFT)
79
81
83{
84 DT_COLORBALANCE_SATURATION_JZAZBZ = 0, // $DESCRIPTION: "JzAzBz (2021)"
85 DT_COLORBALANCE_SATURATION_DTUCS = 1 // $DESCRIPTION: "darktable UCS (2022)"
87
89{
90 /* params of v1 */
91 float shadows_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "lift luminance"
92 float shadows_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "lift chroma"
93 float shadows_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "lift hue"
94 float midtones_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "power luminance"
95 float midtones_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "power chroma"
96 float midtones_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "power hue"
97 float highlights_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "gain luminance"
98 float highlights_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "gain chroma"
99 float highlights_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "gain hue"
100 float global_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "offset luminance"
101 float global_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "offset chroma"
102 float global_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "offset hue"
103 float shadows_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "shadows fall-off"
104 float white_fulcrum; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 1.0 $DESCRIPTION: "white fulcrum"
105 float highlights_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "highlights fall-off"
106 float chroma_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma shadows"
107 float chroma_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma highlights"
108 float chroma_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma global"
109 float chroma_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma mid-tones"
110 float saturation_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation global"
111 float saturation_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation highlights"
112 float saturation_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation mid-tones"
113 float saturation_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation shadows"
114 float hue_angle; // $MIN: -180. $MAX: 180. $DEFAULT: 0.0 $DESCRIPTION: "hue shift"
115
116 /* params of v2 */
117 float brilliance_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance global"
118 float brilliance_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance highlights"
119 float brilliance_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance mid-tones"
120 float brilliance_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance shadows"
121
122 /* params of v3 */
123 float mask_grey_fulcrum; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1845 $DESCRIPTION: "mask middle-gray fulcrum"
124
125 /* params of v4 */
126 float vibrance; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0 $DESCRIPTION: "global vibrance"
127 float grey_fulcrum; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1845 $DESCRIPTION: "contrast gray fulcrum"
128 float contrast; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0. $DESCRIPTION: "contrast"
129
130 /* params of v5 */
131 dt_iop_colorbalancrgb_saturation_t saturation_formula; // $DEFAULT: 1 $DESCRIPTION: "saturation formula"
132
133 /* add future params after this so the legacy params import can use a blind memcpy */
135
136
144
145
163
164
166{
167 // process() loads these as 16-byte aligned vectors: the type guarantees it, and keeping
168 // them first leaves no padding inside the hashed prefix.
180 float vibrance;
181 float contrast;
195 gboolean mask_display;
197 gboolean lut_inited;
198
199 // Keep runtime pointers after the hashed data prefix. Their addresses change when
200 // rebuilding a pipeline and do not describe the rendered pixel contents.
202 float *gamut_LUT;
205
207{
210
211
212const char *name()
213{
214 return _("color _balance");
215}
216
217const char *aliases()
218{
219 return _("offset power slope|cdl|color grading|contrast|chroma_highlights|hue|vibrance|saturation");
220}
221
222const char **description(struct dt_iop_module_t *self)
223{
224 return dt_iop_set_description(self, _("affect color, brightness and contrast"),
225 _("corrective or creative"),
226 _("linear, RGB, scene-referred"),
227 _("non-linear, RGB"),
228 _("non-linear, RGB, scene-referred"));
229}
230
235
237{
238 return IOP_GROUP_COLOR;
239}
240
245
253
254int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
255 const int new_version)
256{
257 if(old_version == 1 && new_version == 5)
258 {
259 typedef struct dt_iop_colorbalancergb_params_v1_t
260 {
261 float shadows_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
262 float shadows_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
263 float shadows_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
264 float midtones_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
265 float midtones_C; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
266 float midtones_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
267 float highlights_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
268 float highlights_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
269 float highlights_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
270 float global_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
271 float global_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
272 float global_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
273 float shadows_weight; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "tonal weight"
274 float white_fulcrum; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: 0.0 $DESCRIPTION: "fulcrum"
275 float highlights_weight; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "tonal weight"
276 float chroma_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
277 float chroma_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
278 float chroma_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
279 float chroma_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
280 float saturation_global; // $MIN: -5.0 $MAX: 5.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation global"
281 float saturation_highlights; // $MIN: -0.2 $MAX: 0.2 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
282 float saturation_midtones; // $MIN: -0.2 $MAX: 0.2 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
283 float saturation_shadows; // $MIN: -0.2 $MAX: 0.2 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
284 float hue_angle; // $MIN: -180. $MAX: 180. $DEFAULT: 0.0 $DESCRIPTION: "hue shift"
285 } dt_iop_colorbalancergb_params_v1_t;
286
287 // Init params with defaults
288 memcpy(new_params, self->default_params, sizeof(dt_iop_colorbalancergb_params_t));
289
290 // Copy the common part of the params struct
291 memcpy(new_params, old_params, sizeof(dt_iop_colorbalancergb_params_v1_t));
292
294 n->saturation_global /= 180.f / M_PI;
295 n->mask_grey_fulcrum = 0.1845f;
296 n->vibrance = 0.f;
297 n->grey_fulcrum = 0.1845f;
298 n->contrast = 0.f;
299 n->saturation_formula = DT_COLORBALANCE_SATURATION_JZAZBZ;
300
301 return 0;
302 }
303
304 if(old_version == 2 && new_version == 5)
305 {
306 typedef struct dt_iop_colorbalancergb_params_v2_t
307 {
308 /* params of v1 */
309 float shadows_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
310 float shadows_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
311 float shadows_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
312 float midtones_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
313 float midtones_C; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
314 float midtones_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
315 float highlights_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
316 float highlights_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
317 float highlights_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
318 float global_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
319 float global_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
320 float global_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
321 float shadows_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "shadows fall-off"
322 float white_fulcrum; // $MIN: -6.0 $MAX: 6.0 $DEFAULT: 0.0 $DESCRIPTION: "white pivot"
323 float highlights_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "highlights fall-off"
324 float chroma_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
325 float chroma_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
326 float chroma_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
327 float chroma_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
328 float saturation_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
329 float saturation_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
330 float saturation_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
331 float saturation_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
332 float hue_angle; // $MIN: -180. $MAX: 180. $DEFAULT: 0.0 $DESCRIPTION: "hue shift"
333
334 /* params of v2 */
335 float brilliance_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
336 float brilliance_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
337 float brilliance_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
338 float brilliance_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
339
340 } dt_iop_colorbalancergb_params_v2_t;
341
342 // Init params with defaults
343 memcpy(new_params, self->default_params, sizeof(dt_iop_colorbalancergb_params_t));
344
345 // Copy the common part of the params struct
346 memcpy(new_params, old_params, sizeof(dt_iop_colorbalancergb_params_v2_t));
347
349 n->mask_grey_fulcrum = 0.1845f;
350 n->vibrance = 0.f;
351 n->grey_fulcrum = 0.1845f;
352 n->contrast = 0.f;
353 n->saturation_formula = DT_COLORBALANCE_SATURATION_JZAZBZ;
354
355 return 0;
356 }
357 if(old_version == 3 && new_version == 5)
358 {
359 typedef struct dt_iop_colorbalancergb_params_v3_t
360 {
361 /* params of v1 */
362 float shadows_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
363 float shadows_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
364 float shadows_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
365 float midtones_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
366 float midtones_C; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
367 float midtones_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
368 float highlights_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
369 float highlights_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
370 float highlights_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
371 float global_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "luminance"
372 float global_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma"
373 float global_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "hue"
374 float shadows_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "shadows fall-off"
375 float white_fulcrum; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "white fulcrum"
376 float highlights_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "highlights fall-off"
377 float chroma_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
378 float chroma_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
379 float chroma_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
380 float chroma_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
381 float saturation_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
382 float saturation_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
383 float saturation_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
384 float saturation_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
385 float hue_angle; // $MIN: -180. $MAX: 180. $DEFAULT: 0.0 $DESCRIPTION: "hue shift"
386
387 /* params of v2 */
388 float brilliance_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "global"
389 float brilliance_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "highlights"
390 float brilliance_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "mid-tones"
391 float brilliance_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "shadows"
392
393 /* params of v3 */
394 float mask_grey_fulcrum; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1845 $DESCRIPTION: "middle-gray fulcrum"
395
396 } dt_iop_colorbalancergb_params_v3_t;
397
398 // Init params with defaults
399 memcpy(new_params, self->default_params, sizeof(dt_iop_colorbalancergb_params_t));
400
401 // Copy the common part of the params struct
402 memcpy(new_params, old_params, sizeof(dt_iop_colorbalancergb_params_v3_t));
403
405 n->vibrance = 0.f;
406 n->grey_fulcrum = 0.1845f;
407 n->contrast = 0.f;
408 n->saturation_formula = DT_COLORBALANCE_SATURATION_JZAZBZ;
409
410 return 0;
411 }
412 if(old_version == 4 && new_version == 5)
413 {
414 typedef struct dt_iop_colorbalancergb_params_v4_t
415 {
416 /* params of v1 */
417 float shadows_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "lift luminance"
418 float shadows_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "lift chroma"
419 float shadows_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "lift hue"
420 float midtones_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "power luminance"
421 float midtones_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "power chroma"
422 float midtones_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "power hue"
423 float highlights_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "gain luminance"
424 float highlights_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "gain chroma"
425 float highlights_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "gain hue"
426 float global_Y; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "offset luminance"
427 float global_C; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "offset chroma"
428 float global_H; // $MIN: 0.0 $MAX: 360.0 $DEFAULT: 0.0 $DESCRIPTION: "offset hue"
429 float shadows_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "shadows fall-off"
430 float white_fulcrum; // $MIN: -16.0 $MAX: 16.0 $DEFAULT: 0.0 $DESCRIPTION: "white fulcrum"
431 float highlights_weight; // $MIN: 0.0 $MAX: 3.0 $DEFAULT: 1.0 $DESCRIPTION: "highlights fall-off"
432 float chroma_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma shadows"
433 float chroma_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma highlights"
434 float chroma_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma global"
435 float chroma_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "chroma mid-tones"
436 float saturation_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation global"
437 float saturation_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation highlights"
438 float saturation_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation mid-tones"
439 float saturation_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "saturation shadows"
440 float hue_angle; // $MIN: -180. $MAX: 180. $DEFAULT: 0.0 $DESCRIPTION: "hue shift"
441
442 /* params of v2 */
443 float brilliance_global; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance global"
444 float brilliance_highlights; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance highlights"
445 float brilliance_midtones; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance mid-tones"
446 float brilliance_shadows; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0.0 $DESCRIPTION: "brilliance shadows"
447
448 /* params of v3 */
449 float mask_grey_fulcrum; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1845 $DESCRIPTION: "mask middle-gray fulcrum"
450
451 /* params of v4 */
452 float vibrance; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0 $DESCRIPTION: "global vibrance"
453 float grey_fulcrum; // $MIN: 0.0 $MAX: 1.0 $DEFAULT: 0.1845 $DESCRIPTION: "contrast gray fulcrum"
454 float contrast; // $MIN: -1.0 $MAX: 1.0 $DEFAULT: 0. $DESCRIPTION: "contrast"
455
456 } dt_iop_colorbalancergb_params_v4_t;
457
458 // Init params with defaults
459 memcpy(new_params, self->default_params, sizeof(dt_iop_colorbalancergb_params_t));
460
461 // Copy the common part of the params struct
462 memcpy(new_params, old_params, sizeof(dt_iop_colorbalancergb_params_v4_t));
463
466
467 return 0;
468 }
469
470 return 1;
471}
472
474{
475 // Note : all the elements of the params structure are scalar floats,
476 // so we can just init them all to 0.f in batch
477 // Then, only 4 params have to be manually inited to non-zero values
479 p.shadows_weight = 1.f; // DEFAULT: 1.0 DESCRIPTION: "shadows fall-off"
480 p.highlights_weight = 1.f; // DEFAULT: 1.0 DESCRIPTION: "highlights fall-off"
481 p.mask_grey_fulcrum = 0.1845f; // DEFAULT: 0.1845 DESCRIPTION: "mask middle-gray fulcrum"
482 p.grey_fulcrum = 0.1845f; // DEFAULT: 0.1845 DESCRIPTION: "contrast gray fulcrum"
483 p.white_fulcrum = 1.f;
484 p.saturation_formula = DT_COLORBALANCE_SATURATION_JZAZBZ;
485
486 // preset
487 p.chroma_global = 0.2f;
488 p.saturation_shadows = 0.1f;
489 p.saturation_midtones = 0.05f;
490 p.saturation_highlights = -0.05f;
491
492 dt_gui_presets_add_generic(_("add basic colorfulness (legacy)"), self->op, self->version(), &p, sizeof(p), 1);
493
494 p.saturation_formula = DT_COLORBALANCE_SATURATION_DTUCS;
495 p.chroma_global = 0.f;
496
497 p.saturation_global = 0.2f;
498 p.saturation_shadows = 0.30f;
499 p.saturation_midtones = 0.f;
500 p.saturation_highlights = 0.f;
501 dt_gui_presets_add_generic(_("basic colorfulness: natural skin"), self->op, self->version(), &p, sizeof(p), 1);
502
503 p.saturation_global = 0.2f;
504 p.saturation_shadows = 0.5f;
505 p.saturation_midtones = 0.f;
506 p.saturation_highlights = 0.0f;
507 dt_gui_presets_add_generic(_("basic colorfulness: vibrant colors"), self->op, self->version(), &p, sizeof(p), 1);
508
509 p.saturation_global = 0.2f;
510 p.saturation_shadows = 0.25f;
511 p.saturation_midtones = 0.f;
512 p.saturation_highlights = 0.0f;
513 dt_gui_presets_add_generic(_("basic colorfulness: standard"), self->op, self->version(), &p, sizeof(p), 1);
514
515 // Duplicate and alias for auto-apply in develop.c
516 dt_gui_presets_add_generic(_("scene-referred default"), self->op, self->version(), &p, sizeof(p), 1);
517 dt_gui_presets_update_ldr(_("scene-referred default"), self->op, self->version(), FOR_RAW);
518}
519
520
521__OMP_DECLARE_SIMD__(aligned(output, output_comp: 16) uniform(shadows_weight, midtones_weight, highlights_weight))
522static inline void opacity_masks(const float x,
523 const float shadows_weight, const float highlights_weight,
524 const float midtones_weight, const float mask_grey_fulcrum,
525 dt_aligned_pixel_t output, dt_aligned_pixel_t output_comp)
526{
527 const float x_offset = (x - mask_grey_fulcrum);
528 const float x_offset_norm = x_offset / mask_grey_fulcrum;
529 const float alpha = 1.f / (1.f + expf(x_offset_norm * shadows_weight)); // opacity of shadows
530 const float beta = 1.f / (1.f + expf(-x_offset_norm * highlights_weight)); // opacity of highlights
531 const float alpha_comp = 1.f - alpha;
532 const float beta_comp = 1.f - beta;
533 const float gamma = expf(-sqf(x_offset) * midtones_weight / 4.f) * sqf(alpha_comp) * sqf(beta_comp) * 8.f; // opacity of midtones
534 const float gamma_comp = 1.f - gamma;
535
536 output[0] = alpha;
537 output[1] = gamma;
538 output[2] = beta;
539 output[3] = 0.f;
540
541 if(output_comp)
542 {
543 output_comp[0] = alpha_comp;
544 output_comp[1] = gamma_comp;
545 output_comp[2] = beta_comp;
546 output_comp[3] = 0.f;
547 }
548}
549
550static inline float soft_clip(const float x, const float soft_threshold, const float hard_threshold)
551{
552 // use an exponential soft clipping above soft_threshold
553 // hard threshold must be > soft threshold
554 const float norm = hard_threshold - soft_threshold;
555 return (x > soft_threshold) ? soft_threshold + (1.f - expf(-(x - soft_threshold) / norm)) * norm : x;
556}
557
558
559static inline float lookup_gamut(const float *const gamut_lut, const float x)
560{
561 // WARNING : x should be between [-pi ; pi ], which is the default output of atan2 anyway
562
563 // convert in LUT coordinate
564 const float x_test = (LUT_ELEM - 1) * (x + M_PI_F) / (2.f * M_PI_F);
565
566 // find the 2 closest integer coordinates (next/previous)
567 float x_prev = floorf(x_test);
568 float x_next = ceilf(x_test);
569
570 // get the 2 closest LUT elements at integer coordinates
571 // cycle on the hue ring if out of bounds
572 int xi = (int)x_prev;
573 if(xi < 0) xi = LUT_ELEM - 1;
574 else if(xi > LUT_ELEM - 1) xi = 0;
575
576 int xii = (int)x_next;
577 if(xii < 0) xii = LUT_ELEM - 1;
578 else if(xii > LUT_ELEM - 1) xii = 0;
579
580 // fetch the corresponding y values
581 const float y_prev = gamut_lut[xi];
582 const float y_next = gamut_lut[xii];
583
584 // assume that we are exactly on an integer LUT element
585 float out = y_prev;
586
587 if(x_next != x_prev)
588 // we are between 2 LUT elements : do linear interpolation
589 // actually, we only add the slope term on the previous one
590 out += (x_test - x_prev) * (y_next - y_prev) / (x_next - x_prev);
591
592 return out;
593}
594
595
597int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
598 void *const ovoid)
599{
600 const dt_iop_roi_t *const roi_out = &piece->roi_out;
602 const struct dt_iop_order_iccprofile_info_t *const work_profile
604 if(IS_NULL_PTR(work_profile)) return 0; // no point
605
606 // work profile can't be fetched in commit_params since it is not yet initialised
607 // work_profile->matrix_in === RGB_to_XYZ
608 // work_profile->matrix_out === XYZ_to_RGB
609
610 // Premultiply the input matrices
611
612 /* What we do here is equivalent to :
613
614 // go to CIE 1931 XYZ 2° D50
615 dot_product(RGB, RGB_to_XYZ, XYZ_D50); // matrice product
616
617 // chroma adapt D50 to D65
618 XYZ_D50_to_65(XYZ_D50, XYZ_D65); // matrice product
619
620 // go to CIE 2006 LMS
621 XYZ_to_LMS(XYZ_D65, LMS); // matrice product
622
623 * so we pre-multiply the 3 conversion matrices and operate only one matrix product
624 */
625 dt_colormatrix_t input_matrix;
626 dt_colormatrix_t output_matrix;
627 dt_colormatrix_t input_matrix_transposed;
628 dt_colormatrix_t output_matrix_transposed;
629
630 dt_colormatrix_mul(output_matrix, XYZ_D50_to_D65_CAT16, work_profile->matrix_in); // output_matrix used as temp buffer
631 dt_colormatrix_mul(input_matrix, XYZ_D65_to_LMS_2006_D65, output_matrix);
632 transpose_3xSSE(input_matrix, input_matrix_transposed);
633
634 // Premultiply the output matrix
635
636 /* What we do here is equivalent to :
637 XYZ_D65_to_50(XYZ_D65, XYZ_D50); // matrix product
638 dot_product(XYZ_D50, XYZ_to_RGB, pix_out); // matrix product
639 */
640
641 dt_colormatrix_mul(output_matrix, work_profile->matrix_out, XYZ_D65_to_D50_CAT16);
642 transpose_3xSSE(output_matrix, output_matrix_transposed);
643
644 const float *const restrict in = __builtin_assume_aligned(((const float *const restrict)ivoid), 64);
645 float *const restrict out = __builtin_assume_aligned(((float *const restrict)ovoid), 64);
646 const float *const restrict gamut_LUT = __builtin_assume_aligned(((const float *const restrict)d->gamut_LUT), 64);
647
648 const float *const restrict global = __builtin_assume_aligned((const float *const restrict)d->global, 16);
649 const float *const restrict highlights = __builtin_assume_aligned((const float *const restrict)d->highlights, 16);
650 const float *const restrict shadows = __builtin_assume_aligned((const float *const restrict)d->shadows, 16);
651 const float *const restrict midtones = __builtin_assume_aligned((const float *const restrict)d->midtones, 16);
652
653 const float *const restrict chroma = __builtin_assume_aligned((const float *const restrict)d->chroma, 16);
654 const float *const restrict saturation = __builtin_assume_aligned((const float *const restrict)d->saturation, 16);
655 const float *const restrict brilliance = __builtin_assume_aligned((const float *const restrict)d->brilliance, 16);
656
657 const dt_aligned_pixel_simd_t input0 = dt_colormatrix_row_to_simd(input_matrix_transposed, 0);
658 const dt_aligned_pixel_simd_t input1 = dt_colormatrix_row_to_simd(input_matrix_transposed, 1);
659 const dt_aligned_pixel_simd_t input2 = dt_colormatrix_row_to_simd(input_matrix_transposed, 2);
660 const dt_aligned_pixel_simd_t output0 = dt_colormatrix_row_to_simd(output_matrix_transposed, 0);
661 const dt_aligned_pixel_simd_t output1 = dt_colormatrix_row_to_simd(output_matrix_transposed, 1);
662 const dt_aligned_pixel_simd_t output2 = dt_colormatrix_row_to_simd(output_matrix_transposed, 2);
663
664 const dt_aligned_pixel_simd_t global_v = dt_load_simd_aligned(global);
665 const dt_aligned_pixel_simd_t highlights_v = dt_load_simd_aligned(highlights);
666 const dt_aligned_pixel_simd_t shadows_v = dt_load_simd_aligned(shadows);
667 const dt_aligned_pixel_simd_t midtones_v = dt_load_simd_aligned(midtones);
668 const dt_aligned_pixel_simd_t jz_ai0 = dt_colormatrix_row_to_simd(AI_transposed, 0);
669 const dt_aligned_pixel_simd_t jz_ai1 = dt_colormatrix_row_to_simd(AI_transposed, 1);
670 const dt_aligned_pixel_simd_t jz_ai2 = dt_colormatrix_row_to_simd(AI_transposed, 2);
671
672 const gboolean mask_display = d->mask_display;
673
674 const dt_aligned_pixel_simd_t checker_color_1_v = dt_load_simd(d->checker_color_1);
675 const dt_aligned_pixel_simd_t checker_color_2_v = dt_load_simd(d->checker_color_2);
676 const size_t checker_1
677 = (mask_display) ? MAX((size_t)DT_PIXEL_APPLY_DPI(d->checker_size), 2) : 0;
678 const size_t checker_2 = 2 * checker_1;
679 const size_t npixels = (size_t)roi_out->width * roi_out->height;
680 const size_t out_width = roi_out->width;
681
682 const float L_white = Y_to_dt_UCS_L_star(d->white_fulcrum);
683 const float DT_ALIGNED_PIXEL hue_rotation_matrix[2][2] = {
684 { cosf(d->hue_angle), -sinf(d->hue_angle) },
685 { sinf(d->hue_angle), cosf(d->hue_angle) },
686 };
688 for(size_t idx = 0; idx < npixels; idx++)
689 {
690 const size_t k = idx * 4;
691 const float *const restrict pix_in = __builtin_assume_aligned(in + k, 16);
692 float *const restrict pix_out = __builtin_assume_aligned(out + k, 16);
693 const dt_aligned_pixel_simd_t pix_in_v = dt_load_simd_aligned(pix_in);
694
695 dt_aligned_pixel_simd_t RGB_v = dt_simd_max_zero(pix_in_v);
696 RGB_v[3] = 0.f;
697 const dt_aligned_pixel_simd_t LMS_v = dt_mat3x4_mul_vec4(RGB_v, input0, input1, input2);
698 dt_aligned_pixel_simd_t Yrg_v = LMS_to_Yrg_simd(LMS_v);
699 Yrg_v[0] = MAX(Yrg_v[0], 0.f);
700 dt_aligned_pixel_t opacities = { 0.f };
701 dt_aligned_pixel_t opacities_comp = { 0.f };
702 opacity_masks(powf(Yrg_v[0], 0.4101205819200422f), d->shadows_weight, d->highlights_weight,
703 d->midtones_weight, d->mask_grey_fulcrum, opacities, opacities_comp);
704
705 // Rotate the centered chromaticity plane directly so we keep the hue shift as a 2D transform
706 // and only rebuild polar hue/chroma once, after the saturation/vibrance scaling.
707 const float r_centered = Yrg_v[1] - 0.21902143f;
708 const float g_centered = Yrg_v[2] - 0.54371398f;
709 const float r_rotated = hue_rotation_matrix[0][0] * r_centered + hue_rotation_matrix[0][1] * g_centered;
710 const float g_rotated = hue_rotation_matrix[1][0] * r_centered + hue_rotation_matrix[1][1] * g_centered;
711 const float chroma_in = dt_fast_hypotf(g_rotated, r_rotated);
712 const float inv_chroma_in = (chroma_in > 0.f) ? 1.f / chroma_in : 0.f;
713 const float cos_h = r_rotated * inv_chroma_in;
714 const float sin_h = g_rotated * inv_chroma_in;
715 const float chroma_boost = d->chroma_global + scalar_product(opacities, chroma);
716 const float vibrance = d->vibrance * (1.0f - powf(chroma_in, fabsf(d->vibrance)));
717 const float chroma_factor = MAX(1.f + chroma_boost + vibrance, 0.f);
718 float chroma_out = chroma_in * chroma_factor;
719
720 // Clamp the rotated chroma before rebuilding Yrg so we avoid a second sin/cos round-trip.
721 const float r_shifted = chroma_out * cos_h + 0.21902143f;
722 const float g_shifted = chroma_out * sin_h + 0.54371398f;
723 if(r_shifted < 0.f)
724 {
725 const float r_limit = -0.21902143f / cos_h;
726 chroma_out = MIN(r_limit, chroma_out);
727 }
728 if(g_shifted < 0.f)
729 {
730 const float g_limit = -0.54371398f / sin_h;
731 chroma_out = MIN(g_limit, chroma_out);
732 }
733 if(r_shifted + g_shifted > 1.f)
734 {
735 const float sum_limit = (1.f - 0.21902143f - 0.54371398f) / (cos_h + sin_h);
736 chroma_out = MIN(sum_limit, chroma_out);
737 }
738 Yrg_v[1] = chroma_out * cos_h + 0.21902143f;
739 Yrg_v[2] = chroma_out * sin_h + 0.54371398f;
740
741 // Go to LMS
742 dt_aligned_pixel_simd_t LMS_work_v = Yrg_to_LMS_simd(Yrg_v);
743
744 // Go to Filmlight RGB
745 RGB_v = LMS_to_gradingRGB_simd(LMS_work_v);
746
747 // Color balance
748 RGB_v += global_v;
749 const dt_aligned_pixel_simd_t slopes_v
750 = opacities_comp[2] * (opacities_comp[0] + opacities[0] * shadows_v) + opacities[2] * highlights_v;
751 RGB_v *= slopes_v;
752 RGB_v[3] = 0.f;
753
754 // highlights, shadows : 2 slopes with masking
755 // factorization of : (RGB[c] * (1.f - alpha) + RGB[c] * d->shadows[c] * alpha) * (1.f - beta) + RGB[c] * d->highlights[c] * beta;
756 const dt_aligned_pixel_simd_t RGB_abs_v = dt_simd_abs(RGB_v) / d->white_fulcrum;
757
758 // midtones : power with sign preservation
759 RGB_v = dt_simd_copysign(dt_simd_pow(RGB_abs_v, midtones_v) * d->white_fulcrum, RGB_v);
760 RGB_v[3] = 0.f;
761
762 // for the non-linear ops we need to go in Yrg again because RGB doesn't preserve color
763 LMS_work_v = gradingRGB_to_LMS_simd(RGB_v);
764 Yrg_v = LMS_to_Yrg_simd(LMS_work_v);
765
766 // Y midtones power (gamma)
767 Yrg_v[0] = powf(MAX(Yrg_v[0] / d->white_fulcrum, 0.f), d->midtones_Y) * d->white_fulcrum;
768
769 // Y fulcrumed contrast
770 Yrg_v[0] = d->grey_fulcrum * powf(Yrg_v[0] / d->grey_fulcrum, d->contrast);
771
772 LMS_work_v = Yrg_to_LMS_simd(Yrg_v);
773 dt_aligned_pixel_simd_t XYZ_D65_v = LMS_to_XYZ_simd(LMS_work_v);
774
775 if(d->saturation_formula == DT_COLORBALANCE_SATURATION_JZAZBZ)
776 {
777 // Perceptual color adjustments
778 dt_aligned_pixel_simd_t Jab_v = dt_XYZ_2_JzAzBz_simd(XYZ_D65_v);
779
780 // Convert to JCh
781 float JC[2] = { Jab_v[0], dt_fast_hypotf(Jab_v[1], Jab_v[2]) }; // brightness/chroma vector
782 const float h = atan2f(Jab_v[2], Jab_v[1]); // hue : (a, b) angle
783 const float inv_chroma = (JC[1] > 0.f) ? 1.f / JC[1] : 0.f;
784 const float cos_H = Jab_v[1] * inv_chroma;
785 const float sin_H = Jab_v[2] * inv_chroma;
786
787 // Project JC onto S, the saturation eigenvector, with orthogonal vector O.
788 // Note : O should be = (C * cosf(T) - J * sinf(T)) = 0 since S is the eigenvector,
789 // so we add the chroma projected along the orthogonal axis to get some control value
790 const float T = atan2f(JC[1], JC[0]); // angle of the eigenvector over the hue plane
791 const float sin_T = sinf(T);
792 const float cos_T = cosf(T);
793 const float DT_ALIGNED_PIXEL M_rot_dir[2][2] = { { cos_T, sin_T },
794 { -sin_T, cos_T } };
795 const float DT_ALIGNED_PIXEL M_rot_inv[2][2] = { { cos_T, -sin_T },
796 { sin_T, cos_T } };
797 float SO[2];
798
799 // brilliance & Saturation : mix of chroma and luminance
800 const float boosts[2] = { 1.f + d->brilliance_global + scalar_product(opacities, brilliance), // move in S direction
801 d->saturation_global + scalar_product(opacities, saturation) }; // move in O direction
802
803 SO[0] = JC[0] * M_rot_dir[0][0] + JC[1] * M_rot_dir[0][1];
804 SO[1] = SO[0] * MIN(MAX(T * boosts[1], -T), DT_M_PI_F / 2.f - T);
805 SO[0] = MAX(SO[0] * boosts[0], 0.f);
806
807 // Project back to JCh, that is rotate back of -T angle
808 JC[0] = MAX(SO[0] * M_rot_inv[0][0] + SO[1] * M_rot_inv[0][1], 0.f);
809 JC[1] = MAX(SO[0] * M_rot_inv[1][0] + SO[1] * M_rot_inv[1][1], 0.f);
810
811 // Gamut mapping
812 const float out_max_sat_h = lookup_gamut(gamut_LUT, h);
813 // if JC[0] == 0.f, the saturation / luminance ratio is infinite - assign the largest practical value we have
814 const float sat = (JC[0] > 0.f) ? soft_clip(JC[1] / JC[0], 0.8f * out_max_sat_h, out_max_sat_h)
815 : out_max_sat_h;
816 const float max_C_at_sat = JC[0] * sat;
817 // if sat == 0.f, the chroma is zero - assign the original luminance because there's no need to gamut map
818 const float max_J_at_sat = (sat > 0.f) ? JC[1] / sat : JC[0];
819 JC[0] = (JC[0] + max_J_at_sat) / 2.f;
820 JC[1] = (JC[1] + max_C_at_sat) / 2.f;
821
822 // Gamut-clip in Jch at constant hue and lightness,
823 // e.g. find the max chroma available at current hue that doesn't
824 // yield negative L'M'S' values, which will need to be clipped during conversion
825 const float d0 = 1.6295499532821566e-11f;
826 const float dd = -0.56f;
827 float Iz = JC[0] + d0;
828 Iz /= (1.f + dd - dd * Iz);
829 Iz = MAX(Iz, 0.f);
830
831 const dt_colormatrix_t AI
832 = { { 1.0f, 0.1386050432715393f, 0.0580473161561189f, 0.0f },
833 { 1.0f, -0.1386050432715393f, -0.0580473161561189f, 0.0f },
834 { 1.0f, -0.0960192420263190f, -0.8118918960560390f, 0.0f } };
835
836 // Do a test conversion to L'M'S'
837 const dt_aligned_pixel_simd_t IzAzBz_v = { Iz, JC[1] * cos_H, JC[1] * sin_H, 0.f };
838 const dt_aligned_pixel_simd_t LMS_test_v = dt_mat3x4_mul_vec4(IzAzBz_v, jz_ai0, jz_ai1, jz_ai2);
839
840 // Clip chroma
841 float max_C = JC[1];
842 if(LMS_test_v[0] < 0.f)
843 max_C = MIN(-Iz / (AI[0][1] * cos_H + AI[0][2] * sin_H), max_C);
844
845 if(LMS_test_v[1] < 0.f)
846 max_C = MIN(-Iz / (AI[1][1] * cos_H + AI[1][2] * sin_H), max_C);
847
848 if(LMS_test_v[2] < 0.f)
849 max_C = MIN(-Iz / (AI[2][1] * cos_H + AI[2][2] * sin_H), max_C);
850
851 // Project back to JzAzBz for real
852 const dt_aligned_pixel_simd_t Jab_out_v = { JC[0], max_C * cos_H, max_C * sin_H, 0.f };
853 XYZ_D65_v = dt_JzAzBz_2_XYZ_simd(Jab_out_v);
854 }
855 else
856 {
857 dt_aligned_pixel_simd_t xyY_v = dt_XYZ_to_xyY_simd(XYZ_D65_v);
858 dt_aligned_pixel_simd_t JCH_v = xyY_to_dt_UCS_JCH_simd(xyY_v, L_white);
859 dt_aligned_pixel_simd_t HCB_v = dt_UCS_JCH_to_HCB_simd(JCH_v);
860
861 const float radius = dt_fast_hypotf(HCB_v[1], HCB_v[2]);
862 const float sin_T = (radius > 0.f) ? HCB_v[1] / radius : 0.f;
863 const float cos_T = (radius > 0.f) ? HCB_v[2] / radius : 0.f;
864 const float DT_ALIGNED_PIXEL M_rot_inv[2][2] = { { cos_T, sin_T }, { -sin_T, cos_T } };
865
866 float P = MAX(HCB_v[1], FLT_MIN);
867 float W = sin_T * HCB_v[1] + cos_T * HCB_v[2];
868
869 const dt_aligned_pixel_simd_t sat_bri_v = dt_simd_max_zero((dt_aligned_pixel_simd_t){
870 1.f + d->saturation_global + scalar_product(opacities, saturation),
871 1.f + d->brilliance_global + scalar_product(opacities, brilliance),
872 0.f, 0.f
873 });
874 float a = sat_bri_v[0];
875 const float b = sat_bri_v[1];
876
877 const float max_a = dt_fast_hypotf(P, W) / P;
878 a = soft_clip(a, 0.5f * max_a, max_a);
879
880 const float P_prime = (a - 1.f) * P;
881 const float W_prime = sqrtf(sqf(P) * (1.f - sqf(a)) + sqf(W)) * b;
882
883 HCB_v[1] = MAX(M_rot_inv[0][0] * P_prime + M_rot_inv[0][1] * W_prime, 0.f);
884 HCB_v[2] = MAX(M_rot_inv[1][0] * P_prime + M_rot_inv[1][1] * W_prime, 0.f);
885
886 JCH_v = dt_UCS_HCB_to_JCH_simd(HCB_v);
887 const float max_colorfulness = lookup_gamut(gamut_LUT, JCH_v[2]);
888 const float max_chroma = 15.932993652962535f * powf(JCH_v[0] * L_white, 0.6523997524738018f)
889 * powf(max_colorfulness, 0.6007557017508491f) / L_white;
890 const dt_aligned_pixel_simd_t JCH_gamut_boundary_v = { JCH_v[0], max_chroma, JCH_v[2], 0.f };
891 const dt_aligned_pixel_simd_t HSB_gamut_boundary_v = dt_UCS_JCH_to_HSB_simd(JCH_gamut_boundary_v);
892 dt_aligned_pixel_simd_t HSB_v = { HCB_v[0], (HCB_v[2] > 0.f) ? HCB_v[1] / HCB_v[2] : 0.f, HCB_v[2], 0.f };
893 HSB_v[1] = soft_clip(HSB_v[1], 0.8f * HSB_gamut_boundary_v[1], HSB_gamut_boundary_v[1]);
894 JCH_v = dt_UCS_HSB_to_JCH_simd(HSB_v);
895 xyY_v = dt_UCS_JCH_to_xyY_simd(JCH_v, L_white);
896 XYZ_D65_v = dt_xyY_to_XYZ_simd(xyY_v);
897 }
898
899 dt_aligned_pixel_simd_t pix_out_v = dt_mat3x4_mul_vec4(XYZ_D65_v, output0, output1, output2);
900 if(mask_display)
901 {
902 const size_t i = idx / out_width;
903 const size_t j = idx - i * out_width;
904
905 dt_aligned_pixel_simd_t color_v;
906 if(i % checker_1 < i % checker_2)
907 {
908 if(j % checker_1 < j % checker_2) color_v = checker_color_2_v;
909 else color_v = checker_color_1_v;
910 }
911 else
912 {
913 if(j % checker_1 < j % checker_2) color_v = checker_color_1_v;
914 else color_v = checker_color_2_v;
915 }
916
917 const float opacity = opacities[d->mask_type];
918 const float opacity_comp = 1.0f - opacity;
919
920 dt_aligned_pixel_simd_t image_v = dt_simd_max_zero(pix_out_v);
921 if(d->mask_preview_black_and_white)
922 {
923 const float gray = 0.3f * image_v[0] + 0.59f * image_v[1] + 0.11f * image_v[2];
924 image_v[0] = image_v[1] = image_v[2] = gray;
925 }
926 pix_out_v = opacity_comp * color_v + opacity * image_v;
927 pix_out_v[3] = 1.0f;
928 }
929 else
930 {
931 pix_out_v = dt_simd_max_zero(pix_out_v);
932 pix_out_v[3] = pix_in_v[3];
933 }
934 dt_store_simd_nontemporal(pix_out, pix_out_v);
935 }
936 dt_omploop_sfence(); // ensure that nontemporal writes complete before the caller reads output
937
938 return 0;
939}
940
941
942#if HAVE_OPENCL
943int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
944{
945 const dt_iop_roi_t *const roi_in = &piece->roi_in;
948
949 cl_int err = -999;
950
951 const int devid = pipe->devid;
952 const int width = roi_in->width;
953 const int height = roi_in->height;
954
955 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
956
957 // Get working color profile
958 const struct dt_iop_order_iccprofile_info_t *const work_profile
960 if(IS_NULL_PTR(work_profile)) return err; // no point
961
962 cl_mem dev_profile_info = NULL;
963 cl_mem dev_profile_lut = NULL;
965 cl_float *profile_lut_cl = NULL;
966
967 cl_mem input_matrix_cl = NULL;
968 cl_mem output_matrix_cl = NULL;
969 cl_mem gamut_LUT = NULL;
970
971 err = dt_ioppr_build_iccprofile_params_cl(work_profile, devid, &profile_info_cl, &profile_lut_cl,
972 &dev_profile_info, &dev_profile_lut);
973 if(err != CL_SUCCESS) goto error;
974
975 // repack the matrices as flat AVX2-compliant matrice
976 // work profile can't be fetched in commit_params since it is not yet initialised
977 // work_profile->matrix_in === RGB_to_XYZ
978 // work_profile->matrix_out === XYZ_to_RGB
979
980 // Premultiply the input matrices
981
982 /* What we do here is equivalent to :
983
984 // go to CIE 1931 XYZ 2° D50
985 dot_product(RGB, RGB_to_XYZ, XYZ_D50); // matrice product
986
987 // chroma adapt D50 to D65
988 XYZ_D50_to_65(XYZ_D50, XYZ_D65); // matrice product
989
990 // go to CIE 2006 LMS
991 XYZ_to_LMS(XYZ_D65, LMS); // matrice product
992
993 * so we pre-multiply the 3 conversion matrices and operate only one matrix product
994 */
995 dt_colormatrix_t input_matrix;
996 dt_colormatrix_t output_matrix;
997
998 dt_colormatrix_mul(output_matrix, XYZ_D50_to_D65_CAT16, work_profile->matrix_in); // output_matrix used as temp buffer
999 dt_colormatrix_mul(input_matrix, XYZ_D65_to_LMS_2006_D65, output_matrix);
1000
1001 // Premultiply the output matrix
1002
1003 /* What we do here is equivalent to :
1004 XYZ_D65_to_50(XYZ_D65, XYZ_D50); // matrix product
1005 dot_product(XYZ_D50, XYZ_to_RGB, pix_out); // matrix product
1006 */
1007
1008 dt_colormatrix_mul(output_matrix, work_profile->matrix_out, XYZ_D65_to_D50_CAT16);
1009
1010 float input_matrix_3x4[12];
1011 float output_matrix_3x4[12];
1012 pack_3xSSE_to_3x4(input_matrix, input_matrix_3x4);
1013 pack_3xSSE_to_3x4(output_matrix, output_matrix_3x4);
1014
1015 input_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(input_matrix_3x4), input_matrix_3x4);
1016 output_matrix_cl = dt_opencl_copy_host_to_device_constant(devid, sizeof(output_matrix_3x4), output_matrix_3x4);
1017
1018 // Send gamut LUT to GPU
1019 gamut_LUT = dt_opencl_copy_host_to_device(devid, d->gamut_LUT, LUT_ELEM, 1, sizeof(float));
1020
1021 const int mask_display = d->mask_display;
1022 const int checker_1
1023 = (mask_display) ? MAX(DT_PIXEL_APPLY_DPI(d->checker_size), 2) : 0;
1024 const int checker_2 = 2 * checker_1;
1025 const int mask_type = d->mask_type;
1026
1027 const float L_white = Y_to_dt_UCS_L_star(d->white_fulcrum);
1028 const cl_float2 hue_rotation_row_0 = {{ cosf(d->hue_angle), -sinf(d->hue_angle) }};
1029 const cl_float2 hue_rotation_row_1 = {{ sinf(d->hue_angle), cosf(d->hue_angle) }};
1030
1031 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 0, sizeof(cl_mem), (void *)&dev_in);
1032 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 1, sizeof(cl_mem), (void *)&dev_out);
1033 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 2, sizeof(int), (void *)&width);
1034 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 3, sizeof(int), (void *)&height);
1035 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 4, sizeof(cl_mem), (void *)&dev_profile_info);
1036 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 5, sizeof(cl_mem), (void *)&input_matrix_cl);
1037 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 6, sizeof(cl_mem), (void *)&output_matrix_cl);
1038 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 7, sizeof(cl_mem), (void *)&gamut_LUT);
1039 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 8, sizeof(float), (void *)&d->shadows_weight);
1040 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 9, sizeof(float), (void *)&d->highlights_weight);
1041 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 10, sizeof(float), (void *)&d->midtones_weight);
1042 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 11, sizeof(float), (void *)&d->mask_grey_fulcrum);
1043 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 12, sizeof(cl_float2), (void *)&hue_rotation_row_0);
1044 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 13, sizeof(cl_float2), (void *)&hue_rotation_row_1);
1045 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 14, sizeof(float), (void *)&d->chroma_global);
1046 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 15, 4 * sizeof(float), (void *)&d->chroma);
1047 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 16, sizeof(float), (void *)&d->vibrance);
1048 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 17, 4 * sizeof(float), (void *)&d->global);
1049 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 18, 4 * sizeof(float), (void *)&d->shadows);
1050 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 19, 4 * sizeof(float), (void *)&d->highlights);
1051 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 20, 4 * sizeof(float), (void *)&d->midtones);
1052 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 21, sizeof(float), (void *)&d->white_fulcrum);
1053 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 22, sizeof(float), (void *)&d->midtones_Y);
1054 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 23, sizeof(float), (void *)&d->grey_fulcrum);
1055 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 24, sizeof(float), (void *)&d->contrast);
1056 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 25, sizeof(float), (void *)&d->brilliance_global);
1057 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 26, 4 * sizeof(float), (void *)&d->brilliance);
1058 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 27, sizeof(float), (void *)&d->saturation_global);
1059 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 28, 4 * sizeof(float), (void *)&d->saturation);
1060 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 29, sizeof(int), (void *)&mask_display);
1061 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 30, sizeof(int), (void *)&mask_type);
1062 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 31, sizeof(int), (void *)&checker_1);
1063 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 32, sizeof(int), (void *)&checker_2);
1064 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 33, 4 * sizeof(float), (void *)&d->checker_color_1);
1065 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 34, 4 * sizeof(float), (void *)&d->checker_color_2);
1066 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 35, sizeof(float), (void *)&L_white);
1067 dt_opencl_set_kernel_arg(devid, gd->kernel_colorbalance_rgb, 36, sizeof(dt_iop_colorbalancrgb_saturation_t), (void *)&d->saturation_formula);
1068
1070 if(err != CL_SUCCESS) goto error;
1071
1072 // cleanup and exit on success
1073 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1074 dt_opencl_release_mem_object(input_matrix_cl);
1075 dt_opencl_release_mem_object(output_matrix_cl);
1077 return TRUE;
1078
1079error:
1080 dt_ioppr_free_iccprofile_params_cl(&profile_info_cl, &profile_lut_cl, &dev_profile_info, &dev_profile_lut);
1081 dt_opencl_release_mem_object(input_matrix_cl);
1082 dt_opencl_release_mem_object(output_matrix_cl);
1084 dt_print(DT_DEBUG_OPENCL, "[opencl_colorbalancergb] couldn't enqueue kernel! %d\n", err);
1085 return FALSE;
1086}
1087
1089{
1090 const int program = 8; // extended.cl in programs.conf
1093
1094 module->data = gd;
1095 gd->kernel_colorbalance_rgb = dt_opencl_create_kernel(program, "colorbalancergb");
1096}
1097
1098
1105#endif
1106
1107
1108static inline float Delta_H(const float h_1, const float h_2)
1109{
1110 // Compute the difference between 2 angles
1111 // and force the result in [-pi; pi] radians
1112 float diff = h_1 - h_2;
1113 diff += (diff < -M_PI_F) ? 2.f * M_PI_F : 0.f;
1114 diff -= (diff > M_PI_F) ? 2.f * M_PI_F : 0.f;
1115 return diff;
1116}
1117
1118
1121{
1126
1127 // Synchronize the global mask-preview appearance into this node so normal processing never reads GUI config.
1128 d->checker_color_1[0] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/red"), 0.f, 1.f);
1129 d->checker_color_1[1] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/green"), 0.f, 1.f);
1130 d->checker_color_1[2] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker1/blue"), 0.f, 1.f);
1131
1132 d->checker_color_2[0] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/red"), 0.f, 1.f);
1133 d->checker_color_2[1] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/green"), 0.f, 1.f);
1134 d->checker_color_2[2] = CLAMP(dt_conf_get_float("plugins/darkroom/colorbalancergb/checker2/blue"), 0.f, 1.f);
1135 d->checker_color_2[3] = 1.f;
1136 d->checker_size = MAX(dt_conf_get_int("plugins/darkroom/colorbalancergb/checker/size"), 2);
1137 d->mask_preview_black_and_white
1138 = dt_conf_get_bool("plugins/darkroom/colorbalancergb/mask_preview/greyscaled");
1139 // The checker alpha is unused by the preview blend, so keep the OpenCL kernel signature stable
1140 // and transport the global grayscale option through that existing argument.
1141 d->checker_color_1[3] = d->mask_preview_black_and_white;
1142 d->mask_display = pipe->type == DT_DEV_PIXELPIPE_FULL
1143 && self->dev->gui_attached
1144 && !IS_NULL_PTR(gui)
1145 && gui->mask_display;
1146 d->mask_type = d->mask_display ? gui->mask_type : MASK_NONE;
1147
1148 d->vibrance = p->vibrance;
1149 d->contrast = 1.0f + p->contrast; // that limits the user param range to [-1, 1], but it seems enough
1150 d->grey_fulcrum = p->grey_fulcrum;
1151
1152 d->chroma_global = p->chroma_global;
1153 d->chroma[0] = p->chroma_shadows;
1154 d->chroma[1] = p->chroma_midtones;
1155 d->chroma[2] = p->chroma_highlights;
1156 d->chroma[3] = 0.f;
1157
1158 d->saturation_global = p->saturation_global;
1159 d->saturation[0] = p->saturation_shadows;
1160 d->saturation[1] = p->saturation_midtones;
1161 d->saturation[2] = p->saturation_highlights;
1162 d->saturation[3] = 0.f;
1163
1164 d->brilliance_global = p->brilliance_global;
1165 d->brilliance[0] = p->brilliance_shadows;
1166 d->brilliance[1] = p->brilliance_midtones;
1167 d->brilliance[2] = p->brilliance_highlights;
1168 d->brilliance[3] = 0.f;
1169
1170 d->hue_angle = M_PI * p->hue_angle / 180.f;
1171
1172 // measure the grading RGB of a pure white
1173 const dt_aligned_pixel_t Ych_norm = { 1.f, 0.f, 0.f, 0.f };
1174 dt_aligned_pixel_t RGB_norm = { 0.f };
1175 Ych_to_gradingRGB(Ych_norm, RGB_norm);
1176
1177 // global
1178 {
1179 dt_aligned_pixel_t Ych = { 1.f, p->global_C, DEG_TO_RAD(p->global_H), 0.f };
1180 Ych_to_gradingRGB(Ych, d->global);
1181 for(size_t c = 0; c < 3; c++) d->global[c] = (d->global[c] - RGB_norm[c]) + RGB_norm[c] * p->global_Y;
1182 }
1183
1184 // shadows
1185 {
1186 dt_aligned_pixel_t Ych = { 1.f, p->shadows_C, DEG_TO_RAD(p->shadows_H), 0.f };
1187 Ych_to_gradingRGB(Ych, d->shadows);
1188 for(size_t c = 0; c < 3; c++) d->shadows[c] = 1.f + (d->shadows[c] - RGB_norm[c]) + p->shadows_Y;
1189 d->shadows_weight = 2.f + p->shadows_weight * 2.f;
1190 }
1191
1192 // highlights
1193 {
1194 dt_aligned_pixel_t Ych = { 1.f, p->highlights_C, DEG_TO_RAD(p->highlights_H), 0.f };
1195 Ych_to_gradingRGB(Ych, d->highlights);
1196 for(size_t c = 0; c < 3; c++) d->highlights[c] = 1.f + (d->highlights[c] - RGB_norm[c]) + p->highlights_Y;
1197 d->highlights_weight = 2.f + p->highlights_weight * 2.f;
1198 }
1199
1200 // midtones
1201 {
1202 dt_aligned_pixel_t Ych = { 1.f, p->midtones_C, DEG_TO_RAD(p->midtones_H), 0.f };
1203 Ych_to_gradingRGB(Ych, d->midtones);
1204 for(size_t c = 0; c < 3; c++) d->midtones[c] = 1.f / (1.f + (d->midtones[c] - RGB_norm[c]));
1205 d->midtones_Y = 1.f / (1.f + p->midtones_Y);
1206 d->white_fulcrum = exp2f(p->white_fulcrum);
1207 d->midtones_weight = sqf(d->shadows_weight) * sqf(d->highlights_weight) /
1208 (sqf(d->shadows_weight) + sqf(d->highlights_weight));
1209 d->mask_grey_fulcrum = powf(p->mask_grey_fulcrum, 0.4101205819200422f);
1210 }
1211
1212 if(p->saturation_formula != d->saturation_formula) d->lut_inited = FALSE;
1213 d->saturation_formula = p->saturation_formula;
1214
1215 // Check if the RGB working profile has changed in pipe
1216 // WARNING: this function is not triggered upon working profile change,
1217 // so the gamut boundaries are wrong until we change some param in this module
1219 if(IS_NULL_PTR(work_profile)) return;
1220 if(work_profile != d->work_profile)
1221 {
1222 d->lut_inited = FALSE;
1223 d->work_profile = work_profile;
1224 }
1225
1226 // find the maximum chroma allowed by the current working gamut in conjunction to hue
1227 // this will be used to prevent users to mess up their images by pushing chroma out of gamut
1228 if(!d->lut_inited)
1229 {
1230 float *const restrict LUT_saturation = dt_alloc_align_float(LUT_ELEM);
1231 if(IS_NULL_PTR(LUT_saturation)) return;
1232
1233 // init the LUT between -pi and pi by increments of 1°
1234 for(size_t k = 0; k < LUT_ELEM; k++) LUT_saturation[k] = 0.f;
1235
1236 // Premultiply both matrices to go from D50 pipeline RGB to D65 XYZ in a single matrix dot product
1237 // instead of D50 pipeline to D50 XYZ (work_profile->matrix_in) and then D50 XYZ to D65 XYZ
1238 dt_colormatrix_t input_matrix;
1239 dt_colormatrix_mul(input_matrix, XYZ_D50_to_D65_CAT16, work_profile->matrix_in);
1240
1241 // make RGB values vary between [0; 1] in working space, convert to Ych and get the max(c(h)))
1242 if(p->saturation_formula == DT_COLORBALANCE_SATURATION_JZAZBZ)
1243 {
1244 __OMP_PARALLEL_FOR__( collapse(3))
1245 for(size_t r = 0; r < STEPS; r++)
1246 for(size_t g = 0; g < STEPS; g++)
1247 for(size_t b = 0; b < STEPS; b++)
1248 {
1249 const dt_aligned_pixel_t rgb = { (float)r / (float)(STEPS - 1), (float)g / (float)(STEPS - 1),
1250 (float)b / (float)(STEPS - 1), 0.f };
1251 dt_aligned_pixel_t XYZ = { 0.f };
1252 float saturation = 0.f;
1253 float hue = 0.f;
1254
1255 dot_product(rgb, input_matrix, XYZ); // Go from D50 pipeline RGB to D65 XYZ in one step
1256
1257 dt_aligned_pixel_t Jab, Jch;
1258 dt_XYZ_2_JzAzBz(XYZ, Jab); // this one expects D65 XYZ
1259
1260 Jch[0] = Jab[0];
1261 Jch[1] = dt_fast_hypotf(Jab[2], Jab[1]);
1262 Jch[2] = atan2f(Jab[2], Jab[1]);
1263
1264 saturation = (Jch[0] > 0.f) ? Jch[1] / Jch[0] : 0.f;
1265 hue = Jch[2];
1266
1267 const size_t index = roundf((LUT_ELEM - 1) * (hue + M_PI_F) / (2.f * M_PI_F));
1268 LUT_saturation[index] = fmaxf(saturation, LUT_saturation[index]);
1269 }
1270
1271 // anti-aliasing on the LUT (simple 5-taps 1D box average)
1272 for(size_t k = 2; k < LUT_ELEM - 2; k++)
1273 d->gamut_LUT[k] = (LUT_saturation[k - 2] + LUT_saturation[k - 1] + LUT_saturation[k] + LUT_saturation[k + 1] + LUT_saturation[k + 2]) / 5.f;
1274
1275 // handle bounds
1276 d->gamut_LUT[0] = (LUT_saturation[LUT_ELEM - 2] + LUT_saturation[LUT_ELEM - 1] + LUT_saturation[0] + LUT_saturation[1] + LUT_saturation[2]) / 5.f;
1277 d->gamut_LUT[1] = (LUT_saturation[LUT_ELEM - 1] + LUT_saturation[0] + LUT_saturation[1] + LUT_saturation[2] + LUT_saturation[3]) / 5.f;
1278 d->gamut_LUT[LUT_ELEM - 1] = (LUT_saturation[LUT_ELEM - 3] + LUT_saturation[LUT_ELEM - 2] + LUT_saturation[LUT_ELEM - 1] + LUT_saturation[0] + LUT_saturation[1]) / 5.f;
1279 d->gamut_LUT[LUT_ELEM - 2] = (LUT_saturation[LUT_ELEM - 4] + LUT_saturation[LUT_ELEM - 3] + LUT_saturation[LUT_ELEM - 2] + LUT_saturation[LUT_ELEM - 1] + LUT_saturation[0]) / 5.f;
1280 }
1281 else if(p->saturation_formula == DT_COLORBALANCE_SATURATION_DTUCS)
1282 {
1283 dt_aligned_pixel_t D65_xyY = { 0.31269999999999992f, 0.32899999999999996f , 1.f, 0.f };
1284
1285 // Compute the RGB space primaries in xyY
1286 dt_aligned_pixel_t RGB_red = { 1.f, 0.f, 0.f, 0.f };
1287 dt_aligned_pixel_t RGB_green = { 0.f, 1.f, 0.f, 0.f };
1288 dt_aligned_pixel_t RGB_blue = { 0.f, 0.f, 1.f, 0.f };
1289
1290 dt_aligned_pixel_t XYZ_red, XYZ_green, XYZ_blue;
1291 dot_product(RGB_red, input_matrix, XYZ_red);
1292 dot_product(RGB_green, input_matrix, XYZ_green);
1293 dot_product(RGB_blue, input_matrix, XYZ_blue);
1294
1295 dt_aligned_pixel_t xyY_red, xyY_green, xyY_blue;
1296 dt_XYZ_to_xyY(XYZ_red, xyY_red);
1297 dt_XYZ_to_xyY(XYZ_green, xyY_green);
1298 dt_XYZ_to_xyY(XYZ_blue, xyY_blue);
1299
1300 // Get the "hue" angles of the primaries in xy compared to D65
1301 const float h_red = atan2f(xyY_red[1] - D65_xyY[1], xyY_red[0] - D65_xyY[0]);
1302 const float h_green = atan2f(xyY_green[1] - D65_xyY[1], xyY_green[0] - D65_xyY[0]);
1303 const float h_blue = atan2f(xyY_blue[1] - D65_xyY[1], xyY_blue[0] - D65_xyY[0]);
1304
1305 float *const restrict dt_UCS_LUT = d->gamut_LUT;
1306
1307 // March the gamut boundary in CIE xyY 1931 by angular steps of 0.02°
1309 for(int i = 0; i < 50 * 360; i++)
1310 {
1311 const float angle = -M_PI_F + ((float)i) / (50.f * 360.f) * 2.f * M_PI_F;
1312 const float tan_angle = tanf(angle);
1313
1314 const float t_1 = Delta_H(angle, h_blue) / Delta_H(h_red, h_blue);
1315 const float t_2 = Delta_H(angle, h_red) / Delta_H(h_green, h_red);
1316 const float t_3 = Delta_H(angle, h_green) / Delta_H(h_blue, h_green);
1317
1318 float x_t = 0;
1319 float y_t = 0;
1320
1321 if(t_1 == CLAMP(t_1, 0, 1))
1322 {
1323 const float t = (D65_xyY[1] - xyY_blue[1] + tan_angle * (xyY_blue[0] - D65_xyY[0]))
1324 / (xyY_red[1] - xyY_blue[1] + tan_angle * (xyY_blue[0] - xyY_red[0]));
1325 x_t = xyY_blue[0] + t * (xyY_red[0] - xyY_blue[0]);
1326 y_t = xyY_blue[1] + t * (xyY_red[1] - xyY_blue[1]);
1327 }
1328 else if(t_2 == CLAMP(t_2, 0, 1))
1329 {
1330 const float t = (D65_xyY[1] - xyY_red[1] + tan_angle * (xyY_red[0] - D65_xyY[0]))
1331 / (xyY_green[1] - xyY_red[1] + tan_angle * (xyY_red[0] - xyY_green[0]));
1332 x_t = xyY_red[0] + t * (xyY_green[0] - xyY_red[0]);
1333 y_t = xyY_red[1] + t * (xyY_green[1] - xyY_red[1]);
1334 }
1335 else if(t_3 == CLAMP(t_3, 0, 1))
1336 {
1337 const float t = (D65_xyY[1] - xyY_green[1] + tan_angle * (xyY_green[0] - D65_xyY[0]))
1338 / (xyY_blue[1] - xyY_green[1] + tan_angle * (xyY_green[0] - xyY_blue[0]));
1339 x_t = xyY_green[0] + t * (xyY_blue[0] - xyY_green[0]);
1340 y_t = xyY_green[1] + t * (xyY_blue[1] - xyY_green[1]);
1341 }
1342
1343 // Convert to darktable UCS
1344 dt_aligned_pixel_t xyY = { x_t, y_t, 1.f, 0.f };
1345 float UV_star_prime[2];
1346 xyY_to_dt_UCS_UV(xyY, UV_star_prime);
1347
1348 // Get the hue angle in darktable UCS
1349 const float H = atan2f(UV_star_prime[1], UV_star_prime[0]) * 180.f / M_PI_F;
1350 const float H_round = roundf(H);
1351 if(fabsf(H - H_round) < 0.02f)
1352 {
1353 int index = (int)(H_round + 180);
1354 index += (index < 0) ? 360 : 0;
1355 index -= (index > 359) ? 360 : 0;
1356 // Warning: we store M², the square of the colorfulness
1357 dt_UCS_LUT[index] = UV_star_prime[0] * UV_star_prime[0] + UV_star_prime[1] * UV_star_prime[1];
1358 }
1359 }
1360 }
1361
1362 dt_free_align(LUT_saturation);
1363 d->lut_inited = TRUE;
1364 }
1365}
1366
1369{
1370 return TRUE;
1371}
1372
1374{
1376 // Hash the complete immutable rendering state, stopping before runtime pointers.
1377 piece->data_size = G_STRUCT_OFFSET(dt_iop_colorbalancergb_data_t, work_profile);
1380}
1381
1383{
1384 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
1385 if(IS_NULL_PTR(piece->data)) return;
1387 dt_free_align(d->gamut_LUT);
1388 d->gamut_LUT = NULL;
1389 dt_free_align(piece->data);
1390 piece->data = NULL;
1391}
1392
1395{
1396 const struct dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_current_profile_info(self, pipe);
1397 if(IS_NULL_PTR(work_profile)) return; // no point
1398
1399 dt_aligned_pixel_t XYZ_D50 = { 0.f };
1400 dt_aligned_pixel_t XYZ_D65 = { 0.f };
1401
1402 dt_ioppr_rgb_matrix_to_xyz(RGB, XYZ_D50, work_profile->matrix_in_transposed, work_profile->lut_in,
1403 work_profile->unbounded_coeffs_in, work_profile->lutsize,
1404 work_profile->nonlinearlut);
1406 XYZ_to_Ych(XYZ_D65, Ych);
1407
1408 if(Ych[2] < 0.f)
1409 Ych[2] = 2.f * M_PI + Ych[2];
1410}
1411
1412
1414{
1417
1418 dt_aligned_pixel_t Ych = { 0.f };
1419 dt_aligned_pixel_t max_Ych = { 0.f };
1420 pipe_RGB_to_Ych(self, pipe, (const float *)self->picked_color, Ych);
1421 pipe_RGB_to_Ych(self, pipe, (const float *)self->picked_color_max, max_Ych);
1422 float hue = RAD_TO_DEG(Ych[2]) + 180.f; // take the opponent color
1423 hue = (hue > 360.f) ? hue - 360.f : hue; // normalize in [0 ; 360]°
1424
1426 if(picker == g->global_H)
1427 {
1428 p->global_H = hue;
1429 p->global_C = Ych[1] * Ych[0];
1430 dt_bauhaus_slider_set(g->global_H, p->global_H);
1431 dt_bauhaus_slider_set(g->global_C, p->global_C);
1432 }
1433 else if(picker == g->shadows_H)
1434 {
1435 p->shadows_H = hue;
1436 p->shadows_C = Ych[1] * Ych[0];
1437 dt_bauhaus_slider_set(g->shadows_H, p->shadows_H);
1438 dt_bauhaus_slider_set(g->shadows_C, p->shadows_C);
1439 }
1440 else if(picker == g->midtones_H)
1441 {
1442 p->midtones_H = hue;
1443 p->midtones_C = Ych[1] * Ych[0];
1444 dt_bauhaus_slider_set(g->midtones_H, p->midtones_H);
1445 dt_bauhaus_slider_set(g->midtones_C, p->midtones_C);
1446 }
1447 else if(picker == g->highlights_H)
1448 {
1449 p->highlights_H = hue;
1450 p->highlights_C = Ych[1] * Ych[0];
1451 dt_bauhaus_slider_set(g->highlights_H, p->highlights_H);
1452 dt_bauhaus_slider_set(g->highlights_C, p->highlights_C);
1453 }
1454 else if(picker == g->white_fulcrum)
1455 {
1456 p->white_fulcrum = log2f(max_Ych[0]);
1457 dt_bauhaus_slider_set(g->white_fulcrum, p->white_fulcrum);
1458 }
1459 else if(picker == g->grey_fulcrum)
1460 {
1461 p->grey_fulcrum = Ych[0];
1462 dt_bauhaus_slider_set(g->grey_fulcrum, p->grey_fulcrum);
1463 }
1464 else
1465 fprintf(stderr, "[colorbalancergb] unknown color picker\n");
1467
1468 gui_changed(self, picker, NULL);
1469 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1470}
1471
1472void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
1473 const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
1474{
1476 if(IS_NULL_PTR(work_profile) || piece->dsc_in.channels != 4) return;
1477
1479 const dt_iop_roi_t *const roi_out = &piece->roi_out;
1480 const float *const restrict in = (const float *)i;
1481 float max_Y = 0.0f;
1482
1483 __OMP_PARALLEL_FOR__(reduction(max:max_Y))
1484 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height * 4; k += 4)
1485 {
1486 dt_aligned_pixel_t Ych = { 0.f };
1487 pipe_RGB_to_Ych(self, (dt_dev_pixelpipe_t *)pipe, in + k, Ych);
1488 if(isfinite(Ych[0]))
1489 max_Y = fmaxf(max_Y, Ych[0]);
1490 }
1491
1492 p->white_fulcrum = log2f(fmaxf(max_Y, 1e-6f));
1493}
1494
1495
1496static void paint_chroma_slider(GtkWidget *w, const float hue)
1497{
1498 const float x_min = 0;
1499 const float x_max = 1;
1500 const float x_range = x_max - x_min;
1501
1502 // Varies x in range around current y param
1503 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
1504 {
1505 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
1506 const float x = x_min + stop * x_range;
1507 const float h = DEG_TO_RAD(hue);
1508
1509 dt_aligned_pixel_t RGB = { 0.f };
1510 dt_aligned_pixel_t Ych = { 0.75f, x, h, 0.f };
1511 dt_aligned_pixel_t XYZ = { 0.f };
1512 Ych_to_XYZ(Ych, XYZ);
1513 dt_XYZ_to_Rec709_D65(XYZ, RGB);
1514 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
1515 for(size_t c = 0; c < 3; c++) RGB[c] = powf(RGB[c] / max_RGB, 1.f / 2.2f);
1516 dt_bauhaus_slider_set_stop(w, stop, RGB[0], RGB[1], RGB[2]);
1517 }
1518
1519 gtk_widget_queue_draw(w);
1520}
1521
1522
1523static void mask_callback(GtkWidget *togglebutton, dt_iop_module_t *self)
1524{
1525 if(dt_gui_widgets_suppressed()) return;
1527
1528 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), TRUE);
1529
1531
1532 // if blend module is displaying mask do not display it here
1535
1536 g->mask_display = !g->mask_display;
1537
1538 if(g->mask_display)
1539 {
1540 if(togglebutton == g->shadows_weight) g->mask_type = MASK_SHADOWS;
1541 if(togglebutton == g->mask_grey_fulcrum) g->mask_type = MASK_MIDTONES;
1542 if(togglebutton == g->highlights_weight) g->mask_type = MASK_HIGHLIGHTS;
1544 }
1545 else
1546 {
1547 g->mask_type = MASK_NONE;
1548 }
1549
1550 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->shadows_weight), g->mask_type == MASK_SHADOWS);
1551 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->mask_grey_fulcrum), g->mask_type == MASK_MIDTONES);
1552 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->highlights_weight), g->mask_type == MASK_HIGHLIGHTS);
1553
1554 dt_iop_set_cache_bypass(self, g->mask_display);
1556}
1557
1558
1559static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
1560{
1561 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1563 const float shadows_weight = 2.f + p->shadows_weight * 2.f;
1564 const float highlights_weight = 2.f + p->highlights_weight * 2.f;
1565
1566 // Cache the graph objects to avoid recomputing all the view at each redraw
1567 GtkAllocation allocation;
1568 gtk_widget_get_allocation(widget, &allocation);
1569 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1570
1571 cairo_surface_t *cst =
1572 dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1573 PangoFontDescription *desc =
1574 pango_font_description_copy_static(dt_bauhaus_get_global()->pango_font_desc);
1575 cairo_t *cr = cairo_create(cst);
1576 PangoLayout *layout = pango_cairo_create_layout(cr);
1577
1578 const gint font_size = pango_font_description_get_size(desc);
1579 pango_font_description_set_size(desc, 0.95 * font_size);
1580 pango_layout_set_font_description(layout, desc);
1582
1583 char text[256];
1584
1585 // Get the text line height for spacing
1586 PangoRectangle ink;
1587 snprintf(text, sizeof(text), "X");
1588 pango_layout_set_text(layout, text, -1);
1589 pango_layout_get_pixel_extents(layout, &ink, NULL);
1590 const float line_height = ink.height;
1591
1592 const float inset = DT_PIXEL_APPLY_DPI(4);
1593 const float margin_top = inset;
1594 const float margin_bottom = line_height + 2 * inset;
1595 const float margin_left = line_height + inset;
1596 const float margin_right = 0;
1597
1598 const float graph_width = allocation.width - margin_right - margin_left; // align the right border on sliders
1599 const float graph_height = allocation.height - margin_bottom - margin_top; // give room to nodes
1600
1601 gtk_render_background(context, cr, 0, 0, allocation.width, allocation.height);
1602
1603 // draw x gradient as axis legend
1604 cairo_pattern_t *grad;
1605 grad = cairo_pattern_create_linear(margin_left, 0.0, graph_width, 0.0);
1607 cairo_set_line_width(cr, 0.0);
1608 cairo_rectangle(cr, margin_left, graph_height + 2 * inset, graph_width, line_height);
1609 cairo_set_source(cr, grad);
1610 cairo_fill(cr);
1611 cairo_pattern_destroy(grad);
1612
1613 // draw y gradient as axis legend
1614 const int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, line_height);
1615 unsigned char *data = malloc(stride * graph_height);
1616 cairo_surface_t *surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, (size_t)line_height, (size_t)graph_height, stride);
1617
1618 const size_t checker_1 = DT_PIXEL_APPLY_DPI(6);
1619 const size_t checker_2 = 2 * checker_1;
1620 __OMP_PARALLEL_FOR__(collapse(2))
1621 for(size_t i = 0; i < (size_t)graph_height; i++)
1622 for(size_t j = 0; j < (size_t)line_height; j++)
1623 {
1624 const size_t k = ((i * (size_t)line_height) + j) * 4;
1625 unsigned char color;
1626 const float alpha = (float)i / graph_height;
1627 if(i % checker_1 < i % checker_2)
1628 {
1629 if(j % checker_1 < j % checker_2) color = 150;
1630 else color = 100;
1631 }
1632 else
1633 {
1634 if(j % checker_1 < j % checker_2) color = 100;
1635 else color = 150;
1636 }
1637
1638 for(size_t c = 0; c < 4; ++c) data[k + c] = color * alpha;
1639 data[k+3] = alpha * 255;
1640 }
1641
1642 cairo_set_source_surface(cr, surface, 0, margin_top);
1643 cairo_paint(cr);
1644 dt_free(data);
1645 cairo_surface_destroy(surface);
1646
1647 // set the graph as the origin of the coordinates
1648 cairo_translate(cr, margin_left, margin_top);
1649 cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
1650
1651 set_color(cr, dt_bauhaus_get_global()->graph_bg);
1652 cairo_rectangle(cr, 0, 0, graph_width, graph_height);
1653 cairo_fill_preserve(cr);
1654 cairo_clip(cr);
1655
1656 // from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.583.3007&rep=rep1&type=pdf
1657 const float midtones_weight
1658 = sqf(shadows_weight) * sqf(highlights_weight) / (sqf(shadows_weight) + sqf(highlights_weight));
1659 const float mask_grey_fulcrum = powf(p->mask_grey_fulcrum, 0.4101205819200422f);
1660
1661 float *LUT[3];
1662 for(size_t c = 0; c < 3; c++) LUT[c] = dt_alloc_align_float(LUT_ELEM);
1664 for(size_t k = 0 ; k < LUT_ELEM; k++)
1665 {
1666 const float Y = k / (float)(LUT_ELEM - 1);
1667 dt_aligned_pixel_t output;
1668 opacity_masks(Y, shadows_weight, highlights_weight, midtones_weight, mask_grey_fulcrum, output, NULL);
1669 for(size_t c = 0; c < 3; c++) LUT[c][k] = output[c];
1670 }
1671
1672 GdkRGBA fg_color = dt_bauhaus_get_global()->graph_fg;
1673 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1674
1675 for(size_t c = 0; c < 3; c++)
1676 {
1677 GdkRGBA line_color = { fg_color.red * (1. - (2 - c) / 4.),
1678 fg_color.green * (1. - (2 - c) / 4.),
1679 fg_color.blue * (1. - (2 - c) / 4.),
1680 fg_color.alpha };
1681 set_color(cr, line_color);
1682
1683 cairo_move_to(cr, 0, (1.f - LUT[c][0]) * graph_height);
1684 for(size_t k = 0; k < LUT_ELEM; k++)
1685 {
1686 const float x = (float)k / (float)(LUT_ELEM - 1) * graph_width;
1687 const float y = (1.f - LUT[c][k]) * graph_height;
1688 cairo_line_to(cr, x, y);
1689 }
1690 cairo_stroke(cr);
1691 }
1692
1693 for(size_t c = 0; c < 3; c++) dt_free_align(LUT[c]);
1694
1695 cairo_restore(cr);
1696
1697 // restore font size
1698 pango_font_description_set_size(desc, font_size);
1699 pango_layout_set_font_description(layout, desc);
1700
1701 cairo_destroy(cr);
1702 cairo_set_source_surface(crf, cst, 0, 0);
1703 cairo_paint(crf);
1704 cairo_surface_destroy(cst);
1705 g_object_unref(layout);
1706 pango_font_description_free(desc);
1707 return TRUE;
1708}
1709
1710
1711void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1712{
1715
1717
1718 if(IS_NULL_PTR(w) || w == g->global_H)
1719 paint_chroma_slider(g->global_C, p->global_H);
1720
1721 if(IS_NULL_PTR(w) || w == g->shadows_H)
1722 paint_chroma_slider(g->shadows_C, p->shadows_H);
1723
1724 if(IS_NULL_PTR(w) || w == g->midtones_H)
1725 paint_chroma_slider(g->midtones_C, p->midtones_H);
1726
1727 if(IS_NULL_PTR(w) || w == g->highlights_H)
1728 paint_chroma_slider(g->highlights_C, p->highlights_H);
1729
1730 if(IS_NULL_PTR(w) || w == g->shadows_weight || w == g->highlights_weight || w == g->mask_grey_fulcrum)
1731 gtk_widget_queue_draw(GTK_WIDGET(g->area));
1732
1734
1735}
1736
1738{
1741
1742 dt_bauhaus_slider_set(g->hue_angle, p->hue_angle);
1743 dt_bauhaus_slider_set(g->vibrance, p->vibrance);
1744 dt_bauhaus_slider_set(g->contrast, p->contrast);
1745
1746 dt_bauhaus_slider_set(g->chroma_global, p->chroma_global);
1747 dt_bauhaus_slider_set(g->chroma_highlights, p->chroma_highlights);
1748 dt_bauhaus_slider_set(g->chroma_midtones, p->chroma_midtones);
1749 dt_bauhaus_slider_set(g->chroma_shadows, p->chroma_shadows);
1750
1751 dt_bauhaus_slider_set(g->saturation_global, p->saturation_global);
1752 dt_bauhaus_slider_set(g->saturation_highlights, p->saturation_highlights);
1753 dt_bauhaus_slider_set(g->saturation_midtones, p->saturation_midtones);
1754 dt_bauhaus_slider_set(g->saturation_shadows, p->saturation_shadows);
1755
1756 dt_bauhaus_slider_set(g->brilliance_global, p->brilliance_global);
1757 dt_bauhaus_slider_set(g->brilliance_highlights, p->brilliance_highlights);
1758 dt_bauhaus_slider_set(g->brilliance_midtones, p->brilliance_midtones);
1759 dt_bauhaus_slider_set(g->brilliance_shadows, p->brilliance_shadows);
1760
1761 dt_bauhaus_slider_set(g->global_C, p->global_C);
1762 dt_bauhaus_slider_set(g->global_H, p->global_H);
1763 dt_bauhaus_slider_set(g->global_Y, p->global_Y);
1764
1765 dt_bauhaus_slider_set(g->shadows_C, p->shadows_C);
1766 dt_bauhaus_slider_set(g->shadows_H, p->shadows_H);
1767 dt_bauhaus_slider_set(g->shadows_Y, p->shadows_Y);
1768 dt_bauhaus_slider_set(g->shadows_weight, p->shadows_weight);
1769
1770 dt_bauhaus_slider_set(g->midtones_C, p->midtones_C);
1771 dt_bauhaus_slider_set(g->midtones_H, p->midtones_H);
1772 dt_bauhaus_slider_set(g->midtones_Y, p->midtones_Y);
1773 dt_bauhaus_slider_set(g->white_fulcrum, p->white_fulcrum);
1774
1775 dt_bauhaus_slider_set(g->highlights_C, p->highlights_C);
1776 dt_bauhaus_slider_set(g->highlights_H, p->highlights_H);
1777 dt_bauhaus_slider_set(g->highlights_Y, p->highlights_Y);
1778 dt_bauhaus_slider_set(g->highlights_weight, p->highlights_weight);
1779
1780 dt_bauhaus_slider_set(g->mask_grey_fulcrum, p->mask_grey_fulcrum);
1781 dt_bauhaus_slider_set(g->grey_fulcrum, p->grey_fulcrum);
1782 dt_bauhaus_combobox_set(g->saturation_formula, p->saturation_formula);
1783
1784 gui_changed(self, NULL, NULL);
1786 g->mask_display = FALSE;
1787 g->mask_type = MASK_NONE;
1788
1789 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->shadows_weight), FALSE);
1790 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->mask_grey_fulcrum), FALSE);
1791 dt_bauhaus_widget_set_quad_active(GTK_WIDGET(g->highlights_weight), FALSE);
1792}
1793
1794
1796{
1797 //dt_iop_colorbalancergb_gui_data_t *g = (dt_iop_colorbalancergb_gui_data_t *)self->gui_data;
1799}
1800
1801static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
1802{
1803 // let scroll events fall through (e.g. to scroll the panel); the height is set via the grip
1804 return FALSE;
1805}
1806
1807
1809{
1811 g->mask_display = FALSE;
1812
1813 // start building top level widget
1814 g->notebook = dt_ui_notebook_new();
1815 // Each page holds its own pickers (global_H on "4 ways", white/grey fulcrum on
1816 // "masks"); reset any active one when the page they live on is switched away from.
1817 dt_ui_notebook_set_picker_owner(g->notebook, self);
1818
1819 // Page master
1820 self->gui->widget = dt_ui_notebook_page(g->notebook, N_("master"), _("global grading"));
1821
1822 g->hue_angle = dt_bauhaus_slider_from_params(self, "hue_angle");
1823 dt_bauhaus_slider_set_format(g->hue_angle, "\302\260");
1824 gtk_widget_set_tooltip_text(g->hue_angle, _("rotate all hues by an angle, at the same luminance"));
1825
1826 g->vibrance = dt_bauhaus_slider_from_params(self, "vibrance");
1827 dt_bauhaus_slider_set_soft_range(g->vibrance, -0.5, 0.5);
1828 dt_bauhaus_slider_set_digits(g->vibrance, 4);
1829 dt_bauhaus_slider_set_format(g->vibrance, "%");
1830 gtk_widget_set_tooltip_text(g->vibrance, _("increase colorfulness mostly on low-chroma colors"));
1831
1832 g->contrast = dt_bauhaus_slider_from_params(self, "contrast");
1833 dt_bauhaus_slider_set_soft_range(g->contrast, -0.5, 0.5);
1834 dt_bauhaus_slider_set_digits(g->contrast, 4);
1835 dt_bauhaus_slider_set_format(g->contrast, "%");
1836 gtk_widget_set_tooltip_text(g->contrast, _("increase the contrast at constant chromaticity"));
1837
1838 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("linear chroma grading")), FALSE, FALSE, 0);
1839
1840 g->chroma_global = dt_bauhaus_slider_from_params(self, "chroma_global");
1841 dt_bauhaus_slider_set_soft_range(g->chroma_global, -0.5, 0.5);
1842 dt_bauhaus_slider_set_digits(g->chroma_global, 4);
1843 dt_bauhaus_slider_set_format(g->chroma_global, "%");
1844 gtk_widget_set_tooltip_text(g->chroma_global, _("increase colorfulness at same luminance globally"));
1845
1846 g->chroma_shadows = dt_bauhaus_slider_from_params(self, "chroma_shadows");
1847 dt_bauhaus_slider_set_digits(g->chroma_shadows, 4);
1848 dt_bauhaus_slider_set_format(g->chroma_shadows, "%");
1849 gtk_widget_set_tooltip_text(g->chroma_shadows, _("increase colorfulness at same luminance mostly in shadows"));
1850
1851 g->chroma_midtones = dt_bauhaus_slider_from_params(self, "chroma_midtones");
1852 dt_bauhaus_slider_set_digits(g->chroma_midtones, 4);
1853 dt_bauhaus_slider_set_format(g->chroma_midtones, "%");
1854 gtk_widget_set_tooltip_text(g->chroma_midtones, _("increase colorfulness at same luminance mostly in mid-tones"));
1855
1856 g->chroma_highlights = dt_bauhaus_slider_from_params(self, "chroma_highlights");
1857 dt_bauhaus_slider_set_digits(g->chroma_highlights, 4);
1858 dt_bauhaus_slider_set_format(g->chroma_highlights, "%");
1859 gtk_widget_set_tooltip_text(g->chroma_highlights, _("increase colorfulness at same luminance mostly in highlights"));
1860
1861 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("perceptual saturation grading")), FALSE, FALSE, 0);
1862
1863 g->saturation_global = dt_bauhaus_slider_from_params(self, "saturation_global");
1864 dt_bauhaus_slider_set_digits(g->saturation_global, 4);
1865 dt_bauhaus_slider_set_format(g->saturation_global, "%");
1866 gtk_widget_set_tooltip_text(g->saturation_global, _("add or remove saturation by an absolute amount"));
1867
1868 g->saturation_shadows = dt_bauhaus_slider_from_params(self, "saturation_shadows");
1869 dt_bauhaus_slider_set_digits(g->saturation_shadows, 4);
1870 dt_bauhaus_slider_set_format(g->saturation_shadows, "%");
1871 gtk_widget_set_tooltip_text(g->saturation_shadows, _("increase or decrease saturation proportionally to the original pixel saturation"));
1872
1873 g->saturation_midtones= dt_bauhaus_slider_from_params(self, "saturation_midtones");
1874 dt_bauhaus_slider_set_digits(g->saturation_midtones, 4);
1875 dt_bauhaus_slider_set_format(g->saturation_midtones, "%");
1876 gtk_widget_set_tooltip_text(g->saturation_midtones, _("increase or decrease saturation proportionally to the original pixel saturation"));
1877
1878 g->saturation_highlights = dt_bauhaus_slider_from_params(self, "saturation_highlights");
1879 dt_bauhaus_slider_set_digits(g->saturation_highlights, 4);
1880 dt_bauhaus_slider_set_format(g->saturation_highlights, "%");
1881 gtk_widget_set_tooltip_text(g->saturation_highlights, _("increase or decrease saturation proportionally to the original pixel saturation"));
1882
1883 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("perceptual brilliance grading")), FALSE, FALSE, 0);
1884
1885 g->brilliance_global = dt_bauhaus_slider_from_params(self, "brilliance_global");
1886 dt_bauhaus_slider_set_digits(g->brilliance_global, 4);
1887 dt_bauhaus_slider_set_format(g->brilliance_global, "%");
1888 gtk_widget_set_tooltip_text(g->brilliance_global, _("add or remove brilliance by an absolute amount"));
1889
1890 g->brilliance_shadows = dt_bauhaus_slider_from_params(self, "brilliance_shadows");
1891 dt_bauhaus_slider_set_digits(g->brilliance_shadows, 4);
1892 dt_bauhaus_slider_set_format(g->brilliance_shadows, "%");
1893 gtk_widget_set_tooltip_text(g->brilliance_shadows, _("increase or decrease brilliance proportionally to the original pixel brilliance"));
1894
1895 g->brilliance_midtones= dt_bauhaus_slider_from_params(self, "brilliance_midtones");
1896 dt_bauhaus_slider_set_digits(g->brilliance_midtones, 4);
1897 dt_bauhaus_slider_set_format(g->brilliance_midtones, "%");
1898 gtk_widget_set_tooltip_text(g->brilliance_midtones, _("increase or decrease brilliance proportionally to the original pixel brilliance"));
1899
1900 g->brilliance_highlights = dt_bauhaus_slider_from_params(self, "brilliance_highlights");
1901 dt_bauhaus_slider_set_digits(g->brilliance_highlights, 4);
1902 dt_bauhaus_slider_set_format(g->brilliance_highlights, "%");
1903 gtk_widget_set_tooltip_text(g->brilliance_highlights, _("increase or decrease brilliance proportionally to the original pixel brilliance"));
1904
1905 // Page 4-ways
1906 self->gui->widget = dt_ui_notebook_page(g->notebook, N_("4 ways"), _("selective color grading"));
1907
1908 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("global offset")), FALSE, FALSE, 0);
1909
1910 g->global_Y = dt_bauhaus_slider_from_params(self, "global_Y");
1911 dt_bauhaus_slider_set_soft_range(g->global_Y, -0.05, 0.05);
1912 dt_bauhaus_slider_set_digits(g->global_Y, 4);
1913 dt_bauhaus_slider_set_format(g->global_Y, "%");
1914 gtk_widget_set_tooltip_text(g->global_Y, _("global luminance offset"));
1915
1916 g->global_H = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "global_H"));
1917 dt_bauhaus_slider_set_feedback(g->global_H, 0);
1918 dt_bauhaus_slider_set_format(g->global_H, "\302\260");
1919 gtk_widget_set_tooltip_text(g->global_H, _("hue of the global color offset"));
1920
1921 g->global_C = dt_bauhaus_slider_from_params(self, "global_C");
1922 dt_bauhaus_slider_set_soft_range(g->global_C, 0., 0.0075);
1923 dt_bauhaus_slider_set_digits(g->global_C, 4);
1924 dt_bauhaus_slider_set_format(g->global_C, "%");
1925 gtk_widget_set_tooltip_text(g->global_C, _("chroma of the global color offset"));
1926
1927 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("shadows lift")), FALSE, FALSE, 0);
1928
1929 g->shadows_Y = dt_bauhaus_slider_from_params(self, "shadows_Y");
1930 dt_bauhaus_slider_set_soft_range(g->shadows_Y, -1.0, 1.0);
1931 dt_bauhaus_slider_set_digits(g->shadows_Y, 4);
1932 dt_bauhaus_slider_set_format(g->shadows_Y, "%");
1933 gtk_widget_set_tooltip_text(g->shadows_Y, _("luminance gain in shadows"));
1934
1935 g->shadows_H = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "shadows_H"));
1936 dt_bauhaus_slider_set_feedback(g->shadows_H, 0);
1937 dt_bauhaus_slider_set_format(g->shadows_H, "\302\260");
1938 gtk_widget_set_tooltip_text(g->shadows_H, _("hue of the color gain in shadows"));
1939
1940 g->shadows_C = dt_bauhaus_slider_from_params(self, "shadows_C");
1941 dt_bauhaus_slider_set_soft_range(g->shadows_C, 0., 0.375);
1942 dt_bauhaus_slider_set_digits(g->shadows_C, 4);
1943 dt_bauhaus_slider_set_format(g->shadows_C, "%");
1944 gtk_widget_set_tooltip_text(g->shadows_C, _("chroma of the color gain in shadows"));
1945
1946 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("highlights gain")), FALSE, FALSE, 0);
1947
1948 g->highlights_Y = dt_bauhaus_slider_from_params(self, "highlights_Y");
1949 dt_bauhaus_slider_set_soft_range(g->highlights_Y, -0.5, 0.5);
1950 dt_bauhaus_slider_set_digits(g->highlights_Y, 4);
1951 dt_bauhaus_slider_set_format(g->highlights_Y, "%");
1952 gtk_widget_set_tooltip_text(g->highlights_Y, _("luminance gain in highlights"));
1953
1954 g->highlights_H = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "highlights_H"));
1955 dt_bauhaus_slider_set_feedback(g->highlights_H, 0);
1956 dt_bauhaus_slider_set_format(g->highlights_H, "\302\260");
1957 gtk_widget_set_tooltip_text(g->highlights_H, _("hue of the color gain in highlights"));
1958
1959 g->highlights_C = dt_bauhaus_slider_from_params(self, "highlights_C");
1960 dt_bauhaus_slider_set_soft_range(g->highlights_C, 0., 0.15);
1961 dt_bauhaus_slider_set_digits(g->highlights_C, 4);
1962 dt_bauhaus_slider_set_format(g->highlights_C, "%");
1963 gtk_widget_set_tooltip_text(g->highlights_C, _("chroma of the color gain in highlights"));
1964
1965 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("power")), FALSE, FALSE, 0);
1966
1967 g->midtones_Y = dt_bauhaus_slider_from_params(self, "midtones_Y");
1968 dt_bauhaus_slider_set_soft_range(g->midtones_Y, -0.25, 0.25);
1969 dt_bauhaus_slider_set_digits(g->midtones_Y, 4);
1970 dt_bauhaus_slider_set_format(g->midtones_Y, "%");
1971 gtk_widget_set_tooltip_text(g->midtones_Y, _("luminance exponent in mid-tones"));
1972
1973 g->midtones_H = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "midtones_H"));
1974 dt_bauhaus_slider_set_feedback(g->midtones_H, 0);
1975 dt_bauhaus_slider_set_format(g->midtones_H, "\302\260");
1976 gtk_widget_set_tooltip_text(g->midtones_H, _("hue of the color exponent in mid-tones"));
1977
1978 g->midtones_C = dt_bauhaus_slider_from_params(self, "midtones_C");
1979 dt_bauhaus_slider_set_soft_range(g->midtones_C, 0., 0.075);
1980 dt_bauhaus_slider_set_digits(g->midtones_C, 4);
1981 dt_bauhaus_slider_set_format(g->midtones_C, "%");
1982 gtk_widget_set_tooltip_text(g->midtones_C, _("chroma of the color exponent in mid-tones"));
1983
1984 // Page masks
1985 self->gui->widget = dt_ui_notebook_page(g->notebook, N_("masks"), _("isolate luminances"));
1986
1987 g->saturation_formula = dt_bauhaus_combobox_from_params(self, "saturation_formula");
1988 gtk_widget_set_tooltip_text(g->saturation_formula, _("choose in which uniform color space the saturation is computed."));
1989
1990 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("luminance ranges")), FALSE, FALSE, 0);
1991
1992 g->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
1993 gtk_widget_set_hexpand(GTK_WIDGET(g->area), TRUE);
1994 g_object_set_data(G_OBJECT(g->area), "iop-instance", self);
1995 g_signal_connect(G_OBJECT(g->area), "draw", G_CALLBACK(dt_iop_tonecurve_draw), self);
1996 gtk_box_pack_start(GTK_BOX(self->gui->widget),
1997 dt_ui_resizable_drawing_area(GTK_WIDGET(g->area),
1998 "plugins/darkroom/colorbalancergb/graphheight", 200, 100),
1999 FALSE, FALSE, 0);
2000 gtk_widget_add_events(GTK_WIDGET(g->area), dt_widget_scroll_mask() | GDK_ENTER_NOTIFY_MASK);
2001 g_signal_connect(G_OBJECT(g->area), "scroll-event", G_CALLBACK(area_scroll_callback), self);
2002
2003 g->shadows_weight = dt_bauhaus_slider_from_params(self, "shadows_weight");
2004 dt_bauhaus_slider_set_digits(g->shadows_weight, 4);
2005 dt_bauhaus_slider_set_format(g->shadows_weight, "%");
2006 gtk_widget_set_tooltip_text(g->shadows_weight, _("weight of the shadows over the whole tonal range"));
2008 dt_bauhaus_widget_set_quad_toggle(g->shadows_weight, TRUE);
2009 g_signal_connect(G_OBJECT(g->shadows_weight), "quad-pressed", G_CALLBACK(mask_callback), self);
2010
2011 g->mask_grey_fulcrum = dt_bauhaus_slider_from_params(self, "mask_grey_fulcrum");
2012 dt_bauhaus_slider_set_digits(g->mask_grey_fulcrum, 4);
2013 dt_bauhaus_slider_set_format(g->mask_grey_fulcrum, "%");
2014 gtk_widget_set_tooltip_text(g->mask_grey_fulcrum, _("position of the middle-gray reference for masking"));
2016 dt_bauhaus_widget_set_quad_toggle(g->mask_grey_fulcrum, TRUE);
2017 g_signal_connect(G_OBJECT(g->mask_grey_fulcrum), "quad-pressed", G_CALLBACK(mask_callback), self);
2018
2019 g->highlights_weight = dt_bauhaus_slider_from_params(self, "highlights_weight");
2020 dt_bauhaus_slider_set_digits(g->highlights_weight, 4);
2021 dt_bauhaus_slider_set_format(g->highlights_weight, "%");
2022 gtk_widget_set_tooltip_text(g->highlights_weight, _("weights of highlights over the whole tonal range"));
2024 dt_bauhaus_widget_set_quad_toggle(g->highlights_weight, TRUE);
2025 g_signal_connect(G_OBJECT(g->highlights_weight), "quad-pressed", G_CALLBACK(mask_callback), self);
2026
2027 gtk_box_pack_start(GTK_BOX(self->gui->widget), dt_ui_section_label_new(_("threshold")), FALSE, FALSE, 0);
2028
2029 g->white_fulcrum = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "white_fulcrum"));
2030 dt_bauhaus_slider_set_soft_range(g->white_fulcrum, -2., +2.);
2031 dt_bauhaus_slider_set_format(g->white_fulcrum, _(" EV"));
2032 gtk_widget_set_tooltip_text(g->white_fulcrum, _("peak white luminance value used to normalize the power function"));
2033
2034 g->grey_fulcrum = dt_color_picker_new(self, DT_COLOR_PICKER_AREA, dt_bauhaus_slider_from_params(self, "grey_fulcrum"));
2035 dt_bauhaus_slider_set_soft_range(g->grey_fulcrum, 0.1, 0.5);
2036 dt_bauhaus_slider_set_digits(g->grey_fulcrum, 4);
2037 dt_bauhaus_slider_set_format(g->grey_fulcrum, "%");
2038 gtk_widget_set_tooltip_text(g->grey_fulcrum, _("peak gray luminance value used to normalize the power function"));
2039
2040 dt_bauhaus_widget_set_label(g->shadows_H, N_("hue"));
2041 dt_bauhaus_widget_set_label(g->midtones_H, N_("hue"));
2042 dt_bauhaus_widget_set_label(g->highlights_H, N_("hue"));
2043 dt_bauhaus_widget_set_label(g->global_H, N_("hue"));
2044 dt_bauhaus_widget_set_label(g->shadows_C, N_("chroma"));
2045 dt_bauhaus_widget_set_label(g->midtones_C, N_("chroma"));
2046 dt_bauhaus_widget_set_label(g->highlights_C, N_("chroma"));
2047 dt_bauhaus_widget_set_label(g->global_C, N_("chroma"));
2048 dt_bauhaus_widget_set_label(g->shadows_Y, N_("luminance"));
2049 dt_bauhaus_widget_set_label(g->midtones_Y, N_("luminance"));
2050 dt_bauhaus_widget_set_label(g->highlights_Y, N_("luminance"));
2051 dt_bauhaus_widget_set_label(g->global_Y, N_("luminance"));
2052
2053 dt_bauhaus_widget_set_label(g->chroma_global, N_("global chroma"));
2054 dt_bauhaus_widget_set_label(g->chroma_highlights, N_("highlights"));
2055 dt_bauhaus_widget_set_label(g->chroma_midtones, N_("mid-tones"));
2056 dt_bauhaus_widget_set_label(g->chroma_shadows, N_("shadows"));
2057 dt_bauhaus_widget_set_label(g->saturation_global, N_("global saturation"));
2058 dt_bauhaus_widget_set_label(g->saturation_highlights, N_("highlights"));
2059 dt_bauhaus_widget_set_label(g->saturation_midtones, N_("mid-tones"));
2060 dt_bauhaus_widget_set_label(g->saturation_shadows, N_("shadows"));
2061 dt_bauhaus_widget_set_label(g->brilliance_global, N_("global brilliance"));
2062 dt_bauhaus_widget_set_label(g->brilliance_highlights, N_("highlights"));
2063 dt_bauhaus_widget_set_label(g->brilliance_midtones, N_("mid-tones"));
2064 dt_bauhaus_widget_set_label(g->brilliance_shadows, N_("shadows"));
2065
2066 // paint backgrounds
2067 for(int i = 0; i < DT_BAUHAUS_SLIDER_MAX_STOPS; i++)
2068 {
2069 const float stop = ((float)i / (float)(DT_BAUHAUS_SLIDER_MAX_STOPS - 1));
2070 const float h = DEG_TO_RAD(stop * (360.f));
2071 dt_aligned_pixel_t RGB = { 0.f };
2072 dt_aligned_pixel_t Ych = { 0.75f, 0.2f, h, 0.f };
2073 dt_aligned_pixel_t XYZ = { 0.f };
2074 Ych_to_XYZ(Ych, XYZ);
2075 dt_XYZ_to_Rec709_D65(XYZ, RGB);
2076 const float max_RGB = fmaxf(fmaxf(RGB[0], RGB[1]), RGB[2]);
2077 for(size_t c = 0; c < 3; c++) RGB[c] = powf(RGB[c] / max_RGB, 1.f / 2.2f);
2078 dt_bauhaus_slider_set_stop(g->global_H, stop, RGB[0], RGB[1], RGB[2]);
2079 dt_bauhaus_slider_set_stop(g->shadows_H, stop, RGB[0], RGB[1], RGB[2]);
2080 dt_bauhaus_slider_set_stop(g->highlights_H, stop, RGB[0], RGB[1], RGB[2]);
2081 dt_bauhaus_slider_set_stop(g->midtones_H, stop, RGB[0], RGB[1], RGB[2]);
2082
2083 const float Y = 0.f + stop;
2084 dt_bauhaus_slider_set_stop(g->global_Y, stop, Y, Y, Y);
2085 dt_bauhaus_slider_set_stop(g->shadows_Y, stop, Y, Y, Y);
2086 dt_bauhaus_slider_set_stop(g->highlights_Y, stop, Y, Y, Y);
2087 dt_bauhaus_slider_set_stop(g->midtones_Y, stop, Y, Y, Y);
2088 }
2089
2090 // main widget is the notebook
2091 self->gui->widget = GTK_WIDGET(g->notebook);
2092}
2093
2094
2096{
2098}
2099
2100// clang-format off
2101// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2102// vim: shiftwidth=2 expandtab tabstop=2 cindent
2103// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2104// clang-format on
void dt_gui_set_pango_resolution(PangoLayout *layout)
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_soft_range(GtkWidget *widget, float soft_min, float soft_max)
Definition bauhaus.c:1498
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3346
void dt_bauhaus_slider_set_stop(GtkWidget *widget, float stop, float r, float g, float b)
Definition bauhaus.c:2164
void dt_bauhaus_slider_set_feedback(GtkWidget *widget, int feedback)
Definition bauhaus.c:3392
void dt_bauhaus_widget_set_quad_toggle(GtkWidget *widget, int toggle)
Definition bauhaus.c:1575
void dt_bauhaus_widget_set_quad_active(GtkWidget *widget, int active)
Definition bauhaus.c:1581
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
void dt_bauhaus_widget_set_quad_paint(GtkWidget *widget, dt_bauhaus_quad_paint_f f, int paint_flags, void *paint_data)
Definition bauhaus.c:1557
#define DT_BAUHAUS_SLIDER_MAX_STOPS
Definition bauhaus.h:73
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static void XYZ_D50_to_D65(const dt_aligned_pixel_t XYZ_in, dt_aligned_pixel_t XYZ_out)
static const dt_colormatrix_t XYZ_D65_to_D50_CAT16
@ IOP_CS_RGB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_AREA
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *p1, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static gboolean area_scroll_callback(GtkWidget *widget, GdkEventScroll *event, gpointer user_data)
const char ** description(struct dt_iop_module_t *self)
int default_group()
__DT_CLONE_TARGETS__ int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid)
static float Delta_H(const float h_1, const float h_2)
void gui_reset(dt_iop_module_t *self)
#define STEPS
static gboolean dt_iop_tonecurve_draw(GtkWidget *widget, cairo_t *crf, gpointer user_data)
dt_iop_colorbalancrgb_saturation_t
@ DT_COLORBALANCE_SATURATION_JZAZBZ
@ DT_COLORBALANCE_SATURATION_DTUCS
void gui_update(dt_iop_module_t *self)
static float soft_clip(const float x, const float soft_threshold, const float hard_threshold)
const char * aliases()
const char * name()
void gui_init(dt_iop_module_t *self)
#define RAD_TO_DEG(x)
void pipe_RGB_to_Ych(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_aligned_pixel_t RGB, dt_aligned_pixel_t Ych)
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
#define DEG_TO_RAD(x)
static void mask_callback(GtkWidget *togglebutton, dt_iop_module_t *self)
struct dt_iop_colorbalance_global_data_t dt_iop_colorbalancergb_global_data_t
void cleanup_global(dt_iop_module_so_t *module)
void cleanup_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
#define LUT_ELEM
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
int flags()
dt_iop_colorbalancergb_mask_data_t
@ MASK_SHADOWS
@ MASK_NONE
@ MASK_MIDTONES
@ MASK_HIGHLIGHTS
void gui_cleanup(struct dt_iop_module_t *self)
void init_presets(dt_iop_module_so_t *self)
void init_pipe(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static float lookup_gamut(const float *const gamut_lut, const float x)
void init_global(dt_iop_module_so_t *module)
void autoset(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, const void *i)
int process_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out)
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
static void paint_chroma_slider(GtkWidget *w, const float hue)
gboolean runtime_data_hash(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
void dt_ioppr_free_iccprofile_params_cl(dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
free parameters build with the previous function.
cl_int dt_ioppr_build_iccprofile_params_cl(const dt_iop_order_iccprofile_info_t *const profile_info, const int devid, dt_colorspaces_iccprofile_info_cl_t **_profile_info_cl, cl_float **_profile_lut_cl, cl_mem *_dev_profile_info, cl_mem *_dev_profile_lut)
build the required parameters for a kernel that uses a profile info.
static const float x
const int t
static void xyY_to_dt_UCS_UV(const float4 xyY, float UV_star_prime[2])
Definition colorspace.h:802
static float Y_to_dt_UCS_L_star(const float Y)
Definition colorspace.h:789
static float4 dt_XYZ_to_xyY(const float4 XYZ)
Definition colorspace.h:635
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t xyY
static dt_aligned_pixel_t rgb
static dt_aligned_pixel_t XYZ
const float max
static dt_aligned_pixel_t XYZ_D65
static dt_aligned_pixel_t XYZ_D50
const dt_colormatrix_t AI
const dt_colormatrix_t dt_aligned_pixel_t out
static dt_aligned_pixel_t RGB
int dt_conf_get_bool(const char *name)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
#define P(V, params)
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
#define M_PI_F
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_resync_history_main(dev)
void default_input_format(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_iop_buffer_dsc_t *dsc)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_current_profile_info(dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe)
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:141
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
#define H
Definition diffuse.c:614
static void set_color(cairo_t *cr, GdkRGBA color)
Definition draw.h:125
static void dt_cairo_perceptual_gradient(cairo_pattern_t *grad, double alpha)
Definition draw.h:506
@ TYPE_FLOAT
Definition format.h:56
GdkRGBA color[]
Definition geotagging.c:541
void dt_gui_presets_update_ldr(const char *name, dt_dev_operation_t op, const int32_t version, const int ldrflag)
void dt_gui_presets_add_generic(const char *name, dt_dev_operation_t op, const int32_t version, const void *params, const int32_t params_size, const int32_t enabled)
@ FOR_RAW
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1909
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1685
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_COLOR
Definition imageop.h:158
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_M_PI_F
Definition math.h:54
#define M_PI
Definition math.h:47
float DT_ALIGNED_ARRAY dt_colormatrix_t[4][4]
Definition matrices.h:34
static void transpose_3xSSE(const dt_colormatrix_t input, dt_colormatrix_t output)
Definition matrices.h:69
static void pack_3xSSE_to_3x4(const dt_colormatrix_t input, float output[12])
Definition matrices.h:150
static void dt_colormatrix_mul(dt_colormatrix_t dst, const dt_colormatrix_t m1, const dt_colormatrix_t m2)
Definition matrices.h:167
#define DT_ALIGNED_PIXEL
Align a 4-float pixel on 16 bytes, enough for SSE. Same struct-member caveat as DT_ALIGNED_ARRAY,...
Definition mem_alloc.h:85
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
GtkNotebook * dt_ui_notebook_new()
Definition notebook.c:83
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2808
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2603
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2823
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define dt_omploop_sfence()
Definition openmp.h:164
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
const float r
GdkRGBA graph_fg
Definition bauhaus.h:281
The device-side view of a dt_iop_order_iccprofile_info_t: the scalar fields only.
dt_dev_pixelpipe_type_t type
int32_t gui_attached
Definition develop.h:167
unsigned int channels
Definition format.h:83
dt_iop_buffer_type_t datatype
Definition format.h:85
dt_aligned_pixel_t checker_color_1
struct dt_iop_order_iccprofile_info_t * work_profile
dt_iop_colorbalancrgb_saturation_t saturation_formula
dt_iop_colorbalancergb_mask_data_t mask_type
dt_aligned_pixel_t checker_color_2
dt_iop_colorbalancergb_mask_data_t mask_type
dt_iop_colorbalancrgb_saturation_t saturation_formula
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
int request_mask_display
Definition imageop.h:276
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
dt_colormatrix_t matrix_out
XYZ (D50) -> RGB, row-major; the inverse of matrix_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_colormatrix_t matrix_in
RGB -> XYZ (D50), row-major. matrix_in[1][*] is the luminance row. NaN in [0][0] marks the whole prof...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)