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