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