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 "widgets/gdkkeys.h"
64#include "common/conf.h"
65#include "config.h"
66#endif
67
68#include "widgets/bauhaus.h"
69#include "system/macros.h"
70#include "system/openmp.h"
72#include "system/mem_alloc.h"
73#include "system/simd.h"
74#include "common/logging.h"
76#include "database/database.h"
77#include "develop/iop_profile.h"
79#include "common/imagebuf.h"
80#include "math/math.h"
81#include "control/control.h"
82#include "develop/imageop.h"
83#include "develop/imageop_gui.h"
84
86#include "widgets/draw.h"
87#include "gui/presets.h"
88#include "libs/colorpicker.h"
89#include "widgets/notebook.h"
90#include "widgets/scroll_wrap.h"
91#include "gui/screen_metrics.h"
93
95
96#define DT_IOP_COLORZONES_INSET DT_PIXEL_APPLY_DPI(5)
97#define DT_IOP_COLORZONES_CURVE_INFL .3f
98#define DT_IOP_COLORZONES_RES 256
99#define DT_IOP_COLORZONES_LUT_RES 0x10000
100
101#define DT_IOP_COLORZONES_BANDS 8
102
103#define DT_IOP_COLORZONES_MAXNODES 20
104#define DT_IOP_COLORZONES_DEFAULT_STEP (0.001f)
105
106#define DT_IOP_COLORZONES_MIN_X_DISTANCE 0.0025f
107
109{
110 DT_IOP_COLORZONES_MODE_SMOOTH = 0, // $DESCRIPTION: "smooth"
111 DT_IOP_COLORZONES_MODE_STRONG = 1 // $DESCRIPTION: "strong"
113
119
121{
122 DT_IOP_COLORZONES_L = 0, // $DESCRIPTION: "lightness"
123 DT_IOP_COLORZONES_C = 1, // $DESCRIPTION: "saturation"
124 DT_IOP_COLORZONES_h = 2, // $DESCRIPTION: "hue"
127
133
134typedef struct dt_iop_colorzones_params_t
135{
136 dt_iop_colorzones_channel_t channel; // $DEFAULT: DT_IOP_COLORZONES_h $DESCRIPTION: "select by"
137 // three curves (L, C, h) with max number of nodes
139 int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]; // number of nodes per curve
140 int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]; // CUBIC_SPLINE, CATMULL_ROM, MONOTONE_HERMITE
141 float strength; // $MIN: -200.0 $MAX: 200.0 $DEFAULT: 0.0 $DESCRIPTION: "mix"
142 dt_iop_colorzones_modes_t mode; // $MIN: 0 $MAX: 1 $DEFAULT: DT_IOP_COLORZONES_MODE_SMOOTH $DESCRIPTION: "process mode"
145
177
187
193
194
195const char *name()
196{
197 return _("color _zones");
198}
199
200const char **description(struct dt_iop_module_t *self)
201{
202 return dt_iop_set_description(self, _("selectively shift hues, saturation and brightness of pixels"),
203 _("creative"),
204 _("linear or non-linear, Lab, display-referred"),
205 _("non-linear, Lab"),
206 _("non-linear, Lab, display-referred"));
207}
208
213
215{
216 return IOP_GROUP_COLOR;
217}
218
220{
221 return IOP_CS_LAB;
222}
223
224int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
225 const int new_version)
226{
227#define DT_IOP_COLORZONES1_BANDS 6
228
229 if(old_version == 1 && new_version == 5)
230 {
231 typedef struct dt_iop_colorzones_params1_t
232 {
233 int32_t channel;
234 float equalizer_x[3][DT_IOP_COLORZONES1_BANDS], equalizer_y[3][DT_IOP_COLORZONES1_BANDS];
235 } dt_iop_colorzones_params1_t;
236
237 const dt_iop_colorzones_params1_t *old = old_params;
238 dt_iop_colorzones_params_t *new = new_params;
239
240 new->channel = old->channel;
241
242 // keep first point
243 for(int i = 0; i < 3; i++)
244 {
245 new->curve[i][0].x = old->equalizer_x[i][0];
246 new->curve[i][0].y = old->equalizer_y[i][0];
247 }
248
249 for(int i = 0; i < 3; i++)
250 for(int k = 0; k < 6; k++)
251 {
252 // first+1 and last-1 are set to just after and before the first and last point
253 if(k == 0)
254 new->curve[i][k + 1].x = old->equalizer_x[i][k] + 0.001f;
255 else if(k == 5)
256 new->curve[i][k + 1].x = old->equalizer_x[i][k] - 0.001f;
257 else
258 new->curve[i][k + 1].x = old->equalizer_x[i][k];
259 new->curve[i][k + 1].y = old->equalizer_y[i][k];
260 }
261
262 // keep last point
263 for(int i = 0; i < 3; i++)
264 {
265 new->curve[i][7].x = old->equalizer_x[i][5];
266 new->curve[i][7].y = old->equalizer_y[i][5];
267 }
268 for(int c = 0; c < 3; c++)
269 {
270 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
271 new->curve_type[c] = CATMULL_ROM;
272 }
273 new->strength = 0.0f;
275 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
276 return 0;
277 }
278 if(old_version == 2 && new_version == 5)
279 {
280 typedef struct dt_iop_colorzones_params2_t
281 {
282 int32_t channel;
283 float equalizer_x[3][DT_IOP_COLORZONES_BANDS], equalizer_y[3][DT_IOP_COLORZONES_BANDS];
284 } dt_iop_colorzones_params2_t;
285
286 const dt_iop_colorzones_params2_t *old = old_params;
287 dt_iop_colorzones_params_t *new = new_params;
288 new->channel = old->channel;
289
290 for(int b = 0; b < DT_IOP_COLORZONES_BANDS; b++)
291 for(int c = 0; c < 3; c++)
292 {
293 new->curve[c][b].x = old->equalizer_x[c][b];
294 new->curve[c][b].y = old->equalizer_y[c][b];
295 }
296 for(int c = 0; c < 3; c++)
297 {
298 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
299 new->curve_type[c] = CATMULL_ROM;
300 }
301 new->strength = 0.0f;
303 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
304 return 0;
305 }
306 if(old_version == 3 && new_version == 5)
307 {
308 typedef struct dt_iop_colorzones_params3_t
309 {
310 int32_t channel;
311 float equalizer_x[3][DT_IOP_COLORZONES_BANDS], equalizer_y[3][DT_IOP_COLORZONES_BANDS];
312 float strength;
313 } dt_iop_colorzones_params3_t;
314
315 const dt_iop_colorzones_params3_t *old = old_params;
316 dt_iop_colorzones_params_t *new = new_params;
317 new->channel = old->channel;
318
319 for(int b = 0; b < DT_IOP_COLORZONES_BANDS; b++)
320 {
321 for(int c = 0; c < 3; c++)
322 {
323 new->curve[c][b].x = old->equalizer_x[c][b];
324 new->curve[c][b].y = old->equalizer_y[c][b];
325 }
326 }
327 for(int c = 0; c < 3; c++)
328 {
329 new->curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
330 new->curve_type[c] = CATMULL_ROM;
331 }
332 new->strength = old->strength;
334 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
335 return 0;
336 }
337 if(old_version == 4 && new_version == 5)
338 {
339 typedef struct dt_iop_colorzones_params4_t
340 {
341 int32_t channel;
343 int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS];
344 int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS];
345 float strength;
346 int mode;
347 } dt_iop_colorzones_params4_t;
348
349 const dt_iop_colorzones_params4_t *old = old_params;
350 dt_iop_colorzones_params_t *new = new_params;
351 new->channel = old->channel;
352
353 for(int i = 0; i < DT_IOP_COLORZONES_MAXNODES; i++)
354 {
355 for(int c = 0; c < DT_IOP_COLORZONES_MAX_CHANNELS; c++)
356 {
357 new->curve[c][i].x = old->curve[c][i].x;
358 new->curve[c][i].y = old->curve[c][i].y;
359 }
360 }
361 for(int c = 0; c < DT_IOP_COLORZONES_MAX_CHANNELS; c++)
362 {
363 new->curve_num_nodes[c] = old->curve_num_nodes[c];
364 new->curve_type[c] = old->curve_type[c];
365 }
366 new->strength = old->strength;
367 new->mode = old->mode;
368 new->splines_version = DT_IOP_COLORZONES_SPLINES_V1;
369 return 0;
370 }
371#undef DT_IOP_COLORZONES1_BANDS
372
373 return 1;
374}
375
376static float _curve_to_mouse(const float x, const float zoom_factor, const float offset)
377{
378 return (x - offset) * zoom_factor;
379}
380
381static float _mouse_to_curve(const float x, const float zoom_factor, const float offset)
382{
383 return (x / zoom_factor) + offset;
384}
385
386// fills in new parameters based on mouse position (in 0,1)
388 const int ch, const double mouse_x, const double mouse_y,
389 const float radius)
390{
391 const int bands = p->curve_num_nodes[ch];
392
393 const float lin_mouse_x = _mouse_to_curve(mouse_x, c->zoom_factor, c->offset_x);
394 const float lin_mouse_y = _mouse_to_curve(mouse_y, c->zoom_factor, c->offset_y);
395
396 const float rad = radius / c->zoom_factor;
397
398 if(p->channel == DT_IOP_COLORZONES_h && p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
399 {
400 // periodic boundary
401 for(int k = 1; k < bands - 1; k++)
402 {
403 const float f = expf(-(lin_mouse_x - p->curve[ch][k].x) * (lin_mouse_x - p->curve[ch][k].x) / (rad * rad));
404 p->curve[ch][k].y = (1.f - f) * p->curve[ch][k].y + f * lin_mouse_y;
405 }
406 const int m = bands - 1;
407 const float mind = fminf((lin_mouse_x - p->curve[ch][0].x) * (lin_mouse_x - p->curve[ch][0].x),
408 (lin_mouse_x - p->curve[ch][m].x) * (lin_mouse_x - p->curve[ch][m].x));
409 const float f = expf(-mind / (rad * rad));
410 p->curve[ch][0].y = (1.f - f) * p->curve[ch][0].y + f * lin_mouse_y;
411 p->curve[ch][m].y = (1.f - f) * p->curve[ch][m].y + f * lin_mouse_y;
412 }
413 else
414 {
415 for(int k = 0; k < bands; k++)
416 {
417 const float f = expf(-(lin_mouse_x - p->curve[ch][k].x) * (lin_mouse_x - p->curve[ch][k].x) / (rad * rad));
418 p->curve[ch][k].y = (1.f - f) * p->curve[ch][k].y + f * lin_mouse_y;
419 }
420 }
421}
422
423static inline __attribute__((always_inline)) float lookup(const float *lut, const float i)
424{
425 const int bin0 = MIN(0xffff, MAX(0, (int)(DT_IOP_COLORZONES_LUT_RES * i)));
426 const int bin1 = MIN(0xffff, MAX(0, (int)(DT_IOP_COLORZONES_LUT_RES * i) + 1));
427 const float f = DT_IOP_COLORZONES_LUT_RES * i - bin0;
428 return lut[bin1] * f + lut[bin0] * (1.f - f);
429}
430
431static inline float strength(float value, float strength)
432{
433 return value + (value - 0.5f) * (strength / 100.0f);
434}
435
438 const void *const ivoid, void *const ovoid, const dt_iop_roi_t *const roi_in,
439 const dt_iop_roi_t *const roi_out)
440{
443
444 const int ch = piece->dsc_in.channels;
445 const float normalize_C = 1.f / (128.0f * sqrtf(2.f));
446
447 const dt_iop_colorzones_channel_t display_channel = g->channel;
448
449 dt_iop_image_copy_by_size(ovoid, ivoid, roi_out->width, roi_out->height, ch);
451 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
452 {
453 float *in = (float *)ivoid + ch * k;
454 float *out = (float *)ovoid + ch * k;
455
457
458 dt_Lab_2_LCH(in, LCh);
459
460 float select = 0.0f;
461 switch(d->channel)
462 {
464 select = LCh[0] * 0.01f;
465 break;
467 select = LCh[1] * normalize_C;
468 break;
470 default:
471 select = LCh[2];
472 break;
473 }
474 select = CLAMP(select, 0.f, 1.f);
475
476 out[3] = fabsf(lookup(d->lut[display_channel], select) - .5f) * 4.f;
477 out[3] = CLAMP(out[3], 0.f, 1.f);
478 }
479
481 pipe->bypass_blendif = 1;
482}
483
485void process_v1(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
486 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
487{
489
490 const int ch = piece->dsc_in.channels;
491 const float normalize_C = 1.f / (128.0f * sqrtf(2.f));
493 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
494 {
495 float *in = (float *)ivoid + ch * k;
496 float *out = (float *)ovoid + ch * k;
497
499
500 dt_Lab_2_LCH(in, LCh);
501
502 float select = 0.0f;
503 switch(d->channel)
504 {
506 select = LCh[0] * 0.01f;
507 break;
509 select = LCh[1] * normalize_C;
510 break;
512 default:
513 select = LCh[2];
514 break;
515 }
516 select = CLAMP(select, 0.f, 1.f);
517
518 LCh[0] *= powf(2.0f, 4.0f * (lookup(d->lut[0], select) - .5f));
519 LCh[1] *= 2.f * lookup(d->lut[1], select);
520 LCh[2] += lookup(d->lut[2], select) - .5f;
521
522 dt_LCH_2_Lab(LCh, out);
523
524 out[3] = in[3];
525 }
526}
527
529void process_v3(struct dt_iop_module_t *self, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
530 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
531{
533 const int ch = piece->dsc_in.channels;
535 for(size_t k = 0; k < (size_t)roi_out->width * roi_out->height; k++)
536 {
537 float *in = (float *)ivoid + ch * k;
538 float *out = (float *)ovoid + ch * k;
539 const float a = in[1], b = in[2];
540 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);
541 const float C = sqrtf(b * b + a * a);
542 float select = 0.0f;
543 float blend = 0.0f;
544 switch(d->channel)
545 {
547 select = fminf(1.0f, in[0] / 100.0f);
548 break;
550 select = fminf(1.0f, C / 128.0f);
551 break;
552 default:
554 select = h;
555 blend = powf(1.0f - C / 128.0f, 2.0f);
556 break;
557 }
558 const float Lm = (blend * .5f + (1.0f - blend) * lookup(d->lut[0], select)) - .5f;
559 const float hm = (blend * .5f + (1.0f - blend) * lookup(d->lut[2], select)) - .5f;
560 blend *= blend; // saturation isn't as prone to artifacts:
561 // const float Cm = 2.0 * (blend*.5f + (1.0f-blend)*lookup(d->lut[1], select));
562 const float Cm = 2.0f * lookup(d->lut[1], select);
563 const float L = in[0] * powf(2.0f, 4.0f * Lm);
564 out[0] = L;
565 out[1] = cosf(2.0f * DT_M_PI_F * (h + hm)) * Cm * C;
566 out[2] = sinf(2.0f * DT_M_PI_F * (h + hm)) * Cm * C;
567 out[3] = in[3];
568 }
569}
570
571int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
572 void *const ovoid)
573{
574 const dt_iop_roi_t *const roi_in = &piece->roi_in;
575 const dt_iop_roi_t *const roi_out = &piece->roi_out;
578
579 // display selection if requested
580 if(pipe->type == DT_DEV_PIXELPIPE_FULL && !IS_NULL_PTR(g) && g->display_mask && self->dev->gui_attached
581 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
582 process_display(self, (dt_dev_pixelpipe_t *)pipe, piece, ivoid, ovoid, roi_in, roi_out);
583 else if(d->mode == DT_IOP_COLORZONES_MODE_SMOOTH)
584 process_v3(self, piece, ivoid, ovoid, roi_in, roi_out);
585 else
586 process_v1(self, piece, ivoid, ovoid, roi_in, roi_out);
587 return 0;
588}
589
590#ifdef HAVE_OPENCL
591int 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)
592{
593 const dt_iop_roi_t *const roi_in = &piece->roi_in;
596 cl_mem dev_L, dev_a, dev_b = NULL;
597 cl_int err = -999;
598
599 const int devid = pipe->devid;
600 const int width = roi_in->width;
601 const int height = roi_in->height;
602 const int kernel_colorzones
604
605 size_t sizes[] = { ROUNDUPDWD(width, devid), ROUNDUPDHT(height, devid), 1 };
606 dev_L = dt_opencl_copy_host_to_device(devid, d->lut[0], 256, 256, sizeof(float));
607 dev_a = dt_opencl_copy_host_to_device(devid, d->lut[1], 256, 256, sizeof(float));
608 dev_b = dt_opencl_copy_host_to_device(devid, d->lut[2], 256, 256, sizeof(float));
609 if(IS_NULL_PTR(dev_L) || IS_NULL_PTR(dev_a) || IS_NULL_PTR(dev_b)) goto error;
610
611 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 0, sizeof(cl_mem), &dev_in);
612 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 1, sizeof(cl_mem), &dev_out);
613 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 2, sizeof(int), &width);
614 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 3, sizeof(int), &height);
615 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 4, sizeof(int), &d->channel);
616 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 5, sizeof(cl_mem), &dev_L);
617 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 6, sizeof(cl_mem), &dev_a);
618 dt_opencl_set_kernel_arg(devid, kernel_colorzones, 7, sizeof(cl_mem), &dev_b);
619 err = dt_opencl_enqueue_kernel_2d(devid, kernel_colorzones, sizes);
620
621 if(err != CL_SUCCESS) goto error;
625 return TRUE;
626
627error:
631 dt_print(DT_DEBUG_OPENCL, "[opencl_colorzones] couldn't enqueue kernel! %d\n", err);
632 return FALSE;
633}
634#endif
635
637{
639 const int version = 5;
640
641 p.strength = 0.f;
643 p.splines_version = DT_IOP_COLORZONES_SPLINES_V2;
644
646
647 // red black white
648 p.channel = DT_IOP_COLORZONES_h;
649 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
650 {
651 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
652 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
653 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
654 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
655 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
656 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
657 }
658 p.curve[DT_IOP_COLORZONES_C][0].y = p.curve[DT_IOP_COLORZONES_C][DT_IOP_COLORZONES_BANDS - 1].y = 0.65f;
659 p.curve[DT_IOP_COLORZONES_C][1].x = 3.f / 16.f;
660 p.curve[DT_IOP_COLORZONES_C][3].x = 0.50f;
661 p.curve[DT_IOP_COLORZONES_C][4].x = 0.51f;
662 p.curve[DT_IOP_COLORZONES_C][6].x = 15.f / 16.f;
663 for(int c = 0; c < 3; c++)
664 {
665 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
666 p.curve_type[c] = CATMULL_ROM;
667 }
668 dt_gui_presets_add_generic(_("red black white"), self->op,
669 version, &p, sizeof(p), 1);
670
671 // black white and skin tones
672 p.channel = DT_IOP_COLORZONES_h;
673 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
674 {
675 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
676 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
677 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
678 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
679 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
680 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
681 }
682 p.curve[DT_IOP_COLORZONES_C][0].y = 0.5f;
683 p.curve[DT_IOP_COLORZONES_C][2].x = 0.25f;
684 p.curve[DT_IOP_COLORZONES_C][1].x = 0.16f;
685 p.curve[DT_IOP_COLORZONES_C][1].y = 0.3f;
686 for(int c = 0; c < 3; c++)
687 {
688 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
689 p.curve_type[c] = CATMULL_ROM;
690 }
691 dt_gui_presets_add_generic(_("black white and skin tones"), self->op,
692 version, &p, sizeof(p), 1);
693
694 // polarizing filter
695 p.channel = DT_IOP_COLORZONES_C;
696 for(int k = 0; k < DT_IOP_COLORZONES_BANDS; k++)
697 {
698 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
699 p.curve[DT_IOP_COLORZONES_C][k].y = .5f;
700 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
701 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
702 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
703 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
704 }
705 for(int k = 3; k < DT_IOP_COLORZONES_BANDS; k++)
706 p.curve[DT_IOP_COLORZONES_C][k].y += (k - 2.5f) / (DT_IOP_COLORZONES_BANDS - 2.0f) * 0.25f;
707 for(int k = 4; k < DT_IOP_COLORZONES_BANDS; k++)
708 p.curve[DT_IOP_COLORZONES_L][k].y -= (k - 3.5f) / (DT_IOP_COLORZONES_BANDS - 3.0f) * 0.35f;
709 for(int c = 0; c < 3; c++)
710 {
711 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS;
712 p.curve_type[c] = CATMULL_ROM;
713 }
714 dt_gui_presets_add_generic(_("polarizing filter"), self->op,
715 version, &p, sizeof(p), 1);
716
717 // natural skin tone
718 p.channel = DT_IOP_COLORZONES_h;
719 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
720 {
721 p.curve[DT_IOP_COLORZONES_L][k].y = .5f;
722 p.curve[DT_IOP_COLORZONES_C][k].y = .5f;
723 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
724 p.curve[DT_IOP_COLORZONES_L][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
725 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
726 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
727 }
728 p.curve[DT_IOP_COLORZONES_C][1].y = .45f;
729 p.curve[DT_IOP_COLORZONES_h][1].y = .55f;
730 for(int c = 0; c < 3; c++)
731 {
732 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
733 p.curve_type[c] = CATMULL_ROM;
734 }
735 dt_gui_presets_add_generic(_("natural skin tones"), self->op,
736 version, &p, sizeof(p), 1);
737
738 // black and white film
739 p.channel = DT_IOP_COLORZONES_h;
740 for(int k = 0; k < DT_IOP_COLORZONES_BANDS - 1; k++)
741 {
742 p.curve[DT_IOP_COLORZONES_C][k].y = .0f;
743 p.curve[DT_IOP_COLORZONES_h][k].y = .5f;
744 p.curve[DT_IOP_COLORZONES_C][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
745 p.curve[DT_IOP_COLORZONES_h][k].x = k / (DT_IOP_COLORZONES_BANDS - 1.f);
746 }
747 p.curve[DT_IOP_COLORZONES_L][0].x = 0.000000f;
748 p.curve[DT_IOP_COLORZONES_L][0].y = 0.613040f;
749 p.curve[DT_IOP_COLORZONES_L][1].x = 0.010000f;
750 p.curve[DT_IOP_COLORZONES_L][1].y = 0.613040f;
751 p.curve[DT_IOP_COLORZONES_L][2].x = 0.245283f;
752 p.curve[DT_IOP_COLORZONES_L][2].y = 0.447962f;
753 p.curve[DT_IOP_COLORZONES_L][3].x = 0.498113f;
754 p.curve[DT_IOP_COLORZONES_L][3].y = 0.529201f;
755 p.curve[DT_IOP_COLORZONES_L][4].x = 0.641509f;
756 p.curve[DT_IOP_COLORZONES_L][4].y = 0.664967f;
757 p.curve[DT_IOP_COLORZONES_L][5].x = 0.879245f;
758 p.curve[DT_IOP_COLORZONES_L][5].y = 0.777294f;
759 p.curve[DT_IOP_COLORZONES_L][6].x = 0.990000f;
760 p.curve[DT_IOP_COLORZONES_L][6].y = 0.613040f;
761 for(int c = 0; c < 3; c++)
762 {
763 p.curve_num_nodes[c] = DT_IOP_COLORZONES_BANDS - 1;
764 p.curve_type[c] = CATMULL_ROM;
765 }
766 dt_gui_presets_add_generic(_("black & white film"), self->op,
767 version, &p, sizeof(p), 1);
768
769 // neutral preset with just a set of nodes uniformly distributed along the hue axis
770 const int colorzones_bands_hsl = 8;
771 p.channel = DT_IOP_COLORZONES_h;
772 for(int k = 0; k < colorzones_bands_hsl; k++)
773 {
774 p.curve[DT_IOP_COLORZONES_L][k].x = (float)k / colorzones_bands_hsl;
775 p.curve[DT_IOP_COLORZONES_L][k].y = 0.5f;
776 p.curve[DT_IOP_COLORZONES_C][k].x = (float)k / colorzones_bands_hsl;
777 p.curve[DT_IOP_COLORZONES_C][k].y = 0.5f;
778 p.curve[DT_IOP_COLORZONES_h][k].x = (float)k / colorzones_bands_hsl;
779 p.curve[DT_IOP_COLORZONES_h][k].y = 0.5f;
780 }
781 for(int c = 0; c < 3; c++)
782 {
783 p.curve_num_nodes[c] = colorzones_bands_hsl;
784 p.curve_type[c] = MONOTONE_HERMITE;
785 }
786 dt_gui_presets_add_generic(_("HSL base setting"), self->op,
787 version, &p, sizeof(p), 1);
788
790}
791
793{
795 if(c)
796 {
797 if(c->display_mask)
798 {
799 c->display_mask = FALSE;
801 }
802 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->bt_showmask)))
803 {
805 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->bt_showmask), FALSE);
807 }
808 }
809}
810
811static void _reset_nodes(dt_iop_colorzones_params_t *p, const int ch, const _Bool touch_edges)
812{
813 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
814 {
815 if(touch_edges)
816 p->curve[ch][k].x = (float)k / (float)(p->curve_num_nodes[ch] - 1);
817 else
818 p->curve[ch][k].x = ((float)k + 0.5f) / (float)p->curve_num_nodes[ch];
819 p->curve[ch][k].y = 0.5f;
820 }
821}
822
823static void _reset_parameters(dt_iop_colorzones_params_t *p, const int channel, const int splines_version)
824{
825 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
826 {
827 p->curve_num_nodes[ch] = 2;
828 p->curve_type[ch] = CATMULL_ROM; // CUBIC_SPLINE, MONOTONE_HERMITE
829 _reset_nodes(p, ch, channel != DT_IOP_COLORZONES_h);
830 }
831 p->strength = 0.0f;
832 p->channel = channel;
834 p->splines_version = splines_version;
835}
836
837static int _select_base_display_color(dt_iop_module_t *self, float *picked_color, float *picker_min,
838 float *picker_max)
839{
840 const int select_by_picker = !(self->request_color_pick != DT_REQUEST_COLORPICK_MODULE
841 || self->picked_color_max[0] < 0.0f || self->picked_color[0] == 0.0f);
842 if(!select_by_picker)
843 {
844 dt_aligned_pixel_t rgb = { 0.0f, 0.3f, 0.7f };
847 dt_sRGB_to_XYZ(rgb, xyz);
848 dt_XYZ_to_Lab(xyz, lab);
849 dt_Lab_2_LCH(lab, picked_color);
850
851 picker_max[0] = picker_min[0] = picked_color[0];
852 picker_max[1] = picker_min[1] = picked_color[1];
853 picker_max[2] = picker_min[2] = picked_color[2];
854 }
855 else
856 {
857 for(int k = 0; k < 3; k++)
858 {
859 picked_color[k] = self->picked_color[k];
860 picker_min[k] = self->picked_color_min[k];
861 picker_max[k] = self->picked_color_max[k];
862 }
863 }
864 return select_by_picker;
865}
866
868 dt_iop_colorzones_gui_data_t *c, const int width, const int height,
869 const float *const picker_color, const float *const picker_min,
870 const float *const picker_max)
871{
873 ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)) ||
874 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker_set_values)) ))
875 {
876 // the global live samples ...
877 GSList *samples = self->dev->color_picker.samples;
878 if(samples)
879 {
880 const dt_iop_order_iccprofile_info_t *const display_profile
882 const dt_iop_order_iccprofile_info_t *const work_profile
884 dt_aligned_pixel_t pick_mean, pick_min, pick_max;
885 int converted_cst;
886
887 if(!IS_NULL_PTR(work_profile) && !IS_NULL_PTR(display_profile))
888 {
889 dt_colorpicker_sample_t *sample = NULL;
890 for(; samples; samples = g_slist_next(samples))
891 {
892 sample = samples->data;
893
894 float picked_i = -1.0f;
895 float picked_min_i = -1.0f;
896 float picked_max_i = -1.0f;
897
898 // this functions need a 4c image
899 for(int k = 0; k < 3; k++)
900 {
901 pick_mean[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MEAN][k];
902 pick_min[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MIN][k];
903 pick_max[k] = sample->scope[DT_LIB_COLORPICKER_STATISTIC_MAX][k];
904 }
905 pick_mean[3] = pick_min[3] = pick_max[3] = 1.f;
906
907 dt_ioppr_transform_image_colorspace_rgb(pick_mean, pick_mean, 1, 1, display_profile, work_profile,
908 "color zones");
909 dt_ioppr_transform_image_colorspace_rgb(pick_min, pick_min, 1, 1, display_profile, work_profile,
910 "color zones");
911 dt_ioppr_transform_image_colorspace_rgb(pick_max, pick_max, 1, 1, display_profile, work_profile,
912 "color zones");
913
914 dt_colorspaces_apply_profile(self->op, self->multi_name, pick_mean, pick_mean, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
915 &converted_cst, work_profile);
916 dt_colorspaces_apply_profile(self->op, self->multi_name, pick_min, pick_min, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
917 &converted_cst, work_profile);
918 dt_colorspaces_apply_profile(self->op, self->multi_name, pick_max, pick_max, 1, 1, IOP_CS_RGB, IOP_CS_LAB,
919 &converted_cst, work_profile);
920
921 dt_Lab_2_LCH(pick_mean, pick_mean);
922 dt_Lab_2_LCH(pick_min, pick_min);
923 dt_Lab_2_LCH(pick_max, pick_max);
924
925 switch(p->channel)
926 {
927 // select by channel, abscissa:
929 picked_i = pick_mean[0] / 100.0f;
930 picked_min_i = pick_min[0] / 100.0f;
931 picked_max_i = pick_max[0] / 100.0f;
932 break;
934 picked_i = pick_mean[1] / (128.0f * sqrtf(2.f));
935 picked_min_i = pick_min[1] / (128.0f * sqrtf(2.f));
936 picked_max_i = pick_max[1] / (128.0f * sqrtf(2.f));
937 break;
938 default: // case DT_IOP_COLORZONES_h:
939 picked_i = pick_mean[2];
940 picked_min_i = pick_min[2];
941 picked_max_i = pick_max[2];
942 break;
943 }
944
945 // Convert abcissa to log coordinates if needed
946 picked_i = _curve_to_mouse(picked_i, c->zoom_factor, c->offset_x);
947 picked_min_i = _curve_to_mouse(picked_min_i, c->zoom_factor, c->offset_x);
948 picked_max_i = _curve_to_mouse(picked_max_i, c->zoom_factor, c->offset_x);
949
950 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.15);
951 cairo_rectangle(cr, width * picked_min_i, 0, width * fmax(picked_max_i - picked_min_i, 0.0f), height);
952 cairo_fill(cr);
953 cairo_set_source_rgba(cr, 0.5, 0.7, 0.5, 0.5);
954 cairo_move_to(cr, width * picked_i, 0);
955 cairo_line_to(cr, width * picked_i, height);
956 cairo_stroke(cr);
957 }
958 }
959 }
960 }
961
963 ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker)) ||
964 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(c->colorpicker_set_values)) ))
965 {
966 // draw marker for currently selected color:
967 float picked_i = -1.0f;
968 float picked_min_i = -1.0f;
969 float picked_max_i = -1.0f;
970 switch(p->channel)
971 {
972 // select by channel, abscissa:
974 picked_i = picker_color[0] / 100.0f;
975 picked_min_i = picker_min[0] / 100.0f;
976 picked_max_i = picker_max[0] / 100.0f;
977 break;
979 picked_i = picker_color[1] / (128.0f * sqrtf(2.f));
980 picked_min_i = picker_min[1] / (128.0f * sqrtf(2.f));
981 picked_max_i = picker_max[1] / (128.0f * sqrtf(2.f));
982 break;
983 default: // case DT_IOP_COLORZONES_h:
984 picked_i = picker_color[2];
985 picked_min_i = picker_min[2];
986 picked_max_i = picker_max[2];
987 break;
988 }
989
990 picked_i = _curve_to_mouse(picked_i, c->zoom_factor, c->offset_x);
991 picked_min_i = _curve_to_mouse(picked_min_i, c->zoom_factor, c->offset_x);
992 picked_max_i = _curve_to_mouse(picked_max_i, c->zoom_factor, c->offset_x);
993
994 cairo_save(cr);
995
996 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.25);
997 cairo_rectangle(cr, width * picked_min_i, 0, width * fmax(picked_max_i - picked_min_i, 0.0), height);
998 cairo_fill(cr);
999
1000 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1001 cairo_set_operator(cr, CAIRO_OPERATOR_XOR);
1002 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1003 cairo_move_to(cr, width * picked_i, 0.0);
1004 cairo_line_to(cr, width * picked_i, height);
1005 cairo_stroke(cr);
1006
1007 cairo_restore(cr);
1008 }
1009}
1010
1011// graph x resulolution
1012#define DT_COLORZONES_CELLSI 64
1013// graph y resulolution
1014#define DT_COLORZONES_CELLSJ 36
1015
1016#define COLORZONES_DRAW_BACKGROUD_BOX \
1017 dt_aligned_pixel_t Lab; \
1018 dt_LCH_2_Lab(LCh, Lab); \
1019 const float L0 = Lab[0]; \
1020 /* gamut mapping magic from iop/exposure.c: */ \
1021 const float Lwhite = 100.0f, Lclip = 20.0f; \
1022 const float Lcap = fminf(100.0f, Lab[0]); \
1023 const float clip \
1024 = 1.0f \
1025 - (Lcap - L0) * (1.0f / 100.0f) * fminf(Lwhite - Lclip, fmaxf(0.0f, Lab[0] - Lclip)) / (Lwhite - Lclip); \
1026 const float clip2 = clip * clip * clip; \
1027 Lab[1] *= Lab[0] / L0 * clip2; \
1028 Lab[2] *= Lab[0] / L0 * clip2; \
1029 \
1030 dt_aligned_pixel_t xyz; \
1031 dt_aligned_pixel_t rgb; \
1032 dt_Lab_to_XYZ(Lab, xyz); \
1033 dt_XYZ_to_sRGB(xyz, rgb); \
1034 \
1035 cairo_set_source_rgb(cr, rgb[0], rgb[1], rgb[2]);
1036
1038 const int select_by_picker, const int width, const int height,
1039 const float *picked_color)
1040{
1041 const float bg_sat_factor = dt_conf_get_float("plugins/darkroom/colorzones/bg_sat_factor");
1042 const float normalize_C = (128.f * bg_sat_factor * sqrtf(2.f));
1043
1044 const int cellsi = DT_COLORZONES_CELLSI;
1045 const int cellsj = DT_COLORZONES_CELLSJ;
1046
1047 for(int j = 0; j < cellsj; j++)
1048 {
1049 for(int i = 0; i < cellsi; i++)
1050 {
1051 dt_aligned_pixel_t LCh = { 0 };
1052
1053 const float jj = _mouse_to_curve(1.0f - ((float)j - .5f) / (float)(cellsj - 1), c->zoom_factor, c->offset_y);
1054 const float jjh
1055 = _mouse_to_curve(1.0f - (((float)j / (float)(cellsj - 1))), c->zoom_factor, c->offset_y) + .5f;
1056 const float ii = _mouse_to_curve(((float)i + .5f) / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1057 const float iih = _mouse_to_curve((float)i / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1058
1059 // select by channel, abscissa:
1060 switch(p->channel)
1061 {
1062 // select by channel, abscissa:
1064 LCh[0] = 100.0f * ii;
1065 LCh[1] = normalize_C * .5f;
1066 LCh[2] = picked_color[2];
1067 break;
1069 LCh[0] = 50.0f;
1070 LCh[1] = picked_color[1] * 2.f * bg_sat_factor * ii;
1071 LCh[2] = picked_color[2];
1072 break;
1073 default: // DT_IOP_COLORZONES_h
1074 LCh[0] = 50.0f;
1075 LCh[1] = normalize_C * .5f;
1076 LCh[2] = iih;
1077 break;
1078 }
1079 // channel to be altered:
1080 switch(c->channel)
1081 {
1082 // select by channel, abscissa:
1084 if(p->channel == DT_IOP_COLORZONES_L)
1085 LCh[0] *= jj;
1086 else
1087 LCh[0] += -50.0f + 100.0f * jj;
1088 break;
1090 LCh[1] *= 2.f * jj;
1091 break;
1092 default: // DT_IOP_COLORZONES_h
1093 LCh[2] += jjh;
1094 break;
1095 }
1096
1098
1099 cairo_rectangle(cr, width * i / (float)cellsi, height * j / (float)cellsj, width / (float)cellsi,
1100 height / (float)cellsj);
1101 cairo_fill(cr);
1102 }
1103 }
1104}
1105
1106static gboolean _area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1107{
1110
1111 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1112 {
1113 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
1114 {
1115 if(c->minmax_curve_type[ch] != p.curve_type[ch] || c->minmax_curve_nodes[ch] != p.curve_num_nodes[ch])
1116 {
1117 dt_draw_curve_destroy(c->minmax_curve[ch]);
1118 c->minmax_curve[ch] = dt_draw_curve_new(0.0f, 1.0f, p.curve_type[ch]);
1119 c->minmax_curve_nodes[ch] = p.curve_num_nodes[ch];
1120 c->minmax_curve_type[ch] = p.curve_type[ch];
1121
1122 if(p.channel == DT_IOP_COLORZONES_h)
1123 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1124 p.curve[ch][p.curve_num_nodes[ch] - 2].y);
1125 else
1126 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1127 p.curve[ch][0].y);
1128 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1129 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][k].x, p.curve[ch][k].y);
1130 if(p.channel == DT_IOP_COLORZONES_h)
1131 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][1].x + 1.0f, p.curve[ch][1].y);
1132 else
1133 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][1].x + 1.0f,
1134 p.curve[ch][p.curve_num_nodes[ch] - 1].y);
1135 }
1136 else
1137 {
1138 if(p.channel == DT_IOP_COLORZONES_h)
1139 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1140 p.curve[ch][p.curve_num_nodes[ch] - 2].y);
1141 else
1142 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][p.curve_num_nodes[ch] - 2].x - 1.0f,
1143 p.curve[ch][0].y);
1144 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1145 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1146 if(p.channel == DT_IOP_COLORZONES_h)
1147 dt_draw_curve_set_point(c->minmax_curve[ch], p.curve_num_nodes[ch] + 1, p.curve[ch][1].x + 1.0f,
1148 p.curve[ch][1].y);
1149 else
1150 dt_draw_curve_set_point(c->minmax_curve[ch], p.curve_num_nodes[ch] + 1, p.curve[ch][1].x + 1.0f,
1151 p.curve[ch][p.curve_num_nodes[ch] - 1].y);
1152 }
1153 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.0f, 1.0f, DT_IOP_COLORZONES_RES, NULL, c->draw_ys[ch]);
1154 }
1155 }
1156 else
1157 {
1158 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
1159 {
1160 if(c->minmax_curve_type[ch] != p.curve_type[ch] || c->minmax_curve_nodes[ch] != p.curve_num_nodes[ch]
1161 || c->minmax_curve[ch]->c.m_numAnchors != p.curve_num_nodes[ch])
1162 {
1163 dt_draw_curve_destroy(c->minmax_curve[ch]);
1164 c->minmax_curve[ch] = dt_draw_curve_new(0.f, 1.f, p.curve_type[ch]);
1165 c->minmax_curve_nodes[ch] = p.curve_num_nodes[ch];
1166 c->minmax_curve_type[ch] = p.curve_type[ch];
1167
1168 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1169 dt_draw_curve_add_point(c->minmax_curve[ch], p.curve[ch][k].x, p.curve[ch][k].y);
1170 }
1171 else
1172 {
1173 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1174 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1175 }
1176 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_ys[ch],
1177 p.channel == DT_IOP_COLORZONES_h);
1178 }
1179 }
1180
1181 const int ch = (int)c->channel;
1182
1183 GtkAllocation allocation;
1184 gtk_widget_get_allocation(widget, &allocation);
1185 const int inset = DT_IOP_COLORZONES_INSET;
1186 int width = allocation.width, height = allocation.height;
1187 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1188 cairo_t *cr = cairo_create(cst);
1189
1190 // clear bg, match color of the notebook tabs:
1191 GdkRGBA color;
1192 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1193 gboolean color_found = gtk_style_context_lookup_color(context, "graph_overlay", &color);
1194 if(!color_found)
1195 {
1196 color.red = 1.0;
1197 color.green = 0.0;
1198 color.blue = 0.0;
1199 color.alpha = 1.0;
1200 }
1201 gdk_cairo_set_source_rgba(cr, &color);
1202 cairo_paint(cr);
1203
1204 cairo_translate(cr, inset, inset);
1205 width -= 2 * inset;
1206 height -= 2 * inset;
1207
1208 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1209 cairo_set_source_rgb(cr, .1, .1, .1);
1210 cairo_rectangle(cr, 0, 0, width, height);
1211 cairo_stroke(cr);
1212
1213 cairo_set_source_rgb(cr, .3, .3, .3);
1214 cairo_rectangle(cr, 0, 0, width, height);
1215 cairo_fill(cr);
1216
1217 // if color picker is active we use it as base color
1218 // otherwise we use a light blue
1219 // we will work on LCh
1220 dt_aligned_pixel_t picked_color, picker_min, picker_max;
1221 const int select_by_picker = _select_base_display_color(self, picked_color, picker_min, picker_max);
1222
1223 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1224
1225 _draw_background(cr, &p, c, select_by_picker, width, height, picked_color);
1226
1227 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1228
1229 // draw histogram in background
1230 // only if module is enabled
1231 if(self->enabled)
1232 {
1233 // only if no color picker
1235 {
1236 const int ch_hist = p.channel;
1237 const uint32_t *hist = self->histogram;
1238 const gboolean is_linear = FALSE;
1239 const float hist_max = is_linear ? self->histogram_max[ch_hist]
1240 : logf(1.0f + self->histogram_max[ch_hist]);
1241 if(!IS_NULL_PTR(hist) && hist_max > 0.0f)
1242 {
1243 cairo_save(cr);
1244 cairo_translate(cr, 0, height);
1245 cairo_scale(cr, width / 255.0, -(height - DT_PIXEL_APPLY_DPI(5)) / hist_max);
1246
1247 cairo_set_source_rgba(cr, .2, .2, .2, 0.5);
1248 dt_draw_histogram_8_zoomed(cr, hist, 4, ch_hist, c->zoom_factor, c->offset_x * 255.f,
1249 c->offset_y * hist_max, is_linear);
1250
1251 cairo_restore(cr);
1252 }
1253 }
1254
1255 _draw_color_picker(self, cr, &p, c, width, height, picked_color, picker_min, picker_max);
1256 }
1257
1258 if(c->edit_by_area)
1259 {
1260 // draw x positions
1261 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1262 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1263 const float arrw = DT_PIXEL_APPLY_DPI(7.0f);
1264 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1265 {
1266 const float x = _curve_to_mouse(p.curve[ch][k].x, c->zoom_factor, c->offset_x);
1267
1268 cairo_move_to(cr, width * x, height + inset - DT_PIXEL_APPLY_DPI(1));
1269 cairo_rel_line_to(cr, -arrw * .5f, 0);
1270 cairo_rel_line_to(cr, arrw * .5f, -arrw);
1271 cairo_rel_line_to(cr, arrw * .5f, arrw);
1272 cairo_close_path(cr);
1273 if(c->x_move == k)
1274 cairo_fill(cr);
1275 else
1276 cairo_stroke(cr);
1277 }
1278 }
1279
1280 cairo_translate(cr, 0, height);
1281
1282 // draw curves, selected last.
1283 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1284 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(2.));
1285 for(int i = 0; i < DT_IOP_COLORZONES_MAX_CHANNELS; i++)
1286 {
1287 const int ch_inv = ((int)c->channel + i + 1) % 3;
1288
1289 if(i == 2)
1290 cairo_set_source_rgba(cr, .7, .7, .7, 1.0);
1291 else
1292 cairo_set_source_rgba(cr, .7, .7, .7, 0.3);
1293
1294 cairo_move_to(cr, 0, -height * _curve_to_mouse(c->draw_ys[ch_inv][0], c->zoom_factor, c->offset_y));
1295 for(int k = 1; k < DT_IOP_COLORZONES_RES; k++)
1296 {
1297 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1298 const float yy = c->draw_ys[ch_inv][k];
1299
1300 const float x = _curve_to_mouse(xx, c->zoom_factor, c->offset_x),
1301 y = _curve_to_mouse(yy, c->zoom_factor, c->offset_y);
1302
1303 cairo_line_to(cr, x * width, -height * y);
1304 }
1305
1306 cairo_stroke(cr);
1307 }
1308
1309 // draw dots on knots
1310 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1311 cairo_set_source_rgb(cr, 0.6, 0.6, 0.6);
1312 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1313 for(int k = 0; k < p.curve_num_nodes[ch]; k++)
1314 {
1315 const float x = _curve_to_mouse(p.curve[ch][k].x, c->zoom_factor, c->offset_x),
1316 y = _curve_to_mouse(p.curve[ch][k].y, c->zoom_factor, c->offset_y);
1317 cairo_arc(cr, width * x, -height * y, DT_PIXEL_APPLY_DPI(3.0), 0.0, 2.0 * M_PI);
1318 cairo_stroke(cr);
1319 }
1320
1321 // draw min/max, if selected
1322 if(c->edit_by_area && (c->mouse_y > 0 || c->dragging))
1323 {
1324 const int bands = p.curve_num_nodes[ch];
1325
1327 dt_iop_colorzones_get_params(&p, c, c->channel, c->mouse_x, 1., c->mouse_radius);
1328 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1329 {
1330 if(p.channel == DT_IOP_COLORZONES_h)
1331 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][bands - 2].y);
1332 else
1333 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][0].y);
1334 for(int k = 0; k < bands; k++)
1335 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1336 if(p.channel == DT_IOP_COLORZONES_h)
1337 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][1].y);
1338 else
1339 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][bands - 1].y);
1340 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_min_ys);
1341 }
1342 else
1343 {
1344 for(int k = 0; k < bands; k++)
1345 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1346 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_min_ys,
1347 p.channel == DT_IOP_COLORZONES_h);
1348 }
1349
1351 dt_iop_colorzones_get_params(&p, c, c->channel, c->mouse_x, .0, c->mouse_radius);
1352 if(p.splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1353 {
1354 if(p.channel == DT_IOP_COLORZONES_h)
1355 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][bands - 2].y);
1356 else
1357 dt_draw_curve_set_point(c->minmax_curve[ch], 0, p.curve[ch][bands - 2].x - 1.f, p.curve[ch][0].y);
1358 for(int k = 0; k < bands; k++)
1359 dt_draw_curve_set_point(c->minmax_curve[ch], k + 1, p.curve[ch][k].x, p.curve[ch][k].y);
1360 if(p.channel == DT_IOP_COLORZONES_h)
1361 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][1].y);
1362 else
1363 dt_draw_curve_set_point(c->minmax_curve[ch], bands + 1, p.curve[ch][1].x + 1.f, p.curve[ch][bands - 1].y);
1364 dt_draw_curve_calc_values(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_max_ys);
1365 }
1366 else
1367 {
1368 for(int k = 0; k < bands; k++)
1369 dt_draw_curve_set_point(c->minmax_curve[ch], k, p.curve[ch][k].x, p.curve[ch][k].y);
1370 dt_draw_curve_calc_values_V2(c->minmax_curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_RES, NULL, c->draw_max_ys,
1371 p.channel == DT_IOP_COLORZONES_h);
1372 }
1373
1374 // restore params values
1376
1377 // draw min/max curves:
1378 cairo_set_source_rgba(cr, .7, .7, .7, .6);
1379 cairo_move_to(cr, 0, -height * _curve_to_mouse(c->draw_min_ys[0], c->zoom_factor, c->offset_y));
1380
1381 for(int k = 1; k < DT_IOP_COLORZONES_RES; k++)
1382 {
1383 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1384 const float yy = c->draw_min_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 for(int k = DT_IOP_COLORZONES_RES - 1; k >= 0; k--)
1393 {
1394 const float xx = (float)k / (float)(DT_IOP_COLORZONES_RES - 1);
1395 const float yy = c->draw_max_ys[k];
1396
1397 const float x = _curve_to_mouse(xx, c->zoom_factor, c->offset_x),
1398 y = _curve_to_mouse(yy, c->zoom_factor, c->offset_y);
1399
1400 cairo_line_to(cr, x * width, -height * y);
1401 }
1402
1403 cairo_close_path(cr);
1404 cairo_fill(cr);
1405
1406 // draw mouse focus circle
1407 cairo_set_source_rgba(cr, .9, .9, .9, .5);
1408
1409 const int k = DT_IOP_COLORZONES_RES * _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1410 const float x = c->mouse_x, y = _curve_to_mouse(c->draw_ys[ch][k], c->zoom_factor, c->offset_y);
1411
1412 cairo_arc(cr, x * width, -height * y, c->mouse_radius * width, 0, 2. * DT_M_PI);
1413 cairo_stroke(cr);
1414 }
1415 else
1416 {
1417 // draw selected cursor
1418 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.));
1419
1420 if(c->selected >= 0)
1421 {
1422 cairo_set_source_rgb(cr, .9, .9, .9);
1423 const float x = _curve_to_mouse(p.curve[c->channel][c->selected].x, c->zoom_factor, c->offset_x),
1424 y = _curve_to_mouse(p.curve[c->channel][c->selected].y, c->zoom_factor, c->offset_y);
1425
1426 cairo_arc(cr, x * width, -y * height, DT_PIXEL_APPLY_DPI(4), 0, 2. * M_PI);
1427 cairo_stroke(cr);
1428 }
1429 }
1430
1431 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1432
1433 cairo_destroy(cr);
1434 cairo_set_source_surface(crf, cst, 0, 0);
1435 cairo_paint(crf);
1436 cairo_surface_destroy(cst);
1437 return TRUE;
1438}
1439
1440static gboolean _bottom_area_draw_callback(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1441{
1444
1445 GtkAllocation allocation;
1446 gtk_widget_get_allocation(widget, &allocation);
1447 const int inset = DT_IOP_COLORZONES_INSET;
1448 int width = allocation.width, height = allocation.height;
1449 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
1450 cairo_t *cr = cairo_create(cst);
1451 // clear bg, match color of the notebook tabs:
1452 GdkRGBA color;
1453 GtkStyleContext *context = gtk_widget_get_style_context(widget);
1454 gboolean color_found = gtk_style_context_lookup_color(context, "graph_overlay", &color);
1455 if(!color_found)
1456 {
1457 color.red = 1.0;
1458 color.green = 0.0;
1459 color.blue = 0.0;
1460 color.alpha = 1.0;
1461 }
1462 gdk_cairo_set_source_rgba(cr, &color);
1463 cairo_paint(cr);
1464
1465 cairo_translate(cr, inset, inset);
1466 width -= 2 * inset;
1467 height -= 2 * inset;
1468
1469 cairo_set_line_width(cr, DT_PIXEL_APPLY_DPI(1.0));
1470 cairo_set_source_rgb(cr, .1, .1, .1);
1471 cairo_rectangle(cr, 0, 0, width, height);
1472 cairo_stroke(cr);
1473
1474 cairo_set_source_rgb(cr, .3, .3, .3);
1475 cairo_rectangle(cr, 0, 0, width, height);
1476 cairo_fill(cr);
1477
1478 // if color picker is active we use it as base color
1479 // otherwise we use a light blue
1480 // we will work on LCh
1481 dt_aligned_pixel_t picked_color, picker_min, picker_max;
1482 _select_base_display_color(self, picked_color, picker_min, picker_max);
1483 const float normalize_C = (128.f * sqrtf(2.f));
1484
1485 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1486
1487 const int cellsi = DT_COLORZONES_CELLSI;
1488
1489 for(int i = 0; i < cellsi; i++)
1490 {
1491 const float ii = _mouse_to_curve(((float)i + .5f) / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1492 const float iih = _mouse_to_curve((float)i / (float)(cellsi - 1), c->zoom_factor, c->offset_x);
1493
1495
1496 switch(p.channel)
1497 {
1498 // select by channel, abscissa:
1500 LCh[0] = 100.0f * ii;
1501 LCh[1] = normalize_C * .5f;
1502 LCh[2] = picked_color[2];
1503 break;
1505 LCh[0] = 50.0f;
1506 LCh[1] = picked_color[1] * 2.f * ii;
1507 LCh[2] = picked_color[2];
1508 break;
1509 default: // DT_IOP_COLORZONES_h
1510 LCh[0] = 50.0f;
1511 LCh[1] = normalize_C * .5f;
1512 LCh[2] = iih;
1513 break;
1514 }
1515
1517
1518 cairo_rectangle(cr, width * i / (float)cellsi, 0, width / (float)cellsi, height);
1519 cairo_fill(cr);
1520 }
1521
1522 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1523
1524 if(self->enabled)
1525 {
1526 _draw_color_picker(self, cr, &p, c, width, height, picked_color, picker_min, picker_max);
1527 }
1528
1529 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1530
1531 cairo_destroy(cr);
1532 cairo_set_source_surface(crf, cst, 0, 0);
1533 cairo_paint(crf);
1534 cairo_surface_destroy(cst);
1535 return TRUE;
1536}
1537
1538#undef COLORZONES_DRAW_BACKGROUD_BOX
1539#undef DT_COLORZONES_CELLSI
1540#undef DT_COLORZONES_CELLSJ
1541
1542static gboolean _bottom_area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1543{
1545
1546 if(event->button == 1 && event->type == GDK_2BUTTON_PRESS)
1547 {
1548 // reset zoom level
1549 c->zoom_factor = 1.f;
1550 c->offset_x = c->offset_y = 0.f;
1551
1552 gtk_widget_queue_draw(self->gui->widget);
1553
1554 return TRUE;
1555 }
1556
1557 return FALSE;
1558}
1559
1560static gboolean _sanity_check(const float x, const int selected, const int nodes,
1561 const dt_iop_colorzones_node_t *curve)
1562{
1563 gboolean point_valid = TRUE;
1564
1565 // check if it is not too close to other node
1566 const float min_dist = DT_IOP_COLORZONES_MIN_X_DISTANCE; // in curve coordinates
1567 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
1568 || (selected < nodes - 1 && curve[selected + 1].x - x <= min_dist))
1569 point_valid = FALSE;
1570
1571 // for all points, x coordinate of point must be strictly larger than
1572 // the x coordinate of the previous point
1573 if((selected > 0 && (curve[selected - 1].x >= x)) || (selected < nodes - 1 && (curve[selected + 1].x <= x)))
1574 {
1575 point_valid = FALSE;
1576 }
1577
1578 return point_valid;
1579}
1580
1581static gboolean _move_point_internal(dt_iop_module_t *self, GtkWidget *widget, int node, float dx, float dy, guint state)
1582{
1585
1586 int ch = c->channel;
1587 dt_iop_colorzones_node_t *curve = p->curve[ch];
1588
1589 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1590 // do not move the first or last nodes on the x-axis
1591 if(node == 0 || node == p->curve_num_nodes[ch] - 1) dx = 0.f;
1592
1593 float new_x = CLAMP(curve[node].x + dx, 0.0f, 1.0f);
1594 const float new_y = CLAMP(curve[node].y + dy, 0.0f, 1.0f);
1595
1596 if(_sanity_check(new_x, node, p->curve_num_nodes[ch], p->curve[ch]))
1597 {
1598 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1599 {
1600 curve[node].x = new_x;
1601 curve[node].y = new_y;
1602
1603 if(p->channel == DT_IOP_COLORZONES_h && (node == 0 || node == p->curve_num_nodes[ch] - 1))
1604 {
1605 if(node == 0)
1606 {
1607 curve[p->curve_num_nodes[ch] - 1].x = 1.f - curve[node].x;
1608 curve[p->curve_num_nodes[ch] - 1].y = curve[node].y;
1609 }
1610 else
1611 {
1612 curve[0].x = 1.f - curve[node].x;
1613 curve[0].y = curve[node].y;
1614 }
1615 }
1616 }
1617 else
1618 {
1619 if(p->channel == DT_IOP_COLORZONES_h && (node == 0 || node == p->curve_num_nodes[ch] - 1))
1620 {
1621 if(node == 0)
1622 {
1623 if(new_x + 1.f - curve[p->curve_num_nodes[ch] - 1].x < DT_IOP_COLORZONES_MIN_X_DISTANCE)
1624 new_x = curve[p->curve_num_nodes[ch] - 1].x + DT_IOP_COLORZONES_MIN_X_DISTANCE - 1.f;
1625 }
1626 else
1627 {
1628 if(curve[0].x + 1.f - new_x < DT_IOP_COLORZONES_MIN_X_DISTANCE)
1629 new_x = curve[0].x + 1.f - DT_IOP_COLORZONES_MIN_X_DISTANCE;
1630 }
1631 }
1632 curve[node].x = new_x;
1633 curve[node].y = new_y;
1634 }
1635
1636 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1637 }
1638
1639 gtk_widget_queue_draw(widget);
1640
1641 return TRUE;
1642}
1643
1644static void _delete_node(dt_iop_module_t *self, dt_iop_colorzones_node_t *curve, int *nodes, int node, gboolean zero)
1645{
1646 if(zero)
1647 {
1648 curve[node].y = 0.5f;
1649 }
1650 else
1651 {
1652 // for p->splines_version == DT_IOP_COLORZONES_SPLINES_V1 condition nodes > 1 always true
1653 if(*nodes > 1)
1654 {
1655 for(int k = node; k < *nodes - 1; k++)
1656 {
1657 curve[k].x = curve[k + 1].x;
1658 curve[k].y = curve[k + 1].y;
1659 }
1660 curve[*nodes - 1].x = curve[*nodes - 1].y = 0;
1661 (*nodes)--;
1662 }
1663 else
1664 {
1665 curve[0].x = 0.5f;
1666 curve[0].y = 0.5f;
1667 }
1668 }
1669
1671 gtk_widget_queue_draw(self->gui->widget);
1672 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1673}
1674
1675static inline int _add_node(dt_iop_colorzones_node_t *curve, int *nodes, float x, float y)
1676{
1677 int selected = -1;
1678 if(curve[0].x > x)
1679 selected = 0;
1680 else
1681 {
1682 for(int k = 1; k < *nodes; k++)
1683 {
1684 if(curve[k].x > x)
1685 {
1686 selected = k;
1687 break;
1688 }
1689 }
1690 }
1691 if(selected == -1) selected = *nodes;
1692
1693 // check if it is not too close to other node
1694 const float min_dist = DT_IOP_COLORZONES_MIN_X_DISTANCE; // in curve coordinates
1695 if((selected > 0 && x - curve[selected - 1].x <= min_dist)
1696 || (selected < *nodes && curve[selected].x - x <= min_dist))
1697 selected = -2;
1698
1699 if(selected >= 0)
1700 {
1701 for(int i = *nodes; i > selected; i--)
1702 {
1703 curve[i].x = curve[i - 1].x;
1704 curve[i].y = curve[i - 1].y;
1705 }
1706 // found a new point
1707 curve[selected].x = x;
1708 curve[selected].y = y;
1709 (*nodes)++;
1710 }
1711 return selected;
1712}
1713
1714static gboolean _area_scrolled_callback(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
1715{
1718
1719 int delta_y;
1720
1721 // (the graph height is now adjusted with the drag grip on its bottom edge, not Ctrl+Scroll)
1722
1723 if(c->selected < 0 && !c->edit_by_area) return TRUE;
1724
1725 if(dt_gui_get_scroll_unit_delta(event, &delta_y))
1726 {
1728
1729 if(c->edit_by_area)
1730 {
1731 const int bands = p->curve_num_nodes[c->channel];
1732 c->mouse_radius = CLAMP(c->mouse_radius * (1.0 + 0.1 * delta_y), 0.2 / bands, 1.0);
1733 gtk_widget_queue_draw(widget);
1734 }
1735 else
1736 {
1738 return _move_point_internal(self, widget, c->selected, 0.f, delta_y, event->state);
1739 }
1740 }
1741
1742 return TRUE;
1743}
1744
1745static gboolean _area_motion_notify_callback(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1746{
1749
1750 const int inset = DT_IOP_COLORZONES_INSET;
1751
1752 GtkAllocation allocation;
1753 gtk_widget_get_allocation(widget, &allocation);
1754
1755 const int height = allocation.height - 2 * inset;
1756 const int width = allocation.width - 2 * inset;
1757
1758 const int ch = c->channel;
1759 const int nodes = p->curve_num_nodes[ch];
1760 dt_iop_colorzones_node_t *curve = p->curve[ch];
1761
1762 const double old_m_x = c->mouse_x;
1763 const double old_m_y = fabs(c->mouse_y);
1764
1765 c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1766 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1767
1768 // move a node
1769 if(event->state & GDK_BUTTON1_MASK)
1770 {
1771 if(c->edit_by_area)
1772 {
1773 if(c->dragging && c->x_move >= 0)
1774 c->selected = c->x_move;
1775 else
1776 c->selected = -1;
1777 }
1778
1779 // got a vertex selected:
1780 if(c->selected >= 0)
1781 {
1782 // this is used to translate mause position in zoom_factor to make this behavior unified with linear scale.
1783 const float translate_mouse_x = old_m_x - _curve_to_mouse(curve[c->selected].x, c->zoom_factor, c->offset_x);
1784 const float translate_mouse_y = old_m_y - _curve_to_mouse(curve[c->selected].y, c->zoom_factor, c->offset_y);
1785 // dx & dy are in linear coordinates
1786 const float dx = _mouse_to_curve(c->mouse_x - translate_mouse_x, c->zoom_factor, c->offset_x)
1787 - _mouse_to_curve(old_m_x - translate_mouse_x, c->zoom_factor, c->offset_x);
1788 const float dy = _mouse_to_curve(c->mouse_y - translate_mouse_y, c->zoom_factor, c->offset_y)
1789 - _mouse_to_curve(old_m_y - translate_mouse_y, c->zoom_factor, c->offset_y);
1790
1792 return _move_point_internal(self, widget, c->selected, dx, dy, event->state);
1793 }
1794 }
1795
1796 if(c->edit_by_area)
1797 {
1798 if(c->dragging)
1799 {
1800 if(c->x_move < 0)
1801 {
1802 dt_iop_colorzones_get_params(p, c, c->channel, c->mouse_x, c->mouse_y, c->mouse_radius);
1804 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1805 }
1806 }
1807 else if(event->y > height)
1808 {
1809 c->x_move = 0;
1810 const int bands = p->curve_num_nodes[c->channel];
1811 const float mouse_x = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1812 float dist = fabsf(p->curve[c->channel][0].x - mouse_x);
1813 for(int k = 1; k < bands; k++)
1814 {
1815 const float d2 = fabsf(p->curve[c->channel][k].x - mouse_x);
1816 if(d2 < dist)
1817 {
1818 c->x_move = k;
1819 dist = d2;
1820 }
1821 }
1822 }
1823 else
1824 {
1825 c->x_move = -1;
1826 }
1827 }
1828 else
1829 {
1830 if(event->state & GDK_BUTTON1_MASK)
1831 {
1832 if(nodes < DT_IOP_COLORZONES_MAXNODES && c->selected == -1)
1833 {
1834 const float linx = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x),
1835 liny = _mouse_to_curve(c->mouse_y, c->zoom_factor, c->offset_y);
1836
1837 // no vertex was close, create a new one!
1838 c->selected = _add_node(curve, &p->curve_num_nodes[ch], linx, liny);
1839
1841 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1842 }
1843 }
1844 else
1845 {
1846 const float mx = c->mouse_x;
1847 const float my = c->mouse_y;
1848
1849 // minimum area around the node to select it:
1850 float min = .04f * .04f; // comparing against square
1851 int nearest = -1;
1852 for(int k = 0; k < nodes; k++)
1853 {
1854 float dist = (my - _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y))
1855 * (my - _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y))
1856 + (mx - _curve_to_mouse(curve[k].x, c->zoom_factor, c->offset_x))
1857 * (mx - _curve_to_mouse(curve[k].x, c->zoom_factor, c->offset_x));
1858 if(dist < min)
1859 {
1860 min = dist;
1861 nearest = k;
1862 }
1863 }
1864 c->selected = nearest;
1865 }
1866 if(c->selected >= 0) gtk_widget_grab_focus(widget);
1867 }
1868
1869 gtk_widget_queue_draw(widget);
1870 return TRUE;
1871}
1872
1873static gboolean _area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1874{
1878
1879 int ch = c->channel;
1880 int nodes = p->curve_num_nodes[ch];
1881 dt_iop_colorzones_node_t *curve = p->curve[ch];
1882
1883 if(event->button == 1)
1884 {
1885 if(c->edit_by_area && event->type != GDK_2BUTTON_PRESS && !dt_modifier_is(event->state, DT_PRIMARY_MASK))
1886 {
1887 c->dragging = 1;
1888 return TRUE;
1889 }
1890 else if(event->type == GDK_BUTTON_PRESS && dt_modifier_is(event->state, DT_PRIMARY_MASK)
1891 && nodes < DT_IOP_COLORZONES_MAXNODES && (c->selected == -1 || c->edit_by_area))
1892 {
1893 // 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
1894 const int inset = DT_IOP_COLORZONES_INSET;
1895 GtkAllocation allocation;
1896 gtk_widget_get_allocation(widget, &allocation);
1897 const int height = allocation.height - 2 * inset;
1898 const int width = allocation.width - 2 * inset;
1899
1900 c->mouse_x = CLAMP(event->x - inset, 0, width) / (float)width;
1901 c->mouse_y = 1.0 - CLAMP(event->y - inset, 0, height) / (float)height;
1902
1903 const float mx = _mouse_to_curve(c->mouse_x, c->zoom_factor, c->offset_x);
1904
1905 // don't add a node too close to others in x direction, it can crash dt
1906 int selected = -1;
1907 if(curve[0].x > mx)
1908 selected = 0;
1909 else
1910 {
1911 for(int k = 1; k < nodes; k++)
1912 {
1913 if(curve[k].x > mx)
1914 {
1915 selected = k;
1916 break;
1917 }
1918 }
1919 }
1920 if(selected == -1) selected = nodes;
1921
1922 // evaluate the curve at the current x position
1923 const float y = dt_draw_curve_calc_value(c->minmax_curve[ch], mx);
1924
1925 if(y >= 0.0f && y <= 1.0f) // never add something outside the viewport, you couldn't change it afterwards
1926 {
1927 // create a new node
1928 selected = _add_node(curve, &p->curve_num_nodes[ch], mx, y);
1929
1930 // maybe set the new one as being selected
1931 const float min = .04f * .04f; // comparing against square
1932
1933 for(int k = 0; k < nodes; k++)
1934 {
1935 const float other_y = _curve_to_mouse(curve[k].y, c->zoom_factor, c->offset_y);
1936 const float dist = (y - other_y) * (y - other_y);
1937 if(dist < min) c->selected = selected;
1938 }
1939
1941 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1942 gtk_widget_queue_draw(self->gui->widget);
1943 }
1944
1945 return TRUE;
1946 }
1947 else if(event->type == GDK_2BUTTON_PRESS)
1948 {
1949 // reset current curve
1950 p->curve_num_nodes[ch] = d->curve_num_nodes[ch];
1951 p->curve_type[ch] = d->curve_type[ch];
1952 _reset_nodes(p, c->channel,
1953 p->splines_version == DT_IOP_COLORZONES_SPLINES_V1 || p->channel != DT_IOP_COLORZONES_h);
1954
1955 c->selected = -2; // avoid motion notify re-inserting immediately.
1956 dt_bauhaus_combobox_set(c->interpolator, p->curve_type[ch]);
1957
1959 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1960 gtk_widget_queue_draw(self->gui->widget);
1961
1962 return TRUE;
1963 }
1964 }
1965 else if(event->button == 3 && c->selected >= 0)
1966 {
1967 if((c->selected == 0 || c->selected == nodes - 1) && p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
1968 {
1969 if(p->channel == DT_IOP_COLORZONES_h)
1970 {
1971 curve[0].y = 0.5f;
1972 curve[0].x = 0.f;
1973 curve[nodes - 1].y = 0.5f;
1974 curve[nodes - 1].x = 1.f;
1975 }
1976 else
1977 {
1978 const float reset_value = c->selected == 0 ? 0.f : 1.f;
1979 curve[c->selected].y = 0.5f;
1980 curve[c->selected].x = reset_value;
1981 }
1982
1984 gtk_widget_queue_draw(self->gui->widget);
1985 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1986 return TRUE;
1987 }
1988
1989 // right click deletes the node, ctrl+right click reset the node to y-zero
1990 _delete_node(self, curve, &p->curve_num_nodes[ch], c->selected, dt_modifier_is(event->state, DT_PRIMARY_MASK));
1991 c->selected = -2; // avoid re-insertion of that point immediately after this
1992
1993 return TRUE;
1994 }
1995
1996 return FALSE;
1997}
1998
1999static gboolean _area_button_release_callback(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
2000{
2001 if(event->button == 1)
2002 {
2003 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
2005 c->dragging = 0;
2006 return TRUE;
2007 }
2008 return FALSE;
2009}
2010
2011static gboolean _area_enter_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
2012{
2014 c->mouse_y = fabs(c->mouse_y);
2015 gtk_widget_queue_draw(widget);
2016 return TRUE;
2017}
2018
2019static gboolean _area_leave_notify_callback(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
2020{
2022 // for fluxbox
2023 c->mouse_y = -fabs(c->mouse_y);
2024 gtk_widget_queue_draw(widget);
2025 return TRUE;
2026}
2027
2028static gboolean _area_resized_callback(GtkWidget *widget, GdkEvent *event, gpointer user_data)
2029{
2030 GtkRequisition r;
2031 GtkAllocation allocation;
2032 gtk_widget_get_allocation(widget, &allocation);
2033 r.width = allocation.width;
2034 r.height = allocation.width;
2035 gtk_widget_get_preferred_size(widget, &r, NULL);
2036 return TRUE;
2037}
2038
2039static gboolean _area_key_press_callback(GtkWidget *widget, GdkEventKey *event, dt_iop_module_t *self)
2040{
2042
2043 if(c->selected < 0) return FALSE;
2044
2045 guint key = dt_keys_mainpad_alternatives(event->keyval);
2046 int handled = 0;
2047 float dx = 0.0f, dy = 0.0f;
2048 if(key == GDK_KEY_Up)
2049 {
2050 handled = 1;
2052 }
2053 else if(key == GDK_KEY_Down)
2054 {
2055 handled = 1;
2057 }
2058 else if(key == GDK_KEY_Right)
2059 {
2060 handled = 1;
2062 }
2063 else if(key == GDK_KEY_Left)
2064 {
2065 handled = 1;
2067 }
2068
2069 if(!handled) return FALSE;
2070
2072 return _move_point_internal(self, widget, c->selected, dx, dy, event->state);
2073}
2074
2075static void _channel_tabs_switch_callback(GtkNotebook *notebook, GtkWidget *page, guint page_num,
2076 dt_iop_module_t *self)
2077{
2078 if(dt_gui_widgets_suppressed()) return;
2081
2082 c->channel = (dt_iop_colorzones_channel_t)page_num;
2083
2085
2086 dt_bauhaus_combobox_set(c->interpolator, p->curve_type[c->channel]);
2087
2089
2090 if(c->display_mask)
2092 gtk_widget_queue_draw(self->gui->widget);
2093}
2094
2095
2096void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2097{
2100
2101 if(w == g->select_by)
2102 {
2103 _reset_parameters(p, p->channel, p->splines_version);
2104 if(g->display_mask) _reset_display_selection(self);
2105 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2106 gtk_widget_queue_draw(GTK_WIDGET(g->bottom_area));
2107 }
2108}
2109
2111{
2112 if(dt_gui_widgets_suppressed()) return;
2115
2116 const int combo = dt_bauhaus_combobox_get(widget);
2117
2118 if(combo == 0)
2119 p->curve_type[g->channel] = CUBIC_SPLINE;
2120 else if(combo == 1)
2121 p->curve_type[g->channel] = CATMULL_ROM;
2122 else if(combo == 2)
2123 p->curve_type[g->channel] = MONOTONE_HERMITE;
2124
2126 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2127 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2128}
2129
2131{
2132 if(dt_gui_widgets_suppressed()) return;
2134
2135 g->edit_by_area = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
2136
2137 gtk_widget_queue_draw(GTK_WIDGET(g->area));
2138}
2139
2140static void _display_mask_callback(GtkToggleButton *togglebutton, dt_iop_module_t *module)
2141{
2142 if(dt_gui_widgets_suppressed()) return;
2143
2145
2146 // if blend module is displaying mask do not display it here
2147 if(module->request_mask_display && !g->display_mask)
2148 {
2149 dt_control_log(_("cannot display masks when the blending mask is displayed"));
2150
2152 gtk_toggle_button_set_active(togglebutton, FALSE);
2154 return;
2155 }
2156
2157 g->display_mask = gtk_toggle_button_get_active(togglebutton);
2158
2159 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), 1);
2160 dt_iop_request_focus(module);
2162}
2163
2165{
2167 if(picker == g->colorpicker_set_values)
2168 {
2171
2172 const int ch_curve = g->channel;
2173 const int ch_val = p->channel;
2174 dt_iop_colorzones_node_t *curve = p->curve[ch_curve];
2175
2176 // reset current curve
2177 p->curve_num_nodes[ch_curve] = d->curve_num_nodes[ch_curve];
2178 p->curve_type[ch_curve] = d->curve_type[ch_curve];
2179 for(int k = 0; k < DT_IOP_COLORZONES_MAXNODES; k++)
2180 {
2181 curve[k].x = d->curve[ch_curve][k].x;
2182 curve[k].y = d->curve[ch_curve][k].y;
2183 }
2184
2185 const GdkModifierType state = dt_key_modifier_state();
2186 int picker_set_upper_lower; // flat=0, lower=-1, upper=1
2188 picker_set_upper_lower = 1;
2189 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
2190 picker_set_upper_lower = -1;
2191 else
2192 picker_set_upper_lower = 0;
2193
2194 // now add 5 nodes: feather, min, center, max, feather
2195 const float feather = 0.02f;
2196 const float increment = 0.1f * picker_set_upper_lower;
2197 float x = 0.f;
2198
2199 if(ch_val == DT_IOP_COLORZONES_L)
2200 x = self->picked_color_min[0] / 100.f;
2201 else if(ch_val == DT_IOP_COLORZONES_C)
2202 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2203 else if(ch_val == DT_IOP_COLORZONES_h)
2204 x = self->picked_color_min[2];
2205 x -= feather;
2206 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2207
2208 if(ch_val == DT_IOP_COLORZONES_L)
2209 x = self->picked_color_min[0] / 100.f;
2210 else if(ch_val == DT_IOP_COLORZONES_C)
2211 x = self->picked_color_min[1] / (128.f * sqrtf(2.f));
2212 else if(ch_val == DT_IOP_COLORZONES_h)
2213 x = self->picked_color_min[2];
2214 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2215
2216 if(ch_val == DT_IOP_COLORZONES_L)
2217 x = self->picked_color[0] / 100.f;
2218 else if(ch_val == DT_IOP_COLORZONES_C)
2219 x = self->picked_color[1] / (128.f * sqrtf(2.f));
2220 else if(ch_val == DT_IOP_COLORZONES_h)
2221 x = self->picked_color[2];
2222 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + 2.f * increment);
2223
2224 if(ch_val == DT_IOP_COLORZONES_L)
2225 x = self->picked_color_max[0] / 100.f;
2226 else if(ch_val == DT_IOP_COLORZONES_C)
2227 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2228 else if(ch_val == DT_IOP_COLORZONES_h)
2229 x = self->picked_color_max[2];
2230 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f + increment);
2231
2232 if(ch_val == DT_IOP_COLORZONES_L)
2233 x = self->picked_color_max[0] / 100.f;
2234 else if(ch_val == DT_IOP_COLORZONES_C)
2235 x = self->picked_color_max[1] / (128.f * sqrtf(2.f));
2236 else if(ch_val == DT_IOP_COLORZONES_h)
2237 x = self->picked_color_max[2];
2238 x += feather;
2239 if(x > 0.f && x < 1.f) _add_node(curve, &p->curve_num_nodes[ch_curve], x, .5f);
2240
2241 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
2242 }
2243
2245}
2246
2247void gui_reset(struct dt_iop_module_t *self)
2248{
2250
2252
2253 c->zoom_factor = 1.f;
2255}
2256
2257void gui_focus(struct dt_iop_module_t *self, gboolean in)
2258{
2259 if(!in)
2260 {
2262 }
2263}
2264
2265void gui_init(struct dt_iop_module_t *self)
2266{
2269
2270 self->histogram_cst = IOP_CS_LCH;
2271
2272 c->channel = dt_conf_get_int("plugins/darkroom/colorzones/gui_channel");
2273 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2274 {
2275 c->minmax_curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2276 c->minmax_curve_nodes[ch] = p->curve_num_nodes[ch];
2277 c->minmax_curve_type[ch] = p->curve_type[ch];
2278
2279 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2280 dt_draw_curve_add_point(c->minmax_curve[ch], p->curve[ch][k].x, p->curve[ch][k].y);
2281 }
2282
2283 c->mouse_x = c->mouse_y = -1.0;
2284 c->selected = -1;
2285 c->offset_x = c->offset_y = 0.f;
2286 c->zoom_factor = 1.f;
2287 c->x_move = -1;
2288 c->mouse_radius = 1.f / DT_IOP_COLORZONES_BANDS;
2289 c->dragging = 0;
2290 c->edit_by_area = 0;
2291 c->display_mask = FALSE;
2292 self->gui->timeout_handle = 0;
2293
2294 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2295
2296 // tabs
2297 GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2298 GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2299 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
2300
2301 c->channel_tabs = dt_ui_notebook_new();
2302
2303 dt_ui_notebook_page(c->channel_tabs, N_("lightness"), NULL);
2304 dt_ui_notebook_page(c->channel_tabs, N_("saturation"), NULL);
2305 dt_ui_notebook_page(c->channel_tabs, N_("hue"), NULL);
2306
2307 gtk_widget_show(gtk_notebook_get_nth_page(c->channel_tabs, c->channel));
2308 gtk_notebook_set_current_page(c->channel_tabs, c->channel);
2309 g_signal_connect(G_OBJECT(c->channel_tabs), "switch_page", G_CALLBACK(_channel_tabs_switch_callback), self);
2310 /* The picker samples for the *currently selected* channel (c->channel), read at apply
2311 time. Leaving it active across a tab switch would apply a sample picked for one
2312 channel to whichever channel is now selected once the async sample lands. */
2313 dt_ui_notebook_set_picker_owner(c->channel_tabs, self);
2314 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(c->channel_tabs), TRUE, TRUE, 0);
2315 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(" "), FALSE, FALSE, 0);
2316
2317 // color pickers
2319 gtk_widget_set_tooltip_text(c->colorpicker, _("pick GUI color from image\nctrl+click or right-click to select an area"));
2320 c->colorpicker_set_values = dt_color_picker_new_with_cst(self, DT_COLOR_PICKER_AREA, hbox, IOP_CS_LCH);
2321 dtgtk_togglebutton_set_paint(DTGTK_TOGGLEBUTTON(c->colorpicker_set_values),
2323
2324 gtk_widget_set_size_request(c->colorpicker_set_values, DT_PIXEL_APPLY_DPI(14), DT_PIXEL_APPLY_DPI(14));
2325 gtk_widget_set_tooltip_text(c->colorpicker_set_values, _("create a curve based on an area from the image\n"
2326 "drag to create a flat curve\n"
2327 "ctrl+drag to create a positive curve\n"
2328 "shift+drag to create a negative curve"));
2329
2330 // the nice graph
2331 c->area = GTK_DRAWING_AREA(gtk_drawing_area_new());
2332 gtk_widget_set_hexpand(GTK_WIDGET(c->area), TRUE);
2333
2334 gtk_box_pack_start(GTK_BOX(vbox),
2335 dt_ui_resizable_drawing_area(GTK_WIDGET(c->area),
2336 "plugins/darkroom/colorzones/graphheight", 280, 100),
2337 FALSE, FALSE, 0);
2338
2339 GtkWidget *dabox = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2340 gtk_widget_set_name(GTK_WIDGET(dabox), "iop-bottom-bar");
2341 c->bottom_area = gtk_drawing_area_new();
2342 gtk_box_pack_start(GTK_BOX(dabox), GTK_WIDGET(c->bottom_area), TRUE, TRUE, 0);
2343 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(dabox), TRUE, TRUE, 0);
2344 gtk_box_pack_start(GTK_BOX(self->gui->widget), GTK_WIDGET(vbox), TRUE, TRUE, 0);
2345
2346 GtkWidget *hbox_select_by = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2347
2348 // edit by area
2349 gchar *label = N_("edit by area");
2350 c->chk_edit_by_area = gtk_check_button_new_with_label(_(label));
2351 gtk_label_set_ellipsize(GTK_LABEL(gtk_bin_get_child(GTK_BIN(c->chk_edit_by_area))), PANGO_ELLIPSIZE_START);
2352 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->chk_edit_by_area), c->edit_by_area);
2353 gtk_widget_set_tooltip_text(c->chk_edit_by_area, _("edit the curve nodes by area"));
2354 gtk_box_pack_start(GTK_BOX(hbox_select_by), c->chk_edit_by_area, TRUE, TRUE, 0);
2355 g_signal_connect(G_OBJECT(c->chk_edit_by_area), "toggled", G_CALLBACK(_edit_by_area_callback), self);
2356
2357 // display selection
2358 c->bt_showmask = dtgtk_togglebutton_new(dtgtk_cairo_paint_showmask, 0, NULL);
2359
2360 gtk_widget_set_tooltip_text(c->bt_showmask, _("display selection"));
2361 g_signal_connect(G_OBJECT(c->bt_showmask), "toggled", G_CALLBACK(_display_mask_callback), self);
2362 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c->bt_showmask), FALSE);
2363 gtk_box_pack_end(GTK_BOX(hbox_select_by), c->bt_showmask, FALSE, FALSE, 0);
2364
2365 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_select_by, TRUE, TRUE, 0);
2366
2367 // select by which dimension
2368 c->select_by = dt_bauhaus_combobox_from_params(self, "channel");
2370 gtk_widget_set_tooltip_text(c->select_by, _("choose selection criterion, will be the abscissa in the graph"));
2371
2372 c->mode = dt_bauhaus_combobox_from_params(self, "mode");
2373 gtk_widget_set_tooltip_text(c->mode, _("choose between a smoother or stronger effect"));
2374
2375 c->strength = dt_bauhaus_slider_from_params(self, "strength");
2376 dt_bauhaus_slider_set_format(c->strength, "%");
2377 gtk_widget_set_tooltip_text(c->strength, _("make effect stronger or weaker"));
2378
2379 gtk_widget_add_events(GTK_WIDGET(c->area), GDK_POINTER_MOTION_MASK
2380 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2381 | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK
2383 g_object_set_data(G_OBJECT(c->area), "iop-instance", self);
2384 gtk_widget_set_can_focus(GTK_WIDGET(c->area), TRUE);
2385 g_signal_connect(G_OBJECT(c->area), "draw", G_CALLBACK(_area_draw_callback), self);
2386 g_signal_connect(G_OBJECT(c->area), "button-press-event", G_CALLBACK(_area_button_press_callback), self);
2387 g_signal_connect(G_OBJECT(c->area), "button-release-event", G_CALLBACK(_area_button_release_callback), self);
2388 g_signal_connect(G_OBJECT(c->area), "motion-notify-event", G_CALLBACK(_area_motion_notify_callback), self);
2389 g_signal_connect(G_OBJECT(c->area), "leave-notify-event", G_CALLBACK(_area_leave_notify_callback), self);
2390 g_signal_connect(G_OBJECT(c->area), "enter-notify-event", G_CALLBACK(_area_enter_notify_callback), self);
2391 g_signal_connect(G_OBJECT(c->area), "scroll-event", G_CALLBACK(_area_scrolled_callback), self);
2392 g_signal_connect(G_OBJECT(c->area), "configure-event", G_CALLBACK(_area_resized_callback), self);
2393 g_signal_connect(G_OBJECT(c->area), "key-press-event", G_CALLBACK(_area_key_press_callback), self);
2394
2395 gtk_widget_add_events(GTK_WIDGET(c->bottom_area), GDK_BUTTON_PRESS_MASK);
2396 g_signal_connect(G_OBJECT(c->bottom_area), "draw", G_CALLBACK(_bottom_area_draw_callback), self);
2397 g_signal_connect(G_OBJECT(c->bottom_area), "button-press-event", G_CALLBACK(_bottom_area_button_press_callback),
2398 self);
2399
2400 /* From src/common/curve_tools.h :
2401 #define CUBIC_SPLINE 0
2402 #define CATMULL_ROM 1
2403 #define MONOTONE_HERMITE 2
2404 */
2406 dt_bauhaus_widget_set_label(c->interpolator, N_("interpolation method"));
2407 dt_bauhaus_combobox_add(c->interpolator, _("cubic spline"));
2408 dt_bauhaus_combobox_add(c->interpolator, _("centripetal spline"));
2409 dt_bauhaus_combobox_add(c->interpolator, _("monotonic spline"));
2410 gtk_box_pack_start(GTK_BOX(self->gui->widget), c->interpolator, TRUE, TRUE, 0);
2411 gtk_widget_set_tooltip_text(c->interpolator,
2412 _("change this method if you see oscillations or cusps in the curve\n"
2413 "- cubic spline is better to produce smooth curves but oscillates when nodes are too close\n"
2414 "- centripetal is better to avoids cusps and oscillations with close nodes but is less smooth\n"
2415 "- monotonic is better for accuracy of pure analytical functions (log, gamma, exp)\n"));
2416 g_signal_connect(G_OBJECT(c->interpolator), "value-changed", G_CALLBACK(_interpolator_callback), self);
2417}
2418
2419void gui_update(struct dt_iop_module_t *self)
2420{
2423
2424 dt_bauhaus_combobox_set(g->interpolator, p->curve_type[g->channel]);
2425
2426
2427 gtk_widget_queue_draw(self->gui->widget);
2428}
2429
2431{
2433 dt_conf_set_int("plugins/darkroom/colorzones/gui_channel", c->channel);
2434
2435 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(c->minmax_curve[ch]);
2436
2437
2439}
2440
2442{
2443 const int program = 2; // basic.cl, from programs.conf
2445 module->data = gd;
2446 gd->kernel_colorzones = dt_opencl_create_kernel(program, "colorzones");
2447 gd->kernel_colorzones_v3 = dt_opencl_create_kernel(program, "colorzones_v3");
2448}
2449
2459
2462{
2463 // pull in new params to pipe
2467
2468 if(dt_dev_pixelpipe_has_preview_output(self->dev, pipe, NULL))
2469 piece->request_histogram |= (DT_REQUEST_ON);
2470 else
2471 piece->request_histogram &= ~(DT_REQUEST_ON);
2472
2473#if 0 // print new preset
2474 printf("p.channel = %d;\n", p->channel);
2475 for(int k=0; k<3; k++) for(int i=0; i<DT_IOP_COLORZONES_MAXNODES; i++)
2476 {
2477 printf("p.curve[%d][%i].x = %f;\n", k, i, p->curve[k][i].x);
2478 printf("p.curve[%d][%i].y = %f;\n", k, i, p->curve[k][i].y);
2479 }
2480#endif
2481
2482 // display selection don't work with opencl
2483 piece->process_cl_ready = (g && g->display_mask) ? 0 : 1;
2484 d->channel = (dt_iop_colorzones_channel_t)p->channel;
2485 d->mode = p->mode;
2486
2487 if(p->splines_version == DT_IOP_COLORZONES_SPLINES_V1)
2488 {
2489 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2490 {
2491 // take care of possible change of curve type or number of nodes (not yet implemented in UI)
2492 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch])
2493 {
2494 dt_draw_curve_destroy(d->curve[ch]);
2495 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2496 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2497 d->curve_type[ch] = p->curve_type[ch];
2498
2499 if(d->channel == DT_IOP_COLORZONES_h)
2500 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2501 strength(p->curve[ch][p->curve_num_nodes[ch] - 2].y, p->strength));
2502 else
2503 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2504 strength(p->curve[ch][0].y, p->strength));
2505 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2506 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2507 if(d->channel == DT_IOP_COLORZONES_h)
2508 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f, strength(p->curve[ch][1].y, p->strength));
2509 else
2510 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][1].x + 1.f,
2511 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2512 }
2513 else
2514 {
2515 if(d->channel == DT_IOP_COLORZONES_h)
2516 dt_draw_curve_set_point(d->curve[ch], 0, p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2517 strength(p->curve[ch][p->curve_num_nodes[ch] - 2].y, p->strength));
2518 else
2519 dt_draw_curve_set_point(d->curve[ch], 0, p->curve[ch][p->curve_num_nodes[ch] - 2].x - 1.f,
2520 strength(p->curve[ch][0].y, p->strength));
2521 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2522 dt_draw_curve_set_point(d->curve[ch], k + 1, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2523 if(d->channel == DT_IOP_COLORZONES_h)
2524 dt_draw_curve_set_point(d->curve[ch], p->curve_num_nodes[ch] + 1, p->curve[ch][1].x + 1.f,
2525 strength(p->curve[ch][1].y, p->strength));
2526 else
2527 dt_draw_curve_set_point(d->curve[ch], p->curve_num_nodes[ch] + 1, p->curve[ch][1].x + 1.f,
2528 strength(p->curve[ch][p->curve_num_nodes[ch] - 1].y, p->strength));
2529 }
2530 dt_draw_curve_calc_values(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch]);
2531 }
2532 }
2533 else
2534 {
2535 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2536 {
2537 if(d->curve_type[ch] != p->curve_type[ch] || d->curve_nodes[ch] != p->curve_num_nodes[ch]
2538 || d->curve[ch]->c.m_numAnchors != p->curve_num_nodes[ch])
2539 {
2540 dt_draw_curve_destroy(d->curve[ch]);
2541 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, p->curve_type[ch]);
2542 d->curve_nodes[ch] = p->curve_num_nodes[ch];
2543 d->curve_type[ch] = p->curve_type[ch];
2544
2545 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2546 dt_draw_curve_add_point(d->curve[ch], p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2547 }
2548 else
2549 {
2550 for(int k = 0; k < p->curve_num_nodes[ch]; k++)
2551 dt_draw_curve_set_point(d->curve[ch], k, p->curve[ch][k].x, strength(p->curve[ch][k].y, p->strength));
2552 }
2553 dt_draw_curve_calc_values_V2(d->curve[ch], 0.f, 1.f, DT_IOP_COLORZONES_LUT_RES, NULL, d->lut[ch],
2554 p->channel == DT_IOP_COLORZONES_h);
2555 }
2556 }
2557}
2558
2560{
2563 piece->data = d;
2564 piece->data_size = sizeof(dt_iop_colorzones_data_t);
2565 // Checked AFTER both piece->data and piece->data_size are set, deliberately.
2566 // dt_iop_init_pipe() disables the node for us when it sees data_size > 0 with a NULL
2567 // data -- returning before data_size is assigned skips that and leaves the node enabled
2568 // with no storage. dt_calloc_align() returns NULL on failure and pipe nodes are built
2569 // exactly when memory is tightest (Sentry 134134395 faulted in atrous's init_pipe).
2570 if(IS_NULL_PTR(piece->data)) return;
2571
2572 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++)
2573 {
2574 d->curve[ch] = dt_draw_curve_new(0.f, 1.f, default_params->curve_type[ch]);
2575 d->curve_nodes[ch] = default_params->curve_num_nodes[ch];
2576 d->curve_type[ch] = default_params->curve_type[ch];
2577 for(int k = 0; k < default_params->curve_num_nodes[ch]; k++)
2578 dt_draw_curve_add_point(d->curve[ch], default_params->curve[ch][k].x, default_params->curve[ch][k].y);
2579 }
2580 d->channel = (dt_iop_colorzones_channel_t)default_params->channel;
2581 d->mode = default_params->mode;
2582}
2583
2585{
2586 /* init_pipe() may have failed to allocate, and cleanup runs regardless. */
2587 if(IS_NULL_PTR(piece->data)) return;
2588 // clean up everything again.
2590
2591 for(int ch = 0; ch < DT_IOP_COLORZONES_MAX_CHANNELS; ch++) dt_draw_curve_destroy(d->curve[ch]);
2592
2593 dt_free_align(piece->data);
2594 piece->data = NULL;
2595}
2596
2598{
2599 module->params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2600 module->default_params = calloc(1, sizeof(dt_iop_colorzones_params_t));
2601 module->default_enabled = 0; // we're a rather slow and rare op.
2602 module->params_size = sizeof(dt_iop_colorzones_params_t);
2603 module->request_histogram |= (DT_REQUEST_ON);
2604
2606}
2607
2608#undef DT_IOP_COLORZONES_INSET
2609#undef DT_IOP_COLORZONES_CURVE_INFL
2610#undef DT_IOP_COLORZONES_RES
2611#undef DT_IOP_COLORZONES_LUT_RES
2612#undef DT_IOP_COLORZONES_BANDS
2613#undef DT_IOP_COLORZONES_MAXNODES
2614#undef DT_IOP_COLORZONES_DEFAULT_STEP
2615#undef DT_IOP_COLORZONES_MIN_X_DISTANCE
2616
2617// clang-format off
2618// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2619// vim: shiftwidth=2 expandtab tabstop=2 cindent
2620// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2621// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
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:283
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:1909
GtkWidget * dt_bauhaus_combobox_new(dt_bauhaus_t *bh, dt_gui_module_t *self)
Definition bauhaus.c:1698
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
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:46
@ DT_LIB_COLORPICKER_STATISTIC_MIN
Definition colorpicker.h:45
@ DT_LIB_COLORPICKER_STATISTIC_MEAN
Definition colorpicker.h:44
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)
void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, 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)
Convert a 4-channel float buffer between RGB and Lab through one profile.
static const float x
const float f
const float *const lut
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static dt_aligned_pixel_t rgb
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:823
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:98
void init(dt_iop_module_t *module)
const char ** description(struct dt_iop_module_t *self)
Definition colorzones.c:200
int default_group()
Definition colorzones.c:214
static gboolean _bottom_area_button_press_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
#define DT_IOP_COLORZONES_INSET
Definition colorzones.c:96
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:485
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:811
#define DT_COLORZONES_CELLSJ
static void _reset_display_selection(dt_iop_module_t *self)
Definition colorzones.c:792
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:381
const char * name()
Definition colorzones.c:195
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:121
@ DT_IOP_COLORZONES_L
Definition colorzones.c:122
@ DT_IOP_COLORZONES_MAX_CHANNELS
Definition colorzones.c:125
@ DT_IOP_COLORZONES_h
Definition colorzones.c:124
@ DT_IOP_COLORZONES_C
Definition colorzones.c:123
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:387
static void _interpolator_callback(GtkWidget *widget, dt_iop_module_t *self)
static float strength(float value, float strength)
Definition colorzones.c:431
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:106
__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:437
#define DT_IOP_COLORZONES_BANDS
Definition colorzones.c:101
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:837
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:867
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition colorzones.c:219
int flags()
Definition colorzones.c:209
#define DT_IOP_COLORZONES_MAXNODES
Definition colorzones.c:103
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:636
__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:529
#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:99
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:571
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:104
#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:109
@ DT_IOP_COLORZONES_MODE_STRONG
Definition colorzones.c:111
@ DT_IOP_COLORZONES_MODE_SMOOTH
Definition colorzones.c:110
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:376
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:591
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:115
@ DT_IOP_COLORZONES_SPLINES_V1
Definition colorzones.c:116
@ DT_IOP_COLORZONES_SPLINES_V2
Definition colorzones.c:117
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:224
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_log(const char *msg,...)
Definition control.c:824
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:969
#define CATMULL_ROM
Definition curve_tools.h:35
#define CUBIC_SPLINE
Definition curve_tools.h:34
#define MONOTONE_HERMITE
Definition curve_tools.h:36
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#define dt_database_start_transaction()
Definition database.h:290
#define dt_database_release_transaction()
Definition database.h:291
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_update_history_main(dev)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_output_profile_info(const struct dt_dev_pixelpipe_t *pipe)
__DT_CLONE_TARGETS__ dt_iop_order_iccprofile_info_t * dt_ioppr_get_iop_work_profile_info(struct dt_iop_module_t *module, GList *iop_list)
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:402
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
GHashTable * selected
set of checked row labels, mirrored to conf on every change
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:324
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:398
static float dt_draw_curve_calc_value(dt_draw_curve_t *c, const float x)
Definition draw.h:360
static void dt_draw_curve_destroy(dt_draw_curve_t *c)
Definition draw.h:297
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:351
static void dt_draw_curve_set_point(dt_draw_curve_t *c, const int num, const float x, const float y)
Definition draw.h:303
static int dt_draw_curve_add_point(dt_draw_curve_t *c, const float x, const float y)
Definition draw.h:379
static dt_draw_curve_t * dt_draw_curve_new(const float min, const float max, unsigned int type)
Definition draw.h:281
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
GdkRGBA color[]
Definition geotagging.c:541
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)
#define DT_GUI_MODULE(x)
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:91
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
@ DT_REQUEST_COLORPICK_MODULE
Definition imageop.h:216
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_INCLUDE_IN_STYLES
Definition imageop.h:185
@ IOP_FLAGS_DEPRECATED
Definition imageop.h:187
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_ALLOW_TILING
Definition imageop.h:188
@ IOP_GROUP_COLOR
Definition imageop.h:158
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
dt_iop_colorzones_channel_t
Definition lightroom.c:184
#define DT_IOP_COLORZONES_BANDS
Definition lightroom.c:181
@ DT_DEBUG_OPENCL
Definition logging.h:57
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
#define DT_M_PI_F
Definition math.h:54
#define DT_M_PI
Definition math.h:55
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
GtkWidget * dt_ui_notebook_page(GtkNotebook *notebook, const char *text, const char *tooltip)
Definition notebook.c:88
GtkNotebook * dt_ui_notebook_new()
Definition notebook.c:83
void dt_ui_notebook_set_picker_owner(GtkNotebook *notebook, gpointer owner)
Register an opaque owner for a GtkNotebook's page switches, and report every "switch_page" to the hos...
Definition notebook.c:118
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2491
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2545
void * dt_opencl_copy_host_to_device(const int devid, void *host, const int width, const int height, const int bpp)
Definition opencl.c:2765
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
@ DT_REQUEST_ON
Definition pixelpipe.h:52
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
GtkWidget * dt_ui_resizable_drawing_area(GtkWidget *area, char *config_str, int default_height, int min_height)
Make a self-drawing widget (typically a GtkDrawingArea graph or scope) vertically resizable.
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
const float r
lib_colorpicker_sample_statistics scope
Definition colorpicker.h:73
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:167
GList * iop
Definition develop.h:269
struct dt_develop_t::@13 color_picker
Authoritative darkroom color-picker state.
struct dt_iop_module_t * gui_module
Definition develop.h:170
GSList * samples
Definition develop.h:375
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
unsigned int channels
Definition format.h:83
dt_draw_curve_t * curve[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:180
dt_iop_colorzones_channel_t channel
Definition colorzones.c:183
float lut[3][0x10000]
Definition colorzones.c:184
int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:182
int curve_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:181
dt_draw_curve_t * minmax_curve[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:148
float draw_ys[DT_IOP_COLORZONES_MAX_CHANNELS][256]
Definition colorzones.c:169
int minmax_curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:150
int minmax_curve_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:149
dt_iop_colorzones_channel_t channel
Definition colorzones.c:168
dt_iop_colorzones_modes_t mode
Definition colorzones.c:142
dt_iop_colorzones_channel_t channel
Definition colorzones.c:136
int curve_type[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:140
dt_iop_colorzones_node_t curve[DT_IOP_COLORZONES_MAX_CHANNELS][20]
Definition colorzones.c:138
int curve_num_nodes[DT_IOP_COLORZONES_MAX_CHANNELS]
Definition colorzones.c:139
GtkWidget * widget
Definition imageop_gui.h:47
dt_dev_operation_t op
Definition imageop.h:235
dt_iop_global_data_t * data
Definition imageop.h:238
dt_dev_request_colorpick_flags_t request_color_pick
Definition imageop.h:272
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
char multi_name[128]
Definition imageop.h:387
struct dt_develop_t * dev
Definition imageop.h:311
uint32_t histogram_max[4]
Definition imageop.h:295
dt_iop_global_data_t * global_data
Definition imageop.h:337
gboolean enabled
Definition imageop.h:313
dt_aligned_pixel_t picked_color_min
Definition imageop.h:287
int request_mask_display
Definition imageop.h:276
dt_aligned_pixel_t picked_color_max
Definition imageop.h:287
uint32_t * histogram
Definition imageop.h:291
dt_iop_colorspace_type_t histogram_cst
Definition imageop.h:300
dt_dev_operation_t op
Definition imageop.h:259
dt_aligned_pixel_t picked_color
Definition imageop.h:287
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
GtkWidget * notebook
#define __DT_CLONE_TARGETS__
#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)
gboolean dt_gui_get_scroll_unit_delta(const GdkEventScroll *event, int *delta)
GdkModifierType dt_key_modifier_state()
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_colorpicker(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_ALTER