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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) 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(darktable.gui->reset) return TRUE;
1494
1495 ++darktable.gui->reset;
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
1519 --darktable.gui->reset;
1520
1521 if(scale_copied) dt_dev_add_history_item(darktable.develop, self, TRUE, TRUE);
1522
1523 return TRUE;
1524}
1525
1526static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1527{
1528 if(darktable.gui->reset) return TRUE;
1529
1532
1533 // if blend module is displaying mask do not display wavelet scales
1534 if(self->request_mask_display && !g->mask_display)
1535 {
1536 dt_control_log(_("cannot display scales when the blending mask is displayed"));
1537
1538 ++darktable.gui->reset;
1539 gtk_toggle_button_set_active(togglebutton, FALSE);
1540 --darktable.gui->reset;
1541 return TRUE;
1542 }
1543
1544 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1546
1547 g->display_wavelet_scale = !gtk_toggle_button_get_active(togglebutton);
1548
1550
1551 // compute auto levels only the first time display wavelet scale is used,
1552 // only if levels values are the default
1553 // and a detail scale is displayed
1555 if(g->displayed_wavelet_scale == 0 && p->preview_levels[0] == RETOUCH_PREVIEW_LVL_MIN
1556 && p->preview_levels[1] == 0.f && p->preview_levels[2] == RETOUCH_PREVIEW_LVL_MAX
1557 && g->preview_auto_levels == 0 && p->curr_scale > 0 && p->curr_scale <= p->num_scales)
1558 {
1559 g->preview_auto_levels = 1;
1560 g->displayed_wavelet_scale = 1;
1561 }
1563
1565
1566 gtk_toggle_button_set_active(togglebutton, g->display_wavelet_scale);
1567 return TRUE;
1568}
1569
1570static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
1571{
1572 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1575
1576 // FIXME: this doesn't seems the right place to update params and GUI ...
1577 // update auto levels
1579 if(g->preview_auto_levels == 2)
1580 {
1581 g->preview_auto_levels = -1;
1582
1584
1585 for(int i = 0; i < 3; i++) p->preview_levels[i] = g->preview_levels[i];
1586
1588
1590
1591 // update the gradient slider
1592 double dlevels[3];
1593 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
1594
1595 ++darktable.gui->reset;
1596 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
1597 --darktable.gui->reset;
1598
1599 g->preview_auto_levels = 0;
1600 }
1602
1603 // just in case zoom level has changed
1604 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
1605}
1606
1607static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1608{
1609 if(darktable.gui->reset) return FALSE;
1610
1612
1613 if(self->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->off), 1);
1615
1617 if(g->preview_auto_levels == 0)
1618 {
1619 g->preview_auto_levels = 1;
1620 }
1622
1624
1625 return TRUE;
1626}
1627
1629{
1630 if(darktable.gui->reset) return;
1631
1632 const int shape_id = rt_get_selected_shape_id(self);
1633
1634 if(shape_id > 0)
1635 {
1636 const float opacity = dt_bauhaus_slider_get(slider);
1637 rt_masks_form_change_opacity(self, shape_id, opacity);
1638 }
1639
1641}
1642
1644 cairo_t *cr,
1645 int32_t width,
1646 int32_t height,
1647 int32_t pointerx,
1648 int32_t pointery)
1649{
1651 if(IS_NULL_PTR(g)) return;
1652
1653 const int shape_id = rt_get_selected_shape_id(self);
1654
1655 if(shape_id > 0)
1656 {
1657 ++darktable.gui->reset;
1658 dt_bauhaus_slider_set(g->sl_mask_opacity, rt_masks_form_get_opacity(self, shape_id));
1659 --darktable.gui->reset;
1660 }
1661}
1662
1663static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1664{
1665 if(darktable.gui->reset) return FALSE;
1666
1667 // if we don't have the focus, request for it and quit, gui_focus() do the rest
1668 if(self->dev->gui_module != self)
1669 {
1671 return FALSE;
1672 }
1673
1676
1677 //hide all shapes and free if some are in creation
1678 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
1680
1682
1683 if(event->button == 1)
1684 {
1685 ++darktable.gui->reset;
1686
1688
1690 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
1691 {
1692 const gboolean control_button_pressed = dt_modifier_is(event->state, GDK_CONTROL_MASK);
1693
1694 switch(bd->masks_shown)
1695 {
1696 case DT_MASKS_EDIT_FULL:
1697 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_OFF;
1698 break;
1699
1701 bd->masks_shown = !control_button_pressed ? DT_MASKS_EDIT_FULL : DT_MASKS_EDIT_OFF;
1702 break;
1703
1704 default:
1705 case DT_MASKS_EDIT_OFF:
1706 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_FULL;
1707 break;
1708 }
1709 }
1710 else
1712
1714
1715 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
1716 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
1717
1718 --darktable.gui->reset;
1719
1720 return TRUE;
1721 }
1722
1723 return TRUE;
1724}
1725
1728{
1729 return ((from == DT_IOP_RETOUCH_CLONE && to == DT_IOP_RETOUCH_HEAL)
1730 || (from == DT_IOP_RETOUCH_HEAL && to == DT_IOP_RETOUCH_CLONE)
1731 || (from == DT_IOP_RETOUCH_BLUR && to == DT_IOP_RETOUCH_FILL)
1732 || (from == DT_IOP_RETOUCH_FILL && to == DT_IOP_RETOUCH_BLUR));
1733}
1734
1735static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e,
1736 dt_iop_module_t *self)
1737{
1738 if(darktable.gui->reset) return FALSE;
1739
1740 ++darktable.gui->reset;
1741
1744
1746
1747 if(togglebutton == (GtkToggleButton *)g->bt_blur)
1748 new_algo = DT_IOP_RETOUCH_BLUR;
1749 else if(togglebutton == (GtkToggleButton *)g->bt_clone)
1750 new_algo = DT_IOP_RETOUCH_CLONE;
1751 else if(togglebutton == (GtkToggleButton *)g->bt_heal)
1752 new_algo = DT_IOP_RETOUCH_HEAL;
1753 else if(togglebutton == (GtkToggleButton *)g->bt_fill)
1754 new_algo = DT_IOP_RETOUCH_FILL;
1755
1756 // check if we have to do something
1757 gboolean accept = TRUE;
1758
1759 const int index = rt_get_selected_shape_index(p);
1760 if(index >= 0 && dt_modifier_is(e->state, GDK_CONTROL_MASK))
1761 {
1762 const dt_iop_retouch_algo_type_t current_algo = p->rt_forms[index].algorithm;
1763 accept = (new_algo != current_algo && !rt_algo_pair_compatible(current_algo, new_algo));
1764 }
1765
1766 if(accept) p->algorithm = new_algo;
1767
1768 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), (p->algorithm == DT_IOP_RETOUCH_CLONE));
1769 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), (p->algorithm == DT_IOP_RETOUCH_HEAL));
1770 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), (p->algorithm == DT_IOP_RETOUCH_BLUR));
1771 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), (p->algorithm == DT_IOP_RETOUCH_FILL));
1772
1774
1775 if(!accept)
1776 {
1777 --darktable.gui->reset;
1778 return FALSE;
1779 }
1780
1781 if(index >= 0 && dt_modifier_is(e->state, GDK_CONTROL_MASK))
1782 {
1783 if(p->algorithm != p->rt_forms[index].algorithm)
1784 {
1785 p->rt_forms[index].algorithm = p->algorithm;
1787 }
1788 }
1789 else if(self->dev->form_gui->creation && (self->dev->form_gui->creation_module == self))
1790 {
1792
1794 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_polygon)))
1796 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle)))
1798 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse)))
1800 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_brush)))
1802
1803 dt_masks_type_t masks_type = DT_MASKS_NONE;
1804 if(p->algorithm == DT_IOP_RETOUCH_CLONE || p->algorithm == DT_IOP_RETOUCH_HEAL)
1805 masks_type = (type | DT_MASKS_CLONE);
1806 else
1807 masks_type = (type | DT_MASKS_NON_CLONE);
1808
1809 dt_masks_creation_mode_enter(self, masks_type);
1810
1812 }
1813
1814 --darktable.gui->reset;
1815
1817
1818 // if we have the shift key pressed, we set it as default
1819 if(dt_modifier_is(e->state, GDK_SHIFT_MASK))
1820 {
1821 dt_conf_set_int("plugins/darkroom/retouch/default_algo", p->algorithm);
1822 // and we show a toat msg to confirm
1823 if(p->algorithm == DT_IOP_RETOUCH_CLONE)
1824 dt_control_log(N_("default tool changed to %s"), N_("cloning"));
1825 else if(p->algorithm == DT_IOP_RETOUCH_HEAL)
1826 dt_control_log(N_("default tool changed to %s"), N_("healing"));
1827 else if(p->algorithm == DT_IOP_RETOUCH_FILL)
1828 dt_control_log(N_("default tool changed to %s"), N_("blur"));
1829 else if(p->algorithm == DT_IOP_RETOUCH_BLUR)
1830 dt_control_log(N_("default tool changed to %s"), N_("fill"));
1831 }
1832
1833 return TRUE;
1834}
1835
1836static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1837{
1838 if(darktable.gui->reset) return TRUE;
1839
1841
1842 // if blend module is displaying mask do not display it here
1843 if(module->request_mask_display && !g->mask_display)
1844 {
1845 dt_control_log(_("cannot display masks when the blending mask is displayed"));
1846
1847 gtk_toggle_button_set_active(togglebutton, FALSE);
1848 return TRUE;
1849 }
1850
1851 g->mask_display = !gtk_toggle_button_get_active(togglebutton);
1852
1853 if(module->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), 1);
1854 dt_iop_request_focus(module);
1855
1857
1858 gtk_toggle_button_set_active(togglebutton, g->mask_display);
1859 return TRUE;
1860}
1861
1862static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1863{
1864 if(darktable.gui->reset) return TRUE;
1865
1867 g->suppress_mask = !gtk_toggle_button_get_active(togglebutton);
1868
1869 if(module->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->off), 1);
1870 dt_iop_request_focus(module);
1871
1873
1874 gtk_toggle_button_set_active(togglebutton, g->suppress_mask);
1875 return TRUE;
1876}
1877
1878void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
1879{
1882
1883 if(w == g->cmb_fill_mode)
1884 {
1885 ++darktable.gui->reset;
1887 --darktable.gui->reset;
1888 }
1889 else
1890 {
1891 const int index = rt_get_selected_shape_index(p);
1892 if(index >= 0)
1893 {
1894 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR)
1895 {
1896 p->rt_forms[index].blur_type = p->blur_type;
1897 p->rt_forms[index].blur_radius = p->blur_radius;
1898 }
1899 else if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1900 {
1901 p->rt_forms[index].fill_mode = p->fill_mode;
1902 p->rt_forms[index].fill_brightness = p->fill_brightness;
1903 }
1904 }
1905 }
1906}
1907
1908//--------------------------------------------------------------------------------------------------
1909// GUI
1910//--------------------------------------------------------------------------------------------------
1911
1912void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
1913{
1915 if(IS_NULL_PTR(g)) return;
1916
1918 rt_load_shape_algo_in_gui(self, form_selected_id);
1920}
1921
1923{
1924 dt_iop_default_init(module);
1925
1926 dt_iop_retouch_params_t *d = module->default_params;
1927
1929 d->preview_levels[1] = 0.f;
1930 d->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
1931 d->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
1932}
1933
1935{
1936 const int program = 21; // retouch.cl, from programs.conf
1938 module->data = gd;
1939 gd->kernel_retouch_clear_alpha = dt_opencl_create_kernel(program, "retouch_clear_alpha");
1940 gd->kernel_retouch_copy_alpha = dt_opencl_create_kernel(program, "retouch_copy_alpha");
1941 gd->kernel_retouch_copy_buffer_to_buffer = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer");
1942 gd->kernel_retouch_copy_buffer_to_image = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_image");
1943 gd->kernel_retouch_fill = dt_opencl_create_kernel(program, "retouch_fill");
1945 = dt_opencl_create_kernel(program, "retouch_copy_image_to_buffer_masked");
1947 = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer_masked");
1948 gd->kernel_retouch_image_rgb2lab = dt_opencl_create_kernel(program, "retouch_image_rgb2lab");
1949 gd->kernel_retouch_image_lab2rgb = dt_opencl_create_kernel(program, "retouch_image_lab2rgb");
1950 gd->kernel_retouch_copy_mask_to_alpha = dt_opencl_create_kernel(program, "retouch_copy_mask_to_alpha");
1951}
1952
1970
1971void gui_focus(struct dt_iop_module_t *self, gboolean in)
1972{
1973 if(self->enabled)
1974 {
1976
1977 if(in)
1978 {
1980 //only show shapes if shapes exist
1982 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
1983 {
1984 // got focus, show all shapes
1987
1989
1990 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
1992 && (self->dev->gui_module == self));
1993 }
1994 }
1995 else
1996 {
1997 // lost focus, hide all shapes and free if some are in creation
1998 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
2000
2002 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2003
2005 }
2006
2007 // if we are switching between display modes we have to reprocess the main image
2008 if(g->display_wavelet_scale || g->mask_display || g->suppress_mask)
2010 }
2011}
2012
2016{
2017 dt_iop_retouch_params_t synced_params = *(dt_iop_retouch_params_t *)params;
2018 if(!IS_NULL_PTR(pipe) && !IS_NULL_PTR(pipe->forms))
2019 {
2020 rt_resynch_params(self, &synced_params, pipe->forms);
2021 }
2022 else
2023 {
2025 rt_resynch_params(self, &synced_params, self->dev->forms);
2027 }
2028
2029 memcpy(piece->data, &synced_params, sizeof(dt_iop_retouch_params_t));
2030}
2031
2032void 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)
2033{
2035 const float require = 2.0f;
2036 const float require_cl = 1.0f // in_retouch
2037 + ((p->num_scales > 0) ? 4.0f : 2.0f); // dwt_wavelet_decompose_cl requires 4 buffers, otherwise 2.0f is enough
2038 // FIXME the above are worst case values, we might iterate through the dt_iop_retouch_form_data_t to get
2039 // the largest bounding box
2040
2041 tiling->factor = 2.0f + require; // input & output buffers + internal requirements
2042 tiling->factor_cl = 2.0f + require_cl;
2043 tiling->maxbuf = 1.0f;
2044 tiling->maxbuf_cl = 1.0f;
2045 tiling->overhead = 0;
2046 tiling->overlap = 0;
2047 tiling->xalign = 1;
2048 tiling->yalign = 1;
2049}
2050
2052{
2053 piece->data = dt_calloc_align(sizeof(dt_iop_retouch_data_t));
2054 piece->data_size = sizeof(dt_iop_retouch_data_t);
2055}
2056
2058{
2059 dt_free_align(piece->data);
2060 piece->data = NULL;
2061}
2062
2064{
2067
2068 // check if there is new or deleted forms
2070 rt_resynch_params(self, p, self->dev->forms);
2072
2073 // update clones count
2074 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id);
2075 guint nb = 0;
2076 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
2077 gchar *str = g_strdup_printf("%d", nb);
2078 gtk_label_set_text(g->label_form, str);
2079 dt_free(str);
2080
2081 // update wavelet decompose labels
2083
2084 // update selected shape label
2086
2087 // show the shapes for the current scale
2089
2090 // enable/disable algorithm toolbar
2091 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), p->algorithm == DT_IOP_RETOUCH_CLONE);
2092 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), p->algorithm == DT_IOP_RETOUCH_BLUR);
2093 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), p->algorithm == DT_IOP_RETOUCH_HEAL);
2094 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), p->algorithm == DT_IOP_RETOUCH_FILL);
2095
2096 // enable/disable shapes toolbar
2097 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), rt_shape_is_being_added(self, DT_MASKS_CIRCLE));
2098 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_polygon), rt_shape_is_being_added(self, DT_MASKS_POLYGON));
2099 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), rt_shape_is_being_added(self, DT_MASKS_ELLIPSE));
2100 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_brush), rt_shape_is_being_added(self, DT_MASKS_BRUSH));
2101
2102 // update the rest of the fields
2103 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
2104
2105 dt_bauhaus_combobox_set(g->cmb_blur_type, p->blur_type);
2106 dt_bauhaus_slider_set(g->sl_blur_radius, p->blur_radius);
2107 dt_bauhaus_slider_set(g->sl_fill_brightness, p->fill_brightness);
2108 dt_bauhaus_combobox_set(g->cmb_fill_mode, p->fill_mode);
2109
2111
2112 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_display_wavelet_scale), g->display_wavelet_scale);
2113 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_copy_scale), g->copied_scale >= 0);
2114 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_paste_scale), g->copied_scale >= 0);
2115 gtk_widget_set_sensitive(g->bt_paste_scale, g->copied_scale >= 0);
2116
2117 // show/hide some fields
2119
2120 // update edit shapes status
2122
2123 //only toggle shape show button if shapes exist
2124 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
2125 {
2126 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
2127 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
2128 }
2129 else
2130 {
2131 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2132 }
2133
2134 // update the gradient slider
2135 double dlevels[3];
2136 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
2137 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
2138}
2139
2141{
2143 if(!IS_NULL_PTR(g))
2144 {
2145 g->copied_scale = -1;
2146 g->mask_display = 0;
2147 g->suppress_mask = 0;
2148 g->display_wavelet_scale = 0;
2149 g->displayed_wavelet_scale = 0;
2150 g->first_scale_visible = RETOUCH_MAX_SCALES + 1;
2151
2152 g->preview_auto_levels = 0;
2153 g->preview_levels[0] = RETOUCH_PREVIEW_LVL_MIN;
2154 g->preview_levels[1] = 0.f;
2155 g->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
2156
2157 g->is_dragging = 0;
2158 g->wdbar_mouse_x = g->wdbar_mouse_y = -1;
2159 g->curr_scale = -1;
2160 g->lower_cursor = g->upper_cursor = FALSE;
2161 g->lower_margin = g->upper_margin = FALSE;
2162 }
2163}
2164
2166{
2169
2170 change_image(self);
2171
2172 // shapes toolbar
2173 GtkWidget *hbox_shapes = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2174
2175 gtk_box_pack_start(GTK_BOX(hbox_shapes), dt_ui_label_new(_("shapes:")), FALSE, TRUE, 0);
2176 g->label_form = GTK_LABEL(gtk_label_new("-1"));
2177 gtk_box_pack_start(GTK_BOX(hbox_shapes), GTK_WIDGET(g->label_form), FALSE, TRUE, DT_PIXEL_APPLY_DPI(5));
2178 gtk_widget_set_tooltip_text(hbox_shapes,
2179 _("to add a shape select an algorithm and a shape type and click on the image.\n"
2180 "shapes are added to the current scale"));
2181
2182 g->bt_edit_masks = dt_iop_togglebutton_new(self, N_("editing"), N_("show and edit shapes on the current scale"),
2183 N_("show and edit shapes in restricted mode"),
2184 G_CALLBACK(rt_edit_masks_callback), TRUE, 0, 0,
2185 dtgtk_cairo_paint_masks_edit, hbox_shapes);
2186
2187 GtkWidget *shape_buttons[DEVELOP_MASKS_NB_SHAPES] = { 0 };
2188 const dt_masks_shape_buttons_config_t shape_buttons_config = {
2189 .owner_module = self,
2190 .creation_module = self,
2191 .buttons = shape_buttons,
2192 .types = NULL,
2193 .action_section = N_("shapes"),
2194 .flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT, // All shapes minus gradient
2195 .register_flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT,
2196 .local = TRUE,
2197 .user_data = NULL,
2198 .can_start = rt_shape_buttons_can_start,
2199 .form_type = rt_shape_buttons_form_type,
2200 .started = NULL,
2201 .exited = NULL,
2202 };
2203 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
2204 gtk_box_pack_start(GTK_BOX(hbox_shapes), shape_buttons_box, FALSE, FALSE, 0);
2205 g->bt_circle = shape_buttons[DT_MASKS_SHAPE_INDEX_CIRCLE];
2206 g->bt_ellipse = shape_buttons[DT_MASKS_SHAPE_INDEX_ELLIPSE];
2207 g->bt_polygon = shape_buttons[DT_MASKS_SHAPE_INDEX_POLYGON];
2208 g->bt_brush = shape_buttons[DT_MASKS_SHAPE_INDEX_BRUSH];
2209
2210 // algorithm toolbar
2211 GtkWidget *hbox_algo = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2212
2213 gtk_box_pack_start(GTK_BOX(hbox_algo), dt_ui_label_new(_("algorithms:")), FALSE, TRUE, 0);
2214
2215 g->bt_blur = dt_iop_togglebutton_new(
2216 self, N_("tools"), N_("activate blur tool"), N_("change algorithm for current form"),
2217 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_blur, hbox_algo);
2218
2219 g->bt_fill = dt_iop_togglebutton_new(
2220 self, N_("tools"), N_("activate fill tool"), N_("change algorithm for current form"),
2221 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_fill, hbox_algo);
2222
2223 g->bt_clone = dt_iop_togglebutton_new(
2224 self, N_("tools"), N_("activate cloning tool"), N_("change algorithm for current form"),
2225 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_clone, hbox_algo);
2226
2227 g->bt_heal = dt_iop_togglebutton_new(
2228 self, N_("tools"), N_("activate healing tool"), N_("change algorithm for current form"),
2229 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_heal, hbox_algo);
2230
2231 // overwrite tooltip ourself to handle shift+click
2232 gchar *tt2 = g_strdup_printf("%s\n%s", _("ctrl+click to change tool for current form"),
2233 _("shift+click to set the tool as default"));
2234 gchar *tt = g_strdup_printf("%s\n%s", _("activate blur tool"), tt2);
2235 gtk_widget_set_tooltip_text(g->bt_blur, tt);
2236 dt_free(tt);
2237 tt = g_strdup_printf("%s\n%s", _("activate fill tool"), tt2);
2238 gtk_widget_set_tooltip_text(g->bt_fill, tt);
2239 dt_free(tt);
2240 tt = g_strdup_printf("%s\n%s", _("activate cloning tool"), tt2);
2241 gtk_widget_set_tooltip_text(g->bt_clone, tt);
2242 dt_free(tt);
2243 tt = g_strdup_printf("%s\n%s", _("activate healing tool"), tt2);
2244 gtk_widget_set_tooltip_text(g->bt_heal, tt);
2245 dt_free(tt);
2246 dt_free(tt2);
2247
2248 // wavelet decompose bar labels
2249 GtkWidget *grid_wd_labels = gtk_grid_new();
2250 gtk_grid_set_column_homogeneous(GTK_GRID(grid_wd_labels), FALSE);
2251
2252 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("scales:")), 0, 0, 1, 1);
2253 g->lbl_num_scales = GTK_LABEL(dt_ui_label_new(NULL));
2254 gtk_label_set_width_chars(g->lbl_num_scales, 2);
2255 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_num_scales), 1, 0, 1, 1);
2256
2257 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("current:")), 0, 1, 1, 1);
2258 g->lbl_curr_scale = GTK_LABEL(dt_ui_label_new(NULL));
2259 gtk_label_set_width_chars(g->lbl_curr_scale, 2);
2260 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_curr_scale), 1, 1, 1, 1);
2261
2262 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("merge from:")), 0, 2, 1, 1);
2263 g->lbl_merge_from_scale = GTK_LABEL(dt_ui_label_new(NULL));
2264 gtk_label_set_width_chars(g->lbl_merge_from_scale, 2);
2265 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_merge_from_scale), 1, 2, 1, 1);
2266
2267 // wavelet decompose bar
2268 g->wd_bar = gtk_drawing_area_new();
2269
2270 gtk_widget_set_tooltip_text(g->wd_bar, _("top slider adjusts where the merge scales start\n"
2271 "bottom slider adjusts the number of scales\n"
2272 "dot indicates the current scale\n"
2273 "top line indicates that the scale is visible at current zoom level\n"
2274 "bottom line indicates that the scale has shapes on it"));
2275 g_signal_connect(G_OBJECT(g->wd_bar), "draw", G_CALLBACK(rt_wdbar_draw), self);
2276 g_signal_connect(G_OBJECT(g->wd_bar), "motion-notify-event", G_CALLBACK(rt_wdbar_motion_notify), self);
2277 g_signal_connect(G_OBJECT(g->wd_bar), "leave-notify-event", G_CALLBACK(rt_wdbar_leave_notify), self);
2278 g_signal_connect(G_OBJECT(g->wd_bar), "button-press-event", G_CALLBACK(rt_wdbar_button_press), self);
2279 g_signal_connect(G_OBJECT(g->wd_bar), "button-release-event", G_CALLBACK(rt_wdbar_button_release), self);
2280 g_signal_connect(G_OBJECT(g->wd_bar), "scroll-event", G_CALLBACK(rt_wdbar_scrolled), self);
2281 gtk_widget_add_events(GTK_WIDGET(g->wd_bar), GDK_POINTER_MOTION_MASK
2282 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2283 | GDK_LEAVE_NOTIFY_MASK | darktable.gui->scroll_mask);
2284 gtk_widget_set_size_request(g->wd_bar, -1, DT_PIXEL_APPLY_DPI(40));
2285
2286 // toolbar display current scale / cut&paste / suppress&display masks
2287 GtkWidget *hbox_scale = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2288
2289 // display & suppress masks
2290 g->bt_showmask = dt_iop_togglebutton_new(self, N_("editing"), N_("display masks"), NULL,
2291 G_CALLBACK(rt_showmask_callback), TRUE, 0, 0,
2292 dtgtk_cairo_paint_showmask, hbox_scale);
2293
2294
2295 g->bt_suppress = dt_iop_togglebutton_new(self, N_("editing"), N_("temporarily switch off shapes"), NULL,
2296 G_CALLBACK(rt_suppress_callback), TRUE, 0, 0,
2297 dtgtk_cairo_paint_eye_toggle, hbox_scale);
2298
2299
2300 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2301
2302 // copy/paste shapes
2303 g->bt_paste_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("paste cut shapes to current scale"), NULL,
2304 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2305 dtgtk_cairo_paint_paste_forms, hbox_scale);
2306
2307 g->bt_copy_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("cut shapes from current scale"), NULL,
2308 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2309 dtgtk_cairo_paint_cut_forms, hbox_scale);
2310
2311 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2312
2313 // display final image/current scale
2314 g->bt_display_wavelet_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("display wavelet scale"), NULL,
2315 G_CALLBACK(rt_display_wavelet_scale_callback), TRUE, 0, 0,
2317
2318
2319 // preview single scale
2320 g->vbox_preview_scale = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2321
2322 GtkWidget *lbl_psc = dt_ui_section_label_new(_("preview single scale"));
2323 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), lbl_psc, FALSE, TRUE, 0);
2324
2325 GtkWidget *prev_lvl = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2326
2327 // gradient slider
2328 #define NEUTRAL_GRAY 0.5
2329 static const GdkRGBA _gradient_L[]
2330 = { { 0, 0, 0, 1.0 }, { NEUTRAL_GRAY, NEUTRAL_GRAY, NEUTRAL_GRAY, 1.0 } };
2331 g->preview_levels_gslider = DTGTK_GRADIENT_SLIDER_MULTIVALUE(
2333 gtk_widget_set_tooltip_text(GTK_WIDGET(g->preview_levels_gslider), _("adjust preview levels"));
2337 (g->preview_levels_gslider)->scale_callback = rt_gslider_scale_callback;
2339 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, vdefault);
2340 dtgtk_gradient_slider_multivalue_set_resetvalues(g->preview_levels_gslider, vdefault);
2341 (g->preview_levels_gslider)->markers_type = PROPORTIONAL_MARKERS;
2342 (g->preview_levels_gslider)->min_spacing = 0.05;
2343 g_signal_connect(G_OBJECT(g->preview_levels_gslider), "value-changed", G_CALLBACK(rt_gslider_changed), self);
2344
2345 gtk_box_pack_start(GTK_BOX(prev_lvl), GTK_WIDGET(g->preview_levels_gslider), TRUE, TRUE, 0);
2346
2347 // auto-levels button
2348 g->bt_auto_levels = dt_iop_togglebutton_new(self, N_("editing"), N_("auto levels"), NULL,
2349 G_CALLBACK(rt_auto_levels_callback), TRUE, 0, 0,
2351
2352 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), prev_lvl, TRUE, TRUE, 0);
2353
2354 // shapes selected (label)
2355 GtkWidget *hbox_shape_sel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2356 GtkWidget *label1 = gtk_label_new(_("shape selected:"));
2357 gtk_label_set_ellipsize(GTK_LABEL(label1), PANGO_ELLIPSIZE_START);
2358 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), label1, FALSE, TRUE, 0);
2359 g->label_form_selected = GTK_LABEL(gtk_label_new("-1"));
2360 gtk_widget_set_tooltip_text(hbox_shape_sel,
2361 _("click on a shape to select it,\nto unselect click on an empty space"));
2362 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), GTK_WIDGET(g->label_form_selected), FALSE, TRUE, 0);
2363
2364 // fill properties
2365 g->vbox_fill = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2366
2367 g->cmb_fill_mode = dt_bauhaus_combobox_from_params(self, "fill_mode");
2368 gtk_widget_set_tooltip_text(g->cmb_fill_mode, _("erase the detail or fills with chosen color"));
2369
2370 // color for fill algorithm
2371 GdkRGBA color
2372 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
2373
2374 g->hbox_color_pick = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2375 GtkWidget *lbl_fill_color = dt_ui_label_new(_("fill color: "));
2376 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), lbl_fill_color, FALSE, TRUE, 0);
2377
2378 g->colorpick = gtk_color_button_new_with_rgba(&color);
2379 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->colorpick), FALSE);
2380 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->colorpick), _("select fill color"));
2381 gtk_widget_set_tooltip_text(g->colorpick, _("select fill color"));
2382 g_signal_connect(G_OBJECT(g->colorpick), "color-set", G_CALLBACK(rt_colorpick_color_set_callback), self);
2383 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), GTK_WIDGET(g->colorpick), TRUE, TRUE, 0);
2384
2385 g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, g->hbox_color_pick);
2386 gtk_widget_set_tooltip_text(g->colorpicker, _("pick fill color from image"));
2387
2388 gtk_box_pack_start(GTK_BOX(g->vbox_fill), g->hbox_color_pick, TRUE, TRUE, 0);
2389
2390 g->sl_fill_brightness = dt_bauhaus_slider_from_params(self, "fill_brightness");
2391 dt_bauhaus_slider_set_digits(g->sl_fill_brightness, 4);
2392 dt_bauhaus_slider_set_format(g->sl_fill_brightness, "%");
2393 gtk_widget_set_tooltip_text(g->sl_fill_brightness,
2394 _("adjusts color brightness to fine-tune it. works with erase as well"));
2395
2396 // blur properties
2397 g->vbox_blur = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2398
2399 g->cmb_blur_type = dt_bauhaus_combobox_from_params(self, "blur_type");
2400 gtk_widget_set_tooltip_text(g->cmb_blur_type, _("type for the blur algorithm"));
2401
2402 g->sl_blur_radius = dt_bauhaus_slider_from_params(self, "blur_radius");
2403 dt_bauhaus_slider_set_format(g->sl_blur_radius, " px");
2404 gtk_widget_set_tooltip_text(g->sl_blur_radius, _("radius of the selected blur type"));
2405
2406 // mask opacity
2407 g->sl_mask_opacity = dt_bauhaus_slider_new_with_range(darktable.bauhaus, DT_GUI_MODULE(self), 0.0, 1.0, 0, 1., 3);
2408 dt_bauhaus_widget_set_label(g->sl_mask_opacity, N_("mask opacity"));
2409 dt_bauhaus_slider_set_format(g->sl_mask_opacity, "%");
2410 gtk_widget_set_tooltip_text(g->sl_mask_opacity, _("set the opacity on the selected shape"));
2411 g_signal_connect(G_OBJECT(g->sl_mask_opacity), "value-changed", G_CALLBACK(rt_mask_opacity_callback), self);
2412
2413 // start building top level widget
2414 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2415
2416 GtkWidget *lbl_rt_tools = dt_ui_section_label_new(_("retouch tools"));
2417 gtk_box_pack_start(GTK_BOX(self->widget), lbl_rt_tools, FALSE, TRUE, 0);
2418
2419 // shapes toolbar
2420 gtk_box_pack_start(GTK_BOX(self->widget), hbox_shapes, TRUE, TRUE, 0);
2421 // algorithms toolbar
2422 gtk_box_pack_start(GTK_BOX(self->widget), hbox_algo, TRUE, TRUE, 0);
2423
2424 // wavelet decompose
2425 GtkWidget *lbl_wd = dt_ui_section_label_new(_("wavelet decompose"));
2426 gtk_box_pack_start(GTK_BOX(self->widget), lbl_wd, FALSE, TRUE, 0);
2427
2428 // wavelet decompose bar & labels
2429 gtk_box_pack_start(GTK_BOX(self->widget), grid_wd_labels, TRUE, TRUE, 0);
2430 gtk_box_pack_start(GTK_BOX(self->widget), g->wd_bar, TRUE, TRUE, DT_PIXEL_APPLY_DPI(3));
2431
2432 // preview scale & cut/paste scale
2433 gtk_box_pack_start(GTK_BOX(self->widget), hbox_scale, TRUE, TRUE, 0);
2434
2435 // preview single scale
2436 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_preview_scale, TRUE, TRUE, 0);
2437
2438 // shapes
2439 GtkWidget *lbl_shapes = dt_ui_section_label_new(_("shapes"));
2440 gtk_box_pack_start(GTK_BOX(self->widget), lbl_shapes, FALSE, TRUE, 0);
2441
2442 // shape selected
2443 gtk_box_pack_start(GTK_BOX(self->widget), hbox_shape_sel, TRUE, TRUE, 0);
2444 // blur radius
2445 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_blur, TRUE, TRUE, 0);
2446 // fill color
2447 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_fill, TRUE, TRUE, 0);
2448 // mask (shape) opacity
2449 gtk_box_pack_start(GTK_BOX(self->widget), g->sl_mask_opacity, TRUE, TRUE, 0);
2450
2451 /* add signal handler for preview pipe finish to redraw the preview */
2453 G_CALLBACK(rt_develop_ui_pipe_finished_callback), self);
2454}
2455
2456void gui_reset(struct dt_iop_module_t *self)
2457{
2458 // hide the previous masks
2460 // set the algo to the default one
2462 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2463}
2464
2466{
2467 // set the algo to the default one
2469 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2470}
2471
2478
2479void modify_roi_out(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2480 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
2481 const dt_iop_roi_t *roi_in)
2482{
2483 *roi_out = *roi_in;
2484}
2485
2486static void rt_compute_roi_in(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *const pipe,
2487 struct dt_dev_pixelpipe_iop_t *piece,
2488 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2489{
2492
2493 int roir = *_roir;
2494 int roib = *_roib;
2495 int roix = *_roix;
2496 int roiy = *_roiy;
2497
2498 // We iterate through all forms
2499 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2500 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2501 {
2502 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2503 {
2504 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2505 if(IS_NULL_PTR(grpt)) continue;
2506
2507 const int formid = grpt->formid;
2508 const int index = rt_get_index_from_formid(p, formid);
2509 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
2510 {
2511 continue;
2512 }
2513
2514 // we get the spot
2515 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2516 if(IS_NULL_PTR(form)) continue;
2517
2518 // if the form is outside the roi, we just skip it
2519 // we get the area for the form
2520 int fl, ft, fw, fh;
2521 if(dt_masks_get_area(self, pipe, piece, form, &fw, &fh, &fl, &ft) != 0)
2522 {
2523 continue;
2524 }
2525
2526 // is the form outside of the roi?
2527 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2528 if(ft >= roi_in->y + roi_in->height || ft + fh <= roi_in->y || fl >= roi_in->x + roi_in->width
2529 || fl + fw <= roi_in->x)
2530 {
2531 continue;
2532 }
2533
2534 // heal need the entire area
2535 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL)
2536 {
2537 // we enlarge the roi if needed
2538 roiy = fminf(ft, roiy);
2539 roix = fminf(fl, roix);
2540 roir = fmaxf(fl + fw, roir);
2541 roib = fmaxf(ft + fh, roib);
2542 }
2543 // blur need an overlap of 4 * radius (scaled)
2544 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR && index >= 0)
2545 {
2546 const int overlap = ceilf(4 * (p->rt_forms[index].blur_radius * roi_in->scale));
2547 if(roiy > ft) roiy = MAX(roiy - overlap, ft);
2548 if(roix > fl) roix = MAX(roix - overlap, fl);
2549 if(roir < fl + fw) roir = MAX(roir + overlap, fl + fw);
2550 if(roib < ft + fh) roib = MAX(roib + overlap, ft + fh);
2551 }
2552 // heal and clone need both source and destination areas
2553 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL
2554 || p->rt_forms[index].algorithm == DT_IOP_RETOUCH_CLONE)
2555 {
2556 float dx = 0.f, dy = 0.f;
2557 if(rt_masks_get_delta_to_destination(self, pipe, piece, roi_in, form, &dx, &dy,
2558 p->rt_forms[index].distort_mode))
2559 {
2560 roiy = fminf(ft - dy, roiy);
2561 roix = fminf(fl - dx, roix);
2562 roir = fmaxf(fl + fw - dx, roir);
2563 roib = fmaxf(ft + fh - dy, roib);
2564 }
2565 }
2566 }
2567 }
2568
2569 *_roir = roir;
2570 *_roib = roib;
2571 *_roix = roix;
2572 *_roiy = roiy;
2573}
2574
2575// for a given form, if a previous clone/heal destination intersects the source area,
2576// include that area in roi_in too
2578 struct dt_dev_pixelpipe_iop_t *piece,
2579 dt_iop_roi_t *roi_in, const int formid_src, const int fl_src,
2580 const int ft_src, const int fw_src, const int fh_src, int *_roir,
2581 int *_roib, int *_roix, int *_roiy)
2582{
2585
2586 int roir = *_roir;
2587 int roib = *_roib;
2588 int roix = *_roix;
2589 int roiy = *_roiy;
2590
2591 // We iterate through all forms
2592 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2593 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2594 {
2595 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2596 {
2597 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2598 if(IS_NULL_PTR(grpt)) continue;
2599
2600 const int formid = grpt->formid;
2601
2602 // just need the previous forms
2603 if(formid == formid_src) break;
2604
2605 const int index = rt_get_index_from_formid(p, formid);
2606
2607 // only process clone and heal
2608 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2609 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2610 {
2611 continue;
2612 }
2613
2614 // we get the spot
2615 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2616 if(IS_NULL_PTR(form)) continue;
2617
2618 // we get the source area
2619 int fl, ft, fw, fh;
2620 if(dt_masks_get_source_area(self, pipe, piece, form, &fw, &fh, &fl, &ft) != 0)
2621 {
2622 continue;
2623 }
2624 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2625
2626 // get the destination area
2627 int fl_dest, ft_dest;
2628 float dx = 0.f, dy = 0.f;
2629 if(!rt_masks_get_delta_to_destination(self, pipe, piece, roi_in, form, &dx, &dy,
2630 p->rt_forms[index].distort_mode))
2631 {
2632 continue;
2633 }
2634
2635 ft_dest = ft + dy;
2636 fl_dest = fl + dx;
2637
2638 // check if the destination of this form intersects the source of the formid_src
2639 const int intersects = !(ft_dest + fh < ft_src || ft_src + fh_src < ft_dest || fl_dest + fw < fl_src
2640 || fl_src + fw_src < fl_dest);
2641 if(intersects)
2642 {
2643 // we enlarge the roi if needed
2644 roiy = fminf(ft, roiy);
2645 roix = fminf(fl, roix);
2646 roir = fmaxf(fl + fw, roir);
2647 roib = fmaxf(ft + fh, roib);
2648
2649 // need both source and destination areas
2650 roiy = fminf(ft + dy, roiy);
2651 roix = fminf(fl + dx, roix);
2652 roir = fmaxf(fl + fw + dx, roir);
2653 roib = fmaxf(ft + fh + dy, roib);
2654 }
2655 }
2656 }
2657
2658 *_roir = roir;
2659 *_roib = roib;
2660 *_roix = roix;
2661 *_roiy = roiy;
2662}
2663
2664// for clone and heal, if the source area is the destination from another clone/heal,
2665// we also need the area from that previous clone/heal
2667 struct dt_dev_pixelpipe_iop_t *piece,
2668 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2669{
2672
2673 int roir = *_roir;
2674 int roib = *_roib;
2675 int roix = *_roix;
2676 int roiy = *_roiy;
2677
2678 // go through all clone and heal forms
2679 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
2680 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
2681 {
2682 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2683 {
2684 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
2685 if(IS_NULL_PTR(grpt)) continue;
2686
2687 const int formid = grpt->formid;
2688 const int index = rt_get_index_from_formid(p, formid);
2689
2690 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2691 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2692 {
2693 continue;
2694 }
2695
2696 // we get the spot
2697 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
2698 if(IS_NULL_PTR(form))
2699 {
2700 continue;
2701 }
2702
2703 // get the source area
2704 int fl_src, ft_src, fw_src, fh_src;
2705 if(dt_masks_get_source_area(self, pipe, piece, form, &fw_src, &fh_src, &fl_src, &ft_src) != 0)
2706 {
2707 continue;
2708 }
2709
2710 fw_src *= roi_in->scale, fh_src *= roi_in->scale, fl_src *= roi_in->scale, ft_src *= roi_in->scale;
2711
2712 // we only want to process forms already in roi_in
2713 const int intersects
2714 = !(roib < ft_src || ft_src + fh_src < roiy || roir < fl_src || fl_src + fw_src < roix);
2715 if(intersects)
2716 rt_extend_roi_in_from_source_clones(self, pipe, piece, roi_in, formid, fl_src, ft_src, fw_src, fh_src,
2717 &roir, &roib, &roix, &roiy);
2718 }
2719 }
2720
2721 *_roir = roir;
2722 *_roib = roib;
2723 *_roix = roix;
2724 *_roiy = roiy;
2725}
2726
2727// needed if mask dest is in roi and mask src is not
2728void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2729 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
2730 dt_iop_roi_t *roi_in)
2731{
2732 dt_dev_pixelpipe_t *const processing_pipe = (dt_dev_pixelpipe_t *)pipe;
2733 *roi_in = *roi_out;
2734
2735 int roir = roi_in->width + roi_in->x;
2736 int roib = roi_in->height + roi_in->y;
2737 int roix = roi_in->x;
2738 int roiy = roi_in->y;
2739
2740 rt_compute_roi_in(self, processing_pipe, piece, roi_in, &roir, &roib, &roix, &roiy);
2741
2742 int roir_prev = -1, roib_prev = -1, roix_prev = -1, roiy_prev = -1;
2743
2744 while(roir != roir_prev || roib != roib_prev || roix != roix_prev || roiy != roiy_prev)
2745 {
2746 roir_prev = roir;
2747 roib_prev = roib;
2748 roix_prev = roix;
2749 roiy_prev = roiy;
2750
2751 rt_extend_roi_in_for_clone(self, processing_pipe, piece, roi_in, &roir, &roib, &roix, &roiy);
2752 }
2753
2754 // now we set the values
2755 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
2756 roi_in->x = CLAMP(roix, 0, scwidth - 1);
2757 roi_in->y = CLAMP(roiy, 0, scheight - 1);
2758 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
2759 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
2760}
2761
2762//--------------------------------------------------------------------------------------------------
2763// process
2764//--------------------------------------------------------------------------------------------------
2765
2766static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
2767{
2768 const int stride = width * height * ch;
2770 for(int i = 0; i < stride; i += ch)
2771 {
2773
2774 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2775 dt_XYZ_to_Lab(XYZ, img_src + i);
2776 }
2777}
2778
2779static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
2780{
2781 const int stride = width * height * ch;
2783 for(int i = 0; i < stride; i += ch)
2784 {
2786
2787 dt_Lab_to_XYZ(img_src + i, XYZ);
2788 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2789 }
2790}
2791
2793static void rt_process_stats(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
2794 float *const img_src, const int width, const int height, const int ch, float levels[3])
2795{
2796 const int size = width * height * ch;
2797 float l_max = -INFINITY;
2798 float l_min = INFINITY;
2799 float l_sum = 0.f;
2800 int count = 0;
2802 __OMP_PARALLEL_FOR__(reduction(+ : count, l_sum) reduction(max : l_max) reduction(min : l_min))
2803 for(int i = 0; i < size; i += ch)
2804 {
2805 dt_aligned_pixel_t Lab = { 0 };
2806
2807 if(work_profile)
2808 {
2809 dt_ioppr_rgb_matrix_to_lab(img_src + i, Lab, work_profile->matrix_in_transposed,
2810 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2811 work_profile->lutsize, work_profile->nonlinearlut);
2812 }
2813 else
2814 {
2816 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2818 }
2819
2820 l_max = MAX(l_max, Lab[0]);
2821 l_min = MIN(l_min, Lab[0]);
2822 l_sum += Lab[0];
2823 count++;
2824 }
2825
2826 levels[0] = l_min / 100.f;
2827 levels[2] = l_max / 100.f;
2828 levels[1] = (l_sum / (float)count) / 100.f;
2829}
2830
2833 float *img_src, const int width, const int height, const int ch,
2834 const float levels[3])
2835{
2836 const int size = width * height * ch;
2838
2839 const float left = levels[0];
2840 const float middle = levels[1];
2841 const float right = levels[2];
2842
2843 if(left == RETOUCH_PREVIEW_LVL_MIN && middle == 0.f && right == RETOUCH_PREVIEW_LVL_MAX) return;
2844
2845 const float delta = (right - left) / 2.0f;
2846 const float mid = left + delta;
2847 const float tmp = (middle - mid) / delta;
2848 const float in_inv_gamma = powf(10, tmp);
2850 for(int i = 0; i < size; i += ch)
2851 {
2852 if(!IS_NULL_PTR(work_profile))
2853 {
2854 dt_ioppr_rgb_matrix_to_lab(img_src + i, img_src + i, work_profile->matrix_in_transposed,
2855 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2856 work_profile->lutsize, work_profile->nonlinearlut);
2857 }
2858 else
2859 {
2861
2862 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2863 dt_XYZ_to_Lab(XYZ, img_src + i);
2864 }
2865
2866 for(int c = 0; c < 1; c++)
2867 {
2868 const float L_in = img_src[i + c] / 100.0f;
2869
2870 if(L_in <= left)
2871 {
2872 img_src[i + c] = 0.f;
2873 }
2874 else
2875 {
2876 const float percentage = (L_in - left) / (right - left);
2877 img_src[i + c] = 100.0f * powf(percentage, in_inv_gamma);
2878 }
2879 }
2880
2881 if(!IS_NULL_PTR(work_profile))
2882 {
2883 dt_ioppr_lab_to_rgb_matrix(img_src + i, img_src + i, work_profile->matrix_out_transposed,
2884 work_profile->lut_out, work_profile->unbounded_coeffs_out,
2885 work_profile->lutsize, work_profile->nonlinearlut);;
2886 }
2887 else
2888 {
2890
2891 dt_Lab_to_XYZ(img_src + i, XYZ);
2892 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2893 }
2894 }
2895}
2896
2897#undef RT_WDBAR_INSET
2898
2899#undef RETOUCH_NO_FORMS
2900#undef RETOUCH_MAX_SCALES
2901#undef RETOUCH_NO_SCALES
2902
2903#undef RETOUCH_PREVIEW_LVL_MIN
2904#undef RETOUCH_PREVIEW_LVL_MAX
2905
2906static 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,
2907 const int padding, dt_iop_roi_t *roi_dest)
2908{
2909 const int x_from = MAX(MAX((roi_1->x + 1 - padding), roi_2->x), (roi_2->x + dx));
2910 const int x_to
2911 = MIN(MIN((roi_1->x + roi_1->width + 1 + padding), roi_2->x + roi_2->width), (roi_2->x + roi_2->width + dx));
2912
2913 const int y_from = MAX(MAX((roi_1->y + 1 - padding), roi_2->y), (roi_2->y + dy));
2914 const int y_to = MIN(MIN((roi_1->y + roi_1->height + 1 + padding), (roi_2->y + roi_2->height)),
2915 (roi_2->y + roi_2->height + dy));
2916
2917 roi_dest->x = x_from;
2918 roi_dest->y = y_from;
2919 roi_dest->width = x_to - x_from;
2920 roi_dest->height = y_to - y_from;
2921}
2922
2924static void rt_copy_in_to_out(const float *const in, const struct dt_iop_roi_t *const roi_in, float *const out,
2925 const struct dt_iop_roi_t *const roi_out, const int ch, const int dx, const int dy)
2926{
2927 const size_t rowsize = sizeof(float) * ch * MIN(roi_out->width, roi_in->width);
2928 const int xoffs = roi_out->x - roi_in->x - dx;
2929 const int yoffs = roi_out->y - roi_in->y - dy;
2930 const int y_to = MIN(roi_out->height, roi_in->height);
2932 for(int y = 0; y < y_to; y++)
2933 {
2934 const size_t iindex = ((size_t)(y + yoffs) * roi_in->width + xoffs) * ch;
2935 const size_t oindex = (size_t)y * roi_out->width * ch;
2936 float *in1 = (float *)in + iindex;
2937 float *out1 = (float *)out + oindex;
2938
2939 memcpy(out1, in1, rowsize);
2940 }
2941}
2942
2943static int rt_build_scaled_mask(float *const mask, dt_iop_roi_t *const roi_mask, float **mask_scaled,
2944 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
2945 const int dy, const int algo)
2946{
2947 float *mask_tmp = NULL;
2948 int err = 0;
2949 *mask_scaled = NULL;
2950
2951 const int padding = (algo == DT_IOP_RETOUCH_HEAL) ? 1 : 0;
2952
2953 *roi_mask_scaled = *roi_mask;
2954
2955 roi_mask_scaled->x = roi_mask->x * roi_in->scale;
2956 roi_mask_scaled->y = roi_mask->y * roi_in->scale;
2957 roi_mask_scaled->width = ((roi_mask->width * roi_in->scale) + .5f);
2958 roi_mask_scaled->height = ((roi_mask->height * roi_in->scale) + .5f);
2959 roi_mask_scaled->scale = roi_in->scale;
2960
2961 rt_intersect_2_rois(roi_mask_scaled, roi_in, dx, dy, padding, roi_mask_scaled);
2962 if(roi_mask_scaled->width < 1 || roi_mask_scaled->height < 1) goto cleanup;
2963
2964 const int x_to = roi_mask_scaled->width + roi_mask_scaled->x;
2965 const int y_to = roi_mask_scaled->height + roi_mask_scaled->y;
2966
2967 mask_tmp = dt_pixelpipe_cache_alloc_align_float_cache((size_t)roi_mask_scaled->width * roi_mask_scaled->height, 0);
2968 if(IS_NULL_PTR(mask_tmp))
2969 {
2970 fprintf(stderr, "rt_build_scaled_mask: error allocating memory\n");
2971 err = 1;
2972 goto cleanup;
2973 }
2974 dt_iop_image_fill(mask_tmp, 0.0f, roi_mask_scaled->width, roi_mask_scaled->height, 1);
2976 for(int yy = roi_mask_scaled->y; yy < y_to; yy++)
2977 {
2978 const int mask_index = ((int)(yy / roi_in->scale)) - roi_mask->y;
2979 if(mask_index < 0 || mask_index >= roi_mask->height) continue;
2980
2981 const int mask_scaled_index = (yy - roi_mask_scaled->y) * roi_mask_scaled->width;
2982
2983 const float *m = mask + mask_index * roi_mask->width;
2984 float *ms = mask_tmp + mask_scaled_index;
2985
2986 for(int xx = roi_mask_scaled->x; xx < x_to; xx++, ms++)
2987 {
2988 const int mx = ((int)(xx / roi_in->scale)) - roi_mask->x;
2989 if(mx < 0 || mx >= roi_mask->width) continue;
2990
2991 *ms = m[mx];
2992 }
2993 }
2994
2995cleanup:
2996 *mask_scaled = mask_tmp;
2997 return err;
2998}
2999
3000// img_src and mask_scaled must have the same roi
3001static void rt_copy_image_masked(float *const img_src, float *img_dest, dt_iop_roi_t *const roi_dest,
3002 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3003 const float opacity)
3004{
3006 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3007 {
3008 const int mask_index = yy * roi_mask_scaled->width;
3009 const int src_index = 4 * mask_index;
3010 const int dest_index
3011 = 4 * (((yy + roi_mask_scaled->y - roi_dest->y) * roi_dest->width) + (roi_mask_scaled->x - roi_dest->x));
3012
3013 const float *s = img_src + src_index;
3014 const float *m = mask_scaled + mask_index;
3015 float *d = img_dest + dest_index;
3016
3017 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3018 {
3019 const float f = m[xx] * opacity;
3020 const float f1 = (1.0f - f);
3021
3022 for_each_channel(c,aligned(s,d))
3023 {
3024 d[4*xx + c] = d[4*xx + c] * f1 + s[4*xx + c] * f;
3025 }
3026 }
3027 }
3028}
3029
3030static void rt_copy_mask_to_alpha(float *const img, dt_iop_roi_t *const roi_img, const int ch,
3031 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3032 const float opacity)
3033{
3035 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3036 {
3037 const int mask_index = yy * roi_mask_scaled->width;
3038 const int dest_index
3039 = (((yy + roi_mask_scaled->y - roi_img->y) * roi_img->width) + (roi_mask_scaled->x - roi_img->x)) * ch;
3040
3041 float *d = img + dest_index;
3042 const float *m = mask_scaled + mask_index;
3043
3044 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3045 {
3046 const float f = (*m) * opacity;
3047 if(f > d[3]) d[3] = f;
3048 }
3049 }
3050}
3051
3052static void _retouch_fill(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3053 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const float *const fill_color)
3054{
3056 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3057 {
3058 const int mask_index = yy * roi_mask_scaled->width;
3059 const int dest_index
3060 = (((yy + roi_mask_scaled->y - roi_in->y) * roi_in->width) + (roi_mask_scaled->x - roi_in->x)) * 4;
3061
3062 float *d = in + dest_index;
3063 const float *m = mask_scaled + mask_index;
3064
3065 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3066 {
3067 const float f = m[xx] * opacity;
3068
3069 for_each_channel(c,aligned(d,fill_color))
3070 d[4*xx + c] = d[4*xx + c] * (1.0f - f) + fill_color[c] * f;
3071 }
3072 }
3073}
3074
3075static int _retouch_clone(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3076 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity)
3077{
3078 // alloc temp image to avoid issues when areas self-intersects
3079 float *img_src = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3080 if(IS_NULL_PTR(img_src))
3081 {
3082 fprintf(stderr, "retouch_clone: error allocating memory for cloning\n");
3083 return 1;
3084 }
3085
3086 // copy source image to tmp
3087 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3088
3089 // clone it
3090 rt_copy_image_masked(img_src, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3091
3093 return 0;
3094}
3095
3096static int _retouch_blur(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *const in,
3097 dt_iop_roi_t *const roi_in, float *const mask_scaled,
3098 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type,
3099 const float blur_radius, const int use_sse)
3100{
3101 if(fabsf(blur_radius) <= 0.1f) return 0;
3102
3103 const float sigma = blur_radius * roi_in->scale;
3104
3105 float *img_dest = NULL;
3106
3107 // alloc temp image to blur
3108 img_dest = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3109 if(IS_NULL_PTR(img_dest))
3110 {
3111 fprintf(stderr, "retouch_blur: error allocating memory for blurring\n");
3112 return 1;
3113 }
3114
3115 // copy source image so we blur just the mask area (at least the smallest rect that covers it)
3116 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3117
3118 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3119 {
3120 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
3121 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3122
3123 dt_gaussian_t *g = dt_gaussian_init(roi_mask_scaled->width, roi_mask_scaled->height, 4, Labmax, Labmin, sigma,
3125 if(!IS_NULL_PTR(g))
3126 {
3127 dt_gaussian_blur_4c(g, img_dest, img_dest);
3129 }
3130 }
3131 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
3132 {
3133 const float sigma_r = 100.0f; // does not depend on scale
3134 const float sigma_s = sigma;
3135 const float detail = -1.0f; // we want the bilateral base layer
3136
3137 dt_bilateral_t *b = dt_bilateral_init(roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
3138 if(b)
3139 {
3140 int converted_cst;
3142
3143 if(!IS_NULL_PTR(work_profile))
3144 dt_ioppr_transform_image_colorspace(self, img_dest, img_dest, roi_mask_scaled->width,
3145 roi_mask_scaled->height, IOP_CS_RGB, IOP_CS_LAB, &converted_cst,
3146 work_profile);
3147 else
3148 image_rgb2lab(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3149
3150 dt_bilateral_splat(b, img_dest);
3152 dt_bilateral_slice(b, img_dest, img_dest, detail);
3154
3155 if(!IS_NULL_PTR(work_profile))
3156 dt_ioppr_transform_image_colorspace(self, img_dest, img_dest, roi_mask_scaled->width,
3157 roi_mask_scaled->height, IOP_CS_LAB, IOP_CS_RGB, &converted_cst,
3158 work_profile);
3159 else
3160 image_lab2rgb(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3161 }
3162 }
3163
3164 // copy blurred (temp) image to destination image
3165 rt_copy_image_masked(img_dest, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3166
3168 return 0;
3169}
3170
3171static int _retouch_heal(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3172 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity, const int max_iter)
3173{
3174 float *img_src = NULL;
3175 float *img_dest = NULL;
3176 int err = 0;
3177
3178 // alloc temp images for source and destination
3179 img_src = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3180 img_dest = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_mask_scaled->width * roi_mask_scaled->height, 0);
3181 if((IS_NULL_PTR(img_src)) || (IS_NULL_PTR(img_dest)))
3182 {
3183 fprintf(stderr, "retouch_heal: error allocating memory for healing\n");
3184 err = 1;
3185 goto cleanup;
3186 }
3187
3188 // copy source and destination to temp images
3189 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3190 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3191
3192 // heal it
3193 dt_heal(img_src, img_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height, 4, max_iter);
3194
3195 // copy healed (temp) image to destination image
3196 rt_copy_image_masked(img_dest, in, roi_in, mask_scaled, roi_mask_scaled, opacity);
3197
3198cleanup:
3201 return err;
3202}
3203
3204static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
3205{
3206 int scale = scale1;
3208 dt_iop_module_t *self = usr_d->self;
3209 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
3210 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
3211
3212 // if preview a single scale, just process that scale and original image
3213 // unless merge is activated
3214 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0) return 0;
3215 // do not process the reconstructed image
3216 if(scale > wt_p->scales + 1) return 0;
3217
3220 dt_iop_roi_t *roi_layer = &usr_d->roi;
3221 const int mask_display = usr_d->mask_display && (scale == usr_d->display_scale);
3222
3223 // when the requested scales is grather than max scales the residual image index will be different from the one
3224 // defined by the user,
3225 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
3226 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
3227 {
3228 scale = p->num_scales + 1;
3229 }
3230
3231 if(usr_d->suppress_mask) return 0;
3232
3233 // iterate through all forms
3234 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
3235 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return 0;
3236
3237 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
3238 {
3239 const dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
3240 if(IS_NULL_PTR(grpt))
3241 {
3242 fprintf(stderr, "rt_process_forms: invalid form\n");
3243 continue;
3244 }
3245 const int formid = grpt->formid;
3246 const float form_opacity = grpt->opacity;
3247 if(formid == 0)
3248 {
3249 fprintf(stderr, "rt_process_forms: form is null\n");
3250 continue;
3251 }
3252 const int index = rt_get_index_from_formid(p, formid);
3253 if(index == -1)
3254 {
3255 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
3256 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
3257 continue;
3258 }
3259
3260 // only process current scale
3261 if(p->rt_forms[index].scale != scale)
3262 {
3263 continue;
3264 }
3265
3266 // get the spot
3267 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
3268 if(IS_NULL_PTR(form))
3269 {
3270 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
3271 continue;
3272 }
3273
3274 // if the form is outside the roi, we just skip it
3275 if(!rt_masks_form_is_in_roi(self, pipe, piece, form, roi_layer, roi_layer))
3276 {
3277 continue;
3278 }
3279
3280 // get the mask
3281 float *mask = NULL;
3282 dt_iop_roi_t roi_mask = { 0 };
3283
3284 dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &roi_mask.width, &roi_mask.height,
3285 &roi_mask.x, &roi_mask.y);
3286 if(IS_NULL_PTR(mask))
3287 {
3288 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
3289 continue;
3290 }
3291
3292 // search the delta with the source
3293 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
3294 float dx = 0.f, dy = 0.f;
3295
3296 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
3297 {
3298 if(!rt_masks_get_delta_to_destination(self, pipe, piece, roi_layer, form, &dx, &dy,
3299 p->rt_forms[index].distort_mode))
3300 {
3302 continue;
3303 }
3304 }
3305
3306 // scale the mask
3307 float *mask_scaled = NULL;
3308 dt_iop_roi_t roi_mask_scaled = { 0 };
3309 if(rt_build_scaled_mask(mask, &roi_mask, &mask_scaled, &roi_mask_scaled, roi_layer, dx, dy, algo) != 0)
3310 {
3312 return 1;
3313 }
3314
3315 // we don't need the original mask anymore
3316 if(!IS_NULL_PTR(mask))
3317 {
3319 mask = NULL;
3320 }
3321
3322 if(IS_NULL_PTR(mask_scaled))
3323 {
3324 continue;
3325 }
3326
3327 if((dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
3328 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
3329 {
3330 if(algo == DT_IOP_RETOUCH_CLONE)
3331 {
3332 if(_retouch_clone(layer, roi_layer, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity) != 0)
3333 {
3334 dt_pixelpipe_cache_free_align(mask_scaled);
3335 return 1;
3336 }
3337 }
3338 else if(algo == DT_IOP_RETOUCH_HEAL)
3339 {
3340 if(_retouch_heal(layer, roi_layer, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity, p->max_heal_iter) != 0)
3341 {
3342 dt_pixelpipe_cache_free_align(mask_scaled);
3343 return 1;
3344 }
3345 }
3346 else if(algo == DT_IOP_RETOUCH_BLUR)
3347 {
3348 if(_retouch_blur(self, pipe, layer, roi_layer, mask_scaled, &roi_mask_scaled, form_opacity,
3349 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, wt_p->use_sse) != 0)
3350 {
3351 dt_pixelpipe_cache_free_align(mask_scaled);
3352 return 1;
3353 }
3354 }
3355 else if(algo == DT_IOP_RETOUCH_FILL)
3356 {
3357 // add a brightness to the color so it can be fine-adjusted by the user
3358 dt_aligned_pixel_t fill_color;
3359
3360 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
3361 {
3362 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
3363 }
3364 else
3365 {
3366 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
3367 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
3368 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
3369 }
3370 fill_color[3] = 0.0f;
3371
3372 _retouch_fill(layer, roi_layer, mask_scaled, &roi_mask_scaled, form_opacity, fill_color);
3373 }
3374 else
3375 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
3376
3377 if(mask_display)
3378 rt_copy_mask_to_alpha(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, form_opacity);
3379 }
3381 dt_pixelpipe_cache_free_align(mask_scaled);
3382 }
3383
3384 return 0;
3385}
3386
3388static int process_internal(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe,
3389 const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3390 void *const ovoid, const dt_iop_roi_t *const roi_in,
3391 const dt_iop_roi_t *const roi_out, const int use_sse)
3392{
3393
3396
3397 float *in_retouch = NULL;
3398 int err = 0;
3399
3400 dt_iop_roi_t roi_retouch = *roi_in;
3401 dt_iop_roi_t *roi_rt = &roi_retouch;
3402
3403 retouch_user_data_t usr_data = { 0 };
3404 dwt_params_t *dwt_p = NULL;
3405
3406 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
3407 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
3408
3409 // we will do all the clone, heal, etc on the input image,
3410 // this way the source for one algorithm can be the destination from a previous one
3411 in_retouch = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_rt->width * roi_rt->height, 0);
3412 if(IS_NULL_PTR(in_retouch))
3413 {
3414 err = 1;
3415 goto cleanup;
3416 }
3417
3418 dt_iop_image_copy_by_size(in_retouch, ivoid, roi_rt->width, roi_rt->height, 4);
3419
3420 // user data passed from the decompose routine to the one that process each scale
3421 usr_data.self = self;
3422 usr_data.pipe = pipe;
3423 usr_data.piece = piece;
3424 usr_data.roi = *roi_rt;
3425 usr_data.mask_display = 0;
3426 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
3427 && (pipe == self->dev->pipe));
3428 usr_data.display_scale = p->curr_scale;
3429
3430 // init the decompose routine
3431 dwt_p = dt_dwt_init(in_retouch, roi_rt->width, roi_rt->height, 4, p->num_scales,
3432 (!display_wavelet_scale || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
3433 p->merge_from_scale, &usr_data,
3434 roi_in->scale, use_sse);
3435 if(IS_NULL_PTR(dwt_p))
3436 {
3437 err = 1;
3438 goto cleanup;
3439 }
3440
3441 // check if this module should expose mask.
3442 if(pipe->type == DT_DEV_PIXELPIPE_FULL && !IS_NULL_PTR(g)
3443 && (g->mask_display || display_wavelet_scale) && self->dev->gui_attached
3444 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
3445 {
3446 for(size_t j = 0; j < (size_t)roi_rt->width * roi_rt->height * 4; j += 4) in_retouch[j + 3] = 0.f;
3447
3448 ((dt_dev_pixelpipe_t *)pipe)->mask_display = g->mask_display ? DT_DEV_PIXELPIPE_DISPLAY_MASK : DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
3449 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
3450 usr_data.mask_display = 1;
3451 }
3452
3453 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
3454 {
3455 // check if the image support this number of scales
3456 if(gui_active)
3457 {
3458 const int max_scales = dwt_get_max_scale(dwt_p);
3459 if(dwt_p->scales > max_scales)
3460 {
3461 dt_control_log(_("max scale is %i for this image size"), max_scales);
3462 }
3463 }
3464 // get first scale visible at this zoom level
3465 if(g) g->first_scale_visible = dt_dwt_first_scale_visible(dwt_p);
3466 }
3467
3468 // decompose it
3469 if(dwt_decompose(dwt_p, rt_process_forms) != 0)
3470 {
3471 err = 1;
3472 goto cleanup;
3473 }
3474
3475 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
3476
3477 // process auto levels
3478 if(!IS_NULL_PTR(g) && pipe->type == DT_DEV_PIXELPIPE_FULL)
3479 {
3481 if(g->preview_auto_levels == 1 && !darktable.gui->reset)
3482 {
3483 g->preview_auto_levels = -1;
3484
3486
3487 levels[0] = levels[1] = levels[2] = 0;
3488 rt_process_stats(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
3489 rt_clamp_minmax(levels, levels);
3490
3491 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
3492
3494 g->preview_auto_levels = 2;
3495 }
3497 }
3498
3499 // if user wants to preview a detail scale adjust levels
3500 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
3501 {
3502 rt_adjust_levels(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
3503 }
3504
3505 // copy alpha channel if needed
3506 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && !IS_NULL_PTR(g) && !g->mask_display)
3507 {
3508 dt_iop_alpha_copy(ivoid, in_retouch, roi_rt->width, roi_rt->height);
3509 }
3510
3511 // return final image
3512 rt_copy_in_to_out(in_retouch, roi_rt, ovoid, roi_out, 4, 0, 0);
3513
3514cleanup:
3516 if(dwt_p) dt_dwt_free(dwt_p);
3517 return err;
3518}
3519
3520int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3521 void *const ovoid)
3522{
3523 const dt_iop_roi_t *const roi_in = &piece->roi_in;
3524 const dt_iop_roi_t *const roi_out = &piece->roi_out;
3525 return process_internal(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, 0);
3526}
3527
3528void distort_mask(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe, struct dt_dev_pixelpipe_iop_t *piece,
3529 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
3530 const dt_iop_roi_t *const roi_out)
3531{
3532 (void)pipe;
3533 rt_copy_in_to_out(in, roi_in, out, roi_out, 1, 0, 0);
3534}
3535
3536#ifdef HAVE_OPENCL
3537
3539 const int devid, cl_mem dev_img, const int width, const int height,
3540 float levels[3])
3541{
3542 cl_int err = CL_SUCCESS;
3543
3544 const int ch = 4;
3545
3546 float *src_buffer = NULL;
3547
3548 src_buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ch * width * height, 0);
3549 if(IS_NULL_PTR(src_buffer))
3550 {
3551 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3553 goto cleanup;
3554 }
3555
3556 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3557 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3558 if(err != CL_SUCCESS)
3559 {
3560 goto cleanup;
3561 }
3562
3563 // just call the CPU version for now
3564 rt_process_stats(self, pipe, src_buffer, width, height, ch, levels);
3565
3566 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
3567 if(err != CL_SUCCESS)
3568 {
3569 goto cleanup;
3570 }
3571
3572cleanup:
3574
3575 return err;
3576}
3577
3579 const int devid, cl_mem dev_img, const int width, const int height,
3580 const float levels[3])
3581{
3582 cl_int err = CL_SUCCESS;
3583
3584 const int ch = 4;
3585
3586 float *src_buffer = NULL;
3587
3588 src_buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ch * width * height, 0);
3589 if(IS_NULL_PTR(src_buffer))
3590 {
3591 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3593 goto cleanup;
3594 }
3595
3596 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3597 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3598 if(err != CL_SUCCESS)
3599 {
3600 goto cleanup;
3601 }
3602
3603 // just call the CPU version for now
3604 rt_adjust_levels(self, pipe, src_buffer, width, height, ch, levels);
3605
3606 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
3607 if(err != CL_SUCCESS)
3608 {
3609 goto cleanup;
3610 }
3611
3612cleanup:
3614
3615 return err;
3616}
3617
3618static cl_int rt_copy_in_to_out_cl(const int devid, cl_mem dev_in, const struct dt_iop_roi_t *const roi_in,
3619 cl_mem dev_out, const struct dt_iop_roi_t *const roi_out, const int dx,
3620 const int dy, const int kernel)
3621{
3622 cl_int err = CL_SUCCESS;
3623
3624 const int xoffs = roi_out->x - roi_in->x - dx;
3625 const int yoffs = roi_out->y - roi_in->y - dy;
3626
3627 cl_mem dev_roi_in = NULL;
3628 cl_mem dev_roi_out = NULL;
3629
3630 const size_t sizes[]
3631 = { ROUNDUPDWD(MIN(roi_out->width, roi_in->width), devid), ROUNDUPDHT(MIN(roi_out->height, roi_in->height), devid), 1 };
3632
3633 dev_roi_in = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_in);
3634 dev_roi_out = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_out);
3635 if(IS_NULL_PTR(dev_roi_in) || IS_NULL_PTR(dev_roi_out))
3636 {
3637 fprintf(stderr, "rt_copy_in_to_out_cl error 1\n");
3638 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3639 goto cleanup;
3640 }
3641
3642 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
3643 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_in);
3644 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_out);
3645 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_out);
3646 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), (void *)&xoffs);
3647 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), (void *)&yoffs);
3648 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3649 if(err != CL_SUCCESS)
3650 {
3651 fprintf(stderr, "rt_copy_in_to_out_cl error 2\n");
3652 goto cleanup;
3653 }
3654
3655cleanup:
3656 dt_opencl_release_mem_object(dev_roi_in);
3657 dt_opencl_release_mem_object(dev_roi_out);
3658
3659 return err;
3660}
3661
3662static cl_int rt_build_scaled_mask_cl(const int devid, float *const mask, dt_iop_roi_t *const roi_mask,
3663 float **mask_scaled, cl_mem *p_dev_mask_scaled,
3664 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
3665 const int dy, const int algo)
3666{
3667 cl_int err = CL_SUCCESS;
3668
3669 rt_build_scaled_mask(mask, roi_mask, mask_scaled, roi_mask_scaled, roi_in, dx, dy, algo);
3670 if(IS_NULL_PTR(*mask_scaled))
3671 {
3672 goto cleanup;
3673 }
3674
3675 const cl_mem dev_mask_scaled
3676 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height);
3677 if(IS_NULL_PTR(dev_mask_scaled))
3678 {
3679 fprintf(stderr, "rt_build_scaled_mask_cl error 2\n");
3680 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3681 goto cleanup;
3682 }
3683
3684 err = dt_opencl_write_buffer_to_device(devid, *mask_scaled, dev_mask_scaled, 0,
3685 sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height, CL_TRUE);
3686 if(err != CL_SUCCESS)
3687 {
3688 fprintf(stderr, "rt_build_scaled_mask_cl error 4\n");
3689 goto cleanup;
3690 }
3691
3692 *p_dev_mask_scaled = dev_mask_scaled;
3693
3694cleanup:
3695 if(err != CL_SUCCESS) fprintf(stderr, "rt_build_scaled_mask_cl error\n");
3696
3697 return err;
3698}
3699
3700static cl_int rt_copy_image_masked_cl(const int devid, cl_mem dev_src, cl_mem dev_dest,
3701 dt_iop_roi_t *const roi_dest, cl_mem dev_mask_scaled,
3702 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int kernel)
3703{
3704 cl_int err = CL_SUCCESS;
3705
3706 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3707
3708 const cl_mem dev_roi_dest =
3709 dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_dest);
3710
3711 const cl_mem dev_roi_mask_scaled
3712 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3713
3714 if(IS_NULL_PTR(dev_roi_dest) || IS_NULL_PTR(dev_roi_mask_scaled))
3715 {
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_src);
3721 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_dest);
3722 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_roi_dest);
3723 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_mask_scaled);
3724 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3725 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&opacity);
3726 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3727 if(err != CL_SUCCESS) goto cleanup;
3728
3729cleanup:
3730 dt_opencl_release_mem_object(dev_roi_dest);
3731 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3732
3733 return err;
3734}
3735
3736static cl_int rt_copy_mask_to_alpha_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3737 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3738 const float opacity, dt_iop_retouch_global_data_t *gd)
3739{
3740 cl_int err = CL_SUCCESS;
3741
3742 // fill it
3744 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3745
3746 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3747 const cl_mem dev_roi_mask_scaled
3748 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3749 if(IS_NULL_PTR(dev_roi_layer) || IS_NULL_PTR(dev_roi_mask_scaled))
3750 {
3751 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3752 goto cleanup;
3753 }
3754
3755 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3756 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3757 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3758 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3759 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3760 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3761 if(err != CL_SUCCESS) goto cleanup;
3762
3763
3764cleanup:
3765 dt_opencl_release_mem_object(dev_roi_layer);
3766 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3767
3768 return err;
3769}
3770
3771static cl_int _retouch_clone_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3772 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
3773 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
3774{
3775 cl_int err = CL_SUCCESS;
3776
3777 const int ch = 4;
3778
3779 // alloc source temp image to avoid issues when areas self-intersects
3780 const cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
3781 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
3782 if(IS_NULL_PTR(dev_src))
3783 {
3784 fprintf(stderr, "retouch_clone_cl error 2\n");
3785 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3786 goto cleanup;
3787 }
3788
3789 // copy source image to tmp
3790 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
3792 if(err != CL_SUCCESS)
3793 {
3794 fprintf(stderr, "retouch_clone_cl error 4\n");
3795 goto cleanup;
3796 }
3797
3798 // clone it
3799 err = rt_copy_image_masked_cl(devid, dev_src, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
3801 if(err != CL_SUCCESS)
3802 {
3803 fprintf(stderr, "retouch_clone_cl error 5\n");
3804 goto cleanup;
3805 }
3806
3807cleanup:
3809
3810 return err;
3811}
3812
3813static cl_int _retouch_fill_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3814 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3815 float *color, dt_iop_retouch_global_data_t *gd)
3816{
3817 cl_int err = CL_SUCCESS;
3818
3819 // fill it
3820 const int kernel = gd->kernel_retouch_fill;
3821 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
3822
3823 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3824 const cl_mem dev_roi_mask_scaled
3825 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3826 if(IS_NULL_PTR(dev_roi_layer) || IS_NULL_PTR(dev_roi_mask_scaled))
3827 {
3828 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3829 goto cleanup;
3830 }
3831
3832 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3833 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3834 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3835 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3836 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3837 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&(color[0]));
3838 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), (void *)&(color[1]));
3839 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), (void *)&(color[2]));
3840 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3841 if(err != CL_SUCCESS) goto cleanup;
3842
3843
3844cleanup:
3845 dt_opencl_release_mem_object(dev_roi_layer);
3846 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3847
3848 return err;
3849}
3850
3851static cl_int _retouch_blur_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3852 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3853 const int blur_type, const float blur_radius, dt_iop_retouch_global_data_t *gd)
3854{
3855 cl_int err = CL_SUCCESS;
3856
3857 if(fabsf(blur_radius) <= 0.1f) return err;
3858
3859 const float sigma = blur_radius * roi_layer->scale;
3860 const int ch = 4;
3861
3862 const cl_mem dev_dest =
3863 dt_opencl_alloc_device(devid, roi_mask_scaled->width, roi_mask_scaled->height, sizeof(float) * ch);
3864 if(IS_NULL_PTR(dev_dest))
3865 {
3866 fprintf(stderr, "retouch_blur_cl error 2\n");
3867 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3868 goto cleanup;
3869 }
3870
3871 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
3872 {
3873 const int kernel = gd->kernel_retouch_image_rgb2lab;
3874 size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
3875
3876 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3877 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
3878 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
3879 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3880 if(err != CL_SUCCESS) goto cleanup;
3881 }
3882
3883 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
3885 if(err != CL_SUCCESS)
3886 {
3887 fprintf(stderr, "retouch_blur_cl error 4\n");
3888 goto cleanup;
3889 }
3890
3891 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3892 {
3893 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
3894 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3895
3896 dt_gaussian_cl_t *g = dt_gaussian_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, ch, Labmax,
3897 Labmin, sigma, DT_IOP_GAUSSIAN_ZERO);
3898 if(g)
3899 {
3900 err = dt_gaussian_blur_cl(g, dev_dest, dev_dest);
3902 if(err != CL_SUCCESS) goto cleanup;
3903 }
3904 }
3905 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
3906 {
3907 const float sigma_r = 100.0f; // does not depend on scale
3908 const float sigma_s = sigma;
3909 const float detail = -1.0f; // we want the bilateral base layer
3910
3912 = dt_bilateral_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
3913 if(b)
3914 {
3915 err = dt_bilateral_splat_cl(b, dev_dest);
3916 if(err == CL_SUCCESS) err = dt_bilateral_blur_cl(b);
3917 if(err == CL_SUCCESS) err = dt_bilateral_slice_cl(b, dev_dest, dev_dest, detail);
3918
3920 }
3921 }
3922
3923 // copy blurred (temp) image to destination image
3924 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
3926 if(err != CL_SUCCESS)
3927 {
3928 fprintf(stderr, "retouch_blur_cl error 5\n");
3929 goto cleanup;
3930 }
3931
3932 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
3933 {
3934 const int kernel = gd->kernel_retouch_image_lab2rgb;
3935 const size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
3936
3937 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3938 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
3939 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
3940 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3941 if(err != CL_SUCCESS) goto cleanup;
3942 }
3943
3944cleanup:
3946
3947 return err;
3948}
3949
3950static cl_int _retouch_heal_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, float *mask_scaled,
3951 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
3952 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd, const int max_iter)
3953{
3954 cl_int err = CL_SUCCESS;
3955
3956 const int ch = 4;
3957
3958 cl_mem dev_dest = NULL;
3959 cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
3960 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
3961 if(IS_NULL_PTR(dev_src))
3962 {
3963 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
3964 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3965 goto cleanup;
3966 }
3967
3968 dev_dest = dt_opencl_alloc_device_buffer(devid,
3969 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
3970 if(IS_NULL_PTR(dev_dest))
3971 {
3972 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
3973 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3974 goto cleanup;
3975 }
3976
3977 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
3979 if(err != CL_SUCCESS)
3980 {
3981 fprintf(stderr, "retouch_heal_cl error 4\n");
3982 goto cleanup;
3983 }
3984
3985 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
3987 if(err != CL_SUCCESS)
3988 {
3989 fprintf(stderr, "retouch_heal_cl error 4\n");
3990 goto cleanup;
3991 }
3992
3993 // heal it
3994 heal_params_cl_t *hp = dt_heal_init_cl(devid);
3995 if(hp)
3996 {
3997 err = dt_heal_cl(hp, dev_src, dev_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height, max_iter);
3998 dt_heal_free_cl(hp);
3999
4001 dev_src = NULL;
4002
4003 if(err != CL_SUCCESS) goto cleanup;
4004 }
4005
4006 // copy healed (temp) image to destination image
4007 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
4009 if(err != CL_SUCCESS)
4010 {
4011 fprintf(stderr, "retouch_heal_cl error 6\n");
4012 goto cleanup;
4013 }
4014
4015cleanup:
4018
4019 return err;
4020}
4021
4022static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
4023{
4024 cl_int err = CL_SUCCESS;
4025
4026 int scale = scale1;
4028 dt_iop_module_t *self = usr_d->self;
4029 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
4030 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
4031
4032 // if preview a single scale, just process that scale and original image
4033 // unless merge is activated
4034 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0)
4035 return err;
4036 // do not process the reconstructed image
4037 if(scale > wt_p->scales + 1) return err;
4038
4042 const int devid = pipe->devid;
4043 dt_iop_roi_t *roi_layer = &usr_d->roi;
4044 const int mask_display = usr_d->mask_display && (scale == usr_d->display_scale);
4045
4046 // when the requested scales is grather than max scales the residual image index will be different from the one
4047 // defined by the user,
4048 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
4049 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
4050 {
4051 scale = p->num_scales + 1;
4052 }
4053
4054 // iterate through all forms
4055 if(!usr_d->suppress_mask)
4056 {
4058 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return err;
4059
4060 for(const GList *forms = grp->points; forms && err == CL_SUCCESS; forms = g_list_next(forms))
4061 {
4062 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
4063 if(IS_NULL_PTR(grpt))
4064 {
4065 fprintf(stderr, "rt_process_forms: invalid form\n");
4066 continue;
4067 }
4068 const int formid = grpt->formid;
4069 const float form_opacity = grpt->opacity;
4070 if(formid == 0)
4071 {
4072 fprintf(stderr, "rt_process_forms: form is null\n");
4073 continue;
4074 }
4075 const int index = rt_get_index_from_formid(p, formid);
4076 if(index == -1)
4077 {
4078 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
4079 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
4080 continue;
4081 }
4082
4083 // only process current scale
4084 if(p->rt_forms[index].scale != scale)
4085 {
4086 continue;
4087 }
4088
4089 // get the spot
4090 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
4091 if(IS_NULL_PTR(form))
4092 {
4093 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
4094 continue;
4095 }
4096
4097 // if the form is outside the roi, we just skip it
4098 if(!rt_masks_form_is_in_roi(self, pipe, piece, form, roi_layer, roi_layer))
4099 {
4100 continue;
4101 }
4102
4103 // get the mask
4104 float *mask = NULL;
4105 dt_iop_roi_t roi_mask = { 0 };
4106
4107 dt_masks_get_mask(self, (dt_dev_pixelpipe_t *)pipe, piece, form, &mask, &roi_mask.width, &roi_mask.height,
4108 &roi_mask.x, &roi_mask.y);
4109 if(IS_NULL_PTR(mask))
4110 {
4111 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
4112 continue;
4113 }
4114
4115 float dx = 0.f, dy = 0.f;
4116
4117 // search the delta with the source
4118 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
4119 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
4120 {
4121 if(!rt_masks_get_delta_to_destination(self, (dt_dev_pixelpipe_t *)pipe, piece, roi_layer, form, &dx, &dy,
4122 p->rt_forms[index].distort_mode))
4123 {
4125 continue;
4126 }
4127 }
4128
4129 // scale the mask
4130 cl_mem dev_mask_scaled = NULL;
4131 float *mask_scaled = NULL;
4132 dt_iop_roi_t roi_mask_scaled = { 0 };
4133
4134 err = rt_build_scaled_mask_cl(devid, mask, &roi_mask, &mask_scaled, &dev_mask_scaled, &roi_mask_scaled,
4135 roi_layer, dx, dy, algo);
4136
4137 // only heal needs mask scaled
4138 if(algo != DT_IOP_RETOUCH_HEAL && !IS_NULL_PTR(mask_scaled))
4139 {
4140 dt_pixelpipe_cache_free_align(mask_scaled);
4141 mask_scaled = NULL;
4142 }
4143
4144 // we don't need the original mask anymore
4145 if(mask)
4146 {
4148 mask = NULL;
4149 }
4150
4151 if(IS_NULL_PTR(mask_scaled) && algo == DT_IOP_RETOUCH_HEAL)
4152 {
4153 dt_opencl_release_mem_object(dev_mask_scaled);
4154 dev_mask_scaled = NULL;
4155 continue;
4156 }
4157
4158 if((err == CL_SUCCESS)
4159 && (dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
4160 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
4161 {
4162 if(algo == DT_IOP_RETOUCH_CLONE)
4163 {
4164 err = _retouch_clone_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, dx, dy,
4165 form_opacity, gd);
4166 }
4167 else if(algo == DT_IOP_RETOUCH_HEAL)
4168 {
4169 err = _retouch_heal_cl(devid, dev_layer, roi_layer, mask_scaled, dev_mask_scaled, &roi_mask_scaled, dx,
4170 dy, form_opacity, gd, p->max_heal_iter);
4171 }
4172 else if(algo == DT_IOP_RETOUCH_BLUR)
4173 {
4174 err = _retouch_blur_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4175 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, gd);
4176 }
4177 else if(algo == DT_IOP_RETOUCH_FILL)
4178 {
4179 // add a brightness to the color so it can be fine-adjusted by the user
4180 dt_aligned_pixel_t fill_color;
4181
4182 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
4183 {
4184 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
4185 }
4186 else
4187 {
4188 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
4189 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
4190 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
4191 }
4192
4193 err = _retouch_fill_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4194 fill_color, gd);
4195 }
4196 else
4197 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
4198
4199 if(mask_display)
4200 rt_copy_mask_to_alpha_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4201 gd);
4202 }
4203
4205 dt_pixelpipe_cache_free_align(mask_scaled);
4206 dt_opencl_release_mem_object(dev_mask_scaled);
4207 }
4208 }
4209
4210 return err;
4211}
4212
4213int 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)
4214{
4215 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4216 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4220
4221 cl_int err = CL_SUCCESS;
4222 const int devid = pipe->devid;
4223
4224 dt_iop_roi_t roi_retouch = *roi_in;
4225 dt_iop_roi_t *roi_rt = &roi_retouch;
4226
4227 const int ch = piece->dsc_in.channels;
4228 retouch_user_data_t usr_data = { 0 };
4229 dwt_params_cl_t *dwt_p = NULL;
4230
4231 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
4232 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
4233
4234 // we will do all the clone, heal, etc on the input image,
4235 // this way the source for one algorithm can be the destination from a previous one
4236 const cl_mem in_retouch = dt_opencl_alloc_device_buffer(devid, sizeof(float) * ch * roi_rt->width * roi_rt->height);
4237 if(IS_NULL_PTR(in_retouch))
4238 {
4239 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error allocating memory for wavelet decompose on device %d\n", devid);
4240 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4241 goto cleanup;
4242 }
4243
4244 // copy input image to the new buffer
4245 {
4246 size_t origin[] = { 0, 0, 0 };
4247 size_t region[] = { roi_rt->width, roi_rt->height, 1 };
4248 err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, in_retouch, origin, region, 0);
4249 if(err != CL_SUCCESS) goto cleanup;
4250 }
4251
4252 // user data passed from the decompose routine to the one that process each scale
4253 usr_data.self = self;
4254 usr_data.pipe = pipe;
4255 usr_data.piece = piece;
4256 usr_data.roi = *roi_rt;
4257 usr_data.mask_display = 0;
4258 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
4259 && (pipe == self->dev->pipe));
4260 usr_data.display_scale = p->curr_scale;
4261
4262 // init the decompose routine
4263 dwt_p = dt_dwt_init_cl(devid, in_retouch, roi_rt->width, roi_rt->height, p->num_scales,
4264 (!display_wavelet_scale
4265 || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
4266 p->merge_from_scale, &usr_data,
4267 roi_in->scale);
4268 if(IS_NULL_PTR(dwt_p))
4269 {
4270 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error initializing wavelet decompose on device %d\n", devid);
4271 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4272 goto cleanup;
4273 }
4274
4275 // check if this module should expose mask.
4276 if(pipe->type == DT_DEV_PIXELPIPE_FULL && g && g->mask_display && self->dev->gui_attached
4277 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
4278 {
4279 const int kernel = gd->kernel_retouch_clear_alpha;
4280 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4281
4282 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&in_retouch);
4283 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_rt->width));
4284 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->height));
4285 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4286 if(err != CL_SUCCESS) goto cleanup;
4287
4288 ((dt_dev_pixelpipe_t *)pipe)->mask_display = DT_DEV_PIXELPIPE_DISPLAY_MASK;
4289 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
4290 usr_data.mask_display = 1;
4291 }
4292
4293 if(pipe->type == DT_DEV_PIXELPIPE_FULL)
4294 {
4295 // check if the image support this number of scales
4296 if(gui_active)
4297 {
4298 const int max_scales = dwt_get_max_scale_cl(dwt_p);
4299 if(dwt_p->scales > max_scales)
4300 {
4301 dt_control_log(_("max scale is %i for this image size"), max_scales);
4302 }
4303 }
4304 // get first scale visible at this zoom level
4305 if(g) g->first_scale_visible = dt_dwt_first_scale_visible_cl(dwt_p);
4306 }
4307
4308 // decompose it
4310 if(err != CL_SUCCESS) goto cleanup;
4311
4312 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
4313
4314 // process auto levels
4315 if(g && pipe->type == DT_DEV_PIXELPIPE_FULL)
4316 {
4318 if(g->preview_auto_levels == 1 && !darktable.gui->reset)
4319 {
4320 g->preview_auto_levels = -1;
4321
4323
4324 levels[0] = levels[1] = levels[2] = 0;
4325 err = rt_process_stats_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4326 if(err != CL_SUCCESS) goto cleanup;
4327
4328 rt_clamp_minmax(levels, levels);
4329
4330 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
4331
4333 g->preview_auto_levels = 2;
4334 }
4336 }
4337
4338 // if user wants to preview a detail scale adjust levels
4339 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
4340 {
4341 err = rt_adjust_levels_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4342 if(err != CL_SUCCESS) goto cleanup;
4343 }
4344
4345 // copy alpha channel if needed
4346 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && g && !g->mask_display)
4347 {
4348 const int kernel = gd->kernel_retouch_copy_alpha;
4349 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4350
4351 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
4352 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&in_retouch);
4353 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->width));
4354 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&(roi_rt->height));
4355 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4356 if(err != CL_SUCCESS) goto cleanup;
4357 }
4358
4359 // return final image
4360 err = rt_copy_in_to_out_cl(devid, in_retouch, roi_in, dev_out, roi_out, 0, 0,
4362
4363cleanup:
4364 if(dwt_p) dt_dwt_free_cl(dwt_p);
4365
4366 dt_opencl_release_mem_object(in_retouch);
4367
4368 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_retouch] couldn't enqueue kernel! %d\n", err);
4369
4370 return (err == CL_SUCCESS) ? TRUE : FALSE;
4371}
4372#endif
4373
4376static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
4377{
4378 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4380
4381 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "formid"));
4382 const int algo = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "algo"));
4383 const int index = rt_get_index_from_formid(p, formid);
4384 if(index < 0) return;
4385 if(algo < DT_IOP_RETOUCH_CLONE || algo > DT_IOP_RETOUCH_FILL) return;
4386 if(p->rt_forms[index].algorithm == algo) return;
4387 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
4388 if(IS_NULL_PTR(form)) return;
4389
4390 // Apply the new algorithm to the form
4391 p->rt_forms[index].algorithm = algo;
4392
4393 // Switch the clone type of the form
4394 dt_masks_type_t masks_type = form->type;
4395 if(algo == DT_IOP_RETOUCH_CLONE || algo == DT_IOP_RETOUCH_HEAL)
4396 {
4397 masks_type |= DT_MASKS_CLONE;
4398 masks_type &= ~DT_MASKS_NON_CLONE;
4399 }
4400 else
4401 {
4402 masks_type &= ~DT_MASKS_CLONE;
4403 masks_type |= DT_MASKS_NON_CLONE;
4404 }
4405 form->type = masks_type;
4406
4407 // Update GUI
4408 rt_load_shape_algo_in_gui(self, formid);
4409
4411}
4412
4413int populate_masks_context_menu(struct dt_iop_module_t *self, GtkWidget *menu, const int formid,const float pzx, const float pzy)
4414{
4416 const int index = rt_get_index_from_formid(p, formid);
4417 if(index == -1)
4418 {
4419 fprintf(stderr, "populate_masks_context_menu: missing form=%i from array\n", formid);
4420 return FALSE;
4421 }
4422
4423 GtkWidget *menu_item = ctx_gtk_menu_item_new_with_markup(_("Retouch correction"), menu, NULL, NULL);
4424 GtkWidget *sub_menu = gtk_menu_new();
4425 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
4426
4427 static const struct
4428 {
4430 const char *name;
4431 } algo_entries[] = {{ DT_IOP_RETOUCH_CLONE, N_("Clone") },
4432 { DT_IOP_RETOUCH_HEAL, N_("Heal") },
4433 { DT_IOP_RETOUCH_BLUR, N_("Blur") },
4434 { DT_IOP_RETOUCH_FILL, N_("Fill") },};
4435
4436 for(size_t i = 0; i < G_N_ELEMENTS(algo_entries); i++)
4437 {
4438 const gboolean is_selected = (p->rt_forms[index].algorithm == algo_entries[i].algo);
4439 const char *const label = _(algo_entries[i].name);
4440 GtkWidget *algo_item = ctx_gtk_check_menu_item_new_with_markup(label, sub_menu,
4441 is_selected ? NULL : rt_menu_select_algorithm_callback, self,
4442 is_selected, FALSE);
4443
4444 g_object_set_data(G_OBJECT(algo_item), "formid", GINT_TO_POINTER(formid));
4445 g_object_set_data(G_OBJECT(algo_item), "algo", GINT_TO_POINTER(algo_entries[i].algo));
4446 }
4447
4448 return TRUE;
4449}
4450// clang-format off
4451// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4452// vim: shiftwidth=2 expandtab tabstop=2 cindent
4453// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4454// 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:3534
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3483
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3506
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2301
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1653
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:1780
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3598
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:761
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:861
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
#define dt_free_align(ptr)
Definition darktable.h:481
static void * dt_calloc_align(size_t size)
Definition darktable.h:488
@ DT_DEBUG_OPENCL
Definition darktable.h:722
#define for_each_channel(_var,...)
Definition darktable.h:662
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
float dt_boundingbox_t[4]
Definition darktable.h:709
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Enable aggressive floating-point arithmetic optimizations, in denormals handling. Set through user pr...
Definition darktable.h:524
#define dt_free(ptr)
Definition darktable.h:456
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
Definition darktable.h:151
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __DT_CLONE_TARGETS__
Definition darktable.h:367
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:893
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:41
#define dt_dev_pixelpipe_update_history_main(dev)
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:1557
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:118
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:136
@ 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
#define dt_pthread_rwlock_unlock
Definition dtpthread.h:392
#define dt_pthread_rwlock_rdlock
Definition dtpthread.h:393
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:219
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
Definition gtk.h:316
static GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition gtk.h:451
#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:461
#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_default_init(dt_iop_module_t *module)
Definition imageop.c:316
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2169
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:3141
#define IOP_GUI_FREE
Definition imageop.h:602
static void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module) ACQUIRE(&module -> gui_lock)
Definition imageop.h:413
@ IOP_FLAGS_INTERNAL_MASKS
Definition imageop.h:179
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:167
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:175
static void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module) RELEASE(&module -> gui_lock)
Definition imageop.h:419
@ IOP_GROUP_REPAIR
Definition imageop.h:140
#define IOP_GUI_ALLOC(module)
Definition imageop.h:599
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:935
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks.h:933
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks.h:934
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks.h:932
void dt_masks_change_form_gui(dt_masks_form_t *newform)
@ DT_MASKS_EDIT_RESTRICTED
Definition masks.h:203
@ DT_MASKS_EDIT_OFF
Definition masks.h:201
@ DT_MASKS_EDIT_FULL
Definition masks.h:202
void dt_masks_reset_form_gui(void)
@ DT_MASKS_STATE_UNION
Definition masks.h:171
@ DT_MASKS_STATE_USE
Definition masks.h:168
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:86
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:180
dt_masks_type_t
Definition masks.h:129
@ DT_MASKS_NON_CLONE
Definition masks.h:138
@ DT_MASKS_POLYGON
Definition masks.h:132
@ DT_MASKS_BRUSH
Definition masks.h:137
@ DT_MASKS_ELLIPSE
Definition masks.h:136
@ DT_MASKS_CLONE
Definition masks.h:134
@ DT_MASKS_NONE
Definition masks.h:130
@ DT_MASKS_CIRCLE
Definition masks.h:131
@ DT_MASKS_GROUP
Definition masks.h:133
void dt_masks_group_ungroup(dt_masks_form_t *dest_grp, dt_masks_form_t *grp)
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks.h:953
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:853
#define DEVELOP_MASKS_NB_SHAPES
Definition masks.h:927
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:173
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:244
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:2136
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:2544
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2471
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2030
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2332
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:2320
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:2309
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2073
int dt_opencl_set_kernel_arg(const int dev, const int kernel, const int num, const size_t size, const void *arg)
Definition opencl.c:2127
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:2272
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2383
#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:3950
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:3662
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:3030
void init(dt_iop_module_t *module)
Definition retouch.c:1922
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:3528
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:3736
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:2666
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:3075
#define RETOUCH_NO_FORMS
Definition retouch.c:71
static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
Definition retouch.c:4376
static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e, dt_iop_module_t *self)
Definition retouch.c:1735
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:2014
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:3851
void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
Definition retouch.c:1912
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition retouch.c:2063
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:3538
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:2779
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 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:3700
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition retouch.c:1971
#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:2051
static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1663
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:2456
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:3771
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:3618
static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition retouch.c:1570
void gui_init(dt_iop_module_t *self)
Definition retouch.c:2165
#define lw
Definition retouch.c:1055
void change_image(struct dt_iop_module_t *self)
Definition retouch.c:2140
static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
Definition retouch.c:2766
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:3388
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:2832
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition retouch.c:1878
#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:2032
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:2472
static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
Definition retouch.c:4022
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:2924
void cleanup_global(dt_iop_module_so_t *module)
Definition retouch.c:1953
void reload_defaults(dt_iop_module_t *self)
Definition retouch.c:2465
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:1726
static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1862
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:1643
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:3052
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:3001
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:3813
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 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:3520
static gboolean rt_wdbar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
Definition retouch.c:1139
static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
Definition retouch.c:3204
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2057
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:2486
static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1836
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:3578
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:2793
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:1934
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:3096
static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1607
static void rt_mask_opacity_callback(GtkWidget *slider, dt_iop_module_t *self)
Definition retouch.c:1628
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:4213
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:2906
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:3171
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:4413
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:2943
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:2728
static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1526
#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:2479
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:2577
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:368
@ 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:357
struct _GtkWidget GtkWidget
Definition splash.h:29
const float sigma
struct dt_gui_gtk_t * gui
Definition darktable.h:775
struct dt_control_signal_t * signals
Definition darktable.h:774
struct dt_bauhaus_t * bauhaus
Definition darktable.h:778
struct dt_develop_t * develop
Definition darktable.h:770
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:327
dt_pthread_rwlock_t masks_mutex
Definition develop.h:333
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:247
GList * forms
Definition develop.h:321
gint scroll_mask
Definition gtk.h:224
int32_t reset
Definition gtk.h:172
unsigned int channels
Definition format.h:54
dt_iop_global_data_t * data
Definition imageop.h:233
GtkDarktableToggleButton * off
Definition imageop.h:339
gpointer blend_data
Definition imageop.h:318
dt_iop_params_t * default_params
Definition imageop.h:307
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:316
GtkWidget * widget
Definition imageop.h:337
struct dt_develop_t * dev
Definition imageop.h:296
dt_iop_gui_data_t * gui_data
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:314
dt_aligned_pixel_t picked_output_color
Definition imageop.h:274
gboolean enabled
Definition imageop.h:298
int request_mask_display
Definition imageop.h:268
dt_iop_params_t * params
Definition imageop.h:307
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:463
dt_iop_module_t * creation_module
Definition masks.h:505
gboolean creation
Definition masks.h:503
dt_masks_type_t type
Definition masks.h:378
float source[2]
Definition masks.h:385
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
dt_iop_module_t * owner_module
Definition masks.h:969
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