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