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(dt_gui_widgets_suppressed()) return;
2070
2071 c->channel = (dt_iop_colorzones_channel_t)page_num;
2072
2074
2075 dt_bauhaus_combobox_set(c->interpolator, p->curve_type[c->channel]);
2076
2078
2079 if(c->display_mask)
2081 gtk_widget_queue_draw(self->widget);
2082}
2083
2084
2085void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2086{
2089
2090 if(w == g->select_by)
2091 {
2092 _reset_parameters(p, p->channel, p->splines_version);
2093 if(g->display_mask) _reset_display_selection(self);
2094 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2095 gtk_widget_queue_draw(GTK_WIDGET(g->bottom_area));
2096 }
2097}
2098
2100{
2101 if(dt_gui_widgets_suppressed()) return;
2104
2105 const int combo = dt_bauhaus_combobox_get(widget);
2106
2107 if(combo == 0)
2108 p->curve_type[g->channel] = CUBIC_SPLINE;
2109 else if(combo == 1)
2110 p->curve_type[g->channel] = CATMULL_ROM;
2111 else if(combo == 2)
2112 p->curve_type[g->channel] = MONOTONE_HERMITE;
2113
2116 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2117}
2118
2120{
2121 if(dt_gui_widgets_suppressed()) return;
2123
2124 g->edit_by_area = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
2125
2126 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2127}
2128
2129static void _display_mask_callback(GtkToggleButton *togglebutton, dt_iop_module_t *module)
2130{
2131 if(dt_gui_widgets_suppressed()) return;
2132
2134
2135 // if blend module is displaying mask do not display it here
2136 if(module->request_mask_display && !g->display_mask)
2137 {
2138 dt_control_log(_("cannot display masks when the blending mask is displayed"));
2139
2141 gtk_toggle_button_set_active(togglebutton, FALSE);
2143 return;
2144 }
2145
2146 g->display_mask = gtk_toggle_button_get_active(togglebutton);
2147
2148 if(module->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), 1);
2149 dt_iop_request_focus(module);
2151}
2152
2154{
2156 if(picker == g->colorpicker_set_values)
2157 {
2160
2161 const int ch_curve = g->channel;
2162 const int ch_val = p->channel;
2163 dt_iop_colorzones_node_t *curve = p->curve[ch_curve];
2164
2165 // reset current curve
2166 p->curve_num_nodes[ch_curve] = d->curve_num_nodes[ch_curve];
2167 p->curve_type[ch_curve] = d->curve_type[ch_curve];
2168 for(int k = 0; k < DT_IOP_COLORZONES_MAXNODES; k++)
2169 {
2170 curve[k].x = d->curve[ch_curve][k].x;
2171 curve[k].y = d->curve[ch_curve][k].y;
2172 }
2173
2174 const GdkModifierType state = dt_key_modifier_state();
2175 int picker_set_upper_lower; // flat=0, lower=-1, upper=1
2176 if(dt_modifier_is(state, GDK_CONTROL_MASK))
2177 picker_set_upper_lower = 1;
2178 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
2179 picker_set_upper_lower = -1;
2180 else
2181 picker_set_upper_lower = 0;
2182
2183 // now add 5 nodes: feather, min, center, max, feather
2184 const float feather = 0.02f;
2185 const float increment = 0.1f * picker_set_upper_lower;
2186 float x = 0.f;
2187
2188 if(ch_val == DT_IOP_COLORZONES_L)
2189 x = self->picked_color_min[0] / 100.f;
2190 else if(ch_val == DT_IOP_COLORZONES_C)
2191 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2192 else if(ch_val == DT_IOP_COLORZONES_h)
2193 x = self->picked_color_min[2];
2194 x -= feather;
2195 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2196
2197 if(ch_val == DT_IOP_COLORZONES_L)
2198 x = self->picked_color_min[0] / 100.f;
2199 else if(ch_val == DT_IOP_COLORZONES_C)
2200 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2201 else if(ch_val == DT_IOP_COLORZONES_h)
2202 x = self->picked_color_min[2];
2203 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2204
2205 if(ch_val == DT_IOP_COLORZONES_L)
2206 x = self->picked_color[0] / 100.f;
2207 else if(ch_val == DT_IOP_COLORZONES_C)
2208 x = self->picked_color[1] / (128.f * sqrtf(2.f));
2209 else if(ch_val == DT_IOP_COLORZONES_h)
2210 x = self->picked_color[2];
2211 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + 2.f * increment);
2212
2213 if(ch_val == DT_IOP_COLORZONES_L)
2214 x = self->picked_color_max[0] / 100.f;
2215 else if(ch_val == DT_IOP_COLORZONES_C)
2216 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2217 else if(ch_val == DT_IOP_COLORZONES_h)
2218 x = self->picked_color_max[2];
2219 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2220
2221 if(ch_val == DT_IOP_COLORZONES_L)
2222 x = self->picked_color_max[0] / 100.f;
2223 else if(ch_val == DT_IOP_COLORZONES_C)
2224 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2225 else if(ch_val == DT_IOP_COLORZONES_h)
2226 x = self->picked_color_max[2];
2227 x += feather;
2228 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2229
2231 }
2232
2234}
2235
2236void gui_reset(struct dt_iop_module_t *self)
2237{
2239
2241
2242 c->zoom_factor = 1.f;
2244}
2245
2246void gui_focus(struct dt_iop_module_t *self, gboolean in)
2247{
2248 if(!in)
2249 {
2251 }
2252}
2253
2254void gui_init(struct dt_iop_module_t *self)
2255{
2258
2259 self->histogram_cst = IOP_CS_LCH;
2260
2261 c->channel = dt_conf_get_int("plugins/darkroom/colorzones/gui_channel");
2262 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2263 {
2264 c->minmax_curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2265 c->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
2266 c->minmax_curve_type[ch] = p->curve_type[ch];
2267
2268 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2269 dt_draw_curve_add_point(c->minmax_curve[ch], p->curve[ch][k].x, p->curve[ch][k].y);
2270 }
2271
2272 c->mouse_x = c->mouse_y = -1.0;
2273 c->selected = -1;
2274 c->offset_x = c->offset_y = 0.f;
2275 c->zoom_factor = 1.f;
2276 c->x_move = -1;
2277 c->mouse_radius = 1.f / DT_IOP_COLORZONES_BANDS;
2278 c->dragging = 0;
2279 c->edit_by_area = 0;
2280 c->display_mask = FALSE;
2281 self->timeout_handle = 0;
2282
2283 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2284
2285 // tabs
2286 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2287 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2288 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2289
2290 c->channel_tabs = dt_ui_notebook_new();
2291
2292 dt_ui_notebook_page(c->channel_tabs, N_("lightness"), NULL);
2293 dt_ui_notebook_page(c->channel_tabs, N_("saturation"), NULL);
2294 dt_ui_notebook_page(c->channel_tabs, N_("hue"), NULL);
2295
2296 gtk_widget_show(gtk_notebook_get_nth_page(c->channel_tabs, c->channel));
2297 gtk_notebook_set_current_page(c->channel_tabs, c->channel);
2298 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(_channel_tabs_switch_callback), self);
2299 /* The picker samples for the *currently selected* channel (c->channel), read at apply
2300 time. Leaving it active across a tab switch would apply a sample picked for one
2301 channel to whichever channel is now selected once the async sample lands. */
2302 dt_ui_notebook_set_picker_owner(c->channel_tabs, self);
2303 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(c->channel_tabs), TRUE, TRUE, 0);
2304 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" "), FALSE, FALSE, 0);
2305
2306 // color pickers
2308 gtk_widget_set_tooltip_text(c->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
2309 c->colorpicker_set_values = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, hbox, IOP_CS_LCH);
2310 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(c->colorpicker_set_values),
2312
2313 gtk_widget_set_size_request(c->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
2314 gtk_widget_set_tooltip_text(c->colorpicker_set_values, _("create a curve based on an area from the image\n"
2315 "drag to create a flat curve\n"
2316 "ctrl+drag to create a positive curve\n"
2317 "shift+drag to create a negative curve"));
2318
2319 // the nice graph
2320 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
2321 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
2322
2323 gtk_box_pack_start(GTK_BOX(vbox),
2324 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
2325 "plugins/darkroom/colorzones/graphheight", 280, 100),
2326 FALSE, FALSE, 0);
2327
2328 GtkWidget *dabox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2329 gtk_widget_set_name(GTK_WIDGET(dabox), "iop-bottom-bar");
2330 c->bottom_area = gtk_drawing_area_new();
2331 gtk_box_pack_start(GTK_BOX(dabox), GTK_WIDGET(c->bottom_area), TRUE, TRUE, 0);
2332 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dabox), TRUE, TRUE, 0);
2333 gtk_box_pack_start(GTK_BOX(self->widget), GTK_WIDGET(vbox), TRUE, TRUE, 0);
2334
2335 GtkWidget *hbox_select_by = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2336
2337 // edit by area
2338 gchar *label = N_("edit by area");
2339 c->chk_edit_by_area = gtk_check_button_new_with_label(_(label));
2340 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(c->chk_edit_by_area))), PANGO_ELLIPSIZE_START);
2341 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->chk_edit_by_area), c->edit_by_area);
2342 gtk_widget_set_tooltip_text(c->chk_edit_by_area, _("edit the curve nodes by area"));
2343 gtk_box_pack_start(GTK_BOX(hbox_select_by), c->chk_edit_by_area, TRUE, TRUE, 0);
2344 g_signal_connect(G_OBJECT(c->chk_edit_by_area), "toggled", G_CALLBACK(_edit_by_area_callback), self);
2345
2346 // display selection
2347 c->bt_showmask = dtgtk_togglebutton_new(dtgtk_cairo_paint_showmask, 0, NULL);
2348
2349 gtk_widget_set_tooltip_text(c->bt_showmask, _("display selection"));
2350 g_signal_connect(G_OBJECT(c->bt_showmask), "toggled", G_CALLBACK(_display_mask_callback), self);
2351 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->bt_showmask), FALSE);
2352 gtk_box_pack_end(GTK_BOX(hbox_select_by), c->bt_showmask, FALSE, FALSE, 0);
2353
2354 gtk_box_pack_start(GTK_BOX(self->widget), hbox_select_by, TRUE, TRUE, 0);
2355
2356 // select by which dimension
2357 c->select_by = dt_bauhaus_combobox_from_params(self, "channel");
2359 gtk_widget_set_tooltip_text(c->select_by, _("choose selection criterion, will be the abscissa in the graph"));
2360
2361 c->mode = dt_bauhaus_combobox_from_params(self, "mode");
2362 gtk_widget_set_tooltip_text(c->mode, _("choose between a smoother or stronger effect"));
2363
2364 c->strength = dt_bauhaus_slider_from_params(self, "strength");
2365 dt_bauhaus_slider_set_format(c->strength, "%");
2366 gtk_widget_set_tooltip_text(c->strength, _("make effect stronger or weaker"));
2367
2368 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
2369 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2370 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
2372 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
2373 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
2374 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(_area_draw_callback), self);
2375 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
2376 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(_area_button_release_callback), self);
2377 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
2378 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
2379 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
2380 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
2381 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
2382 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
2383
2384 gtk_widget_add_events(GTK_WIDGET(c->bottom_area), GDK_BUTTON_PRESS_MASK);
2385 g_signal_connect(G_OBJECT(c->bottom_area), "draw", G_CALLBACK(_bottom_area_draw_callback), self);
2386 g_signal_connect(G_OBJECT(c->bottom_area), "button-press-event", G_CALLBACK(_bottom_area_button_press_callback),
2387 self);
2388
2389 /* From src/common/curve_tools.h :
2390 #define CUBIC_SPLINE 0
2391 #define CATMULL_ROM 1
2392 #define MONOTONE_HERMITE 2
2393 */
2394 c->interpolator = dt_bauhaus_combobox_new(darktable.bauhaus, DT_GUI_MODULE(self));
2395 dt_bauhaus_widget_set_label(c->interpolator, N_("interpolation method"));
2396 dt_bauhaus_combobox_add(c->interpolator, _("cubic spline"));
2397 dt_bauhaus_combobox_add(c->interpolator, _("centripetal spline"));
2398 dt_bauhaus_combobox_add(c->interpolator, _("monotonic spline"));
2399 gtk_box_pack_start(GTK_BOX(self->widget), c->interpolator, TRUE, TRUE, 0);
2400 gtk_widget_set_tooltip_text(c->interpolator,
2401 _("change this method if you see oscillations or cusps in the curve\n"
2402 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
2403 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
2404 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
2405 g_signal_connect(G_OBJECT(c->interpolator), "value-changed", G_CALLBACK(_interpolator_callback), self);
2406}
2407
2408void gui_update(struct dt_iop_module_t *self)
2409{
2412
2413 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[g->channel]);
2414
2416
2417 gtk_widget_queue_draw(self->widget);
2418}
2419
2421{
2423 dt_conf_set_int("plugins/darkroom/colorzones/gui_channel", c->channel);
2424
2425 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(c->minmax_curve[ch]);
2426
2428
2430}
2431
2433{
2434 const int program = 2; // basic.cl, from programs.conf
2436 module->data = gd;
2437 gd->kernel_colorzones = dt_opencl_create_kernel(program, "colorzones");
2438 gd->kernel_colorzones_v3 = dt_opencl_create_kernel(program, "colorzones_v3");
2439}
2440
2450
2453{
2454 // pull in new params to pipe
2458
2459 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
2460 piece->request_histogram |= (DT_REQUEST_ON);
2461 else
2462 piece->request_histogram &= ~(DT_REQUEST_ON);
2463
2464#if 0 // print new preset
2465 printf("p.channel = %d;\n", p->channel);
2466 for(int k=0; k<3; k++) for(int i=0; i<DT_IOP_COLORZONES_MAXNODES; i++)
2467 {
2468 printf("p.curve[%d][%i].x = %f;\n", k, i, p->curve[k][i].x);
2469 printf("p.curve[%d][%i].y = %f;\n", k, i, p->curve[k][i].y);
2470 }
2471#endif
2472
2473 // display selection don't work with opencl
2474 piece->process_cl_ready = (g && g->display_mask) ? 0 : 1;
2475 d->channel = (dt_iop_colorzones_channel_t)p->channel;
2476 d->mode = p->mode;
2477
2478 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
2479 {
2480 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2481 {
2482 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
2483 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch])
2484 {
2485 dt_draw_curve_destroy(d->curve[ch]);
2486 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2487 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2488 d->curve_type[ch] = p->curve_type[ch];
2489
2490 if(d->channel == DT_IOP_COLORZONES_h)
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][p->curve_num_nodes[ch] - 2].y, p->strength));
2493 else
2494 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2495 strength(p->curve[ch][0].y, p->strength));
2496 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2497 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2498 if(d->channel == DT_IOP_COLORZONES_h)
2499 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f, strength(p->curve[ch][1].y, p->strength));
2500 else
2501 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f,
2502 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2503 }
2504 else
2505 {
2506 if(d->channel == DT_IOP_COLORZONES_h)
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][p->curve_num_nodes[ch] - 2].y, p->strength));
2509 else
2510 dt_draw_curve_set_point(d->curve[ch], 0, p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2511 strength(p->curve[ch][0].y, p->strength));
2512 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2513 dt_draw_curve_set_point(d->curve[ch], k + 1, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2514 if(d->channel == DT_IOP_COLORZONES_h)
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][1].y, p->strength));
2517 else
2518 dt_draw_curve_set_point(d->curve[ch], p->curve_num_nodes[ch] + 1, p->curve[ch][1].x + 1.f,
2519 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2520 }
2521 dt_draw_curve_calc_values(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch]);
2522 }
2523 }
2524 else
2525 {
2526 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2527 {
2528 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch]
2529 || d->curve[ch]->c.m_numAnchors != p->curve_num_nodes[ch])
2530 {
2531 dt_draw_curve_destroy(d->curve[ch]);
2532 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2533 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2534 d->curve_type[ch] = p->curve_type[ch];
2535
2536 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2537 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2538 }
2539 else
2540 {
2541 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2542 dt_draw_curve_set_point(d->curve[ch], k, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2543 }
2544 dt_draw_curve_calc_values_V2(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch],
2545 p->channel == DT_IOP_COLORZONES_h);
2546 }
2547 }
2548}
2549
2551{
2554 piece->data = d;
2555 piece->data_size = sizeof(dt_iop_colorzones_data_t);
2556
2557 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2558 {
2559 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, default_params->curve_type[ch]);
2560 d->curve_nodes[ch] = default_params->curve_num_nodes[ch];
2561 d->curve_type[ch] = default_params->curve_type[ch];
2562 for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
2563 dt_draw_curve_add_point(d->curve[ch], default_params->curve[ch][k].x, default_params->curve[ch][k].y);
2564 }
2565 d->channel = (dt_iop_colorzones_channel_t)default_params->channel;
2566 d->mode = default_params->mode;
2567}
2568
2570{
2571 // clean up everything again.
2573
2574 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
2575
2576 dt_free_align(piece->data);
2577 piece->data = NULL;
2578}
2579
2581{
2582 module->params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2583 module->default_params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2584 module->default_enabled = 0; // we're a rather slow and rare op.
2585 module->params_size = sizeof(dt_iop_colorzones_params_t);
2586 module->gui_data = NULL;
2587 module->request_histogram |= (DT_REQUEST_ON);
2588
2590}
2591
2592#undef DT_IOP_COLORZONES_INSET
2593#undef DT_IOP_COLORZONES_CURVE_INFL
2594#undef DT_IOP_COLORZONES_RES
2595#undef DT_IOP_COLORZONES_LUT_RES
2596#undef DT_IOP_COLORZONES_BANDS
2597#undef DT_IOP_COLORZONES_MAXNODES
2598#undef DT_IOP_COLORZONES_DEFAULT_STEP
2599#undef DT_IOP_COLORZONES_MIN_X_DISTANCE
2600
2601// clang-format off
2602// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2603// vim: shiftwidth=2 expandtab tabstop=2 cindent
2604// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2605// 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:2350
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2304
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1656
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2104
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1845
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3613
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2019
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:777
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:922
#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:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
#define dt_free_align(ptr)
Definition darktable.h:503
static void * dt_calloc_align(size_t size)
Definition darktable.h:510
@ DT_DEBUG_OPENCL
Definition darktable.h:744
#define dt_gui_freeze_begin()
Definition darktable.h:900
#define dt_gui_freeze_end()
Definition darktable.h:901
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:546
#define dt_free(ptr)
Definition darktable.h:478
void void void gboolean dt_gui_widgets_suppressed(void)
Definition gtk.c:194
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define __DT_CLONE_TARGETS__
Definition darktable.h:379
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:270
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#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:293
#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:42
#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:414
@ 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:394
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:3068
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition gtk.c:2470
GtkNotebook * dt_ui_notebook_new()
Definition gtk.c:2465
GdkModifierType dt_key_modifier_state()
Definition gtk.c:2397
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and relay every "switch_page" as DT_SIGNA...
Definition gtk.c:2500
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:322
#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:3214
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
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:3220
@ DT_REQUEST_COLORPICK_MODULE
Definition imageop.h:227
#define IOP_GUI_FREE
Definition imageop.h:641
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:196
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:198
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:199
@ IOP_GROUP_COLOR
Definition imageop.h:169
#define IOP_GUI_ALLOC(module)
Definition imageop.h:638
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:2164
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2058
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2101
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:2155
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2375
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2415
#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:803
const struct dt_database_t * db
Definition darktable.h:807
struct dt_bauhaus_t * bauhaus
Definition darktable.h:806
struct dt_develop_t * develop
Definition darktable.h:798
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:285
struct dt_iop_module_t * gui_module
Definition develop.h:165
GSList * samples
Definition develop.h:398
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
gint scroll_mask
Definition gtk.h:230
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:260
dt_iop_global_data_t * data
Definition imageop.h:263
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:294
GtkDarktableToggleButton * off
Definition imageop.h:376
dt_iop_params_t * default_params
Definition imageop.h:344
GtkWidget * widget
Definition imageop.h:374
struct dt_develop_t * dev
Definition imageop.h:333
dt_iop_gui_data_t * gui_data
Definition imageop.h:348
uint32_t histogram_max[4]
Definition imageop.h:317
dt_iop_global_data_t * global_data
Definition imageop.h:351
gboolean enabled
Definition imageop.h:335
dt_aligned_pixel_t picked_color_min
Definition imageop.h:309
int request_mask_display
Definition imageop.h:298
dt_aligned_pixel_t picked_color_max
Definition imageop.h:309
uint32_t * histogram
Definition imageop.h:313
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:322
guint timeout_handle
Definition imageop.h:408
dt_aligned_pixel_t picked_color
Definition imageop.h:309
dt_iop_params_t * params
Definition imageop.h:344
Region of interest passed through the pixelpipe.
Definition imageop.h:72
GtkWidget * notebook
#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)