Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
retouch.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2017-2019 Edgardo Hoszowski.
4 Copyright (C) 2018 Alexandre Prokoudine.
5 Copyright (C) 2018-2020, 2023-2026 Aurélien PIERRE.
6 Copyright (C) 2018-2022 Pascal Obry.
7 Copyright (C) 2018 rawfiner.
8 Copyright (C) 2019-2022 Aldric Renaudin.
9 Copyright (C) 2019 Andreas Schneider.
10 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
11 Copyright (C) 2019 emeikei.
12 Copyright (C) 2019 luzpaz.
13 Copyright (C) 2019 Tobias Ellinghaus.
14 Copyright (C) 2020 Chris Elston.
15 Copyright (C) 2020-2021 Hubert Kowalski.
16 Copyright (C) 2020 Marco.
17 Copyright (C) 2020 Mark-64.
18 Copyright (C) 2020-2021 Ralf Brown.
19 Copyright (C) 2020 Ulrich Pegelow.
20 Copyright (C) 2021 Dan Torop.
21 Copyright (C) 2021 lhietal.
22 Copyright (C) 2022 Hanno Schwalm.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25 Copyright (C) 2023 Alynx Zhou.
26 Copyright (C) 2025-2026 Guillaume Stutin.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41
42#ifdef HAVE_CONFIG_H
44#include "develop/iop_profile.h"
47#include "common/conf.h"
48#include "config.h"
49#endif
50#include "control/control.h"
51#include "widgets/bauhaus.h"
52#include "system/macros.h"
53#include "system/openmp.h"
55#include "system/mem_alloc.h"
56#include "system/simd.h"
57#include "common/logging.h"
59#include "pixel/bilateral.h"
60#include "pixel/bilateralcl.h"
62#include "pixel/dwt.h"
63#include "pixel/gaussian.h"
64#include "pixel/heal.h"
65#include "common/imagebuf.h"
66#include "common/opencl.h"
67#include "develop/blend.h"
68#include "develop/blend_gui.h"
70#include "develop/imageop_gui.h"
71#include "develop/masks.h"
72#include "develop/masks_group.h"
73#include "develop/masks_gui.h"
74#include "develop/tiling.h"
75#include "gui/actions/menu.h"
76#include "iop/iop_api.h"
77
79#include <stdlib.h>
80#include "widgets/label.h"
81#include "gui/screen_metrics.h"
82#include "control/signal.h"
83
84// this is the version of the modules parameters,
85// and includes version information about compile-time dt
87
88#define RETOUCH_NO_FORMS 300
89#define RETOUCH_MAX_SCALES 15
90#define RETOUCH_NO_SCALES (RETOUCH_MAX_SCALES + 2)
91
92#define RETOUCH_PREVIEW_LVL_MIN -3.0f
93#define RETOUCH_PREVIEW_LVL_MAX 3.0f
94
99
101 DT_IOP_RETOUCH_FILL_ERASE = 0, // $DESCRIPTION: "erase"
102 DT_IOP_RETOUCH_FILL_COLOR = 1 // $DESCRIPTION: "color"
104
106 DT_IOP_RETOUCH_BLUR_GAUSSIAN = 0, // $DESCRIPTION: "gaussian"
107 DT_IOP_RETOUCH_BLUR_BILATERAL = 1 // $DESCRIPTION: "bilateral"
109
111 DT_IOP_RETOUCH_NONE = 0, // $DESCRIPTION: "unused"
112 DT_IOP_RETOUCH_CLONE = 1, // $DESCRIPTION: "clone"
113 DT_IOP_RETOUCH_HEAL = 2, // $DESCRIPTION: "heal"
114 DT_IOP_RETOUCH_BLUR = 3, // $DESCRIPTION: "blur"
115 DT_IOP_RETOUCH_FILL = 4 // $DESCRIPTION: "fill"
117
119{
120 int formid; // from masks, form->formid
121 int scale; // 0==original image; 1..RETOUCH_MAX_SCALES==scale; RETOUCH_MAX_SCALES+1==residual
122 dt_iop_retouch_algo_type_t algorithm; // clone, heal, blur, fill
123
125 float blur_radius; // radius for blur algorithm
126
127 dt_iop_retouch_fill_modes_t fill_mode; // mode for fill algorithm, erase or fill with color
128 float fill_color[3]; // color for fill algorithm
129 float fill_brightness; // value to be added to the color
130 int distort_mode; // module v1 => 1, otherwise 2. mode 1 as issues if there's distortion before this module
132
143
145{
146 dt_iop_retouch_form_data_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
147
148 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
149
150 int num_scales; // $DEFAULT: 0 number of wavelets scales
151 int curr_scale; // $DEFAULT: 0 current wavelet scale
152 int merge_from_scale; // $DEFAULT: 0
153
155
156 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "blur type" gaussian, bilateral
157 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "blur radius" radius for blur algorithm
158
159 dt_iop_retouch_fill_modes_t fill_mode; // $DEFAULT: DT_IOP_RETOUCH_FILL_ERASE $DESCRIPTION: "fill mode" mode for fill algorithm, erase or fill with color
160 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
161 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "brightness" value to be added to the color
162 int max_heal_iter; // $DEFAULT: 2000 $DESCRIPTION: "max_iter" numbe of iteration for heal algorithm
164
166{
167 int copied_scale; // scale to be copied to another scale
168 int mask_display; // should we expose masks?
169 int suppress_mask; // do not process masks
170 int display_wavelet_scale; // display current wavelet scale
171 int displayed_wavelet_scale; // was display wavelet scale already used?
172 int preview_auto_levels; // should we calculate levels automatically?
173 float preview_levels[3]; // values for the levels
174 int first_scale_visible; // 1st scale visible at current zoom level
175
176 GtkLabel *label_form; // display number of forms
177 GtkLabel *label_form_selected; // display number of forms selected
180 GtkWidget *bt_showmask, *bt_suppress; // suppress & show masks
181
182 GtkWidget *wd_bar; // wavelet decompose bar
183 GtkLabel *lbl_num_scales;
184 GtkLabel *lbl_curr_scale;
187 int curr_scale; // scale box under mouse
188 gboolean is_dragging;
189 gboolean upper_cursor; // mouse on merge from scale cursor
190 gboolean lower_cursor; // mouse on num scales cursor
191 gboolean upper_margin; // mouse on the upper band
192 gboolean lower_margin; // mouse on the lower band
193
194 GtkWidget *bt_display_wavelet_scale; // show decomposed scale
195
196 GtkWidget *bt_copy_scale; // copy all shapes from one scale to another
198
200
202
204
208
211 GtkWidget *colorpick; // select a specific color
212 GtkWidget *colorpicker; // pick a color from the picture
213
216
217 GtkWidget *sl_mask_opacity; // draw mask opacity
219
221
235
236
237// this returns a translatable name
238const char *name()
239{
240 return _("re_touch");
241}
242
243const char *aliases()
244{
245 return _("split-frequency|healing|cloning|stamp");
246}
247
248
249const char **description(struct dt_iop_module_t *self)
250{
251 return dt_iop_set_description(self, _("remove and clone spots, perform split-frequency skin editing"),
252 _("corrective"),
253 _("linear, RGB, scene-referred"),
254 _("geometric and frequential, RGB"),
255 _("linear, RGB, scene-referred"));
256}
257
259{
260 return IOP_GROUP_REPAIR;
261}
262
267
269{
270 return IOP_CS_RGB;
271}
272
273static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type);
274
275int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
276 const int new_version)
277{
278 if(old_version == 1 && new_version == 3)
279 {
280 typedef struct dt_iop_retouch_form_data_v1_t
281 {
282 int formid; // from masks, form->formid
283 int scale; // 0==original image; 1..RETOUCH_MAX_SCALES==scale; RETOUCH_MAX_SCALES+1==residual
284 dt_iop_retouch_algo_type_t algorithm; // clone, heal, blur, fill
285
286 dt_iop_retouch_blur_types_t blur_type; // gaussian, bilateral
287 float blur_radius; // radius for blur algorithm
288
289 dt_iop_retouch_fill_modes_t fill_mode; // mode for fill algorithm, erase or fill with color
290 float fill_color[3]; // color for fill algorithm
291 float fill_brightness; // value to be added to the color
292 } dt_iop_retouch_form_data_v1_t;
293 typedef struct dt_iop_retouch_params_v1_t
294 {
295 dt_iop_retouch_form_data_v1_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
296
297 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
298
299 int num_scales; // $DEFAULT: 0 number of wavelets scales
300 int curr_scale; // $DEFAULT: 0 current wavelet scale
301 int merge_from_scale; // $DEFAULT: 0
302
303 float preview_levels[3];
304
305 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "blur type"
306 // gaussian, bilateral
307 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "blur radius" radius for blur
308 // algorithm
309
310 dt_iop_retouch_fill_modes_t fill_mode; // $DEFAULT: DT_IOP_RETOUCH_FILL_ERASE $DESCRIPTION: "fill mode" mode
311 // for fill algorithm, erase or fill with color
312 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
313 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "brightness" value to be added to the color
314 } dt_iop_retouch_params_v1_t;
315
316 dt_iop_retouch_params_v1_t *o = (dt_iop_retouch_params_v1_t *)old_params;
319
320 *n = *d; // start with a fresh copy of default parameters
321 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
322 {
323 dt_iop_retouch_form_data_v1_t of = o->rt_forms[i];
324 n->rt_forms[i].algorithm = of.algorithm;
325 n->rt_forms[i].blur_radius = of.blur_radius;
326 n->rt_forms[i].blur_type = of.blur_type;
327 n->rt_forms[i].distort_mode = 1;
328 n->rt_forms[i].fill_brightness = of.fill_brightness;
329 n->rt_forms[i].fill_color[0] = of.fill_color[0];
330 n->rt_forms[i].fill_color[1] = of.fill_color[1];
331 n->rt_forms[i].fill_color[2] = of.fill_color[2];
332 n->rt_forms[i].fill_mode = of.fill_mode;
333 n->rt_forms[i].formid = of.formid;
334 n->rt_forms[i].scale = of.scale;
335 }
336 n->algorithm = o->algorithm;
337 n->blur_radius = o->blur_radius;
338 n->blur_type = o->blur_type;
339 n->curr_scale = o->curr_scale;
340 n->fill_brightness = o->fill_brightness;
341 n->fill_color[0] = o->fill_color[0];
342 n->fill_color[1] = o->fill_color[1];
343 n->fill_color[2] = o->fill_color[2];
344 n->fill_mode = o->fill_mode;
345 n->merge_from_scale = o->merge_from_scale;
346 n->num_scales = o->num_scales;
347 n->preview_levels[0] = o->preview_levels[0];
348 n->preview_levels[1] = o->preview_levels[1];
349 n->preview_levels[2] = o->preview_levels[2];
350
351 n->max_heal_iter = 1000;
352
353 return 0;
354 }
355 if(old_version == 2 && new_version == 3)
356 {
357 typedef struct dt_iop_retouch_params_v2_t
358 {
359 dt_iop_retouch_form_data_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
360
361 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
362
363 int num_scales; // $DEFAULT: 0 number of wavelets scales
364 int curr_scale; // $DEFAULT: 0 current wavelet scale
365 int merge_from_scale; // $DEFAULT: 0
366
367 float preview_levels[3];
368
369 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "blur type" gaussian, bilateral
370 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "blur radius" radius for blur algorithm
371
372 dt_iop_retouch_fill_modes_t fill_mode; // $DEFAULT: DT_IOP_RETOUCH_FILL_ERASE $DESCRIPTION: "fill mode" mode for fill algorithm, erase or fill with color
373 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
374 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "brightness" value to be added to the color
375 } dt_iop_retouch_params_v2_t;
376
377 dt_iop_retouch_params_v2_t *o = (dt_iop_retouch_params_v2_t *)old_params;
380
381 *n = *d; // start with a fresh copy of default parameters
382
383 memcpy(n, o, sizeof(dt_iop_retouch_params_v2_t));
384
385 n->max_heal_iter = 1000;
386
387 return 0;
388 }
389 return 1;
390}
391
393{
394 int index = -1;
395 if(formid > 0)
396 {
397 int i = 0;
398
399 while(index == -1 && i < RETOUCH_NO_FORMS)
400 {
401 if(p->rt_forms[i].formid == formid) index = i;
402 i++;
403 }
404 }
405 return index;
406}
407
409{
410 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return 0;
411
412 const dt_masks_form_gui_t *gui = self->dev->form_gui;
413 const dt_masks_form_t *visible_form = dt_masks_get_visible_form(self->dev);
414 if(IS_NULL_PTR(gui) || IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP)) return 0;
415
416 const dt_masks_form_group_t *selected_group_entry
417 = dt_masks_form_get_selected_group_live(visible_form, gui);
418 return selected_group_entry ? selected_group_entry->formid : 0;
419}
420
422{
423 dt_masks_form_group_t *form_point_group = NULL;
424
425 const dt_develop_blend_params_t *bp = self->blend_params;
426 if(IS_NULL_PTR(bp)) return form_point_group;
427
428 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
429 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
430 {
431 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
432 {
433 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
434 if(grpt->formid == formid)
435 {
436 form_point_group = grpt;
437 break;
438 }
439 }
440 }
441
442 return form_point_group;
443}
444
445static float rt_get_shape_opacity(dt_iop_module_t *self, const int formid)
446{
447 float opacity = 0.f;
448
450 if(grpt) opacity = grpt->opacity;
451
452 return opacity;
453}
454
456{
457 GdkRGBA c
458 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
459 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &c);
460}
461
463{
466
467 switch(p->algorithm)
468 {
470 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
471 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
472 break;
474 gtk_widget_show(GTK_WIDGET(g->vbox_blur));
475 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
476 break;
478 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
479 gtk_widget_show(GTK_WIDGET(g->vbox_fill));
480 if(p->fill_mode == DT_IOP_RETOUCH_FILL_COLOR)
481 gtk_widget_show(GTK_WIDGET(g->hbox_color_pick));
482 else
483 gtk_widget_hide(GTK_WIDGET(g->hbox_color_pick));
484 break;
486 default:
487 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
488 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
489 break;
490 }
491
492 if(g->display_wavelet_scale)
493 gtk_widget_show(GTK_WIDGET(g->vbox_preview_scale));
494 else
495 gtk_widget_hide(GTK_WIDGET(g->vbox_preview_scale));
496
497 const dt_masks_form_t *form = NULL;
498 const int selected_formid = rt_get_selected_shape_id(self);
499 if(selected_formid > 0)
500 form = dt_masks_get_from_id(self->dev, selected_formid);
501 if(!IS_NULL_PTR(form))
502 gtk_widget_show(GTK_WIDGET(g->sl_mask_opacity));
503 else
504 gtk_widget_hide(GTK_WIDGET(g->sl_mask_opacity));
505}
506
508{
509 const int selected_formid = rt_get_selected_shape_id(dev->gui_module);
510 const dt_masks_form_t *form = selected_formid > 0
511 ? dt_masks_get_from_id(dev, selected_formid)
512 : NULL;
513 if(!IS_NULL_PTR(form))
514 gtk_label_set_text(g->label_form_selected, form->name);
515 else
516 gtk_label_set_text(g->label_form_selected, _("none"));
517}
518
520{
521 const int selected_formid = rt_get_selected_shape_id(dev->gui_module);
522 return rt_get_index_from_formid(p, selected_formid);
523}
524
525static void rt_load_shape_algo_in_gui(dt_iop_module_t *self, const int form_selected_id)
526{
529
531
532 gboolean selection_changed = FALSE;
533
534 const int index = rt_get_index_from_formid(p, form_selected_id);
535 if(index >= 0)
536 {
537 const dt_iop_retouch_form_data_t *const selected_form = &p->rt_forms[index];
538 dt_bauhaus_slider_set(g->sl_mask_opacity, rt_get_shape_opacity(self, selected_form->formid));
539
540 if(selected_form->algorithm == DT_IOP_RETOUCH_BLUR)
541 {
542 p->blur_type = selected_form->blur_type;
543 p->blur_radius = selected_form->blur_radius;
544
545 dt_bauhaus_combobox_set(g->cmb_blur_type, p->blur_type);
546 dt_bauhaus_slider_set(g->sl_blur_radius, p->blur_radius);
547
548 selection_changed = TRUE;
549 }
550 else if(selected_form->algorithm == DT_IOP_RETOUCH_FILL)
551 {
552 p->fill_mode = selected_form->fill_mode;
553 p->fill_brightness = selected_form->fill_brightness;
554 p->fill_color[0] = selected_form->fill_color[0];
555 p->fill_color[1] = selected_form->fill_color[1];
556 p->fill_color[2] = selected_form->fill_color[2];
557
558 dt_bauhaus_slider_set(g->sl_fill_brightness, p->fill_brightness);
559 dt_bauhaus_combobox_set(g->cmb_fill_mode, p->fill_mode);
561
562 selection_changed = TRUE;
563 }
564
565 if(p->algorithm != selected_form->algorithm)
566 {
567 p->algorithm = selected_form->algorithm;
568
569 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), (p->algorithm == DT_IOP_RETOUCH_CLONE));
570 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), (p->algorithm == DT_IOP_RETOUCH_HEAL));
571 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), (p->algorithm == DT_IOP_RETOUCH_BLUR));
572 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), (p->algorithm == DT_IOP_RETOUCH_FILL));
573
574 selection_changed = TRUE;
575 }
576 }
577
578 if(selection_changed) rt_show_hide_controls(self);
579
581
582 if(index >= 0)
583 gtk_widget_show(GTK_WIDGET(g->sl_mask_opacity));
584 else
585 gtk_widget_hide(GTK_WIDGET(g->sl_mask_opacity));
586
588}
589
590//---------------------------------------------------------------------------------
591// helpers
592//---------------------------------------------------------------------------------
593
594static void rt_masks_form_change_opacity(dt_iop_module_t *self, int formid, float opacity)
595{
596 const dt_develop_blend_params_t *bp = self->blend_params;
597 if(IS_NULL_PTR(bp)) return;
598
599 /* Retouch keeps a 5% floor of its own, applied BEFORE the call: a fully transparent clone or
600 * heal is indistinguishable from a deleted one in this module's list, so it never offers 0 the
601 * way a generic mask does. The API clamps to [0;1] and would accept it. */
602 dt_masks_member_t member = { 0 };
603 const dt_masks_result_t result = dt_masks_group_set_member_opacity(self->dev, bp->mask_id, formid,
604 CLAMP(opacity, 0.05f, 1.0f), &member);
605 if(result != DT_MASKS_OK && result != DT_MASKS_UNCHANGED) return;
606
607 dt_conf_set_float("plugins/darkroom/masks/opacity", member.opacity);
608
609 /* Only a real change earns a history step. Re-setting the opacity a shape already has used to
610 * push an undo entry that undid nothing. */
611 if(result == DT_MASKS_OK) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
612}
613
614static float rt_masks_form_get_opacity(dt_iop_module_t *self, int formid)
615{
617 if(!IS_NULL_PTR(grpt))
618 return grpt->opacity;
619 else
620 return 1.0f;
621}
622
623static void rt_paste_forms_from_scale(dt_iop_retouch_params_t *p, const int source_scale, const int dest_scale)
624{
625 if(source_scale != dest_scale && source_scale >= 0 && dest_scale >= 0)
626 {
627 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
628 {
629 if(p->rt_forms[i].scale == source_scale) p->rt_forms[i].scale = dest_scale;
630 }
631 }
632}
633
635{
636 int allow = 1;
637
639 if(!IS_NULL_PTR(p))
640 {
641 allow = (p->rt_forms[RETOUCH_NO_FORMS - 1].formid == 0);
642 }
643 return allow;
644}
645
647{
648 if(!self->enabled || self->dev->gui_module != self || self->dev->form_gui->creation)
649 return;
650
654 if(IS_NULL_PTR(bd)) return;
655
656 const int scale = p->curr_scale;
657 int count = 0;
658
659 // Check if there is at least one shape on this scale
660 for(int i = 0; i < RETOUCH_NO_FORMS && count == 0; i++)
661 {
662 if(p->rt_forms[i].formid != 0 && p->rt_forms[i].scale == scale) count++;
663 }
664
665 // If a shape was found on this scale, make the cut shapes button sensitive
666 gtk_widget_set_sensitive(g->bt_copy_scale, count > 0);
667
668 // if no shapes on this scale, we hide all
669 if(bd->masks_shown == DT_MASKS_EDIT_OFF || count == 0)
670 {
671 dt_masks_change_form_gui(self->dev, NULL);
672
673 if(g)
674 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
676 && (self->dev->gui_module == self));
677
679 return;
680 }
681
682 // else, we create a new from group with the shapes and display it
684 const int grid = self->blend_params->mask_id;
685
686 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
687 {
688 if(p->rt_forms[i].scale == scale)
689 {
690 const int formid = p->rt_forms[i].formid;
691 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
692 if(IS_NULL_PTR(form)) continue;
693
694 dt_masks_group_add_form_with_state(self->dev, grp, form, grid,
696 }
697 }
698
700 grp_dest->formid = 0;
701 dt_masks_group_ungroup(self->dev, grp_dest, grp);
702 dt_masks_change_form_gui(self->dev, grp_dest);
703 self->dev->form_gui->edit_mode = bd->masks_shown;
704
705 if(g)
706 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
707 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
708
710}
711
712// called if a shape is added or deleted
713static void rt_resynch_params(struct dt_iop_module_t *self, dt_iop_retouch_params_t *p, GList *forms_list)
714{
716 if(IS_NULL_PTR(p) || IS_NULL_PTR(bp)) return;
717
718 // Create a temporary array to store form data and initialize it to zero
720 memset(forms_d, 0, sizeof(dt_iop_retouch_form_data_t) * RETOUCH_NO_FORMS);
721
722 // we go through all forms in blend params
723 dt_masks_form_t *grp = dt_masks_get_from_id_ext(forms_list, bp->mask_id);
724 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP))
725 return;
726
727 int new_form_index = 0;
728 // For each form in the group, we search if it already exists in the params array,
729 // if it does we copy it, if not we add it to the end of the array
730 for(GList *forms = grp->points; (new_form_index < RETOUCH_NO_FORMS) && forms; forms = g_list_next(forms))
731 {
732 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
733 if(IS_NULL_PTR(grpt)) return;
734
735 const int formid = grpt->formid;
736
737 // search for the form index in the shapes array
738 const int form_index = rt_get_index_from_formid(p, formid);
739
740 // if it exists, copy it to the new array
741 if(form_index >= 0)
742 {
743 forms_d[new_form_index] = p->rt_forms[form_index];
744 }
745 else
746 {
747 // if it does not exists, add it to the new array
748 const dt_masks_form_t *parent_form = dt_masks_get_from_id_ext(forms_list, formid);
749 if(IS_NULL_PTR(parent_form)) continue;
750
751 forms_d[new_form_index].formid = formid;
752 forms_d[new_form_index].scale = p->curr_scale;
753 forms_d[new_form_index].algorithm = p->algorithm;
754 forms_d[new_form_index].distort_mode = 2;
755
756 switch(forms_d[new_form_index].algorithm)
757 {
759 forms_d[new_form_index].blur_type = p->blur_type;
760 forms_d[new_form_index].blur_radius = p->blur_radius;
761 break;
763 forms_d[new_form_index].fill_mode = p->fill_mode;
764 forms_d[new_form_index].fill_color[0] = p->fill_color[0];
765 forms_d[new_form_index].fill_color[1] = p->fill_color[1];
766 forms_d[new_form_index].fill_color[2] = p->fill_color[2];
767 forms_d[new_form_index].fill_brightness = p->fill_brightness;
768 break;
769 default:
770 break;
771 }
772 }
773 new_form_index++;
774 }
775
776 // we reaffect params
777 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
778 {
779 p->rt_forms[i] = forms_d[i];
780 }
781}
782
784{
785 // When drawing events are finished, they commit the shapes to dev->forms
786 // then commit to history, where dev->forms is are copied to hist->forms.
787 // This is where we need to catch the new masks
788 // to sync them with our params here.
789
790 // TODO: share this code with gui_update()
794
796 if(IS_NULL_PTR(g)) return;
797 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), rt_shape_is_being_added(self, DT_MASKS_CIRCLE));
798 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_polygon), rt_shape_is_being_added(self, DT_MASKS_POLYGON));
799 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), rt_shape_is_being_added(self, DT_MASKS_ELLIPSE));
800 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_brush), rt_shape_is_being_added(self, DT_MASKS_BRUSH));
801
802 // Get the total form count and display it in the module
803 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id);
804 guint nb = 0;
805 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
806 gchar *str = g_strdup_printf("%d", nb);
807 gtk_label_set_text(g->label_form, str);
808 dt_free(str);
809
810 //only toggle shape show button if shapes exist
811 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
812 {
814 if(IS_NULL_PTR(bd)) return;
815 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
816 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
817 }
818 else
819 {
820 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
821 }
822}
823
825 const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const dt_iop_roi_t *roi_in,
826 const dt_iop_roi_t *roi_out)
827{
828 // we get the area for the form
829 int fl, ft, fw, fh;
830 dt_dev_pixelpipe_iop_t piece_copy = *piece;
831
832 if(dt_masks_get_area(self, (dt_dev_pixelpipe_t *)pipe, &piece_copy, form, &fw, &fh, &fl, &ft)
834 return FALSE;
835
836 // is the form outside of the roi?
837 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
838 if(ft >= roi_out->y + roi_out->height || ft + fh <= roi_out->y || fl >= roi_out->x + roi_out->width
839 || fl + fw <= roi_out->x)
840 return FALSE;
841
842 return TRUE;
843}
844
846 const float *points,
847 size_t points_count, float *new)
848{
849 const float scalex = pipe->iwidth * roi->scale, scaley = pipe->iheight * roi->scale;
850
851 for(size_t i = 0; i < points_count * 2; i += 2)
852 {
853 new[i] = points[i] * scalex;
854 new[i + 1] = points[i + 1] * scaley;
855 }
856}
857
859 const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi,
860 const float *target, const float *source, float *dx, float *dy,
861 const int distort_mode)
862{
863 // if distort_mode==1 we don't scale at the right place, hence false positions if there's distortion before this
864 // module. we keep it for backward compatibility only. all new forms have distort_mode==2
865 dt_boundingbox_t points;
866 if(distort_mode == 1)
867 {
868 rt_masks_point_denormalize(pipe, roi, target, 1, points);
869 rt_masks_point_denormalize(pipe, roi, source, 1, points + 2);
870 }
871 else
872 {
873 points[0] = target[0] * pipe->iwidth;
874 points[1] = target[1] * pipe->iheight;
875 points[2] = source[0] * pipe->iwidth;
876 points[3] = source[1] * pipe->iheight;
877 }
878
880 points, 2);
881 if(!res) return res;
882
883 if(distort_mode == 1)
884 {
885 *dx = points[0] - points[2];
886 *dy = points[1] - points[3];
887 }
888 else
889 {
890 *dx = (points[0] - points[2]) * roi->scale;
891 *dy = (points[1] - points[3]) * roi->scale;
892 }
893
894 return res;
895}
896
897/* returns (dx dy) to get from the source to the destination */
899 const dt_dev_pixelpipe_iop_t *piece,
900 const dt_iop_roi_t *roi, dt_masks_form_t *form, float *dx, float *dy,
901 const int distort_mode)
902{
903 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
904 int res = 0;
905
906 if(form->type & DT_MASKS_POLYGON)
907 {
908 const dt_masks_node_polygon_t *pt = (dt_masks_node_polygon_t *)form->points->data;
909 if(IS_NULL_PTR(pt)) return 0;
910
911 res = rt_masks_point_calc_delta(self, pipe, piece, roi, pt->node, form->source, dx, dy, distort_mode);
912 }
913 else if(form->type & DT_MASKS_CIRCLE)
914 {
915 const dt_masks_node_circle_t *pt = (dt_masks_node_circle_t *)form->points->data;
916 if(IS_NULL_PTR(pt)) return 0;
917
918 res = rt_masks_point_calc_delta(self, pipe, piece, roi, pt->center, form->source, dx, dy, distort_mode);
919 }
920 else if(form->type & DT_MASKS_ELLIPSE)
921 {
922 const dt_masks_node_ellipse_t *pt = (dt_masks_node_ellipse_t *)form->points->data;
923 if(IS_NULL_PTR(pt)) return 0;
924
925 res = rt_masks_point_calc_delta(self, pipe, piece, roi, pt->center, form->source, dx, dy, distort_mode);
926 }
927 else if(form->type & DT_MASKS_BRUSH)
928 {
929 const dt_masks_node_brush_t *pt = (dt_masks_node_brush_t *)form->points->data;
930 if(IS_NULL_PTR(pt)) return 0;
931
932 res = rt_masks_point_calc_delta(self, pipe, piece, roi, pt->node, form->source, dx, dy, distort_mode);
933 }
934
935 return res;
936}
937
938static inline __attribute__((always_inline)) void rt_clamp_minmax(float levels_old[3], float levels_new[3])
939{
940 // left or right has changed
941 if((levels_old[0] != levels_new[0] || levels_old[2] != levels_new[2]) && levels_old[1] == levels_new[1])
942 {
943 // if old left and right are the same just use the new values
944 if(levels_old[2] != levels_old[0])
945 {
946 // set the new value but keep the middle proportional
947 const float left = MAX(levels_new[0], RETOUCH_PREVIEW_LVL_MIN);
948 const float right = MIN(levels_new[2], RETOUCH_PREVIEW_LVL_MAX);
949
950 const float percentage = (levels_old[1] - levels_old[0]) / (levels_old[2] - levels_old[0]);
951 levels_new[1] = left + (right - left) * percentage;
952 levels_new[0] = left;
953 levels_new[2] = right;
954 }
955 }
956
957 // if all zero make it gray
958 if(levels_new[0] == 0.f && levels_new[1] == 0.f && levels_new[2] == 0.f)
959 {
960 levels_new[0] = -1.5f;
961 levels_new[1] = 0.f;
962 levels_new[2] = 1.5f;
963 }
964
965 // check the range
966 if(levels_new[2] < levels_new[0] + 0.05f * 2.f) levels_new[2] = levels_new[0] + 0.05f * 2.f;
967 if(levels_new[1] < levels_new[0] + 0.05f) levels_new[1] = levels_new[0] + 0.05f;
968 if(levels_new[1] > levels_new[2] - 0.05f) levels_new[1] = levels_new[2] - 0.05f;
969
970 {
971 // set the new value but keep the middle proportional
972 const float left = MAX(levels_new[0], RETOUCH_PREVIEW_LVL_MIN);
973 const float right = MIN(levels_new[2], RETOUCH_PREVIEW_LVL_MAX);
974
975 const float percentage = (levels_new[1] - levels_new[0]) / (levels_new[2] - levels_new[0]);
976 levels_new[1] = left + (right - left) * percentage;
977 levels_new[0] = left;
978 levels_new[2] = right;
979 }
980}
981
982static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type)
983{
984 int being_added = 0;
985
986 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)
987 && (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self))
988 {
990 {
991 GList *forms = dt_masks_get_visible_form(self->dev)->points;
992 if(IS_NULL_PTR(forms)) goto end;
993
994 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
995 if(IS_NULL_PTR(grpt)) goto end;
996
997 const dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
998 if(!IS_NULL_PTR(form)) being_added = (form->type & shape_type);
999 }
1000 else
1001 being_added = (dt_masks_get_visible_form(self->dev)->type & shape_type);
1002 }
1003
1004 end:
1005 return being_added;
1006}
1007
1009 dt_masks_type_t type, gpointer user_data)
1010{
1011 //turn module on (else shape creation won't work)
1012 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), TRUE);
1013
1014 //switch mask edit mode off
1016 if(bd) bd->masks_shown = DT_MASKS_EDIT_OFF;
1017
1018 const int allow = rt_allow_create_form(self);
1019 if(allow)
1020 {
1022
1023 // we want to be sure that Retouch has focus
1026 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
1027 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_showmask), FALSE);
1028 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_suppress), FALSE);
1029 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->colorpicker), FALSE);
1030 }
1031
1032 return allow;
1033}
1034
1036{
1038 if(p->algorithm == DT_IOP_RETOUCH_CLONE || p->algorithm == DT_IOP_RETOUCH_HEAL)
1039 return type | DT_MASKS_CLONE;
1040
1041 return type | DT_MASKS_NON_CLONE;
1042}
1043
1044//---------------------------------------------------------------------------------
1045// GUI callbacks
1046//---------------------------------------------------------------------------------
1047
1048static void rt_colorpick_color_set_callback(GtkColorButton *widget, dt_iop_module_t *self)
1049{
1050 if(dt_gui_widgets_suppressed()) return;
1052 // turn off the other color picker
1054
1055 GdkRGBA c
1056 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
1057 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
1058 p->fill_color[0] = c.red;
1059 p->fill_color[1] = c.green;
1060 p->fill_color[2] = c.blue;
1061
1062 const int index = rt_get_selected_shape_index(self->dev, p);
1063 if(index >= 0)
1064 {
1065 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1066 {
1067 p->rt_forms[index].fill_color[0] = p->fill_color[0];
1068 p->rt_forms[index].fill_color[1] = p->fill_color[1];
1069 p->rt_forms[index].fill_color[2] = p->fill_color[2];
1070 }
1071 }
1072
1073 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1074}
1075
1076// wavelet decompose bar
1077#define RT_WDBAR_INSET 0.2f
1078#define lw DT_PIXEL_APPLY_DPI(1.0f)
1079
1081{
1082 char text[256];
1083
1084 snprintf(text, sizeof(text), "%i", p->curr_scale);
1085 gtk_label_set_text(g->lbl_curr_scale, text);
1086
1087 snprintf(text, sizeof(text), "%i", p->num_scales);
1088 gtk_label_set_text(g->lbl_num_scales, text);
1089
1090 snprintf(text, sizeof(text), "%i", p->merge_from_scale);
1091 gtk_label_set_text(g->lbl_merge_from_scale, text);
1092}
1093
1094static void rt_num_scales_update(const int _num_scales, dt_iop_module_t *self)
1095{
1096 if(dt_gui_widgets_suppressed()) return;
1097
1100
1101 const int num_scales = CLAMP(_num_scales, 0, RETOUCH_MAX_SCALES);
1102 if(p->num_scales == num_scales) return;
1103
1104 p->num_scales = num_scales;
1105
1106 if(p->num_scales < p->merge_from_scale) p->merge_from_scale = p->num_scales;
1107
1109
1110 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1111}
1112
1113static void rt_curr_scale_update(const int _curr_scale, dt_iop_module_t *self)
1114{
1115 if(dt_gui_widgets_suppressed()) return;
1116
1119
1120 const int curr_scale = CLAMP(_curr_scale, 0, RETOUCH_MAX_SCALES + 1);
1121 if(p->curr_scale == curr_scale) return;
1122
1123 p->curr_scale = curr_scale;
1124
1126
1127 // compute auto levels only the first time display wavelet scale is used,
1128 // only if levels values are the default
1129 // and a detail scale is displayed
1131 if(g->displayed_wavelet_scale == 0 && p->preview_levels[0] == RETOUCH_PREVIEW_LVL_MIN
1132 && p->preview_levels[1] == 0.f && p->preview_levels[2] == RETOUCH_PREVIEW_LVL_MAX
1133 && g->preview_auto_levels == 0 && p->curr_scale > 0 && p->curr_scale <= p->num_scales)
1134 {
1135 g->preview_auto_levels = 1;
1136 g->displayed_wavelet_scale = 1;
1137 }
1139
1141
1142 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1143}
1144
1145static void rt_merge_from_scale_update(const int _merge_from_scale, dt_iop_module_t *self)
1146{
1147 if(dt_gui_widgets_suppressed()) return;
1148
1151
1152 const int merge_from_scale = CLAMP(_merge_from_scale, 0, p->num_scales);
1153 if(p->merge_from_scale == merge_from_scale) return;
1154
1155 p->merge_from_scale = merge_from_scale;
1156
1158
1159 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1160}
1161
1162static gboolean rt_wdbar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
1163{
1165
1166 g->wdbar_mouse_x = g->wdbar_mouse_y = -1;
1167 g->curr_scale = -1;
1168 g->lower_cursor = g->upper_cursor = FALSE;
1169 g->lower_margin = g->upper_margin = FALSE;
1170
1171 gtk_widget_queue_draw(g->wd_bar);
1172 return TRUE;
1173}
1174
1175static gboolean rt_wdbar_button_press(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1176{
1177 if(dt_gui_widgets_suppressed()) return TRUE;
1178
1180
1182 GtkAllocation allocation;
1183 gtk_widget_get_allocation(widget, &allocation);
1184 const int inset = round(RT_WDBAR_INSET * allocation.height);
1185 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1186
1187 if(event->button == 1)
1188 {
1189 if(g->lower_margin) // bottom slider
1190 {
1191 if(g->lower_cursor) // is over the arrow?
1192 g->is_dragging = DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM;
1193 else
1194 rt_num_scales_update(g->wdbar_mouse_x / box_w, self);
1195 }
1196 else if(g->upper_margin) // top slider
1197 {
1198 if(g->upper_cursor) // is over the arrow?
1199 g->is_dragging = DT_IOP_RETOUCH_WDBAR_DRAG_TOP;
1200 else
1201 rt_merge_from_scale_update(g->wdbar_mouse_x / box_w, self);
1202 }
1203 else if (g->curr_scale >= 0)
1204 rt_curr_scale_update(g->curr_scale, self);
1205 }
1206
1207 gtk_widget_queue_draw(g->wd_bar);
1208 return TRUE;
1209}
1210
1211static gboolean rt_wdbar_button_release(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1212{
1214
1215 if(event->button == 1) g->is_dragging = 0;
1216
1217 gtk_widget_queue_draw(g->wd_bar);
1218 return TRUE;
1219}
1220
1221static gboolean rt_wdbar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
1222{
1223 if(dt_gui_widgets_suppressed()) return TRUE;
1224
1227
1229
1230 int delta_y;
1231 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
1232 {
1233 if(g->lower_margin) // bottom slider
1234 rt_num_scales_update(p->num_scales - delta_y, self);
1235 else if(g->upper_margin) // top slider
1236 rt_merge_from_scale_update(p->merge_from_scale - delta_y, self);
1237 else if (g->curr_scale >= 0)
1238 rt_curr_scale_update(p->curr_scale - delta_y, self);
1239 }
1240
1241 gtk_widget_queue_draw(g->wd_bar);
1242 return TRUE;
1243}
1244
1245static gboolean rt_wdbar_motion_notify(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1246{
1249
1250 GtkAllocation allocation;
1251 gtk_widget_get_allocation(widget, &allocation);
1252 const int inset = round(RT_WDBAR_INSET * allocation.height);
1253 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1254 const float sh = 3.0f * lw + inset;
1255
1256
1257 /* record mouse position within control */
1258 g->wdbar_mouse_x = CLAMP(event->x - inset, 0, allocation.width - 2.0f * inset - 1.0f);
1259 g->wdbar_mouse_y = event->y;
1260
1261 g->curr_scale = g->wdbar_mouse_x / box_w;
1262 g->lower_cursor = g->upper_cursor = FALSE;
1263 g->lower_margin = g->upper_margin = FALSE;
1264 if(g->wdbar_mouse_y <= sh)
1265 {
1266 g->upper_margin = TRUE;
1267 float middle = box_w * (0.5f + (float)p->merge_from_scale);
1268 g->upper_cursor = (g->wdbar_mouse_x >= (middle - inset)) && (g->wdbar_mouse_x <= (middle + inset));
1269 if (!(g->is_dragging)) g->curr_scale = -1;
1270 }
1271 else if (g->wdbar_mouse_y >= allocation.height - sh)
1272 {
1273 g->lower_margin = TRUE;
1274 float middle = box_w * (0.5f + (float)p->num_scales);
1275 g->lower_cursor = (g->wdbar_mouse_x >= (middle - inset)) && (g->wdbar_mouse_x <= (middle + inset));
1276 if (!(g->is_dragging)) g->curr_scale = -1;
1277 }
1278
1279 if(g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM)
1280 rt_num_scales_update(g->curr_scale, self);
1281
1282 if(g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_TOP)
1283 rt_merge_from_scale_update(g->curr_scale, self);
1284
1285 gtk_widget_queue_draw(g->wd_bar);
1286 return TRUE;
1287}
1288
1289static int rt_scale_has_shapes(dt_iop_retouch_params_t *p, const int scale)
1290{
1291 int has_shapes = 0;
1292
1293 for(int i = 0; i < RETOUCH_NO_FORMS && has_shapes == 0; i++)
1294 has_shapes = (p->rt_forms[i].formid != 0 && p->rt_forms[i].scale == scale);
1295
1296 return has_shapes;
1297}
1298
1299static gboolean rt_wdbar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1300{
1303
1304
1305 GdkRGBA border = {0.066, 0.066, 0.066, 1};
1306 GdkRGBA original = {.1, .1, .1, 1};
1307 GdkRGBA inactive = {.15, .15, .15, 1};
1308 GdkRGBA active = {.35, .35, .35, 1};
1309 GdkRGBA merge_from = {.5, .5, .5, 1};
1310 GdkRGBA residual = {.8, .8, .8, 1};
1311 GdkRGBA shapes = {.75, .5, .0, 1};
1312 GdkRGBA color;
1313
1314 float middle;
1315 const int first_scale_visible = (g->first_scale_visible > 0) ? g->first_scale_visible : RETOUCH_MAX_SCALES;
1316
1317 GtkAllocation allocation;
1318 gtk_widget_get_allocation(widget, &allocation);
1319
1320 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1321 cairo_t *cr = cairo_create(cst);
1322
1323 // clear background
1324 gdk_cairo_set_source_rgba(cr, &inactive);
1325 cairo_paint(cr);
1326 cairo_save(cr);
1327
1328 // geometry
1329 const int inset = round(RT_WDBAR_INSET * allocation.height);
1330 const int mk = 2 * inset;
1331 const float sh = 3.0f * lw + inset;
1332 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1333 const float box_h = allocation.height - 2.0f * sh;
1334
1335 // render the boxes
1336 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1337 for(int i = 0; i < RETOUCH_NO_SCALES; i++)
1338 {
1339 // draw box background
1340 if(i == 0)
1341 color = original;
1342 else if(i == p->num_scales + 1)
1343 color = residual;
1344 else if(i >= p->merge_from_scale && i <= p->num_scales && p->merge_from_scale > 0)
1345 color = merge_from;
1346 else if(i <= p->num_scales)
1347 color = active;
1348 else
1349 color = inactive;
1350
1351 gdk_cairo_set_source_rgba(cr, &color);
1352 cairo_rectangle(cr, box_w * i + inset, sh, box_w, box_h);
1353 cairo_fill(cr);
1354
1355 // if detail scale is visible at current zoom level inform it
1356 if(i >= first_scale_visible && i <= p->num_scales)
1357 {
1358 gdk_cairo_set_source_rgba(cr, &merge_from);
1359 cairo_rectangle(cr, box_w * i + inset, lw, box_w, 2.0f * lw);
1360 cairo_fill(cr);
1361 }
1362
1363 // if the scale has shapes inform it
1364 if(rt_scale_has_shapes(p, i))
1365 {
1366 cairo_set_line_width(cr, lw);
1367 gdk_cairo_set_source_rgba(cr, &shapes);
1368 cairo_rectangle(cr, box_w * i + inset + lw / 2.0f, allocation.height - sh, box_w - lw, 2.0f * lw);
1369 cairo_fill(cr);
1370 }
1371
1372 // draw the border
1373 cairo_set_line_width(cr, lw);
1374 gdk_cairo_set_source_rgba(cr, &border);
1375 cairo_rectangle(cr, box_w * i + inset, sh, box_w, box_h);
1376 cairo_stroke(cr);
1377 }
1378
1379 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1380 cairo_restore(cr);
1381
1382 // dot for the current scale
1383 if(p->curr_scale >= p->merge_from_scale && p->curr_scale <= p->num_scales && p->merge_from_scale > 0)
1384 color = active;
1385 else
1386 color = merge_from;
1387
1388 if(p->curr_scale >= 0 && p->curr_scale < RETOUCH_NO_SCALES)
1389 {
1390 cairo_set_line_width(cr, lw);
1391 gdk_cairo_set_source_rgba(cr, &color);
1392 middle = box_w * (0.5f + (float)p->curr_scale);
1393 cairo_arc(cr, middle + inset, 0.5f * box_h + sh, 0.5f * inset, 0, 2.0f * M_PI);
1394 cairo_fill(cr);
1395 cairo_stroke(cr);
1396 }
1397
1398 // mouse hover on a scale
1399 if(g->curr_scale >= 0)
1400 {
1401 cairo_set_line_width(cr, lw);
1402 if(g->curr_scale == p->num_scales + 1) color = inactive;
1403 else color = residual;
1404 gdk_cairo_set_source_rgba(cr, &color);
1405 cairo_rectangle(cr, box_w * g->curr_scale + inset + lw, sh + lw, box_w - 2.0f * lw, box_h - 2.0f * lw);
1406 cairo_stroke(cr);
1407 }
1408
1409 /* render control points handles */
1410
1411 // draw number of scales arrow (bottom arrow)
1412 middle = box_w * (0.5f + (float)p->num_scales);
1413 if(g->lower_cursor || g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM)
1414 {
1415 cairo_set_source_rgb(cr, 0.67, 0.67, 0.67);
1416 dtgtk_cairo_paint_solid_triangle(cr, middle, box_h + 5.0f * lw, mk, mk, CPF_DIRECTION_UP, NULL);
1417 }
1418 else
1419 {
1420 cairo_set_source_rgb(cr, 0.54, 0.54, 0.54);
1421 dtgtk_cairo_paint_triangle(cr, middle, box_h + 5.0f * lw, mk, mk, CPF_DIRECTION_UP, NULL);
1422 }
1423
1424 // draw merge scales arrow (top arrow)
1425 middle = box_w * (0.5f + (float)p->merge_from_scale);
1426 if(g->upper_cursor || g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_TOP)
1427 {
1428 cairo_set_source_rgb(cr, 0.67, 0.67, 0.67);
1429 dtgtk_cairo_paint_solid_triangle(cr, middle, 3.0f * lw, mk, mk, CPF_DIRECTION_DOWN, NULL);
1430 }
1431 else
1432 {
1433 cairo_set_source_rgb(cr, 0.54, 0.54, 0.54);
1434 dtgtk_cairo_paint_triangle(cr, middle, 3.0f * lw, mk, mk, CPF_DIRECTION_DOWN, NULL);
1435 }
1436
1437 /* push mem surface into widget */
1438 cairo_destroy(cr);
1439 cairo_set_source_surface(crf, cst, 0, 0);
1440 cairo_paint(crf);
1441 cairo_surface_destroy(cst);
1442
1443 return TRUE;
1444}
1445
1446static float rt_gslider_scale_callback(GtkWidget *self, float inval, int dir)
1447{
1448 float outval;
1449 switch(dir)
1450 {
1453 break;
1456 break;
1457 default:
1458 outval = inval;
1459 }
1460 return outval;
1461}
1462
1463
1465{
1467
1468 double dlevels[3];
1469
1470 if(dt_gui_widgets_suppressed()) return;
1471
1473
1474 for (int i = 0; i < 3; i++) p->preview_levels[i] = dlevels[i];
1475
1476 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1477
1478}
1479
1480
1482{
1485
1486 if(fabsf(p->fill_color[0] - self->picked_output_color[0]) < 0.0001f
1487 && fabsf(p->fill_color[1] - self->picked_output_color[1]) < 0.0001f
1488 && fabsf(p->fill_color[2] - self->picked_output_color[2]) < 0.0001f)
1489 {
1490 // interrupt infinite loops
1491 return;
1492 }
1493
1494 p->fill_color[0] = self->picked_output_color[0];
1495 p->fill_color[1] = self->picked_output_color[1];
1496 p->fill_color[2] = self->picked_output_color[2];
1497
1498 const int index = rt_get_selected_shape_index(self->dev, p);
1499 if(index >= 0)
1500 {
1501 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1502 {
1503 p->rt_forms[index].fill_color[0] = p->fill_color[0];
1504 p->rt_forms[index].fill_color[1] = p->fill_color[1];
1505 p->rt_forms[index].fill_color[2] = p->fill_color[2];
1506 }
1507 }
1508
1510
1511 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1512}
1513
1514static gboolean rt_copypaste_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1515{
1516 if(dt_gui_widgets_suppressed()) return TRUE;
1517
1519
1520 int scale_copied = 0;
1521 const int active = !gtk_toggle_button_get_active(togglebutton);
1524
1525 if(togglebutton == (GtkToggleButton *)g->bt_copy_scale)
1526 {
1527 g->copied_scale = (active) ? p->curr_scale : -1;
1528 }
1529 else if(togglebutton == (GtkToggleButton *)g->bt_paste_scale)
1530 {
1531 rt_paste_forms_from_scale(p, g->copied_scale, p->curr_scale);
1533
1534 scale_copied = 1;
1535 g->copied_scale = -1;
1536 }
1537
1538 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_copy_scale), g->copied_scale >= 0);
1539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_paste_scale), g->copied_scale >= 0);
1540 gtk_widget_set_sensitive(g->bt_paste_scale, g->copied_scale >= 0);
1541
1543
1544 if(scale_copied) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1545
1546 return TRUE;
1547}
1548
1549// Whether some OTHER source (the generic blend-mask display) currently occupies
1550// request_mask_display, as opposed to one of retouch's own preview toggles below.
1552{
1553 return module->request_mask_display && !g->mask_display && !g->display_wavelet_scale;
1554}
1555
1556// Whether any of retouch's own preview toggles needs the pipeline cache bypassed for this
1557// module. Combines all three (not just the caller's own flag), or clearing one would drop a
1558// bypass another still needs -- bypass_cache is a single shared field, unlike
1559// request_mask_display below, so no cross-module ownership concern applies here.
1561{
1562 return g->mask_display || g->display_wavelet_scale || g->suppress_mask;
1563}
1564
1565// Syncs bypass_cache (whether the pipe needs to bypass at all) and bypass_cache_variant (which
1566// combination of the three preview toggles is active) together. bypass_cache alone cannot
1567// distinguish e.g. "wavelet scale only" from "wavelet scale + suppress": both keep it TRUE, so
1568// toggling suppress on top of an already-active wavelet-scale preview would not change the
1569// pipeline hash and the display would keep showing the pre-toggle (still-retouched) frame even
1570// though process_internal()/process_cl() had already recomputed the correct, retouch-free one.
1571// bypass_cache_variant closes that gap -- see dt_iop_module_t.bypass_cache_variant.
1573{
1574 const int variant = (g->mask_display ? 1 : 0) | (g->display_wavelet_scale ? 2 : 0) | (g->suppress_mask ? 4 : 0);
1575 dt_iop_set_cache_bypass_variant(module, variant);
1577}
1578
1579// Mirrors mask_display/display_wavelet_scale into the pipeline cache key
1580// (request_mask_display) -- otherwise toggling either back off keeps serving whatever was last
1581// cached instead of the real image, since these GUI-only flags are invisible to the pipeline
1582// hash on their own. Keep them on separate bits (mirroring the MASK-vs-PASSTHRU split
1583// process_internal already applies to pipe->mask_display) rather than collapsing to one value,
1584// or "show mask" and "show wavelet scale" would hash identically whenever curr_scale/params
1585// haven't changed and one preview mode would reuse the other's cached buffer.
1586//
1587// Only call this once rt_blend_mask_conflicts() has already been checked and passed: unlike
1588// bypass_cache, request_mask_display can belong to the generic blend-mask display instead of to
1589// retouch, and must not be blindly overwritten. rt_suppress_callback has no such guard, so it
1590// only calls rt_sync_bypass_cache() (bypass_cache/bypass_cache_variant), never this.
1592{
1594 if(g->mask_display) display_request |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1595 if(g->display_wavelet_scale) display_request |= DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
1596 module->request_mask_display = display_request;
1597}
1598
1599static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1600{
1601 if(dt_gui_widgets_suppressed()) return TRUE;
1602
1605
1606 // if blend module is displaying mask do not display wavelet scales
1607 if(rt_blend_mask_conflicts(self, g))
1608 {
1609 dt_control_log(_("cannot display scales when the blending mask is displayed"));
1610
1612 gtk_toggle_button_set_active(togglebutton, FALSE);
1614 return TRUE;
1615 }
1616
1617 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1619
1620 g->display_wavelet_scale = !gtk_toggle_button_get_active(togglebutton);
1622 rt_sync_bypass_cache(self, g);
1623
1625
1626 // compute auto levels only the first time display wavelet scale is used,
1627 // only if levels values are the default
1628 // and a detail scale is displayed
1630 if(g->displayed_wavelet_scale == 0 && p->preview_levels[0] == RETOUCH_PREVIEW_LVL_MIN
1631 && p->preview_levels[1] == 0.f && p->preview_levels[2] == RETOUCH_PREVIEW_LVL_MAX
1632 && g->preview_auto_levels == 0 && p->curr_scale > 0 && p->curr_scale <= p->num_scales)
1633 {
1634 g->preview_auto_levels = 1;
1635 g->displayed_wavelet_scale = 1;
1636 }
1638
1640
1641 gtk_toggle_button_set_active(togglebutton, g->display_wavelet_scale);
1642 return TRUE;
1643}
1644
1645static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
1646{
1647 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1650
1651 // FIXME: this doesn't seems the right place to update params and GUI ...
1652 // update auto levels
1654 if(g->preview_auto_levels == 2)
1655 {
1656 g->preview_auto_levels = -1;
1657
1659
1660 for(int i = 0; i < 3; i++) p->preview_levels[i] = g->preview_levels[i];
1661
1662 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1663
1665
1666 // update the gradient slider
1667 double dlevels[3];
1668 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
1669
1671 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
1673
1674 g->preview_auto_levels = 0;
1675 }
1677
1678 // just in case zoom level has changed
1679 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
1680}
1681
1682static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1683{
1684 if(dt_gui_widgets_suppressed()) return FALSE;
1685
1687
1688 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1690
1692 if(g->preview_auto_levels == 0)
1693 {
1694 g->preview_auto_levels = 1;
1695 }
1697
1699
1700 return TRUE;
1701}
1702
1704{
1705 if(dt_gui_widgets_suppressed()) return;
1706
1707 const int shape_id = rt_get_selected_shape_id(self);
1708
1709 if(shape_id > 0)
1710 {
1711 const float opacity = dt_bauhaus_slider_get(slider);
1712 rt_masks_form_change_opacity(self, shape_id, opacity);
1713 }
1714
1715 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1716}
1717
1719 cairo_t *cr,
1720 int32_t width,
1721 int32_t height,
1722 int32_t pointerx,
1723 int32_t pointery)
1724{
1726 if(IS_NULL_PTR(g)) return;
1727
1728 const int shape_id = rt_get_selected_shape_id(self);
1729
1730 if(shape_id > 0)
1731 {
1733 dt_bauhaus_slider_set(g->sl_mask_opacity, rt_masks_form_get_opacity(self, shape_id));
1735 }
1736}
1737
1738static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1739{
1740 if(dt_gui_widgets_suppressed()) return FALSE;
1741
1742 // if we don't have the focus, request for it and quit, gui_focus() do the rest
1743 if(self->dev->gui_module != self)
1744 {
1746 return FALSE;
1747 }
1748
1751
1752 //hide all shapes and free if some are in creation
1753 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
1754 dt_masks_change_form_gui(self->dev, NULL);
1755
1757
1758 if(event->button == 1)
1759 {
1761
1763
1765 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
1766 {
1767 const gboolean control_button_pressed = dt_modifier_is(event->state, DT_PRIMARY_MASK);
1768
1769 switch(bd->masks_shown)
1770 {
1771 case DT_MASKS_EDIT_FULL:
1772 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_OFF;
1773 break;
1774
1776 bd->masks_shown = !control_button_pressed ? DT_MASKS_EDIT_FULL : DT_MASKS_EDIT_OFF;
1777 break;
1778
1779 default:
1780 case DT_MASKS_EDIT_OFF:
1781 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_FULL;
1782 break;
1783 }
1784 }
1785 else
1787
1789
1790 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
1791 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
1792
1794
1795 return TRUE;
1796 }
1797
1798 return TRUE;
1799}
1800
1803{
1804 return ((from == DT_IOP_RETOUCH_CLONE && to == DT_IOP_RETOUCH_HEAL)
1805 || (from == DT_IOP_RETOUCH_HEAL && to == DT_IOP_RETOUCH_CLONE)
1806 || (from == DT_IOP_RETOUCH_BLUR && to == DT_IOP_RETOUCH_FILL)
1807 || (from == DT_IOP_RETOUCH_FILL && to == DT_IOP_RETOUCH_BLUR));
1808}
1809
1810static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e,
1811 dt_iop_module_t *self)
1812{
1813 if(dt_gui_widgets_suppressed()) return FALSE;
1814
1816
1819
1821
1822 if(togglebutton == (GtkToggleButton *)g->bt_blur)
1823 new_algo = DT_IOP_RETOUCH_BLUR;
1824 else if(togglebutton == (GtkToggleButton *)g->bt_clone)
1825 new_algo = DT_IOP_RETOUCH_CLONE;
1826 else if(togglebutton == (GtkToggleButton *)g->bt_heal)
1827 new_algo = DT_IOP_RETOUCH_HEAL;
1828 else if(togglebutton == (GtkToggleButton *)g->bt_fill)
1829 new_algo = DT_IOP_RETOUCH_FILL;
1830
1831 // check if we have to do something
1832 gboolean accept = TRUE;
1833
1834 const int index = rt_get_selected_shape_index(self->dev, p);
1835 if(index >= 0 && dt_modifier_is(e->state, DT_PRIMARY_MASK))
1836 {
1837 const dt_iop_retouch_algo_type_t current_algo = p->rt_forms[index].algorithm;
1838 accept = (new_algo != current_algo && !rt_algo_pair_compatible(current_algo, new_algo));
1839 }
1840
1841 if(accept) p->algorithm = new_algo;
1842
1843 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), (p->algorithm == DT_IOP_RETOUCH_CLONE));
1844 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), (p->algorithm == DT_IOP_RETOUCH_HEAL));
1845 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), (p->algorithm == DT_IOP_RETOUCH_BLUR));
1846 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), (p->algorithm == DT_IOP_RETOUCH_FILL));
1847
1849
1850 if(!accept)
1851 {
1853 return FALSE;
1854 }
1855
1856 if(index >= 0 && dt_modifier_is(e->state, DT_PRIMARY_MASK))
1857 {
1858 if(p->algorithm != p->rt_forms[index].algorithm)
1859 {
1860 p->rt_forms[index].algorithm = p->algorithm;
1862 }
1863 }
1864 else if(self->dev->form_gui->creation && (self->dev->form_gui->creation_module == self))
1865 {
1867
1869 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_polygon)))
1871 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle)))
1873 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse)))
1875 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_brush)))
1877
1878 dt_masks_type_t masks_type = DT_MASKS_NONE;
1879 if(p->algorithm == DT_IOP_RETOUCH_CLONE || p->algorithm == DT_IOP_RETOUCH_HEAL)
1880 masks_type = (type | DT_MASKS_CLONE);
1881 else
1882 masks_type = (type | DT_MASKS_NON_CLONE);
1883
1884 dt_masks_creation_mode_enter(self->dev, self, masks_type);
1885
1887 }
1888
1890
1891 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1892
1893 // if we have the shift key pressed, we set it as default
1894 if(dt_modifier_is(e->state, GDK_SHIFT_MASK))
1895 {
1896 dt_conf_set_int("plugins/darkroom/retouch/default_algo", p->algorithm);
1897 // and we show a toat msg to confirm
1898 if(p->algorithm == DT_IOP_RETOUCH_CLONE)
1899 dt_control_log(N_("default tool changed to %s"), N_("cloning"));
1900 else if(p->algorithm == DT_IOP_RETOUCH_HEAL)
1901 dt_control_log(N_("default tool changed to %s"), N_("healing"));
1902 else if(p->algorithm == DT_IOP_RETOUCH_FILL)
1903 dt_control_log(N_("default tool changed to %s"), N_("blur"));
1904 else if(p->algorithm == DT_IOP_RETOUCH_BLUR)
1905 dt_control_log(N_("default tool changed to %s"), N_("fill"));
1906 }
1907
1908 return TRUE;
1909}
1910
1911static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1912{
1913 if(dt_gui_widgets_suppressed()) return TRUE;
1914
1916
1917 // if blend module is displaying mask do not display it here
1918 if(rt_blend_mask_conflicts(module, g))
1919 {
1920 dt_control_log(_("cannot display masks when the blending mask is displayed"));
1921
1922 gtk_toggle_button_set_active(togglebutton, FALSE);
1923 return TRUE;
1924 }
1925
1926 g->mask_display = !gtk_toggle_button_get_active(togglebutton);
1928 rt_sync_bypass_cache(module, g);
1929
1930 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), 1);
1931 dt_iop_request_focus(module);
1932
1934
1935 gtk_toggle_button_set_active(togglebutton, g->mask_display);
1936 return TRUE;
1937}
1938
1939static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1940{
1941 if(dt_gui_widgets_suppressed()) return TRUE;
1942
1944 g->suppress_mask = !gtk_toggle_button_get_active(togglebutton);
1945 // No rt_blend_mask_conflicts() guard on this button, so only bypass_cache/bypass_cache_variant
1946 // are synced here -- request_mask_display may currently belong to the blend-mask display, not
1947 // to retouch.
1948 rt_sync_bypass_cache(module, g);
1949
1950 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), 1);
1951 dt_iop_request_focus(module);
1952
1954
1955 gtk_toggle_button_set_active(togglebutton, g->suppress_mask);
1956 return TRUE;
1957}
1958
1959void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1960{
1963
1964 if(w == g->cmb_fill_mode)
1965 {
1969 }
1970 else
1971 {
1972 const int index = rt_get_selected_shape_index(self->dev, p);
1973 if(index >= 0)
1974 {
1975 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR)
1976 {
1977 p->rt_forms[index].blur_type = p->blur_type;
1978 p->rt_forms[index].blur_radius = p->blur_radius;
1979 }
1980 else if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1981 {
1982 p->rt_forms[index].fill_mode = p->fill_mode;
1983 p->rt_forms[index].fill_brightness = p->fill_brightness;
1984 }
1985 }
1986 }
1987}
1988
1989//--------------------------------------------------------------------------------------------------
1990// GUI
1991//--------------------------------------------------------------------------------------------------
1992
1993void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
1994{
1996 if(IS_NULL_PTR(g)) return;
1997
1999 rt_load_shape_algo_in_gui(self, form_selected_id);
2001}
2002
2004{
2005 dt_iop_default_init(module);
2006
2007 dt_iop_retouch_params_t *d = module->default_params;
2008
2010 d->preview_levels[1] = 0.f;
2011 d->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
2012 d->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2013}
2014
2016{
2017 const int program = 21; // retouch.cl, from programs.conf
2019 module->data = gd;
2020 gd->kernel_retouch_clear_alpha = dt_opencl_create_kernel(program, "retouch_clear_alpha");
2021 gd->kernel_retouch_copy_alpha = dt_opencl_create_kernel(program, "retouch_copy_alpha");
2022 gd->kernel_retouch_copy_buffer_to_buffer = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer");
2023 gd->kernel_retouch_copy_buffer_to_image = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_image");
2024 gd->kernel_retouch_fill = dt_opencl_create_kernel(program, "retouch_fill");
2026 = dt_opencl_create_kernel(program, "retouch_copy_image_to_buffer_masked");
2028 = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer_masked");
2029 gd->kernel_retouch_image_rgb2lab = dt_opencl_create_kernel(program, "retouch_image_rgb2lab");
2030 gd->kernel_retouch_image_lab2rgb = dt_opencl_create_kernel(program, "retouch_image_lab2rgb");
2031 gd->kernel_retouch_copy_mask_to_alpha = dt_opencl_create_kernel(program, "retouch_copy_mask_to_alpha");
2032}
2033
2051
2052void gui_focus(struct dt_iop_module_t *self, gboolean in)
2053{
2054 if(self->enabled)
2055 {
2057
2058 if(in)
2059 {
2061 //only show shapes if shapes exist
2063 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
2064 {
2065 // got focus, show all shapes
2068
2070
2071 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
2073 && (self->dev->gui_module == self));
2074 }
2075 }
2076 else
2077 {
2078 // lost focus, hide all shapes and free if some are in creation
2079 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
2080 dt_masks_change_form_gui(self->dev, NULL);
2081
2083 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2084
2086 }
2087
2088 // if we are switching between display modes we have to reprocess the main image
2089 if(g->display_wavelet_scale || g->mask_display || g->suppress_mask)
2091 }
2092}
2093
2097{
2098 dt_iop_retouch_params_t synced_params = *(dt_iop_retouch_params_t *)params;
2099 if(!IS_NULL_PTR(pipe) && !IS_NULL_PTR(pipe->forms))
2100 {
2101 rt_resynch_params(self, &synced_params, pipe->forms);
2102 }
2103 else
2104 {
2106 rt_resynch_params(self, &synced_params, self->dev->forms);
2108 }
2109
2110 memcpy(piece->data, &synced_params, sizeof(dt_iop_retouch_params_t));
2111}
2112
2113void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
2114{
2116 const float require = 2.0f;
2117 const float require_cl = 1.0f // in_retouch
2118 + ((p->num_scales > 0) ? 4.0f : 2.0f); // dwt_wavelet_decompose_cl requires 4 buffers, otherwise 2.0f is enough
2119 // FIXME the above are worst case values, we might iterate through the dt_iop_retouch_form_data_t to get
2120 // the largest bounding box
2121
2122 tiling->factor = 2.0f + require; // input & output buffers + internal requirements
2123 tiling->factor_cl = 2.0f + require_cl;
2124 tiling->maxbuf = 1.0f;
2125 tiling->maxbuf_cl = 1.0f;
2126 tiling->overhead = 0;
2127 tiling->overlap = 0;
2128 tiling->xalign = 1;
2129 tiling->yalign = 1;
2130}
2131
2133{
2134 piece->data = dt_calloc_align(sizeof(dt_iop_retouch_data_t));
2135 piece->data_size = sizeof(dt_iop_retouch_data_t);
2136}
2137
2139{
2140 dt_free_align(piece->data);
2141 piece->data = NULL;
2142}
2143
2145{
2148
2149 // check if there is new or deleted forms
2151 rt_resynch_params(self, p, self->dev->forms);
2153
2154 // update clones count
2155 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id);
2156 guint nb = 0;
2157 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
2158 gchar *str = g_strdup_printf("%d", nb);
2159 gtk_label_set_text(g->label_form, str);
2160 dt_free(str);
2161
2162 // update wavelet decompose labels
2164
2165 // update selected shape label
2167
2168 // show the shapes for the current scale
2170
2171 // enable/disable algorithm toolbar
2172 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), p->algorithm == DT_IOP_RETOUCH_CLONE);
2173 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), p->algorithm == DT_IOP_RETOUCH_BLUR);
2174 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), p->algorithm == DT_IOP_RETOUCH_HEAL);
2175 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), p->algorithm == DT_IOP_RETOUCH_FILL);
2176
2177 // enable/disable shapes toolbar
2178 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), rt_shape_is_being_added(self, DT_MASKS_CIRCLE));
2179 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_polygon), rt_shape_is_being_added(self, DT_MASKS_POLYGON));
2180 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), rt_shape_is_being_added(self, DT_MASKS_ELLIPSE));
2181 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_brush), rt_shape_is_being_added(self, DT_MASKS_BRUSH));
2182
2183 // update the rest of the fields
2184 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
2185
2186 dt_bauhaus_combobox_set(g->cmb_blur_type, p->blur_type);
2187 dt_bauhaus_slider_set(g->sl_blur_radius, p->blur_radius);
2188 dt_bauhaus_slider_set(g->sl_fill_brightness, p->fill_brightness);
2189 dt_bauhaus_combobox_set(g->cmb_fill_mode, p->fill_mode);
2190
2192
2193 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_display_wavelet_scale), g->display_wavelet_scale);
2194 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_copy_scale), g->copied_scale >= 0);
2195 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_paste_scale), g->copied_scale >= 0);
2196 gtk_widget_set_sensitive(g->bt_paste_scale, g->copied_scale >= 0);
2197
2198 // show/hide some fields
2200
2201 // update edit shapes status
2203
2204 //only toggle shape show button if shapes exist
2205 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
2206 {
2207 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
2208 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
2209 }
2210 else
2211 {
2212 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2213 }
2214
2215 // update the gradient slider
2216 double dlevels[3];
2217 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
2218 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
2219}
2220
2222{
2224 if(!IS_NULL_PTR(g))
2225 {
2226 g->copied_scale = -1;
2227 g->mask_display = 0;
2228 g->suppress_mask = 0;
2229 g->display_wavelet_scale = 0;
2230 g->displayed_wavelet_scale = 0;
2231 g->first_scale_visible = RETOUCH_MAX_SCALES + 1;
2232
2233 g->preview_auto_levels = 0;
2234 g->preview_levels[0] = RETOUCH_PREVIEW_LVL_MIN;
2235 g->preview_levels[1] = 0.f;
2236 g->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
2237
2238 g->is_dragging = 0;
2239 g->wdbar_mouse_x = g->wdbar_mouse_y = -1;
2240 g->curr_scale = -1;
2241 g->lower_cursor = g->upper_cursor = FALSE;
2242 g->lower_margin = g->upper_margin = FALSE;
2243 }
2244}
2245
2247{
2250
2251 change_image(self);
2252
2253 // shapes toolbar
2254 GtkWidget *hbox_shapes = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2255
2256 gtk_box_pack_start(GTK_BOX(hbox_shapes), dt_ui_label_new(_("shapes:")), FALSE, TRUE, 0);
2257 g->label_form = GTK_LABEL(gtk_label_new("-1"));
2258 gtk_box_pack_start(GTK_BOX(hbox_shapes), GTK_WIDGET(g->label_form), FALSE, TRUE, DT_PIXEL_APPLY_DPI(5));
2259 gtk_widget_set_tooltip_text(hbox_shapes,
2260 _("to add a shape select an algorithm and a shape type and click on the image.\n"
2261 "shapes are added to the current scale"));
2262
2263 g->bt_edit_masks = dt_iop_togglebutton_new(self, N_("editing"), N_("show and edit shapes on the current scale"),
2264 N_("show and edit shapes in restricted mode"),
2265 G_CALLBACK(rt_edit_masks_callback), TRUE, 0, 0,
2266 dtgtk_cairo_paint_masks_edit, hbox_shapes);
2267
2268 GtkWidget *shape_buttons[DEVELOP_MASKS_NB_SHAPES] = { 0 };
2269 const dt_masks_shape_buttons_config_t shape_buttons_config = {
2270 .dev = self->dev,
2271 .owner_module = self,
2272 .creation_module = self,
2273 .buttons = shape_buttons,
2274 .types = NULL,
2275 .action_section = N_("shapes"),
2276 .flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT, // All shapes minus gradient
2277 .register_flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT,
2278 .local = TRUE,
2279 .user_data = NULL,
2280 .can_start = rt_shape_buttons_can_start,
2281 .form_type = rt_shape_buttons_form_type,
2282 .started = NULL,
2283 .exited = NULL,
2284 };
2285 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
2286 gtk_box_pack_start(GTK_BOX(hbox_shapes), shape_buttons_box, FALSE, FALSE, 0);
2287 g->bt_circle = shape_buttons[DT_MASKS_SHAPE_INDEX_CIRCLE];
2288 g->bt_ellipse = shape_buttons[DT_MASKS_SHAPE_INDEX_ELLIPSE];
2289 g->bt_polygon = shape_buttons[DT_MASKS_SHAPE_INDEX_POLYGON];
2290 g->bt_brush = shape_buttons[DT_MASKS_SHAPE_INDEX_BRUSH];
2291
2292 // algorithm toolbar
2293 GtkWidget *hbox_algo = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2294
2295 gtk_box_pack_start(GTK_BOX(hbox_algo), dt_ui_label_new(_("algorithms:")), FALSE, TRUE, 0);
2296
2297 g->bt_blur = dt_iop_togglebutton_new(
2298 self, N_("tools"), N_("activate blur tool"), N_("change algorithm for current form"),
2299 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_blur, hbox_algo);
2300
2301 g->bt_fill = dt_iop_togglebutton_new(
2302 self, N_("tools"), N_("activate fill tool"), N_("change algorithm for current form"),
2303 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_fill, hbox_algo);
2304
2305 g->bt_clone = dt_iop_togglebutton_new(
2306 self, N_("tools"), N_("activate cloning tool"), N_("change algorithm for current form"),
2307 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_clone, hbox_algo);
2308
2309 g->bt_heal = dt_iop_togglebutton_new(
2310 self, N_("tools"), N_("activate healing tool"), N_("change algorithm for current form"),
2311 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_heal, hbox_algo);
2312
2313 // overwrite tooltip ourself to handle shift+click
2314 gchar *tt2 = g_strdup_printf("%s\n%s", _("ctrl+click to change tool for current form"),
2315 _("shift+click to set the tool as default"));
2316 gchar *tt = g_strdup_printf("%s\n%s", _("activate blur tool"), tt2);
2317 gtk_widget_set_tooltip_text(g->bt_blur, tt);
2318 dt_free(tt);
2319 tt = g_strdup_printf("%s\n%s", _("activate fill tool"), tt2);
2320 gtk_widget_set_tooltip_text(g->bt_fill, tt);
2321 dt_free(tt);
2322 tt = g_strdup_printf("%s\n%s", _("activate cloning tool"), tt2);
2323 gtk_widget_set_tooltip_text(g->bt_clone, tt);
2324 dt_free(tt);
2325 tt = g_strdup_printf("%s\n%s", _("activate healing tool"), tt2);
2326 gtk_widget_set_tooltip_text(g->bt_heal, tt);
2327 dt_free(tt);
2328 dt_free(tt2);
2329
2330 // wavelet decompose bar labels
2331 GtkWidget *grid_wd_labels = gtk_grid_new();
2332 gtk_grid_set_column_homogeneous(GTK_GRID(grid_wd_labels), FALSE);
2333
2334 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("scales:")), 0, 0, 1, 1);
2335 g->lbl_num_scales = GTK_LABEL(dt_ui_label_new(NULL));
2336 gtk_label_set_width_chars(g->lbl_num_scales, 2);
2337 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_num_scales), 1, 0, 1, 1);
2338
2339 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("current:")), 0, 1, 1, 1);
2340 g->lbl_curr_scale = GTK_LABEL(dt_ui_label_new(NULL));
2341 gtk_label_set_width_chars(g->lbl_curr_scale, 2);
2342 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_curr_scale), 1, 1, 1, 1);
2343
2344 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("merge from:")), 0, 2, 1, 1);
2345 g->lbl_merge_from_scale = GTK_LABEL(dt_ui_label_new(NULL));
2346 gtk_label_set_width_chars(g->lbl_merge_from_scale, 2);
2347 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_merge_from_scale), 1, 2, 1, 1);
2348
2349 // wavelet decompose bar
2350 g->wd_bar = gtk_drawing_area_new();
2351
2352 gtk_widget_set_tooltip_text(g->wd_bar, _("top slider adjusts where the merge scales start\n"
2353 "bottom slider adjusts the number of scales\n"
2354 "dot indicates the current scale\n"
2355 "top line indicates that the scale is visible at current zoom level\n"
2356 "bottom line indicates that the scale has shapes on it"));
2357 g_signal_connect(G_OBJECT(g->wd_bar), "draw", G_CALLBACK(rt_wdbar_draw), self);
2358 g_signal_connect(G_OBJECT(g->wd_bar), "motion-notify-event", G_CALLBACK(rt_wdbar_motion_notify), self);
2359 g_signal_connect(G_OBJECT(g->wd_bar), "leave-notify-event", G_CALLBACK(rt_wdbar_leave_notify), self);
2360 g_signal_connect(G_OBJECT(g->wd_bar), "button-press-event", G_CALLBACK(rt_wdbar_button_press), self);
2361 g_signal_connect(G_OBJECT(g->wd_bar), "button-release-event", G_CALLBACK(rt_wdbar_button_release), self);
2362 g_signal_connect(G_OBJECT(g->wd_bar), "scroll-event", G_CALLBACK(rt_wdbar_scrolled), self);
2363 gtk_widget_add_events(GTK_WIDGET(g->wd_bar), GDK_POINTER_MOTION_MASK
2364 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2365 | GDK_LEAVE_NOTIFY_MASK | dt_widget_scroll_mask());
2366 gtk_widget_set_size_request(g->wd_bar, -1, DT_PIXEL_APPLY_DPI(40));
2367
2368 // toolbar display current scale / cut&paste / suppress&display masks
2369 GtkWidget *hbox_scale = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2370
2371 // display & suppress masks
2372 g->bt_showmask = dt_iop_togglebutton_new(self, N_("editing"), N_("display masks"), NULL,
2373 G_CALLBACK(rt_showmask_callback), TRUE, 0, 0,
2374 dtgtk_cairo_paint_showmask, hbox_scale);
2375
2376
2377 g->bt_suppress = dt_iop_togglebutton_new(self, N_("editing"), N_("temporarily switch off shapes"), NULL,
2378 G_CALLBACK(rt_suppress_callback), TRUE, 0, 0,
2379 dtgtk_cairo_paint_eye_toggle, hbox_scale);
2380
2381
2382 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2383
2384 // copy/paste shapes
2385 g->bt_paste_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("paste cut shapes to current scale"), NULL,
2386 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2387 dtgtk_cairo_paint_paste_forms, hbox_scale);
2388
2389 g->bt_copy_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("cut shapes from current scale"), NULL,
2390 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2391 dtgtk_cairo_paint_cut_forms, hbox_scale);
2392
2393 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2394
2395 // display final image/current scale
2396 g->bt_display_wavelet_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("display wavelet scale"), NULL,
2397 G_CALLBACK(rt_display_wavelet_scale_callback), TRUE, 0, 0,
2399
2400
2401 // preview single scale
2402 g->vbox_preview_scale = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2403
2404 GtkWidget *lbl_psc = dt_ui_section_label_new(_("preview single scale"));
2405 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), lbl_psc, FALSE, TRUE, 0);
2406
2407 GtkWidget *prev_lvl = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2408
2409 // gradient slider
2410 #define NEUTRAL_GRAY 0.5
2411 static const GdkRGBA _gradient_L[]
2412 = { { 0, 0, 0, 1.0 }, { NEUTRAL_GRAY, NEUTRAL_GRAY, NEUTRAL_GRAY, 1.0 } };
2413 g->preview_levels_gslider = DTGTK_GRADIENT_SLIDER_MULTIVALUE(
2415 gtk_widget_set_tooltip_text(GTK_WIDGET(g->preview_levels_gslider), _("adjust preview levels"));
2419 (g->preview_levels_gslider)->scale_callback = rt_gslider_scale_callback;
2421 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, vdefault);
2422 dtgtk_gradient_slider_multivalue_set_resetvalues(g->preview_levels_gslider, vdefault);
2423 (g->preview_levels_gslider)->markers_type = PROPORTIONAL_MARKERS;
2424 (g->preview_levels_gslider)->min_spacing = 0.05;
2425 g_signal_connect(G_OBJECT(g->preview_levels_gslider), "value-changed", G_CALLBACK(rt_gslider_changed), self);
2426
2427 gtk_box_pack_start(GTK_BOX(prev_lvl), GTK_WIDGET(g->preview_levels_gslider), TRUE, TRUE, 0);
2428
2429 // auto-levels button
2430 g->bt_auto_levels = dt_iop_togglebutton_new(self, N_("editing"), N_("auto levels"), NULL,
2431 G_CALLBACK(rt_auto_levels_callback), TRUE, 0, 0,
2433
2434 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), prev_lvl, TRUE, TRUE, 0);
2435
2436 // shapes selected (label)
2437 GtkWidget *hbox_shape_sel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2438 GtkWidget *label1 = gtk_label_new(_("shape selected:"));
2439 gtk_label_set_ellipsize(GTK_LABEL(label1), PANGO_ELLIPSIZE_START);
2440 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), label1, FALSE, TRUE, 0);
2441 g->label_form_selected = GTK_LABEL(gtk_label_new("-1"));
2442 gtk_widget_set_tooltip_text(hbox_shape_sel,
2443 _("click on a shape to select it,\nto unselect click on an empty space"));
2444 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), GTK_WIDGET(g->label_form_selected), FALSE, TRUE, 0);
2445
2446 // fill properties
2447 g->vbox_fill = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2448
2449 g->cmb_fill_mode = dt_bauhaus_combobox_from_params(self, "fill_mode");
2450 gtk_widget_set_tooltip_text(g->cmb_fill_mode, _("erase the detail or fills with chosen color"));
2451
2452 // color for fill algorithm
2453 GdkRGBA color
2454 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
2455
2456 g->hbox_color_pick = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2457 GtkWidget *lbl_fill_color = dt_ui_label_new(_("fill color: "));
2458 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), lbl_fill_color, FALSE, TRUE, 0);
2459
2460 g->colorpick = gtk_color_button_new_with_rgba(&color);
2461 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->colorpick), FALSE);
2462 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->colorpick), _("select fill color"));
2463 gtk_widget_set_tooltip_text(g->colorpick, _("select fill color"));
2464 g_signal_connect(G_OBJECT(g->colorpick), "color-set", G_CALLBACK(rt_colorpick_color_set_callback), self);
2465 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), GTK_WIDGET(g->colorpick), TRUE, TRUE, 0);
2466
2467 g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, g->hbox_color_pick);
2468 gtk_widget_set_tooltip_text(g->colorpicker, _("pick fill color from image"));
2469
2470 gtk_box_pack_start(GTK_BOX(g->vbox_fill), g->hbox_color_pick, TRUE, TRUE, 0);
2471
2472 g->sl_fill_brightness = dt_bauhaus_slider_from_params(self, "fill_brightness");
2473 dt_bauhaus_slider_set_digits(g->sl_fill_brightness, 4);
2474 dt_bauhaus_slider_set_format(g->sl_fill_brightness, "%");
2475 gtk_widget_set_tooltip_text(g->sl_fill_brightness,
2476 _("adjusts color brightness to fine-tune it. works with erase as well"));
2477
2478 // blur properties
2479 g->vbox_blur = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2480
2481 g->cmb_blur_type = dt_bauhaus_combobox_from_params(self, "blur_type");
2482 gtk_widget_set_tooltip_text(g->cmb_blur_type, _("type for the blur algorithm"));
2483
2484 g->sl_blur_radius = dt_bauhaus_slider_from_params(self, "blur_radius");
2485 dt_bauhaus_slider_set_format(g->sl_blur_radius, " px");
2486 gtk_widget_set_tooltip_text(g->sl_blur_radius, _("radius of the selected blur type"));
2487
2488 // mask opacity
2489 g->sl_mask_opacity = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0, 1.0, 0, 1., 3);
2490 dt_bauhaus_widget_set_label(g->sl_mask_opacity, N_("mask opacity"));
2491 dt_bauhaus_slider_set_format(g->sl_mask_opacity, "%");
2492 gtk_widget_set_tooltip_text(g->sl_mask_opacity, _("set the opacity on the selected shape"));
2493 g_signal_connect(G_OBJECT(g->sl_mask_opacity), "value-changed", G_CALLBACK(rt_mask_opacity_callback), self);
2494
2495 // start building top level widget
2496 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2497
2498 GtkWidget *lbl_rt_tools = dt_ui_section_label_new(_("retouch tools"));
2499 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_rt_tools, FALSE, TRUE, 0);
2500
2501 // shapes toolbar
2502 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_shapes, TRUE, TRUE, 0);
2503 // algorithms toolbar
2504 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_algo, TRUE, TRUE, 0);
2505
2506 // wavelet decompose
2507 GtkWidget *lbl_wd = dt_ui_section_label_new(_("wavelet decompose"));
2508 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_wd, FALSE, TRUE, 0);
2509
2510 // wavelet decompose bar & labels
2511 gtk_box_pack_start(GTK_BOX(self->gui->widget), grid_wd_labels, TRUE, TRUE, 0);
2512 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->wd_bar, TRUE, TRUE, DT_PIXEL_APPLY_DPI(3));
2513
2514 // preview scale & cut/paste scale
2515 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_scale, TRUE, TRUE, 0);
2516
2517 // preview single scale
2518 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_preview_scale, TRUE, TRUE, 0);
2519
2520 // shapes
2521 GtkWidget *lbl_shapes = dt_ui_section_label_new(_("shapes"));
2522 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_shapes, FALSE, TRUE, 0);
2523
2524 // shape selected
2525 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_shape_sel, TRUE, TRUE, 0);
2526 // blur radius
2527 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_blur, TRUE, TRUE, 0);
2528 // fill color
2529 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_fill, TRUE, TRUE, 0);
2530 // mask (shape) opacity
2531 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->sl_mask_opacity, TRUE, TRUE, 0);
2532
2533 /* add signal handler for preview pipe finish to redraw the preview */
2535 G_CALLBACK(rt_develop_ui_pipe_finished_callback), self);
2536}
2537
2538void gui_reset(struct dt_iop_module_t *self)
2539{
2540 // hide the previous masks
2542 // set the algo to the default one
2544 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2545}
2546
2548{
2549 // set the algo to the default one
2551 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2552}
2553
2560
2561void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2562 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
2563 const dt_iop_roi_t *roi_in)
2564{
2565 *roi_out = *roi_in;
2566}
2567
2568static void rt_compute_roi_in(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *const pipe,
2569 struct dt_dev_pixelpipe_iop_t *piece,
2570 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2571{
2574
2575 int roir = *_roir;
2576 int roib = *_roib;
2577 int roix = *_roix;
2578 int roiy = *_roiy;
2579
2580 // We iterate through all forms
2581 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2582 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2583 {
2584 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2585 {
2586 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2587 if(IS_NULL_PTR(grpt)) continue;
2588
2589 const int formid = grpt->formid;
2590 const int index = rt_get_index_from_formid(p, formid);
2591 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
2592 {
2593 continue;
2594 }
2595
2596 // we get the spot
2597 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2598 if(IS_NULL_PTR(form)) continue;
2599
2600 // if the form is outside the roi, we just skip it
2601 // we get the area for the form
2602 int fl, ft, fw, fh;
2603 if(dt_masks_get_area(self, pipe, piece, form, &fw, &fh, &fl, &ft) != DT_MASKS_RASTER_OK)
2604 {
2605 continue;
2606 }
2607
2608 // is the form outside of the roi?
2609 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2610 if(ft >= roi_in->y + roi_in->height || ft + fh <= roi_in->y || fl >= roi_in->x + roi_in->width
2611 || fl + fw <= roi_in->x)
2612 {
2613 continue;
2614 }
2615
2616 // heal need the entire area
2617 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL)
2618 {
2619 // we enlarge the roi if needed
2620 roiy = fminf(ft, roiy);
2621 roix = fminf(fl, roix);
2622 roir = fmaxf(fl + fw, roir);
2623 roib = fmaxf(ft + fh, roib);
2624 }
2625 // blur need an overlap of 4 * radius (scaled)
2626 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR && index >= 0)
2627 {
2628 const int overlap = ceilf(4 * (p->rt_forms[index].blur_radius * roi_in->scale));
2629 if(roiy > ft) roiy = MAX(roiy - overlap, ft);
2630 if(roix > fl) roix = MAX(roix - overlap, fl);
2631 if(roir < fl + fw) roir = MAX(roir + overlap, fl + fw);
2632 if(roib < ft + fh) roib = MAX(roib + overlap, ft + fh);
2633 }
2634 // heal and clone need both source and destination areas
2635 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL
2636 || p->rt_forms[index].algorithm == DT_IOP_RETOUCH_CLONE)
2637 {
2638 float dx = 0.f, dy = 0.f;
2639 if(rt_masks_get_delta_to_destination(self, pipe, piece, roi_in, form, &dx, &dy,
2640 p->rt_forms[index].distort_mode))
2641 {
2642 roiy = fminf(ft - dy, roiy);
2643 roix = fminf(fl - dx, roix);
2644 roir = fmaxf(fl + fw - dx, roir);
2645 roib = fmaxf(ft + fh - dy, roib);
2646 }
2647 }
2648 }
2649 }
2650
2651 *_roir = roir;
2652 *_roib = roib;
2653 *_roix = roix;
2654 *_roiy = roiy;
2655}
2656
2657// for a given form, if a previous clone/heal destination intersects the source area,
2658// include that area in roi_in too
2660 struct dt_dev_pixelpipe_iop_t *piece,
2661 dt_iop_roi_t *roi_in, const int formid_src, const int fl_src,
2662 const int ft_src, const int fw_src, const int fh_src, int *_roir,
2663 int *_roib, int *_roix, int *_roiy)
2664{
2667
2668 int roir = *_roir;
2669 int roib = *_roib;
2670 int roix = *_roix;
2671 int roiy = *_roiy;
2672
2673 // We iterate through all forms
2674 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2675 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2676 {
2677 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2678 {
2679 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2680 if(IS_NULL_PTR(grpt)) continue;
2681
2682 const int formid = grpt->formid;
2683
2684 // just need the previous forms
2685 if(formid == formid_src) break;
2686
2687 const int index = rt_get_index_from_formid(p, formid);
2688
2689 // only process clone and heal
2690 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2691 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2692 {
2693 continue;
2694 }
2695
2696 // we get the spot
2697 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2698 if(IS_NULL_PTR(form)) continue;
2699
2700 // we get the source area
2701 int fl, ft, fw, fh;
2702 if(dt_masks_get_source_area(self, pipe, piece, form, &fw, &fh, &fl, &ft) != DT_MASKS_RASTER_OK)
2703 {
2704 continue;
2705 }
2706 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2707
2708 // get the destination area
2709 int fl_dest, ft_dest;
2710 float dx = 0.f, dy = 0.f;
2711 if(!rt_masks_get_delta_to_destination(self, pipe, piece, roi_in, form, &dx, &dy,
2712 p->rt_forms[index].distort_mode))
2713 {
2714 continue;
2715 }
2716
2717 ft_dest = ft + dy;
2718 fl_dest = fl + dx;
2719
2720 // check if the destination of this form intersects the source of the formid_src
2721 const int intersects = !(ft_dest + fh < ft_src || ft_src + fh_src < ft_dest || fl_dest + fw < fl_src
2722 || fl_src + fw_src < fl_dest);
2723 if(intersects)
2724 {
2725 // we enlarge the roi if needed
2726 roiy = fminf(ft, roiy);
2727 roix = fminf(fl, roix);
2728 roir = fmaxf(fl + fw, roir);
2729 roib = fmaxf(ft + fh, roib);
2730
2731 // need both source and destination areas
2732 roiy = fminf(ft + dy, roiy);
2733 roix = fminf(fl + dx, roix);
2734 roir = fmaxf(fl + fw + dx, roir);
2735 roib = fmaxf(ft + fh + dy, roib);
2736 }
2737 }
2738 }
2739
2740 *_roir = roir;
2741 *_roib = roib;
2742 *_roix = roix;
2743 *_roiy = roiy;
2744}
2745
2746// for clone and heal, if the source area is the destination from another clone/heal,
2747// we also need the area from that previous clone/heal
2749 struct dt_dev_pixelpipe_iop_t *piece,
2750 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2751{
2754
2755 int roir = *_roir;
2756 int roib = *_roib;
2757 int roix = *_roix;
2758 int roiy = *_roiy;
2759
2760 // go through all clone and heal forms
2761 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2762 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2763 {
2764 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2765 {
2766 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2767 if(IS_NULL_PTR(grpt)) continue;
2768
2769 const int formid = grpt->formid;
2770 const int index = rt_get_index_from_formid(p, formid);
2771
2772 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2773 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2774 {
2775 continue;
2776 }
2777
2778 // we get the spot
2779 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2780 if(IS_NULL_PTR(form))
2781 {
2782 continue;
2783 }
2784
2785 // get the source area
2786 int fl_src, ft_src, fw_src, fh_src;
2787 if(dt_masks_get_source_area(self, pipe, piece, form, &fw_src, &fh_src, &fl_src, &ft_src)
2789 {
2790 continue;
2791 }
2792
2793 fw_src *= roi_in->scale, fh_src *= roi_in->scale, fl_src *= roi_in->scale, ft_src *= roi_in->scale;
2794
2795 // we only want to process forms already in roi_in
2796 const int intersects
2797 = !(roib < ft_src || ft_src + fh_src < roiy || roir < fl_src || fl_src + fw_src < roix);
2798 if(intersects)
2799 rt_extend_roi_in_from_source_clones(self, pipe, piece, roi_in, formid, fl_src, ft_src, fw_src, fh_src,
2800 &roir, &roib, &roix, &roiy);
2801 }
2802 }
2803
2804 *_roir = roir;
2805 *_roib = roib;
2806 *_roix = roix;
2807 *_roiy = roiy;
2808}
2809
2810// needed if mask dest is in roi and mask src is not
2811void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2812 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
2813 dt_iop_roi_t *roi_in)
2814{
2815 dt_dev_pixelpipe_t *const processing_pipe = (dt_dev_pixelpipe_t *)pipe;
2816 *roi_in = *roi_out;
2817
2818 int roir = roi_in->width + roi_in->x;
2819 int roib = roi_in->height + roi_in->y;
2820 int roix = roi_in->x;
2821 int roiy = roi_in->y;
2822
2823 rt_compute_roi_in(self, processing_pipe, piece, roi_in, &roir, &roib, &roix, &roiy);
2824
2825 int roir_prev = -1, roib_prev = -1, roix_prev = -1, roiy_prev = -1;
2826
2827 while(roir != roir_prev || roib != roib_prev || roix != roix_prev || roiy != roiy_prev)
2828 {
2829 roir_prev = roir;
2830 roib_prev = roib;
2831 roix_prev = roix;
2832 roiy_prev = roiy;
2833
2834 rt_extend_roi_in_for_clone(self, processing_pipe, piece, roi_in, &roir, &roib, &roix, &roiy);
2835 }
2836
2837 // now we set the values
2838 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
2839 roi_in->x = CLAMP(roix, 0, scwidth - 1);
2840 roi_in->y = CLAMP(roiy, 0, scheight - 1);
2841 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
2842 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
2843}
2844
2845//--------------------------------------------------------------------------------------------------
2846// process
2847//--------------------------------------------------------------------------------------------------
2848
2849static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
2850{
2851 const int stride = width * height * ch;
2853 for(int i = 0; i < stride; i += ch)
2854 {
2856
2857 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2858 dt_XYZ_to_Lab(XYZ, img_src + i);
2859 }
2860}
2861
2862static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
2863{
2864 const int stride = width * height * ch;
2866 for(int i = 0; i < stride; i += ch)
2867 {
2869
2870 dt_Lab_to_XYZ(img_src + i, XYZ);
2871 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2872 }
2873}
2874
2876static void rt_process_stats(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
2877 float *const img_src, const int width, const int height, const int ch, float levels[3])
2878{
2879 const int size = width * height * ch;
2880 float l_max = -INFINITY;
2881 float l_min = INFINITY;
2882 float l_sum = 0.f;
2883 int count = 0;
2885 __OMP_PARALLEL_FOR__(reduction(+ : count, l_sum) reduction(max : l_max) reduction(min : l_min))
2886 for(int i = 0; i < size; i += ch)
2887 {
2888 dt_aligned_pixel_t Lab = { 0 };
2889
2890 if(work_profile)
2891 {
2892 dt_ioppr_rgb_matrix_to_lab(img_src + i, Lab, work_profile->matrix_in_transposed,
2893 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2894 work_profile->lutsize, work_profile->nonlinearlut);
2895 }
2896 else
2897 {
2899 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2901 }
2902
2903 l_max = MAX(l_max, Lab[0]);
2904 l_min = MIN(l_min, Lab[0]);
2905 l_sum += Lab[0];
2906 count++;
2907 }
2908
2909 levels[0] = l_min / 100.f;
2910 levels[2] = l_max / 100.f;
2911 levels[1] = (l_sum / (float)count) / 100.f;
2912}
2913
2916 float *img_src, const int width, const int height, const int ch,
2917 const float levels[3])
2918{
2919 const int size = width * height * ch;
2921
2922 const float left = levels[0];
2923 const float middle = levels[1];
2924 const float right = levels[2];
2925
2926 if(left == RETOUCH_PREVIEW_LVL_MIN && middle == 0.f && right == RETOUCH_PREVIEW_LVL_MAX) return;
2927
2928 const float delta = (right - left) / 2.0f;
2929 const float mid = left + delta;
2930 const float tmp = (middle - mid) / delta;
2931 const float in_inv_gamma = powf(10, tmp);
2933 for(int i = 0; i < size; i += ch)
2934 {
2935 // The RGB<->Lab/XYZ conversions below store through 4-wide SIMD (dt_apply_transposed_color_matrix
2936 // et al.), which unconditionally overwrites all 4 lanes of img_src+i even though the color math
2937 // itself is 3-channel -- the 4th lane ends up holding leftover matrix-multiply output, not the
2938 // original value. For most pipeline buffers that 4th channel is meaningless padding, but here it
2939 // is retouch's own mask-display alpha (painted a few lines up the call chain via
2940 // rt_copy_mask_to_alpha), so silently clobbering it left every "mask display" pixel looking
2941 // fully transparent whenever a single wavelet scale was also being previewed. Save and restore it
2942 // around the round trip.
2943 const float preserved_alpha = img_src[i + 3];
2944
2945 if(!IS_NULL_PTR(work_profile))
2946 {
2947 dt_ioppr_rgb_matrix_to_lab(img_src + i, img_src + i, work_profile->matrix_in_transposed,
2948 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2949 work_profile->lutsize, work_profile->nonlinearlut);
2950 }
2951 else
2952 {
2954
2955 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2956 dt_XYZ_to_Lab(XYZ, img_src + i);
2957 }
2958
2959 for(int c = 0; c < 1; c++)
2960 {
2961 const float L_in = img_src[i + c] / 100.0f;
2962
2963 if(L_in <= left)
2964 {
2965 img_src[i + c] = 0.f;
2966 }
2967 else
2968 {
2969 const float percentage = (L_in - left) / (right - left);
2970 img_src[i + c] = 100.0f * powf(percentage, in_inv_gamma);
2971 }
2972 }
2973
2974 if(!IS_NULL_PTR(work_profile))
2975 {
2976 dt_ioppr_lab_to_rgb_matrix(img_src + i, img_src + i, work_profile->matrix_out_transposed,
2977 work_profile->lut_out, work_profile->unbounded_coeffs_out,
2978 work_profile->lutsize, work_profile->nonlinearlut);;
2979 }
2980 else
2981 {
2983
2984 dt_Lab_to_XYZ(img_src + i, XYZ);
2985 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2986 }
2987
2988 img_src[i + 3] = preserved_alpha;
2989 }
2990}
2991
2992#undef RT_WDBAR_INSET
2993
2994#undef RETOUCH_NO_FORMS
2995#undef RETOUCH_MAX_SCALES
2996#undef RETOUCH_NO_SCALES
2997
2998#undef RETOUCH_PREVIEW_LVL_MIN
2999#undef RETOUCH_PREVIEW_LVL_MAX
3000
3001static void rt_intersect_2_rois(dt_iop_roi_t *const roi_1, dt_iop_roi_t *const roi_2, const int dx, const int dy,
3002 const int padding, dt_iop_roi_t *roi_dest)
3003{
3004 const int x_from = MAX(MAX((roi_1->x + 1 - padding), roi_2->x), (roi_2->x + dx));
3005 const int x_to
3006 = MIN(MIN((roi_1->x + roi_1->width + 1 + padding), roi_2->x + roi_2->width), (roi_2->x + roi_2->width + dx));
3007
3008 const int y_from = MAX(MAX((roi_1->y + 1 - padding), roi_2->y), (roi_2->y + dy));
3009 const int y_to = MIN(MIN((roi_1->y + roi_1->height + 1 + padding), (roi_2->y + roi_2->height)),
3010 (roi_2->y + roi_2->height + dy));
3011
3012 roi_dest->x = x_from;
3013 roi_dest->y = y_from;
3014 roi_dest->width = x_to - x_from;
3015 roi_dest->height = y_to - y_from;
3016}
3017
3019static void rt_copy_in_to_out(const float *const in, const struct dt_iop_roi_t *const roi_in, float *const out,
3020 const struct dt_iop_roi_t *const roi_out, const int ch, const int dx, const int dy)
3021{
3022 const size_t rowsize = sizeof(float) * ch * MIN(roi_out->width, roi_in->width);
3023 const int xoffs = roi_out->x - roi_in->x - dx;
3024 const int yoffs = roi_out->y - roi_in->y - dy;
3025 const int y_to = MIN(roi_out->height, roi_in->height);
3027 for(int y = 0; y < y_to; y++)
3028 {
3029 const size_t iindex = ((size_t)(y + yoffs) * roi_in->width + xoffs) * ch;
3030 const size_t oindex = (size_t)y * roi_out->width * ch;
3031 float *in1 = (float *)in + iindex;
3032 float *out1 = (float *)out + oindex;
3033
3034 memcpy(out1, in1, rowsize);
3035 }
3036}
3037
3038static int rt_build_scaled_mask(float *const mask, dt_iop_roi_t *const roi_mask, float **mask_scaled,
3039 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
3040 const int dy, const int algo)
3041{
3042 float *mask_tmp = NULL;
3043 int err = 0;
3044 *mask_scaled = NULL;
3045
3046 const int padding = (algo == DT_IOP_RETOUCH_HEAL) ? 1 : 0;
3047
3048 *roi_mask_scaled = *roi_mask;
3049
3050 roi_mask_scaled->x = roi_mask->x * roi_in->scale;
3051 roi_mask_scaled->y = roi_mask->y * roi_in->scale;
3052 roi_mask_scaled->width = ((roi_mask->width * roi_in->scale) + .5f);
3053 roi_mask_scaled->height = ((roi_mask->height * roi_in->scale) + .5f);
3054 roi_mask_scaled->scale = roi_in->scale;
3055
3056 rt_intersect_2_rois(roi_mask_scaled, roi_in, dx, dy, padding, roi_mask_scaled);
3057 if(roi_mask_scaled->width < 1 || roi_mask_scaled->height < 1) goto cleanup;
3058
3059 const int x_to = roi_mask_scaled->width + roi_mask_scaled->x;
3060 const int y_to = roi_mask_scaled->height + roi_mask_scaled->y;
3061
3062 mask_tmp = dt_pixelpipe_cache_alloc_align_float_cache((size_t)roi_mask_scaled->width * roi_mask_scaled->height, 0);
3063 if(IS_NULL_PTR(mask_tmp))
3064 {
3065 fprintf(stderr, "rt_build_scaled_mask: error allocating memory\n");
3066 err = 1;
3067 goto cleanup;
3068 }
3069 dt_iop_image_fill(mask_tmp, 0.0f, roi_mask_scaled->width, roi_mask_scaled->height, 1);
3071 for(int yy = roi_mask_scaled->y; yy < y_to; yy++)
3072 {
3073 const int mask_index = ((int)(yy / roi_in->scale)) - roi_mask->y;
3074 if(mask_index < 0 || mask_index >= roi_mask->height) continue;
3075
3076 const int mask_scaled_index = (yy - roi_mask_scaled->y) * roi_mask_scaled->width;
3077
3078 const float *m = mask + mask_index * roi_mask->width;
3079 float *ms = mask_tmp + mask_scaled_index;
3080
3081 for(int xx = roi_mask_scaled->x; xx < x_to; xx++, ms++)
3082 {
3083 const int mx = ((int)(xx / roi_in->scale)) - roi_mask->x;
3084 if(mx < 0 || mx >= roi_mask->width) continue;
3085
3086 *ms = m[mx];
3087 }
3088 }
3089
3090cleanup:
3091 *mask_scaled = mask_tmp;
3092 return err;
3093}
3094
3095// img_src and mask_scaled must have the same roi
3096static void rt_copy_image_masked(float *const img_src, float *img_dest, dt_iop_roi_t *const roi_dest,
3097 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3098 const float opacity)
3099{
3101 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3102 {
3103 const int mask_index = yy * roi_mask_scaled->width;
3104 const int src_index = 4 * mask_index;
3105 const int dest_index
3106 = 4 * (((yy + roi_mask_scaled->y - roi_dest->y) * roi_dest->width) + (roi_mask_scaled->x - roi_dest->x));
3107
3108 const float *s = img_src + src_index;
3109 const float *m = mask_scaled + mask_index;
3110 float *d = img_dest + dest_index;
3111
3112 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3113 {
3114 const float f = m[xx] * opacity;
3115 const float f1 = (1.0f - f);
3116
3117 for_each_channel(c,aligned(s,d))
3118 {
3119 d[4*xx + c] = d[4*xx + c] * f1 + s[4*xx + c] * f;
3120 }
3121 }
3122 }
3123}
3124
3125static void rt_copy_mask_to_alpha(float *const img, dt_iop_roi_t *const roi_img, const int ch,
3126 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3127 const float opacity)
3128{
3130 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3131 {
3132 const int mask_index = yy * roi_mask_scaled->width;
3133 const int dest_index
3134 = (((yy + roi_mask_scaled->y - roi_img->y) * roi_img->width) + (roi_mask_scaled->x - roi_img->x)) * ch;
3135
3136 float *d = img + dest_index;
3137 const float *m = mask_scaled + mask_index;
3138
3139 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3140 {
3141 const float f = (*m) * opacity;
3142 if(f > d[3]) d[3] = f;
3143 }
3144 }
3145}
3146
3147static void _retouch_fill(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3148 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const float *const fill_color)
3149{
3151 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3152 {
3153 const int mask_index = yy * roi_mask_scaled->width;
3154 const int dest_index
3155 = (((yy + roi_mask_scaled->y - roi_in->y) * roi_in->width) + (roi_mask_scaled->x - roi_in->x)) * 4;
3156
3157 float *d = in + dest_index;
3158 const float *m = mask_scaled + mask_index;
3159
3160 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3161 {
3162 const float f = m[xx] * opacity;
3163
3164 for_each_channel(c,aligned(d,fill_color))
3165 d[4*xx + c] = d[4*xx + c] * (1.0f - f) + fill_color[c] * f;
3166 }
3167 }
3168}
3169
3170static int _retouch_clone(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3171 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity)
3172{
3173 // alloc temp image to avoid issues when areas self-intersects
3174 float *img_src = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3175 if(IS_NULL_PTR(img_src))
3176 {
3177 fprintf(stderr, "retouch_clone: error allocating memory for cloning\n");
3178 return 1;
3179 }
3180
3181 // copy source image to tmp
3182 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3183
3184 // clone it
3185 rt_copy_image_masked(img_src, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3186
3188 return 0;
3189}
3190
3191static int _retouch_blur(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *const in,
3192 dt_iop_roi_t *const roi_in, float *const mask_scaled,
3193 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type,
3194 const float blur_radius, const int use_sse)
3195{
3196 if(fabsf(blur_radius) <= 0.1f) return 0;
3197
3198 const float sigma = blur_radius * roi_in->scale;
3199
3200 float *img_dest = NULL;
3201
3202 // alloc temp image to blur
3203 img_dest = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3204 if(IS_NULL_PTR(img_dest))
3205 {
3206 fprintf(stderr, "retouch_blur: error allocating memory for blurring\n");
3207 return 1;
3208 }
3209
3210 // copy source image so we blur just the mask area (at least the smallest rect that covers it)
3211 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3212
3213 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3214 {
3215 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
3216 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3217
3218 dt_gaussian_t *g = dt_gaussian_init(roi_mask_scaled->width, roi_mask_scaled->height, 4, Labmax, Labmin, sigma,
3220 if(!IS_NULL_PTR(g))
3221 {
3222 dt_gaussian_blur_4c(g, img_dest, img_dest);
3224 }
3225 }
3226 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
3227 {
3228 const float sigma_r = 100.0f; // does not depend on scale
3229 const float sigma_s = sigma;
3230 const float detail = -1.0f; // we want the bilateral base layer
3231
3232 dt_bilateral_t *b = dt_bilateral_init(roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
3233 if(b)
3234 {
3235 int converted_cst;
3237
3238 if(!IS_NULL_PTR(work_profile))
3239 dt_colorspaces_apply_profile(self->op, self->multi_name, img_dest, img_dest, roi_mask_scaled->width,
3240 roi_mask_scaled->height, IOP_CS_RGB, IOP_CS_LAB, &converted_cst,
3241 work_profile);
3242 else
3243 image_rgb2lab(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3244
3245 dt_bilateral_splat(b, img_dest);
3247 dt_bilateral_slice(b, img_dest, img_dest, detail);
3249
3250 if(!IS_NULL_PTR(work_profile))
3251 dt_colorspaces_apply_profile(self->op, self->multi_name, img_dest, img_dest, roi_mask_scaled->width,
3252 roi_mask_scaled->height, IOP_CS_LAB, IOP_CS_RGB, &converted_cst,
3253 work_profile);
3254 else
3255 image_lab2rgb(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3256 }
3257 }
3258
3259 // copy blurred (temp) image to destination image
3260 rt_copy_image_masked(img_dest, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3261
3263 return 0;
3264}
3265
3266static int _retouch_heal(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3267 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity, const int max_iter)
3268{
3269 float *img_src = NULL;
3270 float *img_dest = NULL;
3271 int err = 0;
3272
3273 // alloc temp images for source and destination
3274 img_src = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3275 img_dest = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3276 if((IS_NULL_PTR(img_src)) || (IS_NULL_PTR(img_dest)))
3277 {
3278 fprintf(stderr, "retouch_heal: error allocating memory for healing\n");
3279 err = 1;
3280 goto cleanup;
3281 }
3282
3283 // copy source and destination to temp images
3284 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3285 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3286
3287 // heal it
3288 dt_heal(img_src, img_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height, 4, max_iter);
3289
3290 // copy healed (temp) image to destination image
3291 rt_copy_image_masked(img_dest, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3292
3293cleanup:
3296 return err;
3297}
3298
3299static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
3300{
3301 int scale = scale1;
3303 dt_iop_module_t *self = usr_d->self;
3304 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
3305 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
3306
3307 // if preview a single scale, just process that scale and original image
3308 // unless merge is activated
3309 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0) return 0;
3310 // do not process the reconstructed image
3311 if(scale > wt_p->scales + 1) return 0;
3312
3315 dt_iop_roi_t *roi_layer = &usr_d->roi;
3316 // In single-scale preview (return_layer > 0), the filter above already restricted us to scale 0
3317 // and the previewed scale itself -- both are exactly what this preview shows, regardless of
3318 // which scale curr_scale/display_scale currently points at (scale 0's shapes always apply here,
3319 // even while a *different* scale is being previewed). Requiring scale == display_scale on top,
3320 // like plain mask display does, would only match when curr_scale happens to equal 0, silently
3321 // dropping the highlight for a scale-0 shape as soon as the user previews any other scale --
3322 // even though that same shape's effect keeps being applied and visible in "mask display" alone.
3323 const int mask_display
3324 = usr_d->mask_display && (wt_p->return_layer > 0 || scale == usr_d->display_scale);
3325
3326 // when the requested scales is grather than max scales the residual image index will be different from the one
3327 // defined by the user,
3328 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
3329 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
3330 {
3331 scale = p->num_scales + 1;
3332 }
3333
3334 if(usr_d->suppress_mask) return 0;
3335
3336 // Iterate through all forms. Look them up in pipe->forms -- the refcounted snapshot taken
3337 // once for this pipeline run (dt_dev_pixelpipe_process(), pixelpipe_hb.c) -- not self->dev->forms,
3338 // which is the live, GUI-owned list: this callback runs on the pipeline/worker thread, and
3339 // self->dev->forms is neither locked nor guaranteed to still hold this module's group by the
3340 // time a detached dev (export, snapshot) gets here.
3341 const dt_masks_form_t *grp = dt_masks_get_from_id_ext(pipe->forms, bp->mask_id);
3342 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return 0;
3343
3344 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
3345 {
3346 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
3347 if(IS_NULL_PTR(grpt))
3348 {
3349 fprintf(stderr, "rt_process_forms: invalid form\n");
3350 continue;
3351 }
3352 const int formid = grpt->formid;
3353 const float form_opacity = grpt->opacity;
3354 if(formid == 0)
3355 {
3356 fprintf(stderr, "rt_process_forms: form is null\n");
3357 continue;
3358 }
3359 const int index = rt_get_index_from_formid(p, formid);
3360 if(index == -1)
3361 {
3362 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
3363 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
3364 continue;
3365 }
3366
3367 // only process current scale
3368 if(p->rt_forms[index].scale != scale)
3369 {
3370 continue;
3371 }
3372
3373 // get the spot
3374 dt_masks_form_t *form = dt_masks_get_from_id_ext(pipe->forms, formid);
3375 if(IS_NULL_PTR(form))
3376 {
3377 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
3378 continue;
3379 }
3380
3381 // if the form is outside the roi, we just skip it
3382 if(!rt_masks_form_is_in_roi(self, pipe, piece, form, roi_layer, roi_layer))
3383 {
3384 continue;
3385 }
3386
3387 // get the mask
3388 float *mask = NULL;
3389 dt_iop_roi_t roi_mask = { 0 };
3390
3391 dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &roi_mask.width, &roi_mask.height,
3392 &roi_mask.x, &roi_mask.y);
3393 if(IS_NULL_PTR(mask))
3394 {
3395 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
3396 continue;
3397 }
3398
3399 // search the delta with the source
3400 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
3401 float dx = 0.f, dy = 0.f;
3402
3403 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
3404 {
3405 if(!rt_masks_get_delta_to_destination(self, pipe, piece, roi_layer, form, &dx, &dy,
3406 p->rt_forms[index].distort_mode))
3407 {
3409 continue;
3410 }
3411 }
3412
3413 // scale the mask
3414 float *mask_scaled = NULL;
3415 dt_iop_roi_t roi_mask_scaled = { 0 };
3416 if(rt_build_scaled_mask(mask, &roi_mask, &mask_scaled, &roi_mask_scaled, roi_layer, dx, dy, algo) != 0)
3417 {
3419 return 1;
3420 }
3421
3422 // we don't need the original mask anymore
3423 if(!IS_NULL_PTR(mask))
3424 {
3426 mask = NULL;
3427 }
3428
3429 if(IS_NULL_PTR(mask_scaled))
3430 {
3431 continue;
3432 }
3433
3434 if((dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
3435 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
3436 {
3437 if(algo == DT_IOP_RETOUCH_CLONE)
3438 {
3439 if(_retouch_clone(layer, roi_layer, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity) != 0)
3440 {
3441 dt_pixelpipe_cache_free_align(mask_scaled);
3442 return 1;
3443 }
3444 }
3445 else if(algo == DT_IOP_RETOUCH_HEAL)
3446 {
3447 if(_retouch_heal(layer, roi_layer, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity, p->max_heal_iter) != 0)
3448 {
3449 dt_pixelpipe_cache_free_align(mask_scaled);
3450 return 1;
3451 }
3452 }
3453 else if(algo == DT_IOP_RETOUCH_BLUR)
3454 {
3455 if(_retouch_blur(self, pipe, layer, roi_layer, mask_scaled, &roi_mask_scaled, form_opacity,
3456 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, wt_p->use_sse) != 0)
3457 {
3458 dt_pixelpipe_cache_free_align(mask_scaled);
3459 return 1;
3460 }
3461 }
3462 else if(algo == DT_IOP_RETOUCH_FILL)
3463 {
3464 // add a brightness to the color so it can be fine-adjusted by the user
3465 dt_aligned_pixel_t fill_color;
3466
3467 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
3468 {
3469 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
3470 }
3471 else
3472 {
3473 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
3474 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
3475 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
3476 }
3477 fill_color[3] = 0.0f;
3478
3479 _retouch_fill(layer, roi_layer, mask_scaled, &roi_mask_scaled, form_opacity, fill_color);
3480 }
3481 else
3482 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
3483
3484 if(mask_display)
3485 rt_copy_mask_to_alpha(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, form_opacity);
3486 }
3488 dt_pixelpipe_cache_free_align(mask_scaled);
3489 }
3490
3491 return 0;
3492}
3493
3495static int process_internal(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
3496 const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3497 void *const ovoid, const dt_iop_roi_t *const roi_in,
3498 const dt_iop_roi_t *const roi_out, const int use_sse)
3499{
3500
3503
3504 float *in_retouch = NULL;
3505 int err = 0;
3506
3507 dt_iop_roi_t roi_retouch = *roi_in;
3508 dt_iop_roi_t *roi_rt = &roi_retouch;
3509
3510 retouch_user_data_t usr_data = { 0 };
3511 dwt_params_t *dwt_p = NULL;
3512
3513 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
3514 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
3515
3516 // we will do all the clone, heal, etc on the input image,
3517 // this way the source for one algorithm can be the destination from a previous one
3518 in_retouch = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_rt->width * roi_rt->height, 0);
3519 if(IS_NULL_PTR(in_retouch))
3520 {
3521 err = 1;
3522 goto cleanup;
3523 }
3524
3525 dt_iop_image_copy_by_size(in_retouch, ivoid, roi_rt->width, roi_rt->height, 4);
3526
3527 // user data passed from the decompose routine to the one that process each scale
3528 usr_data.self = self;
3529 usr_data.pipe = pipe;
3530 usr_data.piece = piece;
3531 usr_data.roi = *roi_rt;
3532 usr_data.mask_display = 0;
3533 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
3534 && (pipe == self->dev->pipe));
3535 usr_data.display_scale = p->curr_scale;
3536
3537 // init the decompose routine
3538 dwt_p = dt_dwt_init(in_retouch, roi_rt->width, roi_rt->height, 4, p->num_scales,
3539 (!display_wavelet_scale || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
3540 p->merge_from_scale, &usr_data,
3541 roi_in->scale, use_sse);
3542 if(IS_NULL_PTR(dwt_p))
3543 {
3544 err = 1;
3545 goto cleanup;
3546 }
3547
3548 // check if this module should expose mask.
3549 if(pipe->type == DT_DEV_PIXELPIPE_FULL && !IS_NULL_PTR(g)
3550 && (g->mask_display || display_wavelet_scale) && self->dev->gui_attached
3551 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
3552 {
3553 for(size_t j = 0; j < (size_t)roi_rt->width * roi_rt->height * 4; j += 4) in_retouch[j + 3] = 0.f;
3554
3555 ((dt_dev_pixelpipe_t *)pipe)->mask_display = g->mask_display ? DT_DEV_PIXELPIPE_DISPLAY_MASK : DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
3556 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
3557 usr_data.mask_display = 1;
3558 }
3559
3560 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
3561 {
3562 // check if the image support this number of scales
3563 if(gui_active)
3564 {
3565 const int max_scales = dwt_get_max_scale(dwt_p);
3566 if(dwt_p->scales > max_scales)
3567 {
3568 dt_control_log(_("max scale is %i for this image size"), max_scales);
3569 }
3570 }
3571 // get first scale visible at this zoom level
3572 if(g) g->first_scale_visible = dt_dwt_first_scale_visible(dwt_p);
3573 }
3574
3575 // decompose it
3576 if(dwt_decompose(dwt_p, rt_process_forms) != 0)
3577 {
3578 err = 1;
3579 goto cleanup;
3580 }
3581
3582 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
3583
3584 // process auto levels
3585 if(!IS_NULL_PTR(g) && pipe->type == DT_DEV_PIXELPIPE_FULL)
3586 {
3588 if(g->preview_auto_levels == 1 && !dt_gui_widgets_suppressed())
3589 {
3590 g->preview_auto_levels = -1;
3591
3593
3594 levels[0] = levels[1] = levels[2] = 0;
3595 rt_process_stats(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
3596 rt_clamp_minmax(levels, levels);
3597
3598 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
3599
3601 g->preview_auto_levels = 2;
3602 }
3604 }
3605
3606 // if user wants to preview a detail scale adjust levels
3607 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
3608 {
3609 rt_adjust_levels(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
3610 }
3611
3612 // copy alpha channel if needed
3613 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && !IS_NULL_PTR(g) && !g->mask_display)
3614 {
3615 dt_iop_alpha_copy(ivoid, in_retouch, roi_rt->width, roi_rt->height);
3616 }
3617
3618 // return final image
3619 rt_copy_in_to_out(in_retouch, roi_rt, ovoid, roi_out, 4, 0, 0);
3620
3621cleanup:
3623 if(dwt_p) dt_dwt_free(dwt_p);
3624 return err;
3625}
3626
3627int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3628 void *const ovoid)
3629{
3630 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3631 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3632 return process_internal(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, 0);
3633}
3634
3635void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
3636 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
3637 const dt_iop_roi_t *const roi_out)
3638{
3639 (void)pipe;
3640 rt_copy_in_to_out(in, roi_in, out, roi_out, 1, 0, 0);
3641}
3642
3643#ifdef HAVE_OPENCL
3644
3646 const int devid, cl_mem dev_img, const int width, const int height,
3647 float levels[3])
3648{
3649 cl_int err = CL_SUCCESS;
3650
3651 const int ch = 4;
3652
3653 float *src_buffer = NULL;
3654
3655 src_buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ch * width * height, 0);
3656 if(IS_NULL_PTR(src_buffer))
3657 {
3658 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3660 goto cleanup;
3661 }
3662
3663 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3664 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3665 if(err != CL_SUCCESS)
3666 {
3667 goto cleanup;
3668 }
3669
3670 // just call the CPU version for now
3671 rt_process_stats(self, pipe, src_buffer, width, height, ch, levels);
3672
3673 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
3674 if(err != CL_SUCCESS)
3675 {
3676 goto cleanup;
3677 }
3678
3679cleanup:
3681
3682 return err;
3683}
3684
3686 const int devid, cl_mem dev_img, const int width, const int height,
3687 const float levels[3])
3688{
3689 cl_int err = CL_SUCCESS;
3690
3691 const int ch = 4;
3692
3693 float *src_buffer = NULL;
3694
3695 src_buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ch * width * height, 0);
3696 if(IS_NULL_PTR(src_buffer))
3697 {
3698 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3700 goto cleanup;
3701 }
3702
3703 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3704 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3705 if(err != CL_SUCCESS)
3706 {
3707 goto cleanup;
3708 }
3709
3710 // just call the CPU version for now
3711 rt_adjust_levels(self, pipe, src_buffer, width, height, ch, levels);
3712
3713 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
3714 if(err != CL_SUCCESS)
3715 {
3716 goto cleanup;
3717 }
3718
3719cleanup:
3721
3722 return err;
3723}
3724
3725static cl_int rt_copy_in_to_out_cl(const int devid, cl_mem dev_in, const struct dt_iop_roi_t *const roi_in,
3726 cl_mem dev_out, const struct dt_iop_roi_t *const roi_out, const int dx,
3727 const int dy, const int kernel)
3728{
3729 cl_int err = CL_SUCCESS;
3730
3731 const int xoffs = roi_out->x - roi_in->x - dx;
3732 const int yoffs = roi_out->y - roi_in->y - dy;
3733
3734 cl_mem dev_roi_in = NULL;
3735 cl_mem dev_roi_out = NULL;
3736
3737 const size_t sizes[]
3738 = { ROUNDUPDWD(MIN(roi_out->width, roi_in->width), devid), ROUNDUPDHT(MIN(roi_out->height, roi_in->height), devid), 1 };
3739
3740 dev_roi_in = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_in);
3741 dev_roi_out = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_out);
3742 if(IS_NULL_PTR(dev_roi_in) || IS_NULL_PTR(dev_roi_out))
3743 {
3744 fprintf(stderr, "rt_copy_in_to_out_cl error 1\n");
3745 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3746 goto cleanup;
3747 }
3748
3749 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
3750 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_in);
3751 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_out);
3752 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_out);
3753 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), (void *)&xoffs);
3754 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), (void *)&yoffs);
3755 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3756 if(err != CL_SUCCESS)
3757 {
3758 fprintf(stderr, "rt_copy_in_to_out_cl error 2\n");
3759 goto cleanup;
3760 }
3761
3762cleanup:
3763 dt_opencl_release_mem_object(dev_roi_in);
3764 dt_opencl_release_mem_object(dev_roi_out);
3765
3766 return err;
3767}
3768
3769static cl_int rt_build_scaled_mask_cl(const int devid, float *const mask, dt_iop_roi_t *const roi_mask,
3770 float **mask_scaled, cl_mem *p_dev_mask_scaled,
3771 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
3772 const int dy, const int algo)
3773{
3774 cl_int err = CL_SUCCESS;
3775
3776 rt_build_scaled_mask(mask, roi_mask, mask_scaled, roi_mask_scaled, roi_in, dx, dy, algo);
3777 if(IS_NULL_PTR(*mask_scaled))
3778 {
3779 goto cleanup;
3780 }
3781
3782 const cl_mem dev_mask_scaled
3783 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height);
3784 if(IS_NULL_PTR(dev_mask_scaled))
3785 {
3786 fprintf(stderr, "rt_build_scaled_mask_cl error 2\n");
3787 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3788 goto cleanup;
3789 }
3790
3791 err = dt_opencl_write_buffer_to_device(devid, *mask_scaled, dev_mask_scaled, 0,
3792 sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height, CL_TRUE);
3793 if(err != CL_SUCCESS)
3794 {
3795 fprintf(stderr, "rt_build_scaled_mask_cl error 4\n");
3796 goto cleanup;
3797 }
3798
3799 *p_dev_mask_scaled = dev_mask_scaled;
3800
3801cleanup:
3802 if(err != CL_SUCCESS) fprintf(stderr, "rt_build_scaled_mask_cl error\n");
3803
3804 return err;
3805}
3806
3807static cl_int rt_copy_image_masked_cl(const int devid, cl_mem dev_src, cl_mem dev_dest,
3808 dt_iop_roi_t *const roi_dest, cl_mem dev_mask_scaled,
3809 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int kernel)
3810{
3811 cl_int err = CL_SUCCESS;
3812
3813 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3814
3815 const cl_mem dev_roi_dest =
3816 dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_dest);
3817
3818 const cl_mem dev_roi_mask_scaled
3819 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3820
3821 if(IS_NULL_PTR(dev_roi_dest) || IS_NULL_PTR(dev_roi_mask_scaled))
3822 {
3823 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3824 goto cleanup;
3825 }
3826
3827 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_src);
3828 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_dest);
3829 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_roi_dest);
3830 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_mask_scaled);
3831 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3832 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&opacity);
3833 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3834 if(err != CL_SUCCESS) goto cleanup;
3835
3836cleanup:
3837 dt_opencl_release_mem_object(dev_roi_dest);
3838 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3839
3840 return err;
3841}
3842
3843static cl_int rt_copy_mask_to_alpha_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3844 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3845 const float opacity, dt_iop_retouch_global_data_t *gd)
3846{
3847 cl_int err = CL_SUCCESS;
3848
3849 // fill it
3851 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3852
3853 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3854 const cl_mem dev_roi_mask_scaled
3855 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3856 if(IS_NULL_PTR(dev_roi_layer) || IS_NULL_PTR(dev_roi_mask_scaled))
3857 {
3858 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3859 goto cleanup;
3860 }
3861
3862 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3863 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3864 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3865 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3866 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3867 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3868 if(err != CL_SUCCESS) goto cleanup;
3869
3870
3871cleanup:
3872 dt_opencl_release_mem_object(dev_roi_layer);
3873 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3874
3875 return err;
3876}
3877
3878static cl_int _retouch_clone_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3879 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
3880 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
3881{
3882 cl_int err = CL_SUCCESS;
3883
3884 const int ch = 4;
3885
3886 // alloc source temp image to avoid issues when areas self-intersects
3887 const cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
3888 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
3889 if(IS_NULL_PTR(dev_src))
3890 {
3891 fprintf(stderr, "retouch_clone_cl error 2\n");
3892 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3893 goto cleanup;
3894 }
3895
3896 // copy source image to tmp
3897 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
3899 if(err != CL_SUCCESS)
3900 {
3901 fprintf(stderr, "retouch_clone_cl error 4\n");
3902 goto cleanup;
3903 }
3904
3905 // clone it
3906 err = rt_copy_image_masked_cl(devid, dev_src, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
3908 if(err != CL_SUCCESS)
3909 {
3910 fprintf(stderr, "retouch_clone_cl error 5\n");
3911 goto cleanup;
3912 }
3913
3914cleanup:
3916
3917 return err;
3918}
3919
3920static cl_int _retouch_fill_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3921 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3923{
3924 cl_int err = CL_SUCCESS;
3925
3926 // fill it
3927 const int kernel = gd->kernel_retouch_fill;
3928 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3929
3930 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3931 const cl_mem dev_roi_mask_scaled
3932 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3933 if(IS_NULL_PTR(dev_roi_layer) || IS_NULL_PTR(dev_roi_mask_scaled))
3934 {
3935 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3936 goto cleanup;
3937 }
3938
3939 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3940 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3941 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3942 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3943 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3944 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&(color[0]));
3945 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), (void *)&(color[1]));
3946 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), (void *)&(color[2]));
3947 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3948 if(err != CL_SUCCESS) goto cleanup;
3949
3950
3951cleanup:
3952 dt_opencl_release_mem_object(dev_roi_layer);
3953 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3954
3955 return err;
3956}
3957
3958static cl_int _retouch_blur_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3959 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3960 const int blur_type, const float blur_radius, dt_iop_retouch_global_data_t *gd)
3961{
3962 cl_int err = CL_SUCCESS;
3963
3964 if(fabsf(blur_radius) <= 0.1f) return err;
3965
3966 const float sigma = blur_radius * roi_layer->scale;
3967 const int ch = 4;
3968
3969 const cl_mem dev_dest =
3970 dt_opencl_alloc_device(devid, roi_mask_scaled->width, roi_mask_scaled->height, sizeof(float) * ch);
3971 if(IS_NULL_PTR(dev_dest))
3972 {
3973 fprintf(stderr, "retouch_blur_cl error 2\n");
3974 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3975 goto cleanup;
3976 }
3977
3978 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
3979 {
3980 const int kernel = gd->kernel_retouch_image_rgb2lab;
3981 size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
3982
3983 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3984 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
3985 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
3986 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3987 if(err != CL_SUCCESS) goto cleanup;
3988 }
3989
3990 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
3992 if(err != CL_SUCCESS)
3993 {
3994 fprintf(stderr, "retouch_blur_cl error 4\n");
3995 goto cleanup;
3996 }
3997
3998 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3999 {
4000 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
4001 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
4002
4003 dt_gaussian_cl_t *g = dt_gaussian_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, ch, Labmax,
4004 Labmin, sigma, DT_IOP_GAUSSIAN_ZERO);
4005 if(g)
4006 {
4007 err = dt_gaussian_blur_cl(g, dev_dest, dev_dest);
4009 if(err != CL_SUCCESS) goto cleanup;
4010 }
4011 }
4012 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
4013 {
4014 const float sigma_r = 100.0f; // does not depend on scale
4015 const float sigma_s = sigma;
4016 const float detail = -1.0f; // we want the bilateral base layer
4017
4019 = dt_bilateral_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
4020 if(b)
4021 {
4022 err = dt_bilateral_splat_cl(b, dev_dest);
4023 if(err == CL_SUCCESS) err = dt_bilateral_blur_cl(b);
4024 if(err == CL_SUCCESS) err = dt_bilateral_slice_cl(b, dev_dest, dev_dest, detail);
4025
4027 }
4028 }
4029
4030 // copy blurred (temp) image to destination image
4031 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
4033 if(err != CL_SUCCESS)
4034 {
4035 fprintf(stderr, "retouch_blur_cl error 5\n");
4036 goto cleanup;
4037 }
4038
4039 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
4040 {
4041 const int kernel = gd->kernel_retouch_image_lab2rgb;
4042 const size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
4043
4044 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4045 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
4046 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
4047 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4048 if(err != CL_SUCCESS) goto cleanup;
4049 }
4050
4051cleanup:
4053
4054 return err;
4055}
4056
4057static cl_int _retouch_heal_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, float *mask_scaled,
4058 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
4059 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd, const int max_iter)
4060{
4061 cl_int err = CL_SUCCESS;
4062
4063 const int ch = 4;
4064
4065 cl_mem dev_dest = NULL;
4066 cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
4067 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4068 if(IS_NULL_PTR(dev_src))
4069 {
4070 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4071 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4072 goto cleanup;
4073 }
4074
4075 dev_dest = dt_opencl_alloc_device_buffer(devid,
4076 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4077 if(IS_NULL_PTR(dev_dest))
4078 {
4079 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4080 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4081 goto cleanup;
4082 }
4083
4084 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
4086 if(err != CL_SUCCESS)
4087 {
4088 fprintf(stderr, "retouch_heal_cl error 4\n");
4089 goto cleanup;
4090 }
4091
4092 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
4094 if(err != CL_SUCCESS)
4095 {
4096 fprintf(stderr, "retouch_heal_cl error 4\n");
4097 goto cleanup;
4098 }
4099
4100 // heal it
4101 heal_params_cl_t *hp = dt_heal_init_cl(devid);
4102 if(hp)
4103 {
4104 err = dt_heal_cl(hp, dev_src, dev_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height, max_iter);
4105 dt_heal_free_cl(hp);
4106
4108 dev_src = NULL;
4109
4110 if(err != CL_SUCCESS) goto cleanup;
4111 }
4112
4113 // copy healed (temp) image to destination image
4114 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
4116 if(err != CL_SUCCESS)
4117 {
4118 fprintf(stderr, "retouch_heal_cl error 6\n");
4119 goto cleanup;
4120 }
4121
4122cleanup:
4125
4126 return err;
4127}
4128
4129static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
4130{
4131 cl_int err = CL_SUCCESS;
4132
4133 int scale = scale1;
4135 dt_iop_module_t *self = usr_d->self;
4136 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
4137 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
4138
4139 // if preview a single scale, just process that scale and original image
4140 // unless merge is activated
4141 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0)
4142 return err;
4143 // do not process the reconstructed image
4144 if(scale > wt_p->scales + 1) return err;
4145
4149 const int devid = pipe->devid;
4150 dt_iop_roi_t *roi_layer = &usr_d->roi;
4151 // See the CPU rt_process_forms() for why return_layer > 0 bypasses the display_scale match.
4152 const int mask_display
4153 = usr_d->mask_display && (wt_p->return_layer > 0 || scale == usr_d->display_scale);
4154
4155 // when the requested scales is grather than max scales the residual image index will be different from the one
4156 // defined by the user,
4157 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
4158 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
4159 {
4160 scale = p->num_scales + 1;
4161 }
4162
4163 // Iterate through all forms. Look them up in pipe->forms, the same frozen snapshot the CPU
4164 // rt_process_forms() uses -- see the comment there for why self->dev->forms is unsafe here.
4165 if(!usr_d->suppress_mask)
4166 {
4168 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return err;
4169
4170 for(const GList *forms = grp->points; forms && err == CL_SUCCESS; forms = g_list_next(forms))
4171 {
4172 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
4173 if(IS_NULL_PTR(grpt))
4174 {
4175 fprintf(stderr, "rt_process_forms: invalid form\n");
4176 continue;
4177 }
4178 const int formid = grpt->formid;
4179 const float form_opacity = grpt->opacity;
4180 if(formid == 0)
4181 {
4182 fprintf(stderr, "rt_process_forms: form is null\n");
4183 continue;
4184 }
4185 const int index = rt_get_index_from_formid(p, formid);
4186 if(index == -1)
4187 {
4188 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
4189 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
4190 continue;
4191 }
4192
4193 // only process current scale
4194 if(p->rt_forms[index].scale != scale)
4195 {
4196 continue;
4197 }
4198
4199 // get the spot
4200 dt_masks_form_t *form = dt_masks_get_from_id_ext(pipe->forms, formid);
4201 if(IS_NULL_PTR(form))
4202 {
4203 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
4204 continue;
4205 }
4206
4207 // if the form is outside the roi, we just skip it
4208 if(!rt_masks_form_is_in_roi(self, pipe, piece, form, roi_layer, roi_layer))
4209 {
4210 continue;
4211 }
4212
4213 // get the mask
4214 float *mask = NULL;
4215 dt_iop_roi_t roi_mask = { 0 };
4216
4217 dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &roi_mask.width, &roi_mask.height,
4218 &roi_mask.x, &roi_mask.y);
4219 if(IS_NULL_PTR(mask))
4220 {
4221 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
4222 continue;
4223 }
4224
4225 float dx = 0.f, dy = 0.f;
4226
4227 // search the delta with the source
4228 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
4229 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
4230 {
4231 if(!rt_masks_get_delta_to_destination(self, (dt_dev_pixelpipe_t *)pipe, piece, roi_layer, form, &dx, &dy,
4232 p->rt_forms[index].distort_mode))
4233 {
4235 continue;
4236 }
4237 }
4238
4239 // scale the mask
4240 cl_mem dev_mask_scaled = NULL;
4241 float *mask_scaled = NULL;
4242 dt_iop_roi_t roi_mask_scaled = { 0 };
4243
4244 err = rt_build_scaled_mask_cl(devid, mask, &roi_mask, &mask_scaled, &dev_mask_scaled, &roi_mask_scaled,
4245 roi_layer, dx, dy, algo);
4246
4247 // only heal needs mask scaled
4248 if(algo != DT_IOP_RETOUCH_HEAL && !IS_NULL_PTR(mask_scaled))
4249 {
4250 dt_pixelpipe_cache_free_align(mask_scaled);
4251 mask_scaled = NULL;
4252 }
4253
4254 // we don't need the original mask anymore
4255 if(mask)
4256 {
4258 mask = NULL;
4259 }
4260
4261 if(IS_NULL_PTR(mask_scaled) && algo == DT_IOP_RETOUCH_HEAL)
4262 {
4263 dt_opencl_release_mem_object(dev_mask_scaled);
4264 dev_mask_scaled = NULL;
4265 continue;
4266 }
4267
4268 if((err == CL_SUCCESS)
4269 && (dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
4270 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
4271 {
4272 if(algo == DT_IOP_RETOUCH_CLONE)
4273 {
4274 err = _retouch_clone_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, dx, dy,
4275 form_opacity, gd);
4276 }
4277 else if(algo == DT_IOP_RETOUCH_HEAL)
4278 {
4279 err = _retouch_heal_cl(devid, dev_layer, roi_layer, mask_scaled, dev_mask_scaled, &roi_mask_scaled, dx,
4280 dy, form_opacity, gd, p->max_heal_iter);
4281 }
4282 else if(algo == DT_IOP_RETOUCH_BLUR)
4283 {
4284 err = _retouch_blur_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4285 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, gd);
4286 }
4287 else if(algo == DT_IOP_RETOUCH_FILL)
4288 {
4289 // add a brightness to the color so it can be fine-adjusted by the user
4290 dt_aligned_pixel_t fill_color;
4291
4292 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
4293 {
4294 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
4295 }
4296 else
4297 {
4298 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
4299 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
4300 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
4301 }
4302
4303 err = _retouch_fill_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4304 fill_color, gd);
4305 }
4306 else
4307 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
4308
4309 if(mask_display)
4310 rt_copy_mask_to_alpha_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4311 gd);
4312 }
4313
4315 dt_pixelpipe_cache_free_align(mask_scaled);
4316 dt_opencl_release_mem_object(dev_mask_scaled);
4317 }
4318 }
4319
4320 return err;
4321}
4322
4323int 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)
4324{
4325 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4326 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4330
4331 cl_int err = CL_SUCCESS;
4332 const int devid = pipe->devid;
4333
4334 dt_iop_roi_t roi_retouch = *roi_in;
4335 dt_iop_roi_t *roi_rt = &roi_retouch;
4336
4337 const int ch = piece->dsc_in.channels;
4338 retouch_user_data_t usr_data = { 0 };
4339 dwt_params_cl_t *dwt_p = NULL;
4340
4341 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
4342 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
4343
4344 // we will do all the clone, heal, etc on the input image,
4345 // this way the source for one algorithm can be the destination from a previous one
4346 const cl_mem in_retouch = dt_opencl_alloc_device_buffer(devid, sizeof(float) * ch * roi_rt->width * roi_rt->height);
4347 if(IS_NULL_PTR(in_retouch))
4348 {
4349 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error allocating memory for wavelet decompose on device %d\n", devid);
4350 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4351 goto cleanup;
4352 }
4353
4354 // copy input image to the new buffer
4355 {
4356 size_t origin[] = { 0, 0, 0 };
4357 size_t region[] = { roi_rt->width, roi_rt->height, 1 };
4358 err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, in_retouch, origin, region, 0);
4359 if(err != CL_SUCCESS) goto cleanup;
4360 }
4361
4362 // user data passed from the decompose routine to the one that process each scale
4363 usr_data.self = self;
4364 usr_data.pipe = pipe;
4365 usr_data.piece = piece;
4366 usr_data.roi = *roi_rt;
4367 usr_data.mask_display = 0;
4368 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
4369 && (pipe == self->dev->pipe));
4370 usr_data.display_scale = p->curr_scale;
4371
4372 // init the decompose routine
4373 dwt_p = dt_dwt_init_cl(devid, in_retouch, roi_rt->width, roi_rt->height, p->num_scales,
4374 (!display_wavelet_scale
4375 || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
4376 p->merge_from_scale, &usr_data,
4377 roi_in->scale);
4378 if(IS_NULL_PTR(dwt_p))
4379 {
4380 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error initializing wavelet decompose on device %d\n", devid);
4381 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4382 goto cleanup;
4383 }
4384
4385 // check if this module should expose mask.
4386 // Must mirror process_internal()'s CPU condition exactly: g->mask_display OR display_wavelet_scale,
4387 // not g->mask_display alone. Missing the wavelet-scale branch meant a wavelet-only OpenCL preview
4388 // never cleared alpha, never set pipe->mask_display, and so never made the downstream color-pipeline
4389 // modules bypass -- they ran their normal processing on the wavelet-domain buffer instead of getting
4390 // skipped, corrupting the preview.
4391 if(pipe->type == DT_DEV_PIXELPIPE_FULL && g && (g->mask_display || display_wavelet_scale)
4392 && self->dev->gui_attached
4393 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
4394 {
4395 const int kernel = gd->kernel_retouch_clear_alpha;
4396 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4397
4398 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&in_retouch);
4399 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_rt->width));
4400 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->height));
4401 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4402 if(err != CL_SUCCESS) goto cleanup;
4403
4404 ((dt_dev_pixelpipe_t *)pipe)->mask_display = g->mask_display ? DT_DEV_PIXELPIPE_DISPLAY_MASK : DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
4405 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
4406 usr_data.mask_display = 1;
4407 }
4408
4409 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
4410 {
4411 // check if the image support this number of scales
4412 if(gui_active)
4413 {
4414 const int max_scales = dwt_get_max_scale_cl(dwt_p);
4415 if(dwt_p->scales > max_scales)
4416 {
4417 dt_control_log(_("max scale is %i for this image size"), max_scales);
4418 }
4419 }
4420 // get first scale visible at this zoom level
4421 if(g) g->first_scale_visible = dt_dwt_first_scale_visible_cl(dwt_p);
4422 }
4423
4424 // decompose it
4426 if(err != CL_SUCCESS) goto cleanup;
4427
4428 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
4429
4430 // process auto levels
4431 if(g && pipe->type == DT_DEV_PIXELPIPE_FULL)
4432 {
4434 if(g->preview_auto_levels == 1 && !dt_gui_widgets_suppressed())
4435 {
4436 g->preview_auto_levels = -1;
4437
4439
4440 levels[0] = levels[1] = levels[2] = 0;
4441 err = rt_process_stats_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4442 if(err != CL_SUCCESS) goto cleanup;
4443
4444 rt_clamp_minmax(levels, levels);
4445
4446 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
4447
4449 g->preview_auto_levels = 2;
4450 }
4452 }
4453
4454 // if user wants to preview a detail scale adjust levels
4455 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
4456 {
4457 err = rt_adjust_levels_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4458 if(err != CL_SUCCESS) goto cleanup;
4459 }
4460
4461 // copy alpha channel if needed
4462 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && g && !g->mask_display)
4463 {
4464 const int kernel = gd->kernel_retouch_copy_alpha;
4465 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4466
4467 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
4468 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&in_retouch);
4469 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->width));
4470 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&(roi_rt->height));
4471 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4472 if(err != CL_SUCCESS) goto cleanup;
4473 }
4474
4475 // return final image
4476 err = rt_copy_in_to_out_cl(devid, in_retouch, roi_in, dev_out, roi_out, 0, 0,
4478
4479cleanup:
4480 if(dwt_p) dt_dwt_free_cl(dwt_p);
4481
4482 dt_opencl_release_mem_object(in_retouch);
4483
4484 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_retouch] couldn't enqueue kernel! %d\n", err);
4485
4486 return (err == CL_SUCCESS) ? TRUE : FALSE;
4487}
4488#endif
4489
4492static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
4493{
4494 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4496
4497 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "formid"));
4498 const int algo = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "algo"));
4499 const int index = rt_get_index_from_formid(p, formid);
4500 if(index < 0) return;
4501 if(algo < DT_IOP_RETOUCH_CLONE || algo > DT_IOP_RETOUCH_FILL) return;
4502 if(p->rt_forms[index].algorithm == algo) return;
4503 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
4504 if(IS_NULL_PTR(form)) return;
4505
4506 // Apply the new algorithm to the form
4507 p->rt_forms[index].algorithm = algo;
4508
4509 // Switch the clone type of the form
4510 dt_masks_type_t masks_type = form->type;
4511 if(algo == DT_IOP_RETOUCH_CLONE || algo == DT_IOP_RETOUCH_HEAL)
4512 {
4513 masks_type |= DT_MASKS_CLONE;
4514 masks_type &= ~DT_MASKS_NON_CLONE;
4515 }
4516 else
4517 {
4518 masks_type &= ~DT_MASKS_CLONE;
4519 masks_type |= DT_MASKS_NON_CLONE;
4520 }
4521 form->type = masks_type;
4522
4523 // Update GUI
4524 rt_load_shape_algo_in_gui(self, formid);
4525
4526 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
4527}
4528
4529int populate_masks_context_menu(struct dt_iop_module_t *self, GtkWidget *menu, const int formid,const float pzx, const float pzy)
4530{
4532 const int index = rt_get_index_from_formid(p, formid);
4533 if(index == -1)
4534 {
4535 fprintf(stderr, "populate_masks_context_menu: missing form=%i from array\n", formid);
4536 return FALSE;
4537 }
4538
4539 GtkWidget *menu_item = ctx_gtk_menu_item_new_with_markup(_("Retouch correction"), menu, NULL, NULL);
4540 GtkWidget *sub_menu = gtk_menu_new();
4541 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
4542
4543 static const struct
4544 {
4546 const char *name;
4547 } algo_entries[] = {{ DT_IOP_RETOUCH_CLONE, N_("Clone") },
4548 { DT_IOP_RETOUCH_HEAL, N_("Heal") },
4549 { DT_IOP_RETOUCH_BLUR, N_("Blur") },
4550 { DT_IOP_RETOUCH_FILL, N_("Fill") },};
4551
4552 for(size_t i = 0; i < G_N_ELEMENTS(algo_entries); i++)
4553 {
4554 const gboolean is_selected = (p->rt_forms[index].algorithm == algo_entries[i].algo);
4555 const char *const label = _(algo_entries[i].name);
4556 GtkWidget *algo_item = ctx_gtk_check_menu_item_new_with_markup(label, sub_menu,
4557 is_selected ? NULL : rt_menu_select_algorithm_callback, self,
4558 is_selected, FALSE);
4559
4560 g_object_set_data(G_OBJECT(algo_item), "formid", GINT_TO_POINTER(formid));
4561 g_object_set_data(G_OBJECT(algo_item), "algo", GINT_TO_POINTER(algo_entries[i].algo));
4562 }
4563
4564 return TRUE;
4565}
4566// clang-format off
4567// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4568// vim: shiftwidth=2 expandtab tabstop=2 cindent
4569// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4570// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
int levels(struct dt_imageio_module_data_t *data)
Definition avif.c:641
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2090
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1632
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
__DT_CLONE_TARGETS__ void dt_bilateral_slice(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:356
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
float sigma_s
Definition bilateral.h:3
float sigma_r
Definition bilateral.h:3
void dt_bilateral_free_cl(dt_bilateral_cl_t *b)
Definition bilateralcl.c:60
cl_int dt_bilateral_slice_cl(dt_bilateral_cl_t *b, cl_mem in, cl_mem out, const float detail)
dt_bilateral_cl_t * dt_bilateral_init_cl(const int devid, const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:91
cl_int dt_bilateral_blur_cl(dt_bilateral_cl_t *b)
cl_int dt_bilateral_splat_cl(dt_bilateral_cl_t *b, cl_mem in)
const dt_iop_gui_blendif_colorstop_t _gradient_L[]
Definition blend_gui.c:95
The blending panel's GUI state and API, cut out of develop/blend.h.
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
@ IOP_CS_LAB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_POINT
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
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_Lab_to_XYZ(Lab, XYZ)
static const float const float const float min
static dt_aligned_pixel_t XYZ
const float max
static dt_aligned_pixel_t Lab
const dt_colormatrix_t dt_aligned_pixel_t out
dt_XYZ_to_Lab(XYZ, Lab)
const float delta
void dt_conf_set_float(const char *name, float val)
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_center()
Request a redraw of the centre view.
Definition control.c:924
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
#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_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1797
dt_dev_pixelpipe_display_mask_t
Definition develop.h:121
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:141
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
int dt_dwt_first_scale_visible_cl(dwt_params_cl_t *p)
Definition dwt.c:610
int dwt_decompose(dwt_params_t *p, _dwt_layer_func layer_func)
Definition dwt.c:380
int dwt_get_max_scale(dwt_params_t *p)
Definition dwt.c:92
void dt_dwt_free(dwt_params_t *p)
Definition dwt.c:64
dwt_params_cl_t * dt_dwt_init_cl(const int devid, cl_mem image, const int width, const int height, const int scales, const int return_layer, const int merge_from_scale, void *user_data, const float preview_scale)
Definition dwt.c:577
void dt_dwt_free_cl(dwt_params_cl_t *p)
Definition dwt.c:599
dwt_params_t * dt_dwt_init(float *image, const int width, const int height, const int ch, const int scales, const int return_layer, const int merge_from_scale, void *user_data, const float preview_scale, const int use_sse)
Definition dwt.c:43
cl_int dwt_decompose_cl(dwt_params_cl_t *p, _dwt_layer_func_cl layer_func)
Definition dwt.c:925
int dwt_get_max_scale_cl(dwt_params_cl_t *p)
Definition dwt.c:605
int dt_dwt_first_scale_visible(dwt_params_t *p)
Definition dwt.c:116
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:335
void dt_gaussian_free_cl(dt_gaussian_cl_t *g)
Definition gaussian.c:365
cl_int dt_gaussian_blur_cl(dt_gaussian_cl_t *g, cl_mem dev_in, cl_mem dev_out)
Definition gaussian.c:453
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:330
dt_gaussian_cl_t * dt_gaussian_init_cl(const int devid, const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:376
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:127
@ DT_IOP_GAUSSIAN_ZERO
Definition gaussian.h:34
GdkRGBA color[]
Definition geotagging.c:541
void dtgtk_gradient_slider_multivalue_set_values(GtkDarktableGradientSlider *gslider, gdouble *values)
void dtgtk_gradient_slider_multivalue_get_values(GtkDarktableGradientSlider *gslider, gdouble *values)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_color_and_name(GdkRGBA start, GdkRGBA end, gint positions, gchar *name)
void dtgtk_gradient_slider_multivalue_set_marker(GtkDarktableGradientSlider *gslider, gint mark, gint pos)
void dtgtk_gradient_slider_multivalue_set_resetvalues(GtkDarktableGradientSlider *gslider, gdouble *values)
#define DTGTK_GRADIENT_SLIDER_MULTIVALUE(obj)
@ GRADIENT_SLIDER_MARKER_LOWER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_FILLED_BIG
@ GRADIENT_SLIDER_GET
@ GRADIENT_SLIDER_SET
@ PROPORTIONAL_MARKERS
#define DT_GUI_MODULE(x)
void dt_heal_free_cl(heal_params_cl_t *p)
Definition heal.c:453
void dt_heal(const float *const src_buffer, float *dest_buffer, const float *const mask_buffer, const int width, const int height, const int ch, const int max_iter)
Definition heal.c:386
cl_int dt_heal_cl(heal_params_cl_t *p, cl_mem dev_src, cl_mem dev_dest, const float *const mask_buffer, const int width, const int height, const int max_iter)
Definition heal.c:459
heal_params_cl_t * dt_heal_init_cl(const int devid)
Definition heal.c:441
float dt_boundingbox_t[4]
Definition image.h:83
__DT_CLONE_TARGETS__ void dt_iop_image_fill(float *const buf, const float fill_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:218
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
void dt_iop_set_cache_bypass_variant(dt_iop_module_t *module, int variant)
Definition imageop.c:1686
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:308
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1893
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1669
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_INTERNAL_MASKS
Definition imageop.h:198
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:194
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_REPAIR
Definition imageop.h:159
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
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
static float kernel(const float *x, const float *y)
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
_lib_location_type_t type
Definition location.c:1
@ 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 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
dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid, const float opacity, dt_masks_member_t *out)
Set a group member's opacity.
Definition masks.c:2029
dt_masks_raster_result_t dt_masks_get_source_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
Definition masks.c:464
dt_masks_raster_result_t dt_masks_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition masks.c:1626
@ DT_MASKS_RASTER_OK
Definition masks.h:314
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:885
dt_masks_form_t * dt_masks_get_from_id_ext(GList *forms, int id)
Definition masks.c:899
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
dt_masks_raster_result_t dt_masks_get_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
Definition masks.c:450
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_group_ungroup(dt_develop_t *dev, dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
Definition masks_gui.c:5356
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
dt_masks_form_group_t * dt_masks_form_get_selected_group_live(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Resolve a "live" selected group entry, even if GUI selection is stale.
Definition masks_gui.c:1539
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:113
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
Definition masks_gui.c:308
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4894
gboolean dt_masks_creation_mode_enter(dt_develop_t *dev, dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
Definition masks_gui.c:5762
dt_masks_form_group_t * dt_masks_group_add_form_with_state(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form, const int parentid, const dt_masks_state_t state, const float opacity)
Definition masks_gui.c:5317
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
@ DT_MASKS_SHAPE_INDEX_BRUSH
Definition masks_gui.h:480
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks_gui.h:478
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks_gui.h:479
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks_gui.h:477
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks_gui.h:498
@ DT_MASKS_EDIT_RESTRICTED
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
@ DT_MASKS_STATE_USE
Definition masks_types.h:89
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_BRUSH
Definition masks_types.h:70
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_NONE
Definition masks_types.h:63
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
dt_masks_result_t
What a request that may change the masks model did.
@ DT_MASKS_OK
@ DT_MASKS_UNCHANGED
#define DEVELOP_MASKS_NB_SHAPES
Definition masks_types.h:58
#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
GtkWidget * ctx_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:170
GtkWidget * ctx_gtk_check_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, const gboolean checked, const gboolean show_checkbox)
Definition menu.c:241
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2554
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2970
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2894
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2448
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2750
int dt_opencl_write_buffer_to_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2738
int dt_opencl_read_buffer_from_device(const int devid, void *host, void *device, const size_t offset, const size_t size, const int blocking)
Definition opencl.c:2727
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
int dt_opencl_enqueue_copy_image_to_buffer(const int devid, cl_mem src_image, cl_mem dst_buffer, size_t *origin, size_t *region, size_t offset)
Definition opencl.c:2690
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2805
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define DT_OPENCL_SYSMEM_ALLOCATION
Definition opencl.h:62
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
static cl_int _retouch_heal_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, float *mask_scaled, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity, dt_iop_retouch_global_data_t *gd, const int max_iter)
Definition retouch.c:4057
static gboolean rt_shape_buttons_can_start(GtkWidget *button, dt_iop_module_t *self, dt_masks_type_t type, gpointer user_data)
Definition retouch.c:1008
static cl_int rt_build_scaled_mask_cl(const int devid, float *const mask, dt_iop_roi_t *const roi_mask, float **mask_scaled, cl_mem *p_dev_mask_scaled, dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx, const int dy, const int algo)
Definition retouch.c:3769
static int rt_masks_get_delta_to_destination(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, dt_masks_form_t *form, float *dx, float *dy, const int distort_mode)
Definition retouch.c:898
static dt_masks_type_t rt_shape_buttons_form_type(dt_iop_module_t *self, dt_masks_type_t type, gpointer user_data)
Definition retouch.c:1035
dt_iop_retouch_fill_modes_t
Definition retouch.c:100
@ DT_IOP_RETOUCH_FILL_COLOR
Definition retouch.c:102
@ DT_IOP_RETOUCH_FILL_ERASE
Definition retouch.c:101
static float rt_gslider_scale_callback(GtkWidget *self, float inval, int dir)
Definition retouch.c:1446
static void rt_copy_mask_to_alpha(float *const img, dt_iop_roi_t *const roi_img, const int ch, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity)
Definition retouch.c:3125
void init(dt_iop_module_t *module)
Definition retouch.c:2003
static void rt_num_scales_update(const int _num_scales, dt_iop_module_t *self)
Definition retouch.c:1094
void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const float *const in, float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
Definition retouch.c:3635
static void rt_resynch_params(struct dt_iop_module_t *self, dt_iop_retouch_params_t *p, GList *forms_list)
Definition retouch.c:713
const char ** description(struct dt_iop_module_t *self)
Definition retouch.c:249
static cl_int rt_copy_mask_to_alpha_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, dt_iop_retouch_global_data_t *gd)
Definition retouch.c:3843
int default_group()
Definition retouch.c:258
static void rt_extend_roi_in_for_clone(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *const pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
Definition retouch.c:2748
static float rt_get_shape_opacity(dt_iop_module_t *self, const int formid)
Definition retouch.c:445
static int _retouch_clone(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity)
Definition retouch.c:3170
#define RETOUCH_NO_FORMS
Definition retouch.c:88
static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
Definition retouch.c:4492
static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e, dt_iop_module_t *self)
Definition retouch.c:1810
dt_iop_retouch_algo_type_t
Definition retouch.c:110
@ DT_IOP_RETOUCH_BLUR
Definition retouch.c:114
@ DT_IOP_RETOUCH_NONE
Definition retouch.c:111
@ DT_IOP_RETOUCH_HEAL
Definition retouch.c:113
@ DT_IOP_RETOUCH_FILL
Definition retouch.c:115
@ DT_IOP_RETOUCH_CLONE
Definition retouch.c:112
void commit_params(struct dt_iop_module_t *self, dt_iop_params_t *params, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2095
static cl_int _retouch_blur_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type, const float blur_radius, dt_iop_retouch_global_data_t *gd)
Definition retouch.c:3958
void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
Definition retouch.c:1993
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition retouch.c:2144
cl_int rt_process_stats_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const int devid, cl_mem dev_img, const int width, const int height, float levels[3])
Definition retouch.c:3645
dt_iop_retouch_drag_types_t
Definition retouch.c:95
@ DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM
Definition retouch.c:97
@ DT_IOP_RETOUCH_WDBAR_DRAG_TOP
Definition retouch.c:96
static float rt_masks_form_get_opacity(dt_iop_module_t *self, int formid)
Definition retouch.c:614
static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
Definition retouch.c:2862
struct dt_iop_retouch_params_t dt_iop_retouch_data_t
Definition retouch.c:220
const char * aliases()
Definition retouch.c:243
static void rt_merge_from_scale_update(const int _merge_from_scale, dt_iop_module_t *self)
Definition retouch.c:1145
static void rt_sync_mask_display_request(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1591
static cl_int rt_copy_image_masked_cl(const int devid, cl_mem dev_src, cl_mem dev_dest, dt_iop_roi_t *const roi_dest, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int kernel)
Definition retouch.c:3807
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition retouch.c:2052
#define RETOUCH_MAX_SCALES
Definition retouch.c:89
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2132
static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1738
static int rt_get_selected_shape_id(const dt_iop_module_t *self)
Definition retouch.c:408
const char * name()
Definition retouch.c:238
#define RT_WDBAR_INSET
Definition retouch.c:1077
static dt_masks_form_group_t * rt_get_mask_point_group(dt_iop_module_t *self, int formid)
Definition retouch.c:421
static gboolean rt_wdbar_motion_notify(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
Definition retouch.c:1245
void gui_reset(struct dt_iop_module_t *self)
Definition retouch.c:2538
static cl_int _retouch_clone_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
Definition retouch.c:3878
static cl_int rt_copy_in_to_out_cl(const int devid, cl_mem dev_in, const struct dt_iop_roi_t *const roi_in, cl_mem dev_out, const struct dt_iop_roi_t *const roi_out, const int dx, const int dy, const int kernel)
Definition retouch.c:3725
static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition retouch.c:1645
static gboolean rt_any_preview_active(dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1560
void gui_init(dt_iop_module_t *self)
Definition retouch.c:2246
#define lw
Definition retouch.c:1078
void change_image(struct dt_iop_module_t *self)
Definition retouch.c:2221
static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
Definition retouch.c:2849
static void rt_display_selected_shapes_lbl(dt_develop_t *dev, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:507
static __DT_CLONE_TARGETS__ int process_internal(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, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out, const int use_sse)
Definition retouch.c:3495
static __DT_CLONE_TARGETS__ void rt_adjust_levels(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *img_src, const int width, const int height, const int ch, const float levels[3])
Definition retouch.c:2915
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition retouch.c:1959
#define RETOUCH_NO_SCALES
Definition retouch.c:90
void tiling_callback(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, const struct dt_dev_pixelpipe_iop_t *piece, struct dt_develop_tiling_t *tiling)
Definition retouch.c:2113
static gboolean rt_masks_form_is_in_roi(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out)
Definition retouch.c:824
static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type)
Definition retouch.c:982
static gboolean rt_wdbar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition retouch.c:1299
void gui_cleanup(dt_iop_module_t *self)
Definition retouch.c:2554
static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
Definition retouch.c:4129
dt_iop_retouch_blur_types_t
Definition retouch.c:105
@ DT_IOP_RETOUCH_BLUR_GAUSSIAN
Definition retouch.c:106
@ DT_IOP_RETOUCH_BLUR_BILATERAL
Definition retouch.c:107
static __DT_CLONE_TARGETS__ void rt_copy_in_to_out(const float *const in, const struct dt_iop_roi_t *const roi_in, float *const out, const struct dt_iop_roi_t *const roi_out, const int ch, const int dx, const int dy)
Definition retouch.c:3019
void cleanup_global(dt_iop_module_so_t *module)
Definition retouch.c:2034
void reload_defaults(dt_iop_module_t *self)
Definition retouch.c:2547
static gboolean rt_wdbar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
Definition retouch.c:1221
void post_history_commit(dt_iop_module_t *self)
Definition retouch.c:783
static gboolean rt_algo_pair_compatible(const dt_iop_retouch_algo_type_t from, const dt_iop_retouch_algo_type_t to)
Definition retouch.c:1801
static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1939
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:268
static void rt_gslider_changed(GtkDarktableGradientSlider *gslider, dt_iop_module_t *self)
Definition retouch.c:1464
int flags()
Definition retouch.c:263
#define RETOUCH_PREVIEW_LVL_MIN
Definition retouch.c:92
void gui_post_expose(struct dt_iop_module_t *self, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition retouch.c:1718
static void rt_show_forms_for_current_scale(dt_iop_module_t *self)
Definition retouch.c:646
static void rt_show_hide_controls(const dt_iop_module_t *self)
Definition retouch.c:462
static void rt_colorpick_color_set_callback(GtkColorButton *widget, dt_iop_module_t *self)
Definition retouch.c:1048
static void _retouch_fill(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, const float *const fill_color)
Definition retouch.c:3147
static void rt_copy_image_masked(float *const img_src, float *img_dest, dt_iop_roi_t *const roi_dest, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity)
Definition retouch.c:3096
static cl_int _retouch_fill_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, float *color, dt_iop_retouch_global_data_t *gd)
Definition retouch.c:3920
static void rt_masks_point_denormalize(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *roi, const float *points, size_t points_count, float *new)
Definition retouch.c:845
static gboolean rt_wdbar_button_release(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1211
static void rt_sync_bypass_cache(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1572
static int rt_scale_has_shapes(dt_iop_retouch_params_t *p, const int scale)
Definition retouch.c:1289
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 retouch.c:3627
static gboolean rt_wdbar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
Definition retouch.c:1162
static gboolean rt_blend_mask_conflicts(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1551
static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
Definition retouch.c:3299
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2138
static void rt_compute_roi_in(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *const pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
Definition retouch.c:2568
static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1911
static void rt_load_shape_algo_in_gui(dt_iop_module_t *self, const int form_selected_id)
Definition retouch.c:525
static int rt_get_index_from_formid(dt_iop_retouch_params_t *p, const int formid)
Definition retouch.c:392
cl_int rt_adjust_levels_cl(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const int devid, cl_mem dev_img, const int width, const int height, const float levels[3])
Definition retouch.c:3685
static void rt_curr_scale_update(const int _curr_scale, dt_iop_module_t *self)
Definition retouch.c:1113
static __DT_CLONE_TARGETS__ void rt_process_stats(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *const img_src, const int width, const int height, const int ch, float levels[3])
Definition retouch.c:2876
static void rt_update_wd_bar_labels(dt_iop_retouch_params_t *p, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1080
void init_global(dt_iop_module_so_t *module)
Definition retouch.c:2015
static int _retouch_blur(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type, const float blur_radius, const int use_sse)
Definition retouch.c:3191
static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1682
static void rt_mask_opacity_callback(GtkWidget *slider, dt_iop_module_t *self)
Definition retouch.c:1703
static void rt_display_selected_fill_color(dt_iop_retouch_gui_data_t *g, dt_iop_retouch_params_t *p)
Definition retouch.c:455
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 retouch.c:4323
static void rt_intersect_2_rois(dt_iop_roi_t *const roi_1, dt_iop_roi_t *const roi_2, const int dx, const int dy, const int padding, dt_iop_roi_t *roi_dest)
Definition retouch.c:3001
static int rt_get_selected_shape_index(dt_develop_t *dev, dt_iop_retouch_params_t *p)
Definition retouch.c:519
static gboolean rt_copypaste_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1514
static gboolean rt_wdbar_button_press(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1175
static int _retouch_heal(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity, const int max_iter)
Definition retouch.c:3266
static int rt_allow_create_form(dt_iop_module_t *self)
Definition retouch.c:634
int populate_masks_context_menu(struct dt_iop_module_t *self, GtkWidget *menu, const int formid, const float pzx, const float pzy)
Definition retouch.c:4529
void color_picker_apply(dt_iop_module_t *self, GtkWidget *picker, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:1481
static int rt_build_scaled_mask(float *const mask, dt_iop_roi_t *const roi_mask, float **mask_scaled, dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx, const int dy, const int algo)
Definition retouch.c:3038
void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out, dt_iop_roi_t *roi_in)
Definition retouch.c:2811
static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1599
#define NEUTRAL_GRAY
#define RETOUCH_PREVIEW_LVL_MAX
Definition retouch.c:93
void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out, const dt_iop_roi_t *roi_in)
Definition retouch.c:2561
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 retouch.c:275
static void rt_extend_roi_in_from_source_clones(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *const pipe, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_in, const int formid_src, const int fl_src, const int ft_src, const int fw_src, const int fh_src, int *_roir, int *_roib, int *_roix, int *_roiy)
Definition retouch.c:2659
static void rt_masks_form_change_opacity(dt_iop_module_t *self, int formid, float opacity)
Definition retouch.c:594
static void rt_paste_forms_from_scale(dt_iop_retouch_params_t *p, const int source_scale, const int dest_scale)
Definition retouch.c:623
static int rt_masks_point_calc_delta(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi, const float *target, const float *source, float *dx, float *dy, const int distort_mode)
Definition retouch.c:858
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED
This signal is raised when pipe is finished and the gui is attached no param, no returned value.
Definition signal.h:182
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
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
const float sigma
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
struct dt_iop_module_t * gui_module
Definition develop.h:170
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
dt_pthread_rwlock_t masks_mutex
Definition develop.h:316
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
GList * forms
Definition develop.h:304
unsigned int channels
Definition format.h:83
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
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
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_output_color
Definition imageop.h:289
gboolean enabled
Definition imageop.h:313
dt_dev_operation_t op
Definition imageop.h:259
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_out[3]
Per-channel linear -> encoded tone curve, same convention as lut_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_iop_retouch_blur_types_t blur_type
Definition retouch.c:124
dt_iop_retouch_fill_modes_t fill_mode
Definition retouch.c:127
dt_iop_retouch_algo_type_t algorithm
Definition retouch.c:122
int kernel_retouch_copy_buffer_to_buffer_masked
Definition retouch.c:230
int kernel_retouch_copy_image_to_buffer_masked
Definition retouch.c:229
GtkDarktableGradientSlider * preview_levels_gslider
Definition retouch.c:201
GtkWidget * bt_showmask
Definition retouch.c:180
GtkWidget * cmb_blur_type
Definition retouch.c:206
GtkWidget * bt_suppress
Definition retouch.c:180
GtkWidget * bt_paste_scale
Definition retouch.c:197
GtkWidget * sl_fill_brightness
Definition retouch.c:215
GtkWidget * hbox_color_pick
Definition retouch.c:210
GtkWidget * cmb_fill_mode
Definition retouch.c:214
GtkWidget * sl_blur_radius
Definition retouch.c:207
GtkWidget * vbox_preview_scale
Definition retouch.c:199
GtkWidget * sl_mask_opacity
Definition retouch.c:217
GtkWidget * bt_edit_masks
Definition retouch.c:178
GtkLabel * label_form_selected
Definition retouch.c:177
GtkWidget * bt_display_wavelet_scale
Definition retouch.c:194
GtkWidget * bt_auto_levels
Definition retouch.c:203
GtkWidget * colorpicker
Definition retouch.c:212
GtkLabel * lbl_num_scales
Definition retouch.c:183
GtkLabel * lbl_curr_scale
Definition retouch.c:184
GtkWidget * bt_copy_scale
Definition retouch.c:196
GtkLabel * lbl_merge_from_scale
Definition retouch.c:185
dt_iop_retouch_blur_types_t blur_type
Definition retouch.c:156
dt_iop_retouch_fill_modes_t fill_mode
Definition retouch.c:159
dt_iop_retouch_algo_type_t algorithm
Definition retouch.c:148
dt_iop_retouch_form_data_t rt_forms[300]
Definition retouch.c:146
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
dt_masks_type_t type
Definition masks.h:254
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
One shape's membership of one group, BY VALUE.
struct dt_develop_t * dev
Definition masks_gui.h:514
int return_layer
Definition dwt.h:128
int merge_from_scale
Definition dwt.h:129
void * user_data
Definition dwt.h:130
int use_sse
Definition dwt.h:38
void * user_data
Definition dwt.h:36
int return_layer
Definition dwt.h:34
int scales
Definition dwt.h:33
int ch
Definition dwt.h:30
int merge_from_scale
Definition dwt.h:35
dt_iop_module_t * self
Definition retouch.c:135
dt_iop_roi_t roi
Definition retouch.c:138
const dt_dev_pixelpipe_t * pipe
Definition retouch.c:136
const dt_dev_pixelpipe_iop_t * piece
Definition retouch.c:137
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
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_paste_forms(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_cut_forms(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_heal(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_blur(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_auto_levels(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_clone(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_solid_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_eye_toggle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_display_wavelet_scale(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_fill(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_edit(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_DIRECTION_UP
@ CPF_DIRECTION_DOWN