Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
colorzones.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2010 Bruce Guenter.
4 Copyright (C) 2010-2012 Henrik Andersson.
5 Copyright (C) 2010-2014, 2016 johannes hanika.
6 Copyright (C) 2010-2012, 2019 Pascal de Bruijn.
7 Copyright (C) 2011 Antony Dovgal.
8 Copyright (C) 2011 Brian Teague.
9 Copyright (C) 2011 Jochen Schroeder.
10 Copyright (C) 2011 Robert Bieber.
11 Copyright (C) 2011-2016, 2018-2019 Tobias Ellinghaus.
12 Copyright (C) 2012 Richard Wonka.
13 Copyright (C) 2012-2016, 2019 Ulrich Pegelow.
14 Copyright (C) 2013 Dennis Gnad.
15 Copyright (C) 2013, 2018-2022 Pascal Obry.
16 Copyright (C) 2013-2016, 2019 Roman Lebedev.
17 Copyright (C) 2013 Simon Spannagel.
18 Copyright (C) 2013 Thomas Pryds.
19 Copyright (C) 2014, 2019 parafin.
20 Copyright (C) 2015 Pedro Côrte-Real.
21 Copyright (C) 2017-2018, 2020-2021 Dan Torop.
22 Copyright (C) 2017, 2019-2020 Heiko Bauke.
23 Copyright (C) 2018-2020, 2022-2026 Aurélien PIERRE.
24 Copyright (C) 2018-2019 Edgardo Hoszowski.
25 Copyright (C) 2018 Matthieu Moy.
26 Copyright (C) 2018 Maurizio Paglia.
27 Copyright (C) 2018 rawfiner.
28 Copyright (C) 2019 Andreas Schneider.
29 Copyright (C) 2019-2022 Diederik Ter Rahe.
30 Copyright (C) 2019 Diederik ter Rahe.
31 Copyright (C) 2019 emeikei.
32 Copyright (C) 2019 Jacques Le Clerc.
33 Copyright (C) 2020, 2022 Aldric Renaudin.
34 Copyright (C) 2020-2021 Chris Elston.
35 Copyright (C) 2020 Hubert Kowalski.
36 Copyright (C) 2020-2021 Marco.
37 Copyright (C) 2020 msdm.
38 Copyright (C) 2020-2021 Ralf Brown.
39 Copyright (C) 2021 lhietal.
40 Copyright (C) 2021 Marco Carrarini.
41 Copyright (C) 2021 Martin Straeten.
42 Copyright (C) 2022 Hanno Schwalm.
43 Copyright (C) 2022 Martin Bařinka.
44 Copyright (C) 2022 Philipp Lutz.
45
46 darktable is free software: you can redistribute it and/or modify
47 it under the terms of the GNU General Public License as published by
48 the Free Software Foundation, either version 3 of the License, or
49 (at your option) any later version.
50
51 darktable is distributed in the hope that it will be useful,
52 but WITHOUT ANY WARRANTY; without even the implied warranty of
53 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
54 GNU General Public License for more details.
55
56 You should have received a copy of the GNU General Public License
57 along with darktable. If not, see <http://www.gnu.org/licenses/>.
58*/
59
60#ifdef HAVE_CONFIG_H
61#include "common/darktable.h"
62#include "gui/gdkkeys.h"
63#include "config.h"
64#endif
65
66#include "bauhaus/bauhaus.h"
67#include "common/iop_profile.h"
69#include "common/imagebuf.h"
70#include "common/math.h"
71#include "control/control.h"
72#include "develop/imageop.h"
73#include "develop/imageop_gui.h"
74#include "dtgtk/drawingarea.h"
75
77#include "gui/gtk.h"
78#include "gui/draw.h"
79#include "gui/presets.h"
80#include "libs/colorpicker.h"
81#include "libs/lib.h"
82
84
85#define DT_IOP_COLORZONES_INSET DT_PIXEL_APPLY_DPI(5)
86#define DT_IOP_COLORZONES_CURVE_INFL .3f
87#define DT_IOP_COLORZONES_RES 256
88#define DT_IOP_COLORZONES_LUT_RES 0x10000
89
90#define DT_IOP_COLORZONES_BANDS 8
91
92#define DT_IOP_COLORZONES_MAXNODES 20
93#define DT_IOP_COLORZONES_DEFAULT_STEP (0.001f)
94
95#define DT_IOP_COLORZONES_MIN_X_DISTANCE 0.0025f
96
98{
99 DT_IOP_COLORZONES_MODE_SMOOTH = 0, // $DESCRIPTION: "smooth"
100 DT_IOP_COLORZONES_MODE_STRONG = 1 // $DESCRIPTION: "strong"
102
108
110{
111 DT_IOP_COLORZONES_L = 0, // $DESCRIPTION: "lightness"
112 DT_IOP_COLORZONES_C = 1, // $DESCRIPTION: "saturation"
113 DT_IOP_COLORZONES_h = 2, // $DESCRIPTION: "hue"
116
122
123typedef struct dt_iop_colorzones_params_t
124{
125 dt_iop_colorzones_channel_t channel; // $DEFAULT: DT_IOP_COLORZONES_h $DESCRIPTION: "select by"
126 // three curves (L, C, h) with max number of nodes
128 int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]; // number of nodes per curve
129 int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]; // CUBIC_SPLINE, CATMULL_ROM, MONOTONE_HERMITE
130 float strength; // $MIN: -200.0 $MAX: 200.0 $DEFAULT: 0.0 $DESCRIPTION: "mix"
131 dt_iop_colorzones_modes_t mode; // $MIN: 0 $MAX: 1 $DEFAULT: DT_IOP_COLORZONES_MODE_SMOOTH $DESCRIPTION: "process mode"
134
166
176
182
183
184const char *name()
185{
186 return _("color _zones");
187}
188
189const char **description(struct dt_iop_module_t *self)
190{
191 return dt_iop_set_description(self, _("selectively shift hues, saturation and brightness of pixels"),
192 _("creative"),
193 _("linear or non-linear, Lab, display-referred"),
194 _("non-linear, Lab"),
195 _("non-linear, Lab, display-referred"));
196}
197
202
204{
205 return IOP_GROUP_COLOR;
206}
207
209{
210 return IOP_CS_LAB;
211}
212
213int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
214 const int new_version)
215{
216#define DT_IOP_COLORZONES1_BANDS 6
217
218 if(old_version == 1 && new_version == 5)
219 {
220 typedef struct dt_iop_colorzones_params1_t
221 {
222 int32_t channel;
223 float equalizer_x[3][DT_IOP_COLORZONES1_BANDS], equalizer_y[3][DT_IOP_COLORZONES1_BANDS];
224 } dt_iop_colorzones_params1_t;
225
226 const dt_iop_colorzones_params1_t *old = old_params;
227 dt_iop_colorzones_params_t *new = new_params;
228
229 new->channel = old->channel;
230
231 // keep first point
232 for(int i = 0; i < 3; i++)
233 {
234 new->curve[i][0].x = old->equalizer_x[i][0];
235 new->curve[i][0].y = old->equalizer_y[i][0];
236 }
237
238 for(int i = 0; i < 3; i++)
239 for(int k = 0; k < 6; k++)
240 {
241 // first+1 and last-1 are set to just after and before the first and last point
242 if(k == 0)
243 new->curve[i][k + 1].x = old->equalizer_x[i][k] + 0.001f;
244 else if(k == 5)
245 new->curve[i][k + 1].x = old->equalizer_x[i][k] - 0.001f;
246 else
247 new->curve[i][k + 1].x = old->equalizer_x[i][k];
248 new->curve[i][k + 1].y = old->equalizer_y[i][k];
249 }
250
251 // keep last point
252 for(int i = 0; i < 3; i++)
253 {
254 new->curve[i][7].x = old->equalizer_x[i][5];
255 new->curve[i][7].y = old->equalizer_y[i][5];
256 }
257 for(int c = 0; c < 3; c++)
258 {
259 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
260 new->curve_type[c] = CATMULL_ROM;
261 }
262 new->strength = 0.0f;
264 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
265 return 0;
266 }
267 if(old_version == 2 && new_version == 5)
268 {
269 typedef struct dt_iop_colorzones_params2_t
270 {
271 int32_t channel;
272 float equalizer_x[3][DT_IOP_COLORZONES_BANDS], equalizer_y[3][DT_IOP_COLORZONES_BANDS];
273 } dt_iop_colorzones_params2_t;
274
275 const dt_iop_colorzones_params2_t *old = old_params;
276 dt_iop_colorzones_params_t *new = new_params;
277 new->channel = old->channel;
278
279 for(int b = 0; b < DT_IOP_COLORZONES_BANDS; b++)
280 for(int c = 0; c < 3; c++)
281 {
282 new->curve[c][b].x = old->equalizer_x[c][b];
283 new->curve[c][b].y = old->equalizer_y[c][b];
284 }
285 for(int c = 0; c < 3; c++)
286 {
287 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
288 new->curve_type[c] = CATMULL_ROM;
289 }
290 new->strength = 0.0f;
292 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
293 return 0;
294 }
295 if(old_version == 3 && new_version == 5)
296 {
297 typedef struct dt_iop_colorzones_params3_t
298 {
299 int32_t channel;
300 float equalizer_x[3][DT_IOP_COLORZONES_BANDS], equalizer_y[3][DT_IOP_COLORZONES_BANDS];
301 float strength;
302 } dt_iop_colorzones_params3_t;
303
304 const dt_iop_colorzones_params3_t *old = old_params;
305 dt_iop_colorzones_params_t *new = new_params;
306 new->channel = old->channel;
307
308 for(int b = 0; b < DT_IOP_COLORZONES_BANDS; b++)
309 {
310 for(int c = 0; c < 3; c++)
311 {
312 new->curve[c][b].x = old->equalizer_x[c][b];
313 new->curve[c][b].y = old->equalizer_y[c][b];
314 }
315 }
316 for(int c = 0; c < 3; c++)
317 {
318 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
319 new->curve_type[c] = CATMULL_ROM;
320 }
321 new->strength = old->strength;
323 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
324 return 0;
325 }
326 if(old_version == 4 && new_version == 5)
327 {
328 typedef struct dt_iop_colorzones_params4_t
329 {
330 int32_t channel;
332 int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS];
333 int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS];
334 float strength;
335 int mode;
336 } dt_iop_colorzones_params4_t;
337
338 const dt_iop_colorzones_params4_t *old = old_params;
339 dt_iop_colorzones_params_t *new = new_params;
340 new->channel = old->channel;
341
342 for(int i = 0; i < DT_IOP_COLORZONES_MAXNODES; i++)
343 {
344 for(int c = 0; c < DT_IOP_COLORZONES_MAX_CHANNELS; c++)
345 {
346 new->curve[c][i].x = old->curve[c][i].x;
347 new->curve[c][i].y = old->curve[c][i].y;
348 }
349 }
350 for(int c = 0; c < DT_IOP_COLORZONES_MAX_CHANNELS; c++)
351 {
352 new->curve_num_nodes[c] = old->curve_num_nodes[c];
353 new->curve_type[c] = old->curve_type[c];
354 }
355 new->strength = old->strength;
356 new->mode = old->mode;
357 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
358 return 0;
359 }
360#undef DT_IOP_COLORZONES1_BANDS
361
362 return 1;
363}
364
365static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
366{
367 return (x - offset) * zoom_factor;
368}
369
370static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
371{
372 return (x / zoom_factor) + offset;
373}
374
375// fills in new parameters based on mouse position (in 0,1)
377 const int ch, const double mouse_x, const double mouse_y,
378 const float radius)
379{
380 const int bands = p->curve_num_nodes[ch];
381
382 const float lin_mouse_x = _mouse_to_curve(mouse_x, c->zoom_factor, c->offset_x);
383 const float lin_mouse_y = _mouse_to_curve(mouse_y, c->zoom_factor, c->offset_y);
384
385 const float rad = radius / c->zoom_factor;
386
387 if(p->channel == DT_IOP_COLORZONES_h && p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
388 {
389 // periodic boundary
390 for(int k = 1; k < bands - 1; k++)
391 {
392 const float f = expf(-(lin_mouse_x - p->curve[ch][k].x) * (lin_mouse_x - p->curve[ch][k].x) / (rad * rad));
393 p->curve[ch][k].y = (1.f - f) * p->curve[ch][k].y + f * lin_mouse_y;
394 }
395 const int m = bands - 1;
396 const float mind = fminf((lin_mouse_x - p->curve[ch][0].x) * (lin_mouse_x - p->curve[ch][0].x),
397 (lin_mouse_x - p->curve[ch][m].x) * (lin_mouse_x - p->curve[ch][m].x));
398 const float f = expf(-mind / (rad * rad));
399 p->curve[ch][0].y = (1.f - f) * p->curve[ch][0].y + f * lin_mouse_y;
400 p->curve[ch][m].y = (1.f - f) * p->curve[ch][m].y + f * lin_mouse_y;
401 }
402 else
403 {
404 for(int k = 0; k < bands; k++)
405 {
406 const float f = expf(-(lin_mouse_x - p->curve[ch][k].x) * (lin_mouse_x - p->curve[ch][k].x) / (rad * rad));
407 p->curve[ch][k].y = (1.f - f) * p->curve[ch][k].y + f * lin_mouse_y;
408 }
409 }
410}
411
412static inline __attribute__((always_inline)) float lookup(const float *lut, const float i)
413{
414 const int bin0 = MIN(0xffff, MAX(0, (int)(DT_IOP_COLORZONES_LUT_RES * i)));
415 const int bin1 = MIN(0xffff, MAX(0, (int)(DT_IOP_COLORZONES_LUT_RES * i) + 1));
416 const float f = DT_IOP_COLORZONES_LUT_RES * i - bin0;
417 return lut[bin1] * f + lut[bin0] * (1.f - f);
418}
419
420static inline float strength(float value, float strength)
421{
422 return value + (value - 0.5f) * (strength / 100.0f);
423}
424
427 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
428 const dt_iop_roi_t *const roi_out)
429{
432
433 const int ch = piece->dsc_in.channels;
434 const float normalize_C = 1.f / (128.0f * sqrtf(2.f));
435
436 const dt_iop_colorzones_channel_t display_channel = g->channel;
437
438 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
440 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
441 {
442 float *in = (float *)ivoid + ch * k;
443 float *out = (float *)ovoid + ch * k;
444
446
447 dt_Lab_2_LCH(in, LCh);
448
449 float select = 0.0f;
450 switch(d->channel)
451 {
453 select = LCh[0] * 0.01f;
454 break;
456 select = LCh[1] * normalize_C;
457 break;
459 default:
460 select = LCh[2];
461 break;
462 }
463 select = CLAMP(select, 0.f, 1.f);
464
465 out[3] = fabsf(lookup(d->lut[display_channel], select) - .5f) * 4.f;
466 out[3] = CLAMP(out[3], 0.f, 1.f);
467 }
468
470 pipe->bypass_blendif = 1;
471}
472
474void process_v1(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
475 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
476{
478
479 const int ch = piece->dsc_in.channels;
480 const float normalize_C = 1.f / (128.0f * sqrtf(2.f));
482 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
483 {
484 float *in = (float *)ivoid + ch * k;
485 float *out = (float *)ovoid + ch * k;
486
488
489 dt_Lab_2_LCH(in, LCh);
490
491 float select = 0.0f;
492 switch(d->channel)
493 {
495 select = LCh[0] * 0.01f;
496 break;
498 select = LCh[1] * normalize_C;
499 break;
501 default:
502 select = LCh[2];
503 break;
504 }
505 select = CLAMP(select, 0.f, 1.f);
506
507 LCh[0] *= powf(2.0f, 4.0f * (lookup(d->lut[0], select) - .5f));
508 LCh[1] *= 2.f * lookup(d->lut[1], select);
509 LCh[2] += lookup(d->lut[2], select) - .5f;
510
511 dt_LCH_2_Lab(LCh, out);
512
513 out[3] = in[3];
514 }
515}
516
518void process_v3(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
519 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
520{
522 const int ch = piece->dsc_in.channels;
524 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
525 {
526 float *in = (float *)ivoid + ch * k;
527 float *out = (float *)ovoid + ch * k;
528 const float a = in[1], b = in[2];
529 const float h = fmodf(atan2f(b, a) + 2.0f * DT_M_PI_F, 2.0f * DT_M_PI_F) / (2.0f * DT_M_PI_F);
530 const float C = sqrtf(b * b + a * a);
531 float select = 0.0f;
532 float blend = 0.0f;
533 switch(d->channel)
534 {
536 select = fminf(1.0f, in[0] / 100.0f);
537 break;
539 select = fminf(1.0f, C / 128.0f);
540 break;
541 default:
543 select = h;
544 blend = powf(1.0f - C / 128.0f, 2.0f);
545 break;
546 }
547 const float Lm = (blend * .5f + (1.0f - blend) * lookup(d->lut[0], select)) - .5f;
548 const float hm = (blend * .5f + (1.0f - blend) * lookup(d->lut[2], select)) - .5f;
549 blend *= blend; // saturation isn't as prone to artifacts:
550 // const float Cm = 2.0 * (blend*.5f + (1.0f-blend)*lookup(d->lut[1], select));
551 const float Cm = 2.0f * lookup(d->lut[1], select);
552 const float L = in[0] * powf(2.0f, 4.0f * Lm);
553 out[0] = L;
554 out[1] = cosf(2.0f * DT_M_PI_F * (h + hm)) * Cm * C;
555 out[2] = sinf(2.0f * DT_M_PI_F * (h + hm)) * Cm * C;
556 out[3] = in[3];
557 }
558}
559
560int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
561 void *const ovoid)
562{
563 const dt_iop_roi_t *const roi_in = &piece->roi_in;
564 const dt_iop_roi_t *const roi_out = &piece->roi_out;
567
568 // display selection if requested
569 if(pipe->type == DT_DEV_PIXELPIPE_FULL && !IS_NULL_PTR(g) && g->display_mask && self->dev->gui_attached
570 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
571 process_display(self, (dt_dev_pixelpipe_t *)pipe, piece, ivoid, ovoid, roi_in, roi_out);
572 else if(d->mode == DT_IOP_COLORZONES_MODE_SMOOTH)
573 process_v3(self, piece, ivoid, ovoid, roi_in, roi_out);
574 else
575 process_v1(self, piece, ivoid, ovoid, roi_in, roi_out);
576 return 0;
577}
578
579#ifdef HAVE_OPENCL
580int 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)
581{
582 const dt_iop_roi_t *const roi_in = &piece->roi_in;
585 cl_mem dev_L, dev_a, dev_b = NULL;
586 cl_int err = -999;
587
588 const int devid = pipe->devid;
589 const int width = roi_in->width;
590 const int height = roi_in->height;
591 const int kernel_colorzones
593
594 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
595 dev_L = dt_opencl_copy_host_to_device(devid, d->lut[0], 256, 256, sizeof(float));
596 dev_a = dt_opencl_copy_host_to_device(devid, d->lut[1], 256, 256, sizeof(float));
597 dev_b = dt_opencl_copy_host_to_device(devid, d->lut[2], 256, 256, sizeof(float));
598 if(IS_NULL_PTR(dev_L) || IS_NULL_PTR(dev_a) || IS_NULL_PTR(dev_b)) goto error;
599
600 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 0, sizeof(cl_mem), &dev_in);
601 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 1, sizeof(cl_mem), &dev_out);
602 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 2, sizeof(int), &width);
603 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 3, sizeof(int), &height);
604 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 4, sizeof(int), &d->channel);
605 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 5, sizeof(cl_mem), &dev_L);
606 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 6, sizeof(cl_mem), &dev_a);
607 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 7, sizeof(cl_mem), &dev_b);
608 err = dt_opencl_enqueue_kernel_2d(devid, kernel_colorzones, sizes);
609
610 if(err != CL_SUCCESS) goto error;
614 return TRUE;
615
616error:
620 dt_print(DT_DEBUG_OPENCL, "[opencl_colorzones] couldn't enqueue kernel! %d\n", err);
621 return FALSE;
622}
623#endif
624
626{
628 const int version = 5;
629
630 p.strength = 0.f;
632 p.splines_version = DT_IOP_COLORZONES_SPLINES_V2;
633
635
636 // red black white
637 p.channel = DT_IOP_COLORZONES_h;
638 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
639 {
640 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
641 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
642 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
643 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
644 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
645 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
646 }
647 p.curve[DT_IOP_COLORZONES_C][0].y = p.curve[DT_IOP_COLORZONES_C][DT_IOP_COLORZONES_BANDS - 1].y = 0.65f;
648 p.curve[DT_IOP_COLORZONES_C][1].x = 3.f / 16.f;
649 p.curve[DT_IOP_COLORZONES_C][3].x = 0.50f;
650 p.curve[DT_IOP_COLORZONES_C][4].x = 0.51f;
651 p.curve[DT_IOP_COLORZONES_C][6].x = 15.f / 16.f;
652 for(int c = 0; c < 3; c++)
653 {
654 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
655 p.curve_type[c] = CATMULL_ROM;
656 }
657 dt_gui_presets_add_generic(_("red black white"), self->op,
658 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
659
660 // black white and skin tones
661 p.channel = DT_IOP_COLORZONES_h;
662 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
663 {
664 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
665 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
666 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
667 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
668 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
669 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
670 }
671 p.curve[DT_IOP_COLORZONES_C][0].y = 0.5f;
672 p.curve[DT_IOP_COLORZONES_C][2].x = 0.25f;
673 p.curve[DT_IOP_COLORZONES_C][1].x = 0.16f;
674 p.curve[DT_IOP_COLORZONES_C][1].y = 0.3f;
675 for(int c = 0; c < 3; c++)
676 {
677 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
678 p.curve_type[c] = CATMULL_ROM;
679 }
680 dt_gui_presets_add_generic(_("black white and skin tones"), self->op,
681 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
682
683 // polarizing filter
684 p.channel = DT_IOP_COLORZONES_C;
685 for(int k = 0; k < DT_IOP_COLORZONES_BANDS; k++)
686 {
687 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
688 p.curve[DT_IOP_COLORZONES_C][k].y = .5f;
689 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
690 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
691 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
692 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
693 }
694 for(int k = 3; k < DT_IOP_COLORZONES_BANDS; k++)
695 p.curve[DT_IOP_COLORZONES_C][k].y += (k - 2.5f) / (DT_IOP_COLORZONES_BANDS - 2.0f) * 0.25f;
696 for(int k = 4; k < DT_IOP_COLORZONES_BANDS; k++)
697 p.curve[DT_IOP_COLORZONES_L][k].y -= (k - 3.5f) / (DT_IOP_COLORZONES_BANDS - 3.0f) * 0.35f;
698 for(int c = 0; c < 3; c++)
699 {
700 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
701 p.curve_type[c] = CATMULL_ROM;
702 }
703 dt_gui_presets_add_generic(_("polarizing filter"), self->op,
704 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
705
706 // natural skin tone
707 p.channel = DT_IOP_COLORZONES_h;
708 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
709 {
710 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
711 p.curve[DT_IOP_COLORZONES_C][k].y = .5f;
712 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
713 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
714 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
715 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
716 }
717 p.curve[DT_IOP_COLORZONES_C][1].y = .45f;
718 p.curve[DT_IOP_COLORZONES_h][1].y = .55f;
719 for(int c = 0; c < 3; c++)
720 {
721 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
722 p.curve_type[c] = CATMULL_ROM;
723 }
724 dt_gui_presets_add_generic(_("natural skin tones"), self->op,
725 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
726
727 // black and white film
728 p.channel = DT_IOP_COLORZONES_h;
729 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
730 {
731 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
732 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
733 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
734 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
735 }
736 p.curve[DT_IOP_COLORZONES_L][0].x = 0.000000f;
737 p.curve[DT_IOP_COLORZONES_L][0].y = 0.613040f;
738 p.curve[DT_IOP_COLORZONES_L][1].x = 0.010000f;
739 p.curve[DT_IOP_COLORZONES_L][1].y = 0.613040f;
740 p.curve[DT_IOP_COLORZONES_L][2].x = 0.245283f;
741 p.curve[DT_IOP_COLORZONES_L][2].y = 0.447962f;
742 p.curve[DT_IOP_COLORZONES_L][3].x = 0.498113f;
743 p.curve[DT_IOP_COLORZONES_L][3].y = 0.529201f;
744 p.curve[DT_IOP_COLORZONES_L][4].x = 0.641509f;
745 p.curve[DT_IOP_COLORZONES_L][4].y = 0.664967f;
746 p.curve[DT_IOP_COLORZONES_L][5].x = 0.879245f;
747 p.curve[DT_IOP_COLORZONES_L][5].y = 0.777294f;
748 p.curve[DT_IOP_COLORZONES_L][6].x = 0.990000f;
749 p.curve[DT_IOP_COLORZONES_L][6].y = 0.613040f;
750 for(int c = 0; c < 3; c++)
751 {
752 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
753 p.curve_type[c] = CATMULL_ROM;
754 }
755 dt_gui_presets_add_generic(_("black & white film"), self->op,
756 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
757
758 // neutral preset with just a set of nodes uniformly distributed along the hue axis
759 const int colorzones_bands_hsl = 8;
760 p.channel = DT_IOP_COLORZONES_h;
761 for(int k = 0; k < colorzones_bands_hsl; k++)
762 {
763 p.curve[DT_IOP_COLORZONES_L][k].x = (float)k / colorzones_bands_hsl;
764 p.curve[DT_IOP_COLORZONES_L][k].y = 0.5f;
765 p.curve[DT_IOP_COLORZONES_C][k].x = (float)k / colorzones_bands_hsl;
766 p.curve[DT_IOP_COLORZONES_C][k].y = 0.5f;
767 p.curve[DT_IOP_COLORZONES_h][k].x = (float)k / colorzones_bands_hsl;
768 p.curve[DT_IOP_COLORZONES_h][k].y = 0.5f;
769 }
770 for(int c = 0; c < 3; c++)
771 {
772 p.curve_num_nodes[c] = colorzones_bands_hsl;
773 p.curve_type[c] = MONOTONE_HERMITE;
774 }
775 dt_gui_presets_add_generic(_("HSL base setting"), self->op,
776 version, &p, sizeof(p), 1, DEVELOP_BLEND_CS_RGB_DISPLAY);
777
779}
780
782{
784 if(c)
785 {
786 if(c->display_mask)
787 {
788 c->display_mask = FALSE;
790 }
791 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->bt_showmask)))
792 {
794 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->bt_showmask), FALSE);
796 }
797 }
798}
799
800static void _reset_nodes(dt_iop_colorzones_params_t *p, const int ch, const _Bool touch_edges)
801{
802 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
803 {
804 if(touch_edges)
805 p->curve[ch][k].x = (float)k / (float)(p->curve_num_nodes[ch] - 1);
806 else
807 p->curve[ch][k].x = ((float)k + 0.5f) / (float)p->curve_num_nodes[ch];
808 p->curve[ch][k].y = 0.5f;
809 }
810}
811
812static void _reset_parameters(dt_iop_colorzones_params_t *p, const int channel, const int splines_version)
813{
814 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
815 {
816 p->curve_num_nodes[ch] = 2;
817 p->curve_type[ch] = CATMULL_ROM; // CUBIC_SPLINE, MONOTONE_HERMITE
818 _reset_nodes(p, ch, channel != DT_IOP_COLORZONES_h);
819 }
820 p->strength = 0.0f;
821 p->channel = channel;
823 p->splines_version = splines_version;
824}
825
826static int _select_base_display_color(dt_iop_module_t *self, float *picked_color, float *picker_min,
827 float *picker_max)
828{
829 const int select_by_picker = !(self->request_color_pick != DT_REQUEST_COLORPICK_MODULE
830 || self->picked_color_max[0] < 0.0f || self->picked_color[0] == 0.0f);
831 if(!select_by_picker)
832 {
833 dt_aligned_pixel_t rgb = { 0.0f, 0.3f, 0.7f };
836 dt_sRGB_to_XYZ(rgb, xyz);
837 dt_XYZ_to_Lab(xyz, lab);
838 dt_Lab_2_LCH(lab, picked_color);
839
840 picker_max[0] = picker_min[0] = picked_color[0];
841 picker_max[1] = picker_min[1] = picked_color[1];
842 picker_max[2] = picker_min[2] = picked_color[2];
843 }
844 else
845 {
846 for(int k = 0; k < 3; k++)
847 {
848 picked_color[k] = self->picked_color[k];
849 picker_min[k] = self->picked_color_min[k];
850 picker_max[k] = self->picked_color_max[k];
851 }
852 }
853 return select_by_picker;
854}
855
857 dt_iop_colorzones_gui_data_t *c, const int width, const int height,
858 const float *const picker_color, const float *const picker_min,
859 const float *const picker_max)
860{
862 ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)) ||
863 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker_set_values)) ))
864 {
865 // the global live samples ...
866 GSList *samples = darktable.develop->color_picker.samples;
867 if(samples)
868 {
869 const dt_iop_order_iccprofile_info_t *const display_profile
871 const dt_iop_order_iccprofile_info_t *const work_profile
873 dt_aligned_pixel_t pick_mean, pick_min, pick_max;
874 int converted_cst;
875
876 if(!IS_NULL_PTR(work_profile) && !IS_NULL_PTR(display_profile))
877 {
878 dt_colorpicker_sample_t *sample = NULL;
879 for(; samples; samples = g_slist_next(samples))
880 {
881 sample = samples->data;
882
883 float picked_i = -1.0f;
884 float picked_min_i = -1.0f;
885 float picked_max_i = -1.0f;
886
887 // this functions need a 4c image
888 for(int k = 0; k < 3; k++)
889 {
890 pick_mean[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k];
891 pick_min[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MIN][k];
892 pick_max[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MAX][k];
893 }
894 pick_mean[3] = pick_min[3] = pick_max[3] = 1.f;
895
896 dt_ioppr_transform_image_colorspace_rgb(pick_mean, pick_mean, 1, 1, display_profile, work_profile,
897 "color zones");
898 dt_ioppr_transform_image_colorspace_rgb(pick_min, pick_min, 1, 1, display_profile, work_profile,
899 "color zones");
900 dt_ioppr_transform_image_colorspace_rgb(pick_max, pick_max, 1, 1, display_profile, work_profile,
901 "color zones");
902
903 dt_ioppr_transform_image_colorspace(self, pick_mean, pick_mean, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
904 &converted_cst, work_profile);
905 dt_ioppr_transform_image_colorspace(self, pick_min, pick_min, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
906 &converted_cst, work_profile);
907 dt_ioppr_transform_image_colorspace(self, pick_max, pick_max, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
908 &converted_cst, work_profile);
909
910 dt_Lab_2_LCH(pick_mean, pick_mean);
911 dt_Lab_2_LCH(pick_min, pick_min);
912 dt_Lab_2_LCH(pick_max, pick_max);
913
914 switch(p->channel)
915 {
916 // select by channel, abscissa:
918 picked_i = pick_mean[0] / 100.0f;
919 picked_min_i = pick_min[0] / 100.0f;
920 picked_max_i = pick_max[0] / 100.0f;
921 break;
923 picked_i = pick_mean[1] / (128.0f * sqrtf(2.f));
924 picked_min_i = pick_min[1] / (128.0f * sqrtf(2.f));
925 picked_max_i = pick_max[1] / (128.0f * sqrtf(2.f));
926 break;
927 default: // case DT_IOP_COLORZONES_h:
928 picked_i = pick_mean[2];
929 picked_min_i = pick_min[2];
930 picked_max_i = pick_max[2];
931 break;
932 }
933
934 // Convert abcissa to log coordinates if needed
935 picked_i = _curve_to_mouse(picked_i, c->zoom_factor, c->offset_x);
936 picked_min_i = _curve_to_mouse(picked_min_i, c->zoom_factor, c->offset_x);
937 picked_max_i = _curve_to_mouse(picked_max_i, c->zoom_factor, c->offset_x);
938
939 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.15);
940 cairo_rectangle(cr, width * picked_min_i, 0, width * fmax(picked_max_i - picked_min_i, 0.0f), height);
941 cairo_fill(cr);
942 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
943 cairo_move_to(cr, width * picked_i, 0);
944 cairo_line_to(cr, width * picked_i, height);
945 cairo_stroke(cr);
946 }
947 }
948 }
949 }
950
952 ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)) ||
953 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker_set_values)) ))
954 {
955 // draw marker for currently selected color:
956 float picked_i = -1.0f;
957 float picked_min_i = -1.0f;
958 float picked_max_i = -1.0f;
959 switch(p->channel)
960 {
961 // select by channel, abscissa:
963 picked_i = picker_color[0] / 100.0f;
964 picked_min_i = picker_min[0] / 100.0f;
965 picked_max_i = picker_max[0] / 100.0f;
966 break;
968 picked_i = picker_color[1] / (128.0f * sqrtf(2.f));
969 picked_min_i = picker_min[1] / (128.0f * sqrtf(2.f));
970 picked_max_i = picker_max[1] / (128.0f * sqrtf(2.f));
971 break;
972 default: // case DT_IOP_COLORZONES_h:
973 picked_i = picker_color[2];
974 picked_min_i = picker_min[2];
975 picked_max_i = picker_max[2];
976 break;
977 }
978
979 picked_i = _curve_to_mouse(picked_i, c->zoom_factor, c->offset_x);
980 picked_min_i = _curve_to_mouse(picked_min_i, c->zoom_factor, c->offset_x);
981 picked_max_i = _curve_to_mouse(picked_max_i, c->zoom_factor, c->offset_x);
982
983 cairo_save(cr);
984
985 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.25);
986 cairo_rectangle(cr, width * picked_min_i, 0, width * fmax(picked_max_i - picked_min_i, 0.0), height);
987 cairo_fill(cr);
988
989 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
990 cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
991 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
992 cairo_move_to(cr, width * picked_i, 0.0);
993 cairo_line_to(cr, width * picked_i, height);
994 cairo_stroke(cr);
995
996 cairo_restore(cr);
997 }
998}
999
1000// graph x resulolution
1001#define DT_COLORZONES_CELLSI 64
1002// graph y resulolution
1003#define DT_COLORZONES_CELLSJ 36
1004
1005#define COLORZONES_DRAW_BACKGROUD_BOX \
1006 dt_aligned_pixel_t Lab; \
1007 dt_LCH_2_Lab(LCh, Lab); \
1008 const float L0 = Lab[0]; \
1009 /* gamut mapping magic from iop/exposure.c: */ \
1010 const float Lwhite = 100.0f, Lclip = 20.0f; \
1011 const float Lcap = fminf(100.0f, Lab[0]); \
1012 const float clip \
1013 = 1.0f \
1014 - (Lcap - L0) * (1.0f / 100.0f) * fminf(Lwhite - Lclip, fmaxf(0.0f, Lab[0] - Lclip)) / (Lwhite - Lclip); \
1015 const float clip2 = clip * clip * clip; \
1016 Lab[1] *= Lab[0] / L0 * clip2; \
1017 Lab[2] *= Lab[0] / L0 * clip2; \
1018 \
1019 dt_aligned_pixel_t xyz; \
1020 dt_aligned_pixel_t rgb; \
1021 dt_Lab_to_XYZ(Lab, xyz); \
1022 dt_XYZ_to_sRGB(xyz, rgb); \
1023 \
1024 cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
1025
1027 const int select_by_picker, const int width, const int height,
1028 const float *picked_color)
1029{
1030 const float bg_sat_factor = dt_conf_get_float("plugins/darkroom/colorzones/bg_sat_factor");
1031 const float normalize_C = (128.f * bg_sat_factor * sqrtf(2.f));
1032
1033 const int cellsi = DT_COLORZONES_CELLSI;
1034 const int cellsj = DT_COLORZONES_CELLSJ;
1035
1036 for(int j = 0; j < cellsj; j++)
1037 {
1038 for(int i = 0; i < cellsi; i++)
1039 {
1040 dt_aligned_pixel_t LCh = { 0 };
1041
1042 const float jj = _mouse_to_curve(1.0f - ((float)j - .5f) / (float)(cellsj - 1), c->zoom_factor, c->offset_y);
1043 const float jjh
1044 = _mouse_to_curve(1.0f - (((float)j / (float)(cellsj - 1))), c->zoom_factor, c->offset_y) + .5f;
1045 const float ii = _mouse_to_curve(((float)i + .5f) / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1046 const float iih = _mouse_to_curve((float)i / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1047
1048 // select by channel, abscissa:
1049 switch(p->channel)
1050 {
1051 // select by channel, abscissa:
1053 LCh[0] = 100.0f * ii;
1054 LCh[1] = normalize_C * .5f;
1055 LCh[2] = picked_color[2];
1056 break;
1058 LCh[0] = 50.0f;
1059 LCh[1] = picked_color[1] * 2.f * bg_sat_factor * ii;
1060 LCh[2] = picked_color[2];
1061 break;
1062 default: // DT_IOP_COLORZONES_h
1063 LCh[0] = 50.0f;
1064 LCh[1] = normalize_C * .5f;
1065 LCh[2] = iih;
1066 break;
1067 }
1068 // channel to be altered:
1069 switch(c->channel)
1070 {
1071 // select by channel, abscissa:
1073 if(p->channel == DT_IOP_COLORZONES_L)
1074 LCh[0] *= jj;
1075 else
1076 LCh[0] += -50.0f + 100.0f * jj;
1077 break;
1079 LCh[1] *= 2.f * jj;
1080 break;
1081 default: // DT_IOP_COLORZONES_h
1082 LCh[2] += jjh;
1083 break;
1084 }
1085
1087
1088 cairo_rectangle(cr, width * i / (float)cellsi, height * j / (float)cellsj, width / (float)cellsi,
1089 height / (float)cellsj);
1090 cairo_fill(cr);
1091 }
1092 }
1093}
1094
1095static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1096{
1099
1100 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1101 {
1102 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
1103 {
1104 if(c->minmax_curve_type[ch] != p.curve_type[ch] || c->minmax_curve_nodes[ch] != p.curve_num_nodes[ch])
1105 {
1106 dt_draw_curve_destroy(c->minmax_curve[ch]);
1107 c->minmax_curve[ch] = dt_draw_curve_new(0.0f, 1.0f, p.curve_type[ch]);
1108 c->minmax_curve_nodes[ch] = p.curve_num_nodes[ch];
1109 c->minmax_curve_type[ch] = p.curve_type[ch];
1110
1111 if(p.channel == DT_IOP_COLORZONES_h)
1112 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1113 p.curve[ch][p.curve_num_nodes[ch] - 2].y);
1114 else
1115 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1116 p.curve[ch][0].y);
1117 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1118 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][k].x, p.curve[ch][k].y);
1119 if(p.channel == DT_IOP_COLORZONES_h)
1120 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][1].x + 1.0f, p.curve[ch][1].y);
1121 else
1122 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][1].x + 1.0f,
1123 p.curve[ch][p.curve_num_nodes[ch] - 1].y);
1124 }
1125 else
1126 {
1127 if(p.channel == DT_IOP_COLORZONES_h)
1128 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1129 p.curve[ch][p.curve_num_nodes[ch] - 2].y);
1130 else
1131 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1132 p.curve[ch][0].y);
1133 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1134 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1135 if(p.channel == DT_IOP_COLORZONES_h)
1136 dt_draw_curve_set_point(c->minmax_curve[ch], p.curve_num_nodes[ch] + 1, p.curve[ch][1].x + 1.0f,
1137 p.curve[ch][1].y);
1138 else
1139 dt_draw_curve_set_point(c->minmax_curve[ch], p.curve_num_nodes[ch] + 1, p.curve[ch][1].x + 1.0f,
1140 p.curve[ch][p.curve_num_nodes[ch] - 1].y);
1141 }
1142 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.0f, 1.0f, DT_IOP_COLORZONES_RES, NULL, c->draw_ys[ch]);
1143 }
1144 }
1145 else
1146 {
1147 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
1148 {
1149 if(c->minmax_curve_type[ch] != p.curve_type[ch] || c->minmax_curve_nodes[ch] != p.curve_num_nodes[ch]
1150 || c->minmax_curve[ch]->c.m_numAnchors != p.curve_num_nodes[ch])
1151 {
1152 dt_draw_curve_destroy(c->minmax_curve[ch]);
1153 c->minmax_curve[ch] = dt_draw_curve_new(0.f, 1.f, p.curve_type[ch]);
1154 c->minmax_curve_nodes[ch] = p.curve_num_nodes[ch];
1155 c->minmax_curve_type[ch] = p.curve_type[ch];
1156
1157 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1158 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][k].x, p.curve[ch][k].y);
1159 }
1160 else
1161 {
1162 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1163 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1164 }
1165 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_ys[ch],
1166 p.channel == DT_IOP_COLORZONES_h);
1167 }
1168 }
1169
1170 const int ch = (int)c->channel;
1171
1172 GtkAllocation allocation;
1173 gtk_widget_get_allocation(widget, &allocation);
1174 const int inset = DT_IOP_COLORZONES_INSET;
1175 int width = allocation.width, height = allocation.height;
1176 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1177 cairo_t *cr = cairo_create(cst);
1178
1179 // clear bg, match color of the notebook tabs:
1180 GdkRGBA color;
1181 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1182 gboolean color_found = gtk_style_context_lookup_color(context, "graph_overlay", &color);
1183 if(!color_found)
1184 {
1185 color.red = 1.0;
1186 color.green = 0.0;
1187 color.blue = 0.0;
1188 color.alpha = 1.0;
1189 }
1190 gdk_cairo_set_source_rgba(cr, &color);
1191 cairo_paint(cr);
1192
1193 cairo_translate(cr, inset, inset);
1194 width -= 2 * inset;
1195 height -= 2 * inset;
1196
1197 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1198 cairo_set_source_rgb(cr, .1, .1, .1);
1199 cairo_rectangle(cr, 0, 0, width, height);
1200 cairo_stroke(cr);
1201
1202 cairo_set_source_rgb(cr, .3, .3, .3);
1203 cairo_rectangle(cr, 0, 0, width, height);
1204 cairo_fill(cr);
1205
1206 // if color picker is active we use it as base color
1207 // otherwise we use a light blue
1208 // we will work on LCh
1209 dt_aligned_pixel_t picked_color, picker_min, picker_max;
1210 const int select_by_picker = _select_base_display_color(self, picked_color, picker_min, picker_max);
1211
1212 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1213
1214 _draw_background(cr, &p, c, select_by_picker, width, height, picked_color);
1215
1216 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1217
1218 // draw histogram in background
1219 // only if module is enabled
1220 if(self->enabled)
1221 {
1222 // only if no color picker
1224 {
1225 const int ch_hist = p.channel;
1226 const uint32_t *hist = self->histogram;
1227 const gboolean is_linear = FALSE;
1228 const float hist_max = is_linear ? self->histogram_max[ch_hist]
1229 : logf(1.0f + self->histogram_max[ch_hist]);
1230 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
1231 {
1232 cairo_save(cr);
1233 cairo_translate(cr, 0, height);
1234 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
1235
1236 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
1237 dt_draw_histogram_8_zoomed(cr, hist, 4, ch_hist, c->zoom_factor, c->offset_x * 255.f,
1238 c->offset_y * hist_max, is_linear);
1239
1240 cairo_restore(cr);
1241 }
1242 }
1243
1244 _draw_color_picker(self, cr, &p, c, width, height, picked_color, picker_min, picker_max);
1245 }
1246
1247 if(c->edit_by_area)
1248 {
1249 // draw x positions
1250 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1251 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1252 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
1253 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1254 {
1255 const float x = _curve_to_mouse(p.curve[ch][k].x, c->zoom_factor, c->offset_x);
1256
1257 cairo_move_to(cr, width * x, height + inset - DT_PIXEL_APPLY_DPI(1));
1258 cairo_rel_line_to(cr, -arrw * .5f, 0);
1259 cairo_rel_line_to(cr, arrw * .5f, -arrw);
1260 cairo_rel_line_to(cr, arrw * .5f, arrw);
1261 cairo_close_path(cr);
1262 if(c->x_move == k)
1263 cairo_fill(cr);
1264 else
1265 cairo_stroke(cr);
1266 }
1267 }
1268
1269 cairo_translate(cr, 0, height);
1270
1271 // draw curves, selected last.
1272 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1273 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1274 for(int i = 0; i < DT_IOP_COLORZONES_MAX_CHANNELS; i++)
1275 {
1276 const int ch_inv = ((int)c->channel + i + 1) % 3;
1277
1278 if(i == 2)
1279 cairo_set_source_rgba(cr, .7, .7, .7, 1.0);
1280 else
1281 cairo_set_source_rgba(cr, .7, .7, .7, 0.3);
1282
1283 cairo_move_to(cr, 0, -height * _curve_to_mouse(c->draw_ys[ch_inv][0], c->zoom_factor, c->offset_y));
1284 for(int k = 1; k < DT_IOP_COLORZONES_RES; k++)
1285 {
1286 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1287 const float yy = c->draw_ys[ch_inv][k];
1288
1289 const float x = _curve_to_mouse(xx, c->zoom_factor, c->offset_x),
1290 y = _curve_to_mouse(yy, c->zoom_factor, c->offset_y);
1291
1292 cairo_line_to(cr, x * width, -height * y);
1293 }
1294
1295 cairo_stroke(cr);
1296 }
1297
1298 // draw dots on knots
1299 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1300 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1301 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1302 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1303 {
1304 const float x = _curve_to_mouse(p.curve[ch][k].x, c->zoom_factor, c->offset_x),
1305 y = _curve_to_mouse(p.curve[ch][k].y, c->zoom_factor, c->offset_y);
1306 cairo_arc(cr, width * x, -height * y, DT_PIXEL_APPLY_DPI(3.0), 0.0, 2.0 * M_PI);
1307 cairo_stroke(cr);
1308 }
1309
1310 // draw min/max, if selected
1311 if(c->edit_by_area && (c->mouse_y > 0 || c->dragging))
1312 {
1313 const int bands = p.curve_num_nodes[ch];
1314
1316 dt_iop_colorzones_get_params(&p, c, c->channel, c->mouse_x, 1., c->mouse_radius);
1317 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1318 {
1319 if(p.channel == DT_IOP_COLORZONES_h)
1320 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][bands - 2].y);
1321 else
1322 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][0].y);
1323 for(int k = 0; k < bands; k++)
1324 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1325 if(p.channel == DT_IOP_COLORZONES_h)
1326 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][1].y);
1327 else
1328 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][bands - 1].y);
1329 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_min_ys);
1330 }
1331 else
1332 {
1333 for(int k = 0; k < bands; k++)
1334 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1335 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_min_ys,
1336 p.channel == DT_IOP_COLORZONES_h);
1337 }
1338
1340 dt_iop_colorzones_get_params(&p, c, c->channel, c->mouse_x, .0, c->mouse_radius);
1341 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1342 {
1343 if(p.channel == DT_IOP_COLORZONES_h)
1344 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][bands - 2].y);
1345 else
1346 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][0].y);
1347 for(int k = 0; k < bands; k++)
1348 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1349 if(p.channel == DT_IOP_COLORZONES_h)
1350 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][1].y);
1351 else
1352 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][bands - 1].y);
1353 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_max_ys);
1354 }
1355 else
1356 {
1357 for(int k = 0; k < bands; k++)
1358 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1359 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_max_ys,
1360 p.channel == DT_IOP_COLORZONES_h);
1361 }
1362
1363 // restore params values
1365
1366 // draw min/max curves:
1367 cairo_set_source_rgba(cr, .7, .7, .7, .6);
1368 cairo_move_to(cr, 0, -height * _curve_to_mouse(c->draw_min_ys[0], c->zoom_factor, c->offset_y));
1369
1370 for(int k = 1; k < DT_IOP_COLORZONES_RES; k++)
1371 {
1372 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1373 const float yy = c->draw_min_ys[k];
1374
1375 const float x = _curve_to_mouse(xx, c->zoom_factor, c->offset_x),
1376 y = _curve_to_mouse(yy, c->zoom_factor, c->offset_y);
1377
1378 cairo_line_to(cr, x * width, -height * y);
1379 }
1380
1381 for(int k = DT_IOP_COLORZONES_RES - 1; k >= 0; k--)
1382 {
1383 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1384 const float yy = c->draw_max_ys[k];
1385
1386 const float x = _curve_to_mouse(xx, c->zoom_factor, c->offset_x),
1387 y = _curve_to_mouse(yy, c->zoom_factor, c->offset_y);
1388
1389 cairo_line_to(cr, x * width, -height * y);
1390 }
1391
1392 cairo_close_path(cr);
1393 cairo_fill(cr);
1394
1395 // draw mouse focus circle
1396 cairo_set_source_rgba(cr, .9, .9, .9, .5);
1397
1398 const int k = DT_IOP_COLORZONES_RES * _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1399 const float x = c->mouse_x, y = _curve_to_mouse(c->draw_ys[ch][k], c->zoom_factor, c->offset_y);
1400
1401 cairo_arc(cr, x * width, -height * y, c->mouse_radius * width, 0, 2. * DT_M_PI);
1402 cairo_stroke(cr);
1403 }
1404 else
1405 {
1406 // draw selected cursor
1407 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1408
1409 if(c->selected >= 0)
1410 {
1411 cairo_set_source_rgb(cr, .9, .9, .9);
1412 const float x = _curve_to_mouse(p.curve[c->channel][c->selected].x, c->zoom_factor, c->offset_x),
1413 y = _curve_to_mouse(p.curve[c->channel][c->selected].y, c->zoom_factor, c->offset_y);
1414
1415 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1416 cairo_stroke(cr);
1417 }
1418 }
1419
1420 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1421
1422 cairo_destroy(cr);
1423 cairo_set_source_surface(crf, cst, 0, 0);
1424 cairo_paint(crf);
1425 cairo_surface_destroy(cst);
1426 return TRUE;
1427}
1428
1429static gboolean _bottom_area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1430{
1433
1434 GtkAllocation allocation;
1435 gtk_widget_get_allocation(widget, &allocation);
1436 const int inset = DT_IOP_COLORZONES_INSET;
1437 int width = allocation.width, height = allocation.height;
1438 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1439 cairo_t *cr = cairo_create(cst);
1440 // clear bg, match color of the notebook tabs:
1441 GdkRGBA color;
1442 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1443 gboolean color_found = gtk_style_context_lookup_color(context, "graph_overlay", &color);
1444 if(!color_found)
1445 {
1446 color.red = 1.0;
1447 color.green = 0.0;
1448 color.blue = 0.0;
1449 color.alpha = 1.0;
1450 }
1451 gdk_cairo_set_source_rgba(cr, &color);
1452 cairo_paint(cr);
1453
1454 cairo_translate(cr, inset, inset);
1455 width -= 2 * inset;
1456 height -= 2 * inset;
1457
1458 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1459 cairo_set_source_rgb(cr, .1, .1, .1);
1460 cairo_rectangle(cr, 0, 0, width, height);
1461 cairo_stroke(cr);
1462
1463 cairo_set_source_rgb(cr, .3, .3, .3);
1464 cairo_rectangle(cr, 0, 0, width, height);
1465 cairo_fill(cr);
1466
1467 // if color picker is active we use it as base color
1468 // otherwise we use a light blue
1469 // we will work on LCh
1470 dt_aligned_pixel_t picked_color, picker_min, picker_max;
1471 _select_base_display_color(self, picked_color, picker_min, picker_max);
1472 const float normalize_C = (128.f * sqrtf(2.f));
1473
1474 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1475
1476 const int cellsi = DT_COLORZONES_CELLSI;
1477
1478 for(int i = 0; i < cellsi; i++)
1479 {
1480 const float ii = _mouse_to_curve(((float)i + .5f) / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1481 const float iih = _mouse_to_curve((float)i / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1482
1484
1485 switch(p.channel)
1486 {
1487 // select by channel, abscissa:
1489 LCh[0] = 100.0f * ii;
1490 LCh[1] = normalize_C * .5f;
1491 LCh[2] = picked_color[2];
1492 break;
1494 LCh[0] = 50.0f;
1495 LCh[1] = picked_color[1] * 2.f * ii;
1496 LCh[2] = picked_color[2];
1497 break;
1498 default: // DT_IOP_COLORZONES_h
1499 LCh[0] = 50.0f;
1500 LCh[1] = normalize_C * .5f;
1501 LCh[2] = iih;
1502 break;
1503 }
1504
1506
1507 cairo_rectangle(cr, width * i / (float)cellsi, 0, width / (float)cellsi, height);
1508 cairo_fill(cr);
1509 }
1510
1511 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1512
1513 if(self->enabled)
1514 {
1515 _draw_color_picker(self, cr, &p, c, width, height, picked_color, picker_min, picker_max);
1516 }
1517
1518 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1519
1520 cairo_destroy(cr);
1521 cairo_set_source_surface(crf, cst, 0, 0);
1522 cairo_paint(crf);
1523 cairo_surface_destroy(cst);
1524 return TRUE;
1525}
1526
1527#undef COLORZONES_DRAW_BACKGROUD_BOX
1528#undef DT_COLORZONES_CELLSI
1529#undef DT_COLORZONES_CELLSJ
1530
1531static gboolean _bottom_area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1532{
1534
1535 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1536 {
1537 // reset zoom level
1538 c->zoom_factor = 1.f;
1539 c->offset_x = c->offset_y = 0.f;
1540
1541 gtk_widget_queue_draw(self->widget);
1542
1543 return TRUE;
1544 }
1545
1546 return FALSE;
1547}
1548
1549static gboolean _sanity_check(const float x, const int selected, const int nodes,
1550 const dt_iop_colorzones_node_t *curve)
1551{
1552 gboolean point_valid = TRUE;
1553
1554 // check if it is not too close to other node
1555 const float min_dist = DT_IOP_COLORZONES_MIN_X_DISTANCE; // in curve coordinates
1556 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
1557 || (selected < nodes - 1 && curve[selected + 1].x - x <= min_dist))
1558 point_valid = FALSE;
1559
1560 // for all points, x coordinate of point must be strictly larger than
1561 // the x coordinate of the previous point
1562 if((selected > 0 && (curve[selected - 1].x >= x)) || (selected < nodes - 1 && (curve[selected + 1].x <= x)))
1563 {
1564 point_valid = FALSE;
1565 }
1566
1567 return point_valid;
1568}
1569
1570static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, int node, float dx, float dy, guint state)
1571{
1574
1575 int ch = c->channel;
1576 dt_iop_colorzones_node_t *curve = p->curve[ch];
1577
1578 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1579 // do not move the first or last nodes on the x-axis
1580 if(node == 0 || node == p->curve_num_nodes[ch] - 1) dx = 0.f;
1581
1582 float new_x = CLAMP(curve[node].x + dx, 0.0f, 1.0f);
1583 const float new_y = CLAMP(curve[node].y + dy, 0.0f, 1.0f);
1584
1585 if(_sanity_check(new_x, node, p->curve_num_nodes[ch], p->curve[ch]))
1586 {
1587 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1588 {
1589 curve[node].x = new_x;
1590 curve[node].y = new_y;
1591
1592 if(p->channel == DT_IOP_COLORZONES_h && (node == 0 || node == p->curve_num_nodes[ch] - 1))
1593 {
1594 if(node == 0)
1595 {
1596 curve[p->curve_num_nodes[ch] - 1].x = 1.f - curve[node].x;
1597 curve[p->curve_num_nodes[ch] - 1].y = curve[node].y;
1598 }
1599 else
1600 {
1601 curve[0].x = 1.f - curve[node].x;
1602 curve[0].y = curve[node].y;
1603 }
1604 }
1605 }
1606 else
1607 {
1608 if(p->channel == DT_IOP_COLORZONES_h && (node == 0 || node == p->curve_num_nodes[ch] - 1))
1609 {
1610 if(node == 0)
1611 {
1612 if(new_x + 1.f - curve[p->curve_num_nodes[ch] - 1].x < DT_IOP_COLORZONES_MIN_X_DISTANCE)
1613 new_x = curve[p->curve_num_nodes[ch] - 1].x + DT_IOP_COLORZONES_MIN_X_DISTANCE - 1.f;
1614 }
1615 else
1616 {
1617 if(curve[0].x + 1.f - new_x < DT_IOP_COLORZONES_MIN_X_DISTANCE)
1618 new_x = curve[0].x + 1.f - DT_IOP_COLORZONES_MIN_X_DISTANCE;
1619 }
1620 }
1621 curve[node].x = new_x;
1622 curve[node].y = new_y;
1623 }
1624
1626 }
1627
1628 gtk_widget_queue_draw(widget);
1629
1630 return TRUE;
1631}
1632
1633static void _delete_node(dt_iop_module_t *self, dt_iop_colorzones_node_t *curve, int *nodes, int node, gboolean zero)
1634{
1635 if(zero)
1636 {
1637 curve[node].y = 0.5f;
1638 }
1639 else
1640 {
1641 // for p->splines_version == DT_IOP_COLORZONES_SPLINES_V1 condition nodes > 1 always true
1642 if(*nodes > 1)
1643 {
1644 for(int k = node; k < *nodes - 1; k++)
1645 {
1646 curve[k].x = curve[k + 1].x;
1647 curve[k].y = curve[k + 1].y;
1648 }
1649 curve[*nodes - 1].x = curve[*nodes - 1].y = 0;
1650 (*nodes)--;
1651 }
1652 else
1653 {
1654 curve[0].x = 0.5f;
1655 curve[0].y = 0.5f;
1656 }
1657 }
1658
1660 gtk_widget_queue_draw(self->widget);
1662}
1663
1664static inline int _add_node(dt_iop_colorzones_node_t *curve, int *nodes, float x, float y)
1665{
1666 int selected = -1;
1667 if(curve[0].x > x)
1668 selected = 0;
1669 else
1670 {
1671 for(int k = 1; k < *nodes; k++)
1672 {
1673 if(curve[k].x > x)
1674 {
1675 selected = k;
1676 break;
1677 }
1678 }
1679 }
1680 if(selected == -1) selected = *nodes;
1681
1682 // check if it is not too close to other node
1683 const float min_dist = DT_IOP_COLORZONES_MIN_X_DISTANCE; // in curve coordinates
1684 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
1685 || (selected < *nodes && curve[selected].x - x <= min_dist))
1686 selected = -2;
1687
1688 if(selected >= 0)
1689 {
1690 for(int i = *nodes; i > selected; i--)
1691 {
1692 curve[i].x = curve[i - 1].x;
1693 curve[i].y = curve[i - 1].y;
1694 }
1695 // found a new point
1696 curve[selected].x = x;
1697 curve[selected].y = y;
1698 (*nodes)++;
1699 }
1700 return selected;
1701}
1702
1703static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
1704{
1707
1708 int delta_y;
1709
1710 // (the graph height is now adjusted with the drag grip on its bottom edge, not Ctrl+Scroll)
1711
1712 if(c->selected < 0 && !c->edit_by_area) return TRUE;
1713
1714 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
1715 {
1717
1718 if(c->edit_by_area)
1719 {
1720 const int bands = p->curve_num_nodes[c->channel];
1721 c->mouse_radius = CLAMP(c->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / bands, 1.0);
1722 gtk_widget_queue_draw(widget);
1723 }
1724 else
1725 {
1727 return _move_point_internal(self, widget, c->selected, 0.f, delta_y, event->state);
1728 }
1729 }
1730
1731 return TRUE;
1732}
1733
1734static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1735{
1738
1739 const int inset = DT_IOP_COLORZONES_INSET;
1740
1741 GtkAllocation allocation;
1742 gtk_widget_get_allocation(widget, &allocation);
1743
1744 const int height = allocation.height - 2 * inset;
1745 const int width = allocation.width - 2 * inset;
1746
1747 const int ch = c->channel;
1748 const int nodes = p->curve_num_nodes[ch];
1749 dt_iop_colorzones_node_t *curve = p->curve[ch];
1750
1751 const double old_m_x = c->mouse_x;
1752 const double old_m_y = fabs(c->mouse_y);
1753
1754 c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1755 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1756
1757 // move a node
1758 if(event->state & GDK_BUTTON1_MASK)
1759 {
1760 if(c->edit_by_area)
1761 {
1762 if(c->dragging && c->x_move >= 0)
1763 c->selected = c->x_move;
1764 else
1765 c->selected = -1;
1766 }
1767
1768 // got a vertex selected:
1769 if(c->selected >= 0)
1770 {
1771 // this is used to translate mause position in zoom_factor to make this behavior unified with linear scale.
1772 const float translate_mouse_x = old_m_x - _curve_to_mouse(curve[c->selected].x, c->zoom_factor, c->offset_x);
1773 const float translate_mouse_y = old_m_y - _curve_to_mouse(curve[c->selected].y, c->zoom_factor, c->offset_y);
1774 // dx & dy are in linear coordinates
1775 const float dx = _mouse_to_curve(c->mouse_x - translate_mouse_x, c->zoom_factor, c->offset_x)
1776 - _mouse_to_curve(old_m_x - translate_mouse_x, c->zoom_factor, c->offset_x);
1777 const float dy = _mouse_to_curve(c->mouse_y - translate_mouse_y, c->zoom_factor, c->offset_y)
1778 - _mouse_to_curve(old_m_y - translate_mouse_y, c->zoom_factor, c->offset_y);
1779
1781 return _move_point_internal(self, widget, c->selected, dx, dy, event->state);
1782 }
1783 }
1784
1785 if(c->edit_by_area)
1786 {
1787 if(c->dragging)
1788 {
1789 if(c->x_move < 0)
1790 {
1791 dt_iop_colorzones_get_params(p, c, c->channel, c->mouse_x, c->mouse_y, c->mouse_radius);
1794 }
1795 }
1796 else if(event->y > height)
1797 {
1798 c->x_move = 0;
1799 const int bands = p->curve_num_nodes[c->channel];
1800 const float mouse_x = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1801 float dist = fabsf(p->curve[c->channel][0].x - mouse_x);
1802 for(int k = 1; k < bands; k++)
1803 {
1804 const float d2 = fabsf(p->curve[c->channel][k].x - mouse_x);
1805 if(d2 < dist)
1806 {
1807 c->x_move = k;
1808 dist = d2;
1809 }
1810 }
1811 }
1812 else
1813 {
1814 c->x_move = -1;
1815 }
1816 }
1817 else
1818 {
1819 if(event->state & GDK_BUTTON1_MASK)
1820 {
1821 if(nodes < DT_IOP_COLORZONES_MAXNODES && c->selected == -1)
1822 {
1823 const float linx = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x),
1824 liny = _mouse_to_curve(c->mouse_y, c->zoom_factor, c->offset_y);
1825
1826 // no vertex was close, create a new one!
1827 c->selected = _add_node(curve, &p->curve_num_nodes[ch], linx, liny);
1828
1831 }
1832 }
1833 else
1834 {
1835 const float mx = c->mouse_x;
1836 const float my = c->mouse_y;
1837
1838 // minimum area around the node to select it:
1839 float min = .04f * .04f; // comparing against square
1840 int nearest = -1;
1841 for(int k = 0; k < nodes; k++)
1842 {
1843 float dist = (my - _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y))
1844 * (my - _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y))
1845 + (mx - _curve_to_mouse(curve[k].x, c->zoom_factor, c->offset_x))
1846 * (mx - _curve_to_mouse(curve[k].x, c->zoom_factor, c->offset_x));
1847 if(dist < min)
1848 {
1849 min = dist;
1850 nearest = k;
1851 }
1852 }
1853 c->selected = nearest;
1854 }
1855 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1856 }
1857
1858 gtk_widget_queue_draw(widget);
1859 return TRUE;
1860}
1861
1862static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1863{
1867
1868 int ch = c->channel;
1869 int nodes = p->curve_num_nodes[ch];
1870 dt_iop_colorzones_node_t *curve = p->curve[ch];
1871
1872 if(event->button == 1)
1873 {
1874 if(c->edit_by_area && event->type != GDK_2BUTTON_PRESS && !dt_modifier_is(event->state, GDK_CONTROL_MASK))
1875 {
1876 c->dragging = 1;
1877 return TRUE;
1878 }
1879 else if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, GDK_CONTROL_MASK)
1880 && nodes < DT_IOP_COLORZONES_MAXNODES && (c->selected == -1 || c->edit_by_area))
1881 {
1882 // if we are not on a node -> add a new node at the current x of the pointer and y of the curve at that x
1883 const int inset = DT_IOP_COLORZONES_INSET;
1884 GtkAllocation allocation;
1885 gtk_widget_get_allocation(widget, &allocation);
1886 const int height = allocation.height - 2 * inset;
1887 const int width = allocation.width - 2 * inset;
1888
1889 c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1890 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1891
1892 const float mx = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1893
1894 // don't add a node too close to others in x direction, it can crash dt
1895 int selected = -1;
1896 if(curve[0].x > mx)
1897 selected = 0;
1898 else
1899 {
1900 for(int k = 1; k < nodes; k++)
1901 {
1902 if(curve[k].x > mx)
1903 {
1904 selected = k;
1905 break;
1906 }
1907 }
1908 }
1909 if(selected == -1) selected = nodes;
1910
1911 // evaluate the curve at the current x position
1912 const float y = dt_draw_curve_calc_value(c->minmax_curve[ch], mx);
1913
1914 if(y >= 0.0f && y <= 1.0f) // never add something outside the viewport, you couldn't change it afterwards
1915 {
1916 // create a new node
1917 selected = _add_node(curve, &p->curve_num_nodes[ch], mx, y);
1918
1919 // maybe set the new one as being selected
1920 const float min = .04f * .04f; // comparing against square
1921
1922 for(int k = 0; k < nodes; k++)
1923 {
1924 const float other_y = _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y);
1925 const float dist = (y - other_y) * (y - other_y);
1926 if(dist < min) c->selected = selected;
1927 }
1928
1931 gtk_widget_queue_draw(self->widget);
1932 }
1933
1934 return TRUE;
1935 }
1936 else if(event->type == GDK_2BUTTON_PRESS)
1937 {
1938 // reset current curve
1939 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
1940 p->curve_type[ch] = d->curve_type[ch];
1941 _reset_nodes(p, c->channel,
1942 p->splines_version == DT_IOP_COLORZONES_SPLINES_V1 || p->channel != DT_IOP_COLORZONES_h);
1943
1944 c->selected = -2; // avoid motion notify re-inserting immediately.
1945 dt_bauhaus_combobox_set(c->interpolator, p->curve_type[ch]);
1946
1949 gtk_widget_queue_draw(self->widget);
1950
1951 return TRUE;
1952 }
1953 }
1954 else if(event->button == 3 && c->selected >= 0)
1955 {
1956 if((c->selected == 0 || c->selected == nodes - 1) && p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1957 {
1958 if(p->channel == DT_IOP_COLORZONES_h)
1959 {
1960 curve[0].y = 0.5f;
1961 curve[0].x = 0.f;
1962 curve[nodes - 1].y = 0.5f;
1963 curve[nodes - 1].x = 1.f;
1964 }
1965 else
1966 {
1967 const float reset_value = c->selected == 0 ? 0.f : 1.f;
1968 curve[c->selected].y = 0.5f;
1969 curve[c->selected].x = reset_value;
1970 }
1971
1973 gtk_widget_queue_draw(self->widget);
1975 return TRUE;
1976 }
1977
1978 // right click deletes the node, ctrl+right click reset the node to y-zero
1979 _delete_node(self, curve, &p->curve_num_nodes[ch], c->selected, dt_modifier_is(event->state, GDK_CONTROL_MASK));
1980 c->selected = -2; // avoid re-insertion of that point immediately after this
1981
1982 return TRUE;
1983 }
1984
1985 return FALSE;
1986}
1987
1988static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
1989{
1990 if(event->button == 1)
1991 {
1992 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1994 c->dragging = 0;
1995 return TRUE;
1996 }
1997 return FALSE;
1998}
1999
2000static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
2001{
2003 c->mouse_y = fabs(c->mouse_y);
2004 gtk_widget_queue_draw(widget);
2005 return TRUE;
2006}
2007
2008static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
2009{
2011 // for fluxbox
2012 c->mouse_y = -fabs(c->mouse_y);
2013 gtk_widget_queue_draw(widget);
2014 return TRUE;
2015}
2016
2017static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
2018{
2019 GtkRequisition r;
2020 GtkAllocation allocation;
2021 gtk_widget_get_allocation(widget, &allocation);
2022 r.width = allocation.width;
2023 r.height = allocation.width;
2024 gtk_widget_get_preferred_size(widget, &r, NULL);
2025 return TRUE;
2026}
2027
2028static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
2029{
2031
2032 if(c->selected < 0) return FALSE;
2033
2034 guint key = dt_keys_mainpad_alternatives(event->keyval);
2035 int handled = 0;
2036 float dx = 0.0f, dy = 0.0f;
2037 if(key == GDK_KEY_Up)
2038 {
2039 handled = 1;
2041 }
2042 else if(key == GDK_KEY_Down)
2043 {
2044 handled = 1;
2046 }
2047 else if(key == GDK_KEY_Right)
2048 {
2049 handled = 1;
2051 }
2052 else if(key == GDK_KEY_Left)
2053 {
2054 handled = 1;
2056 }
2057
2058 if(!handled) return FALSE;
2059
2061 return _move_point_internal(self, widget, c->selected, dx, dy, event->state);
2062}
2063
2064static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num,
2065 dt_iop_module_t *self)
2066{
2067 if(darktable.gui->reset) return;
2070
2071 c->channel = (dt_iop_colorzones_channel_t)page_num;
2072
2073 ++darktable.gui->reset;
2074
2075 dt_bauhaus_combobox_set(c->interpolator, p->curve_type[c->channel]);
2076
2077 --darktable.gui->reset;
2078
2080 if(c->display_mask)
2082 gtk_widget_queue_draw(self->widget);
2083}
2084
2085
2086void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2087{
2090
2091 if(w == g->select_by)
2092 {
2093 _reset_parameters(p, p->channel, p->splines_version);
2094 if(g->display_mask) _reset_display_selection(self);
2095 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2096 gtk_widget_queue_draw(GTK_WIDGET(g->bottom_area));
2097 }
2098}
2099
2101{
2102 if(darktable.gui->reset) return;
2105
2106 const int combo = dt_bauhaus_combobox_get(widget);
2107
2108 if(combo == 0)
2109 p->curve_type[g->channel] = CUBIC_SPLINE;
2110 else if(combo == 1)
2111 p->curve_type[g->channel] = CATMULL_ROM;
2112 else if(combo == 2)
2113 p->curve_type[g->channel] = MONOTONE_HERMITE;
2114
2117 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2118}
2119
2121{
2122 if(darktable.gui->reset) return;
2124
2125 g->edit_by_area = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
2126
2127 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2128}
2129
2130static void _display_mask_callback(GtkToggleButton *togglebutton, dt_iop_module_t *module)
2131{
2132 if(darktable.gui->reset) return;
2133
2135
2136 // if blend module is displaying mask do not display it here
2137 if(module->request_mask_display && !g->display_mask)
2138 {
2139 dt_control_log(_("cannot display masks when the blending mask is displayed"));
2140
2141 ++darktable.gui->reset;
2142 gtk_toggle_button_set_active(togglebutton, FALSE);
2143 --darktable.gui->reset;
2144 return;
2145 }
2146
2147 g->display_mask = gtk_toggle_button_get_active(togglebutton);
2148
2149 if(module->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), 1);
2150 dt_iop_request_focus(module);
2152}
2153
2155{
2157 if(picker == g->colorpicker_set_values)
2158 {
2161
2162 const int ch_curve = g->channel;
2163 const int ch_val = p->channel;
2164 dt_iop_colorzones_node_t *curve = p->curve[ch_curve];
2165
2166 // reset current curve
2167 p->curve_num_nodes[ch_curve] = d->curve_num_nodes[ch_curve];
2168 p->curve_type[ch_curve] = d->curve_type[ch_curve];
2169 for(int k = 0; k < DT_IOP_COLORZONES_MAXNODES; k++)
2170 {
2171 curve[k].x = d->curve[ch_curve][k].x;
2172 curve[k].y = d->curve[ch_curve][k].y;
2173 }
2174
2175 const GdkModifierType state = dt_key_modifier_state();
2176 int picker_set_upper_lower; // flat=0, lower=-1, upper=1
2177 if(dt_modifier_is(state, GDK_CONTROL_MASK))
2178 picker_set_upper_lower = 1;
2179 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
2180 picker_set_upper_lower = -1;
2181 else
2182 picker_set_upper_lower = 0;
2183
2184 // now add 5 nodes: feather, min, center, max, feather
2185 const float feather = 0.02f;
2186 const float increment = 0.1f * picker_set_upper_lower;
2187 float x = 0.f;
2188
2189 if(ch_val == DT_IOP_COLORZONES_L)
2190 x = self->picked_color_min[0] / 100.f;
2191 else if(ch_val == DT_IOP_COLORZONES_C)
2192 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2193 else if(ch_val == DT_IOP_COLORZONES_h)
2194 x = self->picked_color_min[2];
2195 x -= feather;
2196 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2197
2198 if(ch_val == DT_IOP_COLORZONES_L)
2199 x = self->picked_color_min[0] / 100.f;
2200 else if(ch_val == DT_IOP_COLORZONES_C)
2201 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2202 else if(ch_val == DT_IOP_COLORZONES_h)
2203 x = self->picked_color_min[2];
2204 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2205
2206 if(ch_val == DT_IOP_COLORZONES_L)
2207 x = self->picked_color[0] / 100.f;
2208 else if(ch_val == DT_IOP_COLORZONES_C)
2209 x = self->picked_color[1] / (128.f * sqrtf(2.f));
2210 else if(ch_val == DT_IOP_COLORZONES_h)
2211 x = self->picked_color[2];
2212 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + 2.f * increment);
2213
2214 if(ch_val == DT_IOP_COLORZONES_L)
2215 x = self->picked_color_max[0] / 100.f;
2216 else if(ch_val == DT_IOP_COLORZONES_C)
2217 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2218 else if(ch_val == DT_IOP_COLORZONES_h)
2219 x = self->picked_color_max[2];
2220 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2221
2222 if(ch_val == DT_IOP_COLORZONES_L)
2223 x = self->picked_color_max[0] / 100.f;
2224 else if(ch_val == DT_IOP_COLORZONES_C)
2225 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2226 else if(ch_val == DT_IOP_COLORZONES_h)
2227 x = self->picked_color_max[2];
2228 x += feather;
2229 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2230
2232 }
2233
2235}
2236
2237void gui_reset(struct dt_iop_module_t *self)
2238{
2240
2242
2243 c->zoom_factor = 1.f;
2245}
2246
2247void gui_focus(struct dt_iop_module_t *self, gboolean in)
2248{
2249 if(!in)
2250 {
2252 }
2253}
2254
2255void gui_init(struct dt_iop_module_t *self)
2256{
2259
2260 self->histogram_cst = IOP_CS_LCH;
2261
2262 c->channel = dt_conf_get_int("plugins/darkroom/colorzones/gui_channel");
2263 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2264 {
2265 c->minmax_curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2266 c->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
2267 c->minmax_curve_type[ch] = p->curve_type[ch];
2268
2269 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2270 dt_draw_curve_add_point(c->minmax_curve[ch], p->curve[ch][k].x, p->curve[ch][k].y);
2271 }
2272
2273 c->mouse_x = c->mouse_y = -1.0;
2274 c->selected = -1;
2275 c->offset_x = c->offset_y = 0.f;
2276 c->zoom_factor = 1.f;
2277 c->x_move = -1;
2278 c->mouse_radius = 1.f / DT_IOP_COLORZONES_BANDS;
2279 c->dragging = 0;
2280 c->edit_by_area = 0;
2281 c->display_mask = FALSE;
2282 self->timeout_handle = 0;
2283
2284 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2285
2286 // tabs
2287 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2288 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2289 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2290
2291 c->channel_tabs = dt_ui_notebook_new();
2292
2293 dt_ui_notebook_page(c->channel_tabs, N_("lightness"), NULL);
2294 dt_ui_notebook_page(c->channel_tabs, N_("saturation"), NULL);
2295 dt_ui_notebook_page(c->channel_tabs, N_("hue"), NULL);
2296
2297 gtk_widget_show(gtk_notebook_get_nth_page(c->channel_tabs, c->channel));
2298 gtk_notebook_set_current_page(c->channel_tabs, c->channel);
2299 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(_channel_tabs_switch_callback), self);
2300 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(c->channel_tabs), TRUE, TRUE, 0);
2301 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" "), FALSE, FALSE, 0);
2302
2303 // color pickers
2305 gtk_widget_set_tooltip_text(c->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
2306 c->colorpicker_set_values = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, hbox, IOP_CS_LCH);
2307 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(c->colorpicker_set_values),
2309
2310 gtk_widget_set_size_request(c->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
2311 gtk_widget_set_tooltip_text(c->colorpicker_set_values, _("create a curve based on an area from the image\n"
2312 "drag to create a flat curve\n"
2313 "ctrl+drag to create a positive curve\n"
2314 "shift+drag to create a negative curve"));
2315
2316 // the nice graph
2317 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
2318 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
2319
2320 gtk_box_pack_start(GTK_BOX(vbox),
2321 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
2322 "plugins/darkroom/colorzones/graphheight", 280, 100),
2323 FALSE, FALSE, 0);
2324
2325 GtkWidget *dabox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2326 gtk_widget_set_name(GTK_WIDGET(dabox), "iop-bottom-bar");
2327 c->bottom_area = gtk_drawing_area_new();
2328 gtk_box_pack_start(GTK_BOX(dabox), GTK_WIDGET(c->bottom_area), TRUE, TRUE, 0);
2329 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dabox), TRUE, TRUE, 0);
2330 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(vbox), TRUE, TRUE, 0);
2331
2332 GtkWidget *hbox_select_by = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2333
2334 // edit by area
2335 gchar *label = N_("edit by area");
2336 c->chk_edit_by_area = gtk_check_button_new_with_label(_(label));
2337 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(c->chk_edit_by_area))), PANGO_ELLIPSIZE_START);
2338 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->chk_edit_by_area), c->edit_by_area);
2339 gtk_widget_set_tooltip_text(c->chk_edit_by_area, _("edit the curve nodes by area"));
2340 gtk_box_pack_start(GTK_BOX(hbox_select_by), c->chk_edit_by_area, TRUE, TRUE, 0);
2341 g_signal_connect(G_OBJECT(c->chk_edit_by_area), "toggled", G_CALLBACK(_edit_by_area_callback), self);
2342
2343 // display selection
2344 c->bt_showmask = dtgtk_togglebutton_new(dtgtk_cairo_paint_showmask, 0, NULL);
2345
2346 gtk_widget_set_tooltip_text(c->bt_showmask, _("display selection"));
2347 g_signal_connect(G_OBJECT(c->bt_showmask), "toggled", G_CALLBACK(_display_mask_callback), self);
2348 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->bt_showmask), FALSE);
2349 gtk_box_pack_end(GTK_BOX(hbox_select_by), c->bt_showmask, FALSE, FALSE, 0);
2350
2351 gtk_box_pack_start(GTK_BOX(self->widget), hbox_select_by, TRUE, TRUE, 0);
2352
2353 // select by which dimension
2354 c->select_by = dt_bauhaus_combobox_from_params(self, "channel");
2356 gtk_widget_set_tooltip_text(c->select_by, _("choose selection criterion, will be the abscissa in the graph"));
2357
2358 c->mode = dt_bauhaus_combobox_from_params(self, "mode");
2359 gtk_widget_set_tooltip_text(c->mode, _("choose between a smoother or stronger effect"));
2360
2361 c->strength = dt_bauhaus_slider_from_params(self, "strength");
2362 dt_bauhaus_slider_set_format(c->strength, "%");
2363 gtk_widget_set_tooltip_text(c->strength, _("make effect stronger or weaker"));
2364
2365 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
2366 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2367 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
2369 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
2370 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
2371 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(_area_draw_callback), self);
2372 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
2373 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(_area_button_release_callback), self);
2374 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
2375 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
2376 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
2377 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
2378 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
2379 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
2380
2381 gtk_widget_add_events(GTK_WIDGET(c->bottom_area), GDK_BUTTON_PRESS_MASK);
2382 g_signal_connect(G_OBJECT(c->bottom_area), "draw", G_CALLBACK(_bottom_area_draw_callback), self);
2383 g_signal_connect(G_OBJECT(c->bottom_area), "button-press-event", G_CALLBACK(_bottom_area_button_press_callback),
2384 self);
2385
2386 /* From src/common/curve_tools.h :
2387 #define CUBIC_SPLINE 0
2388 #define CATMULL_ROM 1
2389 #define MONOTONE_HERMITE 2
2390 */
2391 c->interpolator = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
2392 dt_bauhaus_widget_set_label(c->interpolator, N_("interpolation method"));
2393 dt_bauhaus_combobox_add(c->interpolator, _("cubic spline"));
2394 dt_bauhaus_combobox_add(c->interpolator, _("centripetal spline"));
2395 dt_bauhaus_combobox_add(c->interpolator, _("monotonic spline"));
2396 gtk_box_pack_start(GTK_BOX(self->widget), c->interpolator, TRUE, TRUE, 0);
2397 gtk_widget_set_tooltip_text(c->interpolator,
2398 _("change this method if you see oscillations or cusps in the curve\n"
2399 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
2400 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
2401 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
2402 g_signal_connect(G_OBJECT(c->interpolator), "value-changed", G_CALLBACK(_interpolator_callback), self);
2403}
2404
2405void gui_update(struct dt_iop_module_t *self)
2406{
2409
2410 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[g->channel]);
2411
2413
2414 gtk_widget_queue_draw(self->widget);
2415}
2416
2418{
2420 dt_conf_set_int("plugins/darkroom/colorzones/gui_channel", c->channel);
2421
2422 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(c->minmax_curve[ch]);
2423
2425
2427}
2428
2430{
2431 const int program = 2; // basic.cl, from programs.conf
2433 module->data = gd;
2434 gd->kernel_colorzones = dt_opencl_create_kernel(program, "colorzones");
2435 gd->kernel_colorzones_v3 = dt_opencl_create_kernel(program, "colorzones_v3");
2436}
2437
2447
2450{
2451 // pull in new params to pipe
2455
2456 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
2457 piece->request_histogram |= (DT_REQUEST_ON);
2458 else
2459 piece->request_histogram &= ~(DT_REQUEST_ON);
2460
2461#if 0 // print new preset
2462 printf("p.channel = %d;\n", p->channel);
2463 for(int k=0; k<3; k++) for(int i=0; i<DT_IOP_COLORZONES_MAXNODES; i++)
2464 {
2465 printf("p.curve[%d][%i].x = %f;\n", k, i, p->curve[k][i].x);
2466 printf("p.curve[%d][%i].y = %f;\n", k, i, p->curve[k][i].y);
2467 }
2468#endif
2469
2470 // display selection don't work with opencl
2471 piece->process_cl_ready = (g && g->display_mask) ? 0 : 1;
2472 d->channel = (dt_iop_colorzones_channel_t)p->channel;
2473 d->mode = p->mode;
2474
2475 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
2476 {
2477 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2478 {
2479 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
2480 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch])
2481 {
2482 dt_draw_curve_destroy(d->curve[ch]);
2483 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2484 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2485 d->curve_type[ch] = p->curve_type[ch];
2486
2487 if(d->channel == DT_IOP_COLORZONES_h)
2488 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2489 strength(p->curve[ch][p->curve_num_nodes[ch] - 2].y, p->strength));
2490 else
2491 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2492 strength(p->curve[ch][0].y, p->strength));
2493 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2494 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2495 if(d->channel == DT_IOP_COLORZONES_h)
2496 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f, strength(p->curve[ch][1].y, p->strength));
2497 else
2498 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f,
2499 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2500 }
2501 else
2502 {
2503 if(d->channel == DT_IOP_COLORZONES_h)
2504 dt_draw_curve_set_point(d->curve[ch], 0, p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2505 strength(p->curve[ch][p->curve_num_nodes[ch] - 2].y, p->strength));
2506 else
2507 dt_draw_curve_set_point(d->curve[ch], 0, p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2508 strength(p->curve[ch][0].y, p->strength));
2509 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2510 dt_draw_curve_set_point(d->curve[ch], k + 1, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2511 if(d->channel == DT_IOP_COLORZONES_h)
2512 dt_draw_curve_set_point(d->curve[ch], p->curve_num_nodes[ch] + 1, p->curve[ch][1].x + 1.f,
2513 strength(p->curve[ch][1].y, p->strength));
2514 else
2515 dt_draw_curve_set_point(d->curve[ch], p->curve_num_nodes[ch] + 1, p->curve[ch][1].x + 1.f,
2516 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2517 }
2518 dt_draw_curve_calc_values(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch]);
2519 }
2520 }
2521 else
2522 {
2523 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2524 {
2525 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch]
2526 || d->curve[ch]->c.m_numAnchors != p->curve_num_nodes[ch])
2527 {
2528 dt_draw_curve_destroy(d->curve[ch]);
2529 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2530 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2531 d->curve_type[ch] = p->curve_type[ch];
2532
2533 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2534 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2535 }
2536 else
2537 {
2538 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2539 dt_draw_curve_set_point(d->curve[ch], k, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2540 }
2541 dt_draw_curve_calc_values_V2(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch],
2542 p->channel == DT_IOP_COLORZONES_h);
2543 }
2544 }
2545}
2546
2548{
2551 piece->data = d;
2552 piece->data_size = sizeof(dt_iop_colorzones_data_t);
2553
2554 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2555 {
2556 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, default_params->curve_type[ch]);
2557 d->curve_nodes[ch] = default_params->curve_num_nodes[ch];
2558 d->curve_type[ch] = default_params->curve_type[ch];
2559 for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
2560 dt_draw_curve_add_point(d->curve[ch], default_params->curve[ch][k].x, default_params->curve[ch][k].y);
2561 }
2562 d->channel = (dt_iop_colorzones_channel_t)default_params->channel;
2563 d->mode = default_params->mode;
2564}
2565
2567{
2568 // clean up everything again.
2570
2571 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
2572
2573 dt_free_align(piece->data);
2574 piece->data = NULL;
2575}
2576
2578{
2579 module->params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2580 module->default_params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2581 module->default_enabled = 0; // we're a rather slow and rare op.
2582 module->params_size = sizeof(dt_iop_colorzones_params_t);
2583 module->gui_data = NULL;
2584 module->request_histogram |= (DT_REQUEST_ON);
2585
2587}
2588
2589#undef DT_IOP_COLORZONES_INSET
2590#undef DT_IOP_COLORZONES_CURVE_INFL
2591#undef DT_IOP_COLORZONES_RES
2592#undef DT_IOP_COLORZONES_LUT_RES
2593#undef DT_IOP_COLORZONES_BANDS
2594#undef DT_IOP_COLORZONES_MAXNODES
2595#undef DT_IOP_COLORZONES_DEFAULT_STEP
2596#undef DT_IOP_COLORZONES_MIN_X_DISTANCE
2597
2598// clang-format off
2599// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2600// vim: shiftwidth=2 expandtab tabstop=2 cindent
2601// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2602// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:278
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2347
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_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2101
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1842
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2016
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
@ DEVELOP_BLEND_CS_RGB_DISPLAY
Definition blend.h:59
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static float lookup(read_only image2d_t lut, const float x)
@ IOP_CS_LCH
@ IOP_CS_RGB
@ IOP_CS_LAB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new_with_cst(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w, const dt_iop_colorspace_type_t cst)
@ DT_COLOR_PICKER_AREA
@ DT_COLOR_PICKER_POINT_AREA
@ DT_LIB_COLORPICKER_STATISTIC_MAX
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:44
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:43
static dt_aligned_pixel_t rgb
const dt_aligned_pixel_t f
static const float const float const float min
const dt_colormatrix_t dt_aligned_pixel_t out
static const float const float C
dt_XYZ_to_Lab(XYZ, Lab)
static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
#define DT_COLORZONES_CELLSI
static void _reset_parameters(dt_iop_colorzones_params_t *p, const int channel, const int splines_version)
Definition colorzones.c:812
static void _edit_by_area_callback(GtkWidget *widget, dt_iop_module_t *self)
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)
#define DT_IOP_COLORZONES_RES
Definition colorzones.c:87
void init(dt_iop_module_t *module)
const char ** description(struct dt_iop_module_t *self)
Definition colorzones.c:189
int default_group()
Definition colorzones.c:203
static gboolean _bottom_area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
#define DT_IOP_COLORZONES_INSET
Definition colorzones.c:85
static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
__DT_CLONE_TARGETS__ void process_v1(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition colorzones.c:474
static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
static void _reset_nodes(dt_iop_colorzones_params_t *p, const int ch, const _Bool touch_edges)
Definition colorzones.c:800
#define DT_COLORZONES_CELLSJ
static void _reset_display_selection(dt_iop_module_t *self)
Definition colorzones.c:781
static void _delete_node(dt_iop_module_t *self, dt_iop_colorzones_node_t *curve, int *nodes, int node, gboolean zero)
void gui_focus(struct dt_iop_module_t *self, gboolean in)
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
Definition colorzones.c:370
const char * name()
Definition colorzones.c:184
void gui_reset(struct dt_iop_module_t *self)
void gui_update(struct dt_iop_module_t *self)
dt_iop_colorzones_channel_t
Definition colorzones.c:110
@ DT_IOP_COLORZONES_L
Definition colorzones.c:111
@ DT_IOP_COLORZONES_MAX_CHANNELS
Definition colorzones.c:114
@ DT_IOP_COLORZONES_h
Definition colorzones.c:113
@ DT_IOP_COLORZONES_C
Definition colorzones.c:112
static void dt_iop_colorzones_get_params(dt_iop_colorzones_params_t *p, dt_iop_colorzones_gui_data_t *c, const int ch, const double mouse_x, const double mouse_y, const float radius)
Definition colorzones.c:376
static void _interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
static float strength(float value, float strength)
Definition colorzones.c:420
void gui_init(struct dt_iop_module_t *self)
static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
#define DT_IOP_COLORZONES_MIN_X_DISTANCE
Definition colorzones.c:95
__DT_CLONE_TARGETS__ void process_display(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition colorzones.c:426
#define DT_IOP_COLORZONES_BANDS
Definition colorzones.c:90
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
static int _select_base_display_color(dt_iop_module_t *self, float *picked_color, float *picker_min, float *picker_max)
Definition colorzones.c:826
void cleanup_global(dt_iop_module_so_t *module)
static void _draw_color_picker(dt_iop_module_t *self, cairo_t *cr, dt_iop_colorzones_params_t *p, dt_iop_colorzones_gui_data_t *c, const int width, const int height, const float *const picker_color, const float *const picker_min, const float *const picker_max)
Definition colorzones.c:856
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition colorzones.c:208
int flags()
Definition colorzones.c:198
#define DT_IOP_COLORZONES_MAXNODES
Definition colorzones.c:92
void gui_cleanup(struct dt_iop_module_t *self)
static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
void init_presets(dt_iop_module_so_t *self)
Definition colorzones.c:625
__DT_CLONE_TARGETS__ void process_v3(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition colorzones.c:518
#define COLORZONES_DRAW_BACKGROUD_BOX
static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
#define DT_IOP_COLORZONES_LUT_RES
Definition colorzones.c:88
static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, int node, float dx, float dy, guint state)
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)
Definition colorzones.c:560
static int _add_node(dt_iop_colorzones_node_t *curve, int *nodes, float x, float y)
#define DT_IOP_COLORZONES_DEFAULT_STEP
Definition colorzones.c:93
#define DT_IOP_COLORZONES1_BANDS
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
dt_iop_colorzones_modes_t
Definition colorzones.c:98
@ DT_IOP_COLORZONES_MODE_STRONG
Definition colorzones.c:100
@ DT_IOP_COLORZONES_MODE_SMOOTH
Definition colorzones.c:99
static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
static void _draw_background(cairo_t *cr, dt_iop_colorzones_params_t *p, dt_iop_colorzones_gui_data_t *c, const int select_by_picker, const int width, const int height, const float *picked_color)
static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
Definition colorzones.c:365
void init_global(dt_iop_module_so_t *module)
static gboolean _bottom_area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
static gboolean _sanity_check(const float x, const int selected, const int nodes, const dt_iop_colorzones_node_t *curve)
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)
Definition colorzones.c:580
static void _display_mask_callback(GtkToggleButton *togglebutton, dt_iop_module_t *module)
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num, dt_iop_module_t *self)
dt_iop_colorzones_splines_version_t
Definition colorzones.c:104
@ DT_IOP_COLORZONES_SPLINES_V1
Definition colorzones.c:105
@ DT_IOP_COLORZONES_SPLINES_V2
Definition colorzones.c:106
int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params, const int new_version)
Definition colorzones.c:213
char * key
float dt_conf_get_float(const char *name)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
void dt_control_log(const char *msg,...)
Definition control.c:761
void dt_control_queue_redraw_widget(GtkWidget *widget)
threadsafe request of redraw of specific widget. Use this function if you need to redraw a specific w...
Definition control.c:906
#define CATMULL_ROM
Definition curve_tools.h:34
#define CUBIC_SPLINE
Definition curve_tools.h:33
#define MONOTONE_HERMITE
Definition curve_tools.h:35
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#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
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static const dt_aligned_pixel_simd_t value
Definition darktable.h:577
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#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 dt_database_start_transaction(db)
Definition database.h:77
#define dt_database_release_transaction(db)
Definition database.h:78
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
#define dt_dev_pixelpipe_update_history_main(dev)
gboolean dt_dev_pixelpipe_has_preview_output(const dt_develop_t *dev, const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi)
Definition develop.c:367
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
static void dt_draw_curve_calc_values(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y)
Definition draw.h:309
static void dt_draw_histogram_8_zoomed(cairo_t *cr, const uint32_t *hist, int32_t channels, int32_t channel, const float zoom_factor, const float zoom_offset_x, const float zoom_offset_y, gboolean linear)
Definition draw.h:383
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:345
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:282
static void dt_draw_curve_calc_values_V2(dt_draw_curve_t *c, const float min, const float max, const int res, float *x, float *y, const gboolean periodic)
Definition draw.h:336
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:288
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:364
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:266
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_ALTER
Definition dtgtk/paint.h:70
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
Definition gtk.c:313
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
GdkModifierType dt_key_modifier_state()
Definition gtk.c:2186
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
#define DT_GUI_BOX_SPACING
Definition gtk.h:109
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
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)
#define DT_GUI_MODULE(x)
void dt_gui_throttle_cancel(gpointer source)
void dt_gui_throttle_queue(gpointer source, dt_gui_throttle_callback_t callback, gpointer user_data)
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:87
void dt_iop_throttled_history_update(gpointer data)
Definition imageop.c:3135
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
@ DT_REQUEST_COLORPICK_MODULE
Definition imageop.h:197
#define IOP_GUI_FREE
Definition imageop.h:602
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:166
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:168
@ 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_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
void dt_ioppr_transform_image_colorspace(struct dt_iop_module_t *self, const float *const image_in, float *const image_out, const int width, const int height, const int cst_from, const int cst_to, int *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
void dt_ioppr_transform_image_colorspace_rgb(const float *const restrict image_in, float *const restrict image_out, const int width, const int height, const dt_iop_order_iccprofile_info_t *const profile_info_from, const dt_iop_order_iccprofile_info_t *const profile_info_to, const char *message)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
static const float x
const float *const lut
dt_iop_colorzones_channel_t
Definition lightroom.c:183
#define DT_IOP_COLORZONES_BANDS
Definition lightroom.c:180
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define DT_M_PI_F
Definition math.h:52
#define DT_M_PI
Definition math.h:53
#define M_PI
Definition math.h:45
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_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_REQUEST_ON
Definition pixelpipe.h:48
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:39
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
const float r
struct dt_gui_gtk_t * gui
Definition darktable.h:775
const struct dt_database_t * db
Definition darktable.h:779
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:72
dt_dev_request_flags_t request_histogram
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
struct dt_develop_t::@19 color_picker
Authoritative darkroom color-picker state.
GList * iop
Definition develop.h:279
struct dt_iop_module_t * gui_module
Definition develop.h:165
GSList * samples
Definition develop.h:392
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
gint scroll_mask
Definition gtk.h:224
int32_t reset
Definition gtk.h:172
unsigned int channels
Definition format.h:54
dt_draw_curve_t * curve[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:169
dt_iop_colorzones_channel_t channel
Definition colorzones.c:172
float lut[3][0x10000]
Definition colorzones.c:173
int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:171
int curve_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:170
dt_draw_curve_t * minmax_curve[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:137
float draw_ys[DT_IOP_COLORZONES_MAX_CHANNELS][256]
Definition colorzones.c:158
int minmax_curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:139
int minmax_curve_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:138
dt_iop_colorzones_channel_t channel
Definition colorzones.c:157
dt_iop_colorzones_modes_t mode
Definition colorzones.c:131
dt_iop_colorzones_channel_t channel
Definition colorzones.c:125
int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:129
dt_iop_colorzones_node_t curve[DT_IOP_COLORZONES_MAX_CHANNELS][20]
Definition colorzones.c:127
int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:128
GModule *dt_dev_operation_t op
Definition imageop.h:230
dt_iop_global_data_t * data
Definition imageop.h:233
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:264
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
uint32_t histogram_max[4]
Definition imageop.h:280
dt_iop_global_data_t * global_data
Definition imageop.h:314
gboolean enabled
Definition imageop.h:298
dt_aligned_pixel_t picked_color_min
Definition imageop.h:272
int request_mask_display
Definition imageop.h:268
dt_aligned_pixel_t picked_color_max
Definition imageop.h:272
uint32_t * histogram
Definition imageop.h:276
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:285
guint timeout_handle
Definition imageop.h:371
dt_aligned_pixel_t picked_color
Definition imageop.h:272
dt_iop_params_t * params
Definition imageop.h:307
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
void dtgtk_togglebutton_set_paint(GtkDarktableToggleButton *button, DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
#define DTGTK_TOGGLEBUTTON(obj)