Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
retouch.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2017-2019 Edgardo Hoszowski.
4 Copyright (C) 2018 Alexandre Prokoudine.
5 Copyright (C) 2018-2020, 2023-2026 Aurélien PIERRE.
6 Copyright (C) 2018-2022 Pascal Obry.
7 Copyright (C) 2018 rawfiner.
8 Copyright (C) 2019-2022 Aldric Renaudin.
9 Copyright (C) 2019 Andreas Schneider.
10 Copyright (C) 2019-2020, 2022 Diederik Ter Rahe.
11 Copyright (C) 2019 emeikei.
12 Copyright (C) 2019 luzpaz.
13 Copyright (C) 2019 Tobias Ellinghaus.
14 Copyright (C) 2020 Chris Elston.
15 Copyright (C) 2020-2021 Hubert Kowalski.
16 Copyright (C) 2020 Marco.
17 Copyright (C) 2020 Mark-64.
18 Copyright (C) 2020-2021 Ralf Brown.
19 Copyright (C) 2020 Ulrich Pegelow.
20 Copyright (C) 2021 Dan Torop.
21 Copyright (C) 2021 lhietal.
22 Copyright (C) 2022 Hanno Schwalm.
23 Copyright (C) 2022 Martin Bařinka.
24 Copyright (C) 2022 Philipp Lutz.
25 Copyright (C) 2023 Alynx Zhou.
26 Copyright (C) 2025-2026 Guillaume Stutin.
27
28 darktable is free software: you can redistribute it and/or modify
29 it under the terms of the GNU General Public License as published by
30 the Free Software Foundation, either version 3 of the License, or
31 (at your option) any later version.
32
33 darktable is distributed in the hope that it will be useful,
34 but WITHOUT ANY WARRANTY; without even the implied warranty of
35 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 GNU General Public License for more details.
37
38 You should have received a copy of the GNU General Public License
39 along with darktable. If not, see <http://www.gnu.org/licenses/>.
40*/
41
42#ifdef HAVE_CONFIG_H
44#include "develop/iop_profile.h"
47#include "common/conf.h"
48#include "config.h"
49#endif
51#include "common/hash.h"
52#include "control/control.h"
53#include "widgets/bauhaus.h"
54#include "system/macros.h"
55#include "system/openmp.h"
57#include "system/mem_alloc.h"
58#include "system/simd.h"
59#include "common/logging.h"
61#include "pixel/bilateral.h"
62#include "pixel/bilateralcl.h"
64#include "pixel/dwt.h"
65#include "pixel/gaussian.h"
66#include "pixel/heal.h"
67#include "common/imagebuf.h"
68#include "common/opencl.h"
69#include "develop/blend.h"
70#include "develop/blend_gui.h"
72#include "develop/imageop_gui.h"
73#include "develop/masks.h"
74#include "develop/masks_group.h"
75#include "develop/masks_gui.h"
76#include "develop/tiling.h"
77#include "gui/actions/menu.h"
78#include "iop/iop_api.h"
79
81#include <stdlib.h>
82#include "widgets/label.h"
83#include "gui/screen_metrics.h"
84#include "control/signal.h"
85
86// this is the version of the modules parameters,
87// and includes version information about compile-time dt
89
90#define RETOUCH_NO_FORMS 300
91#define RETOUCH_MAX_SCALES 15
92#define RETOUCH_NO_SCALES (RETOUCH_MAX_SCALES + 2)
93
94#define RETOUCH_PREVIEW_LVL_MIN -3.0f
95#define RETOUCH_PREVIEW_LVL_MAX 3.0f
96
101
103 DT_IOP_RETOUCH_FILL_ERASE = 0, // $DESCRIPTION: "Erase"
104 DT_IOP_RETOUCH_FILL_COLOR = 1 // $DESCRIPTION: "Color"
106
107// How the heal algorithm carries the source's texture over to the destination level,
108// see dt_heal_domain_t. Edits made before the square-root algorithm existed keep the linear one.
110 DT_IOP_RETOUCH_HEAL_LINEAR = 0, // $DESCRIPTION: "Linear"
111 DT_IOP_RETOUCH_HEAL_SQRT = 1 // $DESCRIPTION: "Square root"
113
115 DT_IOP_RETOUCH_BLUR_GAUSSIAN = 0, // $DESCRIPTION: "Gaussian"
116 DT_IOP_RETOUCH_BLUR_BILATERAL = 1 // $DESCRIPTION: "Bilateral"
118
120 DT_IOP_RETOUCH_NONE = 0, // $DESCRIPTION: "Unused"
121 DT_IOP_RETOUCH_CLONE = 1, // $DESCRIPTION: "Clone"
122 DT_IOP_RETOUCH_HEAL = 2, // $DESCRIPTION: "Heal"
123 DT_IOP_RETOUCH_BLUR = 3, // $DESCRIPTION: "Blur"
124 DT_IOP_RETOUCH_FILL = 4 // $DESCRIPTION: "Fill"
126
128{
129 int formid; // from masks, form->formid
130 int scale; // 0==original image; 1..RETOUCH_MAX_SCALES==scale; RETOUCH_MAX_SCALES+1==residual
131 dt_iop_retouch_algo_type_t algorithm; // clone, heal, blur, fill
132
134 float blur_radius; // radius for blur algorithm
135
136 dt_iop_retouch_fill_modes_t fill_mode; // mode for fill algorithm, erase or fill with color
137 float fill_color[3]; // color for fill algorithm
138 float fill_brightness; // value to be added to the color
139 int distort_mode; // module v1 => 1, otherwise 2. mode 1 as issues if there's distortion before this module
141
143{
151 // Accumulated across every wavelet scale of one render, reported once by the caller of
152 // dwt_decompose(). NULL `stats` means -d perf did not ask. See rt_memo_stats_t.
155
157{
158 dt_iop_retouch_form_data_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
159
160 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
161
162 int num_scales; // $DEFAULT: 0 number of wavelets scales
163 int curr_scale; // $DEFAULT: 0 current wavelet scale
164 int merge_from_scale; // $DEFAULT: 0
165
167
168 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "Blur type" gaussian, bilateral
169 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "Blur radius" radius for blur algorithm
170
171 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
172 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
173 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "Brightness" value to be added to the color
174 int max_heal_iter; // $DEFAULT: 2000 $DESCRIPTION: "Max_iter" numbe of iteration for heal algorithm
175 dt_iop_retouch_heal_algorithm_t heal_algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL_SQRT $DESCRIPTION: "Heal algorithm"
177
179{
180 int copied_scale; // scale to be copied to another scale
181 int mask_display; // should we expose masks?
182 int suppress_mask; // do not process masks
183 int display_wavelet_scale; // display current wavelet scale
184 int displayed_wavelet_scale; // was display wavelet scale already used?
185 int preview_auto_levels; // should we calculate levels automatically?
186 float preview_levels[3]; // values for the levels
187 int first_scale_visible; // 1st scale visible at current zoom level
188
189 GtkLabel *label_form; // display number of forms
190 GtkLabel *label_form_selected; // display number of forms selected
193 GtkWidget *bt_showmask, *bt_suppress; // suppress & show masks
194
195 GtkWidget *wd_bar; // wavelet decompose bar
196 GtkLabel *lbl_num_scales;
197 GtkLabel *lbl_curr_scale;
200 int curr_scale; // scale box under mouse
201 gboolean is_dragging;
202 gboolean upper_cursor; // mouse on merge from scale cursor
203 gboolean lower_cursor; // mouse on num scales cursor
204 gboolean upper_margin; // mouse on the upper band
205 gboolean lower_margin; // mouse on the lower band
206
207 GtkWidget *bt_display_wavelet_scale; // show decomposed scale
208
209 GtkWidget *bt_copy_scale; // copy all shapes from one scale to another
211
213
215
217
221
224 GtkWidget *colorpick; // select a specific color
225 GtkWidget *colorpicker; // pick a color from the picture
226
230
231 GtkWidget *sl_mask_opacity; // draw mask opacity
233
235
249
250
251// this returns a translatable name
252const char *name()
253{
254 return _("re_touch");
255}
256
257const char *aliases()
258{
259 return _("split-frequency|healing|cloning|stamp");
260}
261
262
263const char **description(struct dt_iop_module_t *self)
264{
265 return dt_iop_set_description(self, _("remove and clone spots, perform split-frequency skin editing"),
266 _("corrective"),
267 _("linear, RGB, scene-referred"),
268 _("geometric and frequential, RGB"),
269 _("linear, RGB, scene-referred"));
270}
271
273{
274 return IOP_GROUP_REPAIR;
275}
276
281
286
287static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type);
288
289// No new params version before Ansel 1.0: v4 is the last one until then.
290// New params are appended to v4 instead.
291int legacy_params(dt_iop_module_t *self, const void *const old_params, const int old_version, void *new_params,
292 const int new_version)
293{
294 if(old_version == 1 && new_version == 4)
295 {
296 typedef struct dt_iop_retouch_form_data_v1_t
297 {
298 int formid; // from masks, form->formid
299 int scale; // 0==original image; 1..RETOUCH_MAX_SCALES==scale; RETOUCH_MAX_SCALES+1==residual
300 dt_iop_retouch_algo_type_t algorithm; // clone, heal, blur, fill
301
302 dt_iop_retouch_blur_types_t blur_type; // gaussian, bilateral
303 float blur_radius; // radius for blur algorithm
304
305 dt_iop_retouch_fill_modes_t fill_mode; // mode for fill algorithm, erase or fill with color
306 float fill_color[3]; // color for fill algorithm
307 float fill_brightness; // value to be added to the color
308 } dt_iop_retouch_form_data_v1_t;
309 typedef struct dt_iop_retouch_params_v1_t
310 {
311 dt_iop_retouch_form_data_v1_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
312
313 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
314
315 int num_scales; // $DEFAULT: 0 number of wavelets scales
316 int curr_scale; // $DEFAULT: 0 current wavelet scale
317 int merge_from_scale; // $DEFAULT: 0
318
319 float preview_levels[3];
320
321 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "blur type"
322 // gaussian, bilateral
323 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "blur radius" radius for blur
324 // algorithm
325
326 dt_iop_retouch_fill_modes_t fill_mode; // $DEFAULT: DT_IOP_RETOUCH_FILL_ERASE $DESCRIPTION: "fill mode" mode
327 // for fill algorithm, erase or fill with color
328 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
329 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "brightness" value to be added to the color
330 } dt_iop_retouch_params_v1_t;
331
332 dt_iop_retouch_params_v1_t *o = (dt_iop_retouch_params_v1_t *)old_params;
335
336 *n = *d; // start with a fresh copy of default parameters
337 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
338 {
339 dt_iop_retouch_form_data_v1_t of = o->rt_forms[i];
340 n->rt_forms[i].algorithm = of.algorithm;
341 n->rt_forms[i].blur_radius = of.blur_radius;
342 n->rt_forms[i].blur_type = of.blur_type;
343 n->rt_forms[i].distort_mode = 1;
344 n->rt_forms[i].fill_brightness = of.fill_brightness;
345 n->rt_forms[i].fill_color[0] = of.fill_color[0];
346 n->rt_forms[i].fill_color[1] = of.fill_color[1];
347 n->rt_forms[i].fill_color[2] = of.fill_color[2];
348 n->rt_forms[i].fill_mode = of.fill_mode;
349 n->rt_forms[i].formid = of.formid;
350 n->rt_forms[i].scale = of.scale;
351 }
352 n->algorithm = o->algorithm;
353 n->blur_radius = o->blur_radius;
354 n->blur_type = o->blur_type;
355 n->curr_scale = o->curr_scale;
356 n->fill_brightness = o->fill_brightness;
357 n->fill_color[0] = o->fill_color[0];
358 n->fill_color[1] = o->fill_color[1];
359 n->fill_color[2] = o->fill_color[2];
360 n->fill_mode = o->fill_mode;
361 n->merge_from_scale = o->merge_from_scale;
362 n->num_scales = o->num_scales;
363 n->preview_levels[0] = o->preview_levels[0];
364 n->preview_levels[1] = o->preview_levels[1];
365 n->preview_levels[2] = o->preview_levels[2];
366
367 n->max_heal_iter = 1000;
368 n->heal_algorithm = DT_IOP_RETOUCH_HEAL_LINEAR;
369
370 return 0;
371 }
372 if(old_version == 2 && new_version == 4)
373 {
374 typedef struct dt_iop_retouch_params_v2_t
375 {
376 dt_iop_retouch_form_data_t rt_forms[RETOUCH_NO_FORMS]; // array of masks index and additional data
377
378 dt_iop_retouch_algo_type_t algorithm; // $DEFAULT: DT_IOP_RETOUCH_HEAL clone, heal, blur, fill
379
380 int num_scales; // $DEFAULT: 0 number of wavelets scales
381 int curr_scale; // $DEFAULT: 0 current wavelet scale
382 int merge_from_scale; // $DEFAULT: 0
383
384 float preview_levels[3];
385
386 dt_iop_retouch_blur_types_t blur_type; // $DEFAULT: DT_IOP_RETOUCH_BLUR_GAUSSIAN $DESCRIPTION: "blur type" gaussian, bilateral
387 float blur_radius; // $MIN: 0.1 $MAX: 200.0 $DEFAULT: 10.0 $DESCRIPTION: "blur radius" radius for blur algorithm
388
389 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
390 float fill_color[3]; // $DEFAULT: 0.0 color for fill algorithm
391 float fill_brightness; // $MIN: -1.0 $MAX: 1.0 $DESCRIPTION: "brightness" value to be added to the color
392 } dt_iop_retouch_params_v2_t;
393
394 dt_iop_retouch_params_v2_t *o = (dt_iop_retouch_params_v2_t *)old_params;
397
398 *n = *d; // start with a fresh copy of default parameters
399
400 memcpy(n, o, sizeof(dt_iop_retouch_params_v2_t));
401
402 n->max_heal_iter = 1000;
403 n->heal_algorithm = DT_IOP_RETOUCH_HEAL_LINEAR;
404
405 return 0;
406 }
407 if(old_version == 3 && new_version == 4)
408 {
409 // v3 is v4 without the trailing heal_algorithm
410 const size_t v3_size = offsetof(dt_iop_retouch_params_t, heal_algorithm);
413
414 *n = *d;
415 memcpy(n, old_params, v3_size);
416 n->heal_algorithm = DT_IOP_RETOUCH_HEAL_LINEAR;
417
418 return 0;
419 }
420 return 1;
421}
422
423static int rt_get_index_from_formid(const dt_iop_retouch_params_t *p, const int formid)
424{
425 int index = -1;
426 if(formid > 0)
427 {
428 int i = 0;
429
430 while(index == -1 && i < RETOUCH_NO_FORMS)
431 {
432 if(p->rt_forms[i].formid == formid) index = i;
433 i++;
434 }
435 }
436 return index;
437}
438
440{
441 if(IS_NULL_PTR(self) || IS_NULL_PTR(self->dev)) return 0;
442
443 const dt_masks_form_gui_t *gui = self->dev->form_gui;
444 const dt_masks_form_t *visible_form = dt_masks_get_visible_form(self->dev);
445 if(IS_NULL_PTR(gui) || IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP)) return 0;
446
447 const dt_masks_form_group_t *selected_group_entry
448 = dt_masks_form_get_selected_group_live(visible_form, gui);
449 return selected_group_entry ? selected_group_entry->formid : 0;
450}
451
453{
454 dt_masks_form_group_t *form_point_group = NULL;
455
456 const dt_develop_blend_params_t *bp = self->blend_params;
457 if(IS_NULL_PTR(bp)) return form_point_group;
458
459 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, bp->mask_id);
460 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP))
461 {
462 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
463 {
464 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
465 if(grpt->formid == formid)
466 {
467 form_point_group = grpt;
468 break;
469 }
470 }
471 }
472
473 return form_point_group;
474}
475
476static float rt_get_shape_opacity(dt_iop_module_t *self, const int formid)
477{
478 float opacity = 0.f;
479
481 if(grpt) opacity = grpt->opacity;
482
483 return opacity;
484}
485
487{
488 GdkRGBA c
489 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
490 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(g->colorpick), &c);
491}
492
494{
497
498 switch(p->algorithm)
499 {
501 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
502 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
503 break;
505 gtk_widget_show(GTK_WIDGET(g->vbox_blur));
506 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
507 break;
509 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
510 gtk_widget_show(GTK_WIDGET(g->vbox_fill));
511 if(p->fill_mode == DT_IOP_RETOUCH_FILL_COLOR)
512 gtk_widget_show(GTK_WIDGET(g->hbox_color_pick));
513 else
514 gtk_widget_hide(GTK_WIDGET(g->hbox_color_pick));
515 break;
517 default:
518 gtk_widget_hide(GTK_WIDGET(g->vbox_blur));
519 gtk_widget_hide(GTK_WIDGET(g->vbox_fill));
520 break;
521 }
522
523 if(g->display_wavelet_scale)
524 gtk_widget_show(GTK_WIDGET(g->vbox_preview_scale));
525 else
526 gtk_widget_hide(GTK_WIDGET(g->vbox_preview_scale));
527
528 const dt_masks_form_t *form = NULL;
529 const int selected_formid = rt_get_selected_shape_id(self);
530 if(selected_formid > 0)
531 form = dt_masks_get_from_id(self->dev, selected_formid);
532 if(!IS_NULL_PTR(form))
533 gtk_widget_show(GTK_WIDGET(g->sl_mask_opacity));
534 else
535 gtk_widget_hide(GTK_WIDGET(g->sl_mask_opacity));
536}
537
539{
540 const int selected_formid = rt_get_selected_shape_id(dev->gui_module);
541 const dt_masks_form_t *form = selected_formid > 0
542 ? dt_masks_get_from_id(dev, selected_formid)
543 : NULL;
544 if(!IS_NULL_PTR(form))
545 gtk_label_set_text(g->label_form_selected, form->name);
546 else
547 gtk_label_set_text(g->label_form_selected, _("none"));
548}
549
551{
552 const int selected_formid = rt_get_selected_shape_id(dev->gui_module);
553 return rt_get_index_from_formid(p, selected_formid);
554}
555
556static void rt_load_shape_algo_in_gui(dt_iop_module_t *self, const int form_selected_id)
557{
560
562
563 gboolean selection_changed = FALSE;
564
565 const int index = rt_get_index_from_formid(p, form_selected_id);
566 if(index >= 0)
567 {
568 const dt_iop_retouch_form_data_t *const selected_form = &p->rt_forms[index];
569 dt_bauhaus_slider_set(g->sl_mask_opacity, rt_get_shape_opacity(self, selected_form->formid));
570
571 if(selected_form->algorithm == DT_IOP_RETOUCH_BLUR)
572 {
573 p->blur_type = selected_form->blur_type;
574 p->blur_radius = selected_form->blur_radius;
575
576 dt_bauhaus_combobox_set(g->cmb_blur_type, p->blur_type);
577 dt_bauhaus_slider_set(g->sl_blur_radius, p->blur_radius);
578
579 selection_changed = TRUE;
580 }
581 else if(selected_form->algorithm == DT_IOP_RETOUCH_FILL)
582 {
583 p->fill_mode = selected_form->fill_mode;
584 p->fill_brightness = selected_form->fill_brightness;
585 p->fill_color[0] = selected_form->fill_color[0];
586 p->fill_color[1] = selected_form->fill_color[1];
587 p->fill_color[2] = selected_form->fill_color[2];
588
589 dt_bauhaus_slider_set(g->sl_fill_brightness, p->fill_brightness);
590 dt_bauhaus_combobox_set(g->cmb_fill_mode, p->fill_mode);
592
593 selection_changed = TRUE;
594 }
595
596 if(p->algorithm != selected_form->algorithm)
597 {
598 p->algorithm = selected_form->algorithm;
599
600 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), (p->algorithm == DT_IOP_RETOUCH_CLONE));
601 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), (p->algorithm == DT_IOP_RETOUCH_HEAL));
602 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), (p->algorithm == DT_IOP_RETOUCH_BLUR));
603 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), (p->algorithm == DT_IOP_RETOUCH_FILL));
604
605 selection_changed = TRUE;
606 }
607 }
608
609 if(selection_changed) rt_show_hide_controls(self);
610
612
613 if(index >= 0)
614 gtk_widget_show(GTK_WIDGET(g->sl_mask_opacity));
615 else
616 gtk_widget_hide(GTK_WIDGET(g->sl_mask_opacity));
617
619}
620
621//---------------------------------------------------------------------------------
622// helpers
623//---------------------------------------------------------------------------------
624
625static void rt_masks_form_change_opacity(dt_iop_module_t *self, int formid, float opacity)
626{
627 const dt_develop_blend_params_t *bp = self->blend_params;
628 if(IS_NULL_PTR(bp)) return;
629
630 /* Retouch keeps a 5% floor of its own, applied BEFORE the call: a fully transparent clone or
631 * heal is indistinguishable from a deleted one in this module's list, so it never offers 0 the
632 * way a generic mask does. The API clamps to [0;1] and would accept it. */
633 dt_masks_member_t member = { 0 };
634 const dt_masks_result_t result = dt_masks_group_set_member_opacity(self->dev, bp->mask_id, formid,
635 CLAMP(opacity, 0.05f, 1.0f), &member);
636 if(result != DT_MASKS_OK && result != DT_MASKS_UNCHANGED) return;
637
638 dt_conf_set_float("plugins/darkroom/masks/opacity", member.opacity);
639
640 /* Only a real change earns a history step. Re-setting the opacity a shape already has used to
641 * push an undo entry that undid nothing. */
642 if(result == DT_MASKS_OK) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
643}
644
645static float rt_masks_form_get_opacity(dt_iop_module_t *self, int formid)
646{
648 if(!IS_NULL_PTR(grpt))
649 return grpt->opacity;
650 else
651 return 1.0f;
652}
653
654static void rt_paste_forms_from_scale(dt_iop_retouch_params_t *p, const int source_scale, const int dest_scale)
655{
656 if(source_scale != dest_scale && source_scale >= 0 && dest_scale >= 0)
657 {
658 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
659 {
660 if(p->rt_forms[i].scale == source_scale) p->rt_forms[i].scale = dest_scale;
661 }
662 }
663}
664
666{
667 int allow = 1;
668
670 if(!IS_NULL_PTR(p))
671 {
672 allow = (p->rt_forms[RETOUCH_NO_FORMS - 1].formid == 0);
673 }
674 return allow;
675}
676
678{
679 if(!self->enabled || self->dev->gui_module != self || self->dev->form_gui->creation)
680 return;
681
685 if(IS_NULL_PTR(bd)) return;
686
687 const int scale = p->curr_scale;
688 int count = 0;
689
690 // Check if there is at least one shape on this scale
691 for(int i = 0; i < RETOUCH_NO_FORMS && count == 0; i++)
692 {
693 if(p->rt_forms[i].formid != 0 && p->rt_forms[i].scale == scale) count++;
694 }
695
696 // If a shape was found on this scale, make the cut shapes button sensitive
697 gtk_widget_set_sensitive(g->bt_copy_scale, count > 0);
698
699 // if no shapes on this scale, we hide all
700 if(bd->masks_shown == DT_MASKS_EDIT_OFF || count == 0)
701 {
702 dt_masks_change_form_gui(self->dev, NULL);
703
704 if(g)
705 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
707 && (self->dev->gui_module == self));
708
710 return;
711 }
712
713 // else, we create a new from group with the shapes and display it
715 const int grid = self->blend_params->mask_id;
716
717 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
718 {
719 if(p->rt_forms[i].scale == scale)
720 {
721 const int formid = p->rt_forms[i].formid;
722 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
723 if(IS_NULL_PTR(form)) continue;
724
725 dt_masks_group_add_form_with_state(self->dev, grp, form, grid,
727 }
728 }
729
731 grp_dest->formid = 0;
732 dt_masks_group_ungroup(self->dev, grp_dest, grp);
733 dt_masks_change_form_gui(self->dev, grp_dest);
734 self->dev->form_gui->edit_mode = bd->masks_shown;
735
736 if(g)
737 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
738 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
739
741}
742
743// called if a shape is added or deleted
744static void rt_resynch_params(struct dt_iop_module_t *self, dt_iop_retouch_params_t *p, GList *forms_list)
745{
747 if(IS_NULL_PTR(p) || IS_NULL_PTR(bp)) return;
748
749 // Create a temporary array to store form data and initialize it to zero
751 memset(forms_d, 0, sizeof(dt_iop_retouch_form_data_t) * RETOUCH_NO_FORMS);
752
753 // we go through all forms in blend params
754 dt_masks_form_t *grp = dt_masks_get_from_id_ext(forms_list, bp->mask_id);
755 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP))
756 return;
757
758 int new_form_index = 0;
759 // For each form in the group, we search if it already exists in the params array,
760 // if it does we copy it, if not we add it to the end of the array
761 for(GList *forms = grp->points; (new_form_index < RETOUCH_NO_FORMS) && forms; forms = g_list_next(forms))
762 {
763 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
764 if(IS_NULL_PTR(grpt)) return;
765
766 const int formid = grpt->formid;
767
768 // search for the form index in the shapes array
769 const int form_index = rt_get_index_from_formid(p, formid);
770
771 // if it exists, copy it to the new array
772 if(form_index >= 0)
773 {
774 forms_d[new_form_index] = p->rt_forms[form_index];
775 }
776 else
777 {
778 // if it does not exists, add it to the new array
779 const dt_masks_form_t *parent_form = dt_masks_get_from_id_ext(forms_list, formid);
780 if(IS_NULL_PTR(parent_form)) continue;
781
782 forms_d[new_form_index].formid = formid;
783 forms_d[new_form_index].scale = p->curr_scale;
784 forms_d[new_form_index].algorithm = p->algorithm;
785 forms_d[new_form_index].distort_mode = 2;
786
787 switch(forms_d[new_form_index].algorithm)
788 {
790 forms_d[new_form_index].blur_type = p->blur_type;
791 forms_d[new_form_index].blur_radius = p->blur_radius;
792 break;
794 forms_d[new_form_index].fill_mode = p->fill_mode;
795 forms_d[new_form_index].fill_color[0] = p->fill_color[0];
796 forms_d[new_form_index].fill_color[1] = p->fill_color[1];
797 forms_d[new_form_index].fill_color[2] = p->fill_color[2];
798 forms_d[new_form_index].fill_brightness = p->fill_brightness;
799 break;
800 default:
801 break;
802 }
803 }
804 new_form_index++;
805 }
806
807 // we reaffect params
808 for(int i = 0; i < RETOUCH_NO_FORMS; i++)
809 {
810 p->rt_forms[i] = forms_d[i];
811 }
812}
813
815{
816 // When drawing events are finished, they commit the shapes to dev->forms
817 // then commit to history, where dev->forms is are copied to hist->forms.
818 // This is where we need to catch the new masks
819 // to sync them with our params here.
820
821 // TODO: share this code with gui_update()
825
827 if(IS_NULL_PTR(g)) return;
828 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), rt_shape_is_being_added(self, DT_MASKS_CIRCLE));
829 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_polygon), rt_shape_is_being_added(self, DT_MASKS_POLYGON));
830 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), rt_shape_is_being_added(self, DT_MASKS_ELLIPSE));
831 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_brush), rt_shape_is_being_added(self, DT_MASKS_BRUSH));
832
833 // Get the total form count and display it in the module
834 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id);
835 guint nb = 0;
836 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
837 gchar *str = g_strdup_printf("%d", nb);
838 gtk_label_set_text(g->label_form, str);
839 dt_free(str);
840
841 //only toggle shape show button if shapes exist
842 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
843 {
845 if(IS_NULL_PTR(bd)) return;
846 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
847 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
848 }
849 else
850 {
851 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
852 }
853}
854
856 const float *points,
857 size_t points_count, float *new)
858{
859 const float scalex = pipe->iwidth * roi->scale, scaley = pipe->iheight * roi->scale;
860
861 for(size_t i = 0; i < points_count * 2; i += 2)
862 {
863 new[i] = points[i] * scalex;
864 new[i + 1] = points[i + 1] * scaley;
865 }
866}
867
868/* What one planning pass or one render did with the memo, and what the algorithms cost.
869 *
870 * This exists for the drag case, which nothing else reports: moving a shape recomputes this
871 * module and everything under it with the whole upstream served from the cache, and neither the
872 * pipeline's own `processed \`Retouch'` line nor the per-shape `[masks]` timings say which part
873 * of that frame went where -- the pipeline line does not cover modify_roi_in() at all, and that
874 * is where most of the cost used to sit. Counted only under `-d perf`: `ctx->stats` is NULL
875 * otherwise and every site below is one test. */
876typedef struct rt_memo_stats_t
877{
878 int boxes_hit; // a shape's area or source area answered from the memo
879 int boxes_computed; // the same, rasterised because the memo had no answer
880 int masks_hit; // its mask likewise answered from the memo
881 int masks_rasterised; // and likewise rasterised
882 int shapes_applied; // members that reached an algorithm
883 double algo_seconds; // time inside clone / heal / blur / fill
885
886/* The three things every one of these functions needs and none of them writes: the module, the
887 * run it belongs to and its node. They always travel together, so they travel as one -- and the
888 * counters with them, which is what keeps them out of every signature. */
889typedef struct rt_masks_ctx_t
890{
894 rt_memo_stats_t *stats; // NULL unless -d perf asked for the count
896
897// TRUE when the timings below are wanted at all; building the counters otherwise is waste.
898static inline gboolean rt_perf_enabled(void)
899{
901}
902
903static int rt_masks_point_calc_delta(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi,
904 const float *target, const float *source, float *dx, float *dy,
905 const int distort_mode)
906{
907 // if distort_mode==1 we don't scale at the right place, hence false positions if there's distortion before this
908 // module. we keep it for backward compatibility only. all new forms have distort_mode==2
909 dt_boundingbox_t points;
910 if(distort_mode == 1)
911 {
912 rt_masks_point_denormalize(ctx->pipe, roi, target, 1, points);
913 rt_masks_point_denormalize(ctx->pipe, roi, source, 1, points + 2);
914 }
915 else
916 {
917 points[0] = target[0] * ctx->pipe->iwidth;
918 points[1] = target[1] * ctx->pipe->iheight;
919 points[2] = source[0] * ctx->pipe->iwidth;
920 points[3] = source[1] * ctx->pipe->iheight;
921 }
922
923 const int res = dt_dev_distort_transform_plus(ctx->pipe, ctx->self->iop_order,
925 if(!res) return res;
926
927 if(distort_mode == 1)
928 {
929 *dx = points[0] - points[2];
930 *dy = points[1] - points[3];
931 }
932 else
933 {
934 *dx = (points[0] - points[2]) * roi->scale;
935 *dy = (points[1] - points[3]) * roi->scale;
936 }
937
938 return res;
939}
940
941/* returns (dx dy) to get from the source to the destination */
942static int rt_masks_get_delta_to_destination(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi,
943 dt_masks_form_t *form, float *dx, float *dy,
944 const int distort_mode)
945{
946 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points)) return 0;
947 int res = 0;
948
949 if(form->type & DT_MASKS_POLYGON)
950 {
951 const dt_masks_node_polygon_t *pt = (dt_masks_node_polygon_t *)form->points->data;
952 if(IS_NULL_PTR(pt)) return 0;
953
954 res = rt_masks_point_calc_delta(ctx, roi, pt->node, form->source, dx, dy, distort_mode);
955 }
956 else if(form->type & DT_MASKS_CIRCLE)
957 {
958 const dt_masks_node_circle_t *pt = (dt_masks_node_circle_t *)form->points->data;
959 if(IS_NULL_PTR(pt)) return 0;
960
961 res = rt_masks_point_calc_delta(ctx, roi, pt->center, form->source, dx, dy, distort_mode);
962 }
963 else if(form->type & DT_MASKS_ELLIPSE)
964 {
965 const dt_masks_node_ellipse_t *pt = (dt_masks_node_ellipse_t *)form->points->data;
966 if(IS_NULL_PTR(pt)) return 0;
967
968 res = rt_masks_point_calc_delta(ctx, roi, pt->center, form->source, dx, dy, distort_mode);
969 }
970 else if(form->type & DT_MASKS_BRUSH)
971 {
972 const dt_masks_node_brush_t *pt = (dt_masks_node_brush_t *)form->points->data;
973 if(IS_NULL_PTR(pt)) return 0;
974
975 res = rt_masks_point_calc_delta(ctx, roi, pt->node, form->source, dx, dy, distort_mode);
976 }
977
978 return res;
979}
980
981static inline __attribute__((always_inline)) void rt_clamp_minmax(float levels_old[3], float levels_new[3])
982{
983 // left or right has changed
984 if((levels_old[0] != levels_new[0] || levels_old[2] != levels_new[2]) && levels_old[1] == levels_new[1])
985 {
986 // if old left and right are the same just use the new values
987 if(levels_old[2] != levels_old[0])
988 {
989 // set the new value but keep the middle proportional
990 const float left = MAX(levels_new[0], RETOUCH_PREVIEW_LVL_MIN);
991 const float right = MIN(levels_new[2], RETOUCH_PREVIEW_LVL_MAX);
992
993 const float percentage = (levels_old[1] - levels_old[0]) / (levels_old[2] - levels_old[0]);
994 levels_new[1] = left + (right - left) * percentage;
995 levels_new[0] = left;
996 levels_new[2] = right;
997 }
998 }
999
1000 // if all zero make it gray
1001 if(levels_new[0] == 0.f && levels_new[1] == 0.f && levels_new[2] == 0.f)
1002 {
1003 levels_new[0] = -1.5f;
1004 levels_new[1] = 0.f;
1005 levels_new[2] = 1.5f;
1006 }
1007
1008 // check the range
1009 if(levels_new[2] < levels_new[0] + 0.05f * 2.f) levels_new[2] = levels_new[0] + 0.05f * 2.f;
1010 if(levels_new[1] < levels_new[0] + 0.05f) levels_new[1] = levels_new[0] + 0.05f;
1011 if(levels_new[1] > levels_new[2] - 0.05f) levels_new[1] = levels_new[2] - 0.05f;
1012
1013 {
1014 // set the new value but keep the middle proportional
1015 const float left = MAX(levels_new[0], RETOUCH_PREVIEW_LVL_MIN);
1016 const float right = MIN(levels_new[2], RETOUCH_PREVIEW_LVL_MAX);
1017
1018 const float percentage = (levels_new[1] - levels_new[0]) / (levels_new[2] - levels_new[0]);
1019 levels_new[1] = left + (right - left) * percentage;
1020 levels_new[0] = left;
1021 levels_new[2] = right;
1022 }
1023}
1024
1025static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type)
1026{
1027 int being_added = 0;
1028
1029 if(self->dev->form_gui && dt_masks_get_visible_form(self->dev)
1030 && (self->dev->form_gui->creation && self->dev->form_gui->creation_module == self))
1031 {
1033 {
1034 GList *forms = dt_masks_get_visible_form(self->dev)->points;
1035 if(IS_NULL_PTR(forms)) goto end;
1036
1037 dt_masks_form_group_t *grpt = (dt_masks_form_group_t *)forms->data;
1038 if(IS_NULL_PTR(grpt)) goto end;
1039
1040 const dt_masks_form_t *form = dt_masks_get_from_id(self->dev, grpt->formid);
1041 if(!IS_NULL_PTR(form)) being_added = (form->type & shape_type);
1042 }
1043 else
1044 being_added = (dt_masks_get_visible_form(self->dev)->type & shape_type);
1045 }
1046
1047 end:
1048 return being_added;
1049}
1050
1052 dt_masks_type_t type, gpointer user_data)
1053{
1054 //turn module on (else shape creation won't work)
1055 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), TRUE);
1056
1057 //switch mask edit mode off
1059 if(bd) bd->masks_shown = DT_MASKS_EDIT_OFF;
1060
1061 const int allow = rt_allow_create_form(self);
1062 if(allow)
1063 {
1065
1066 // we want to be sure that Retouch has focus
1069 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
1070 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_showmask), FALSE);
1071 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_suppress), FALSE);
1072 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->colorpicker), FALSE);
1073 }
1074
1075 return allow;
1076}
1077
1079{
1081 if(p->algorithm == DT_IOP_RETOUCH_CLONE || p->algorithm == DT_IOP_RETOUCH_HEAL)
1082 return type | DT_MASKS_CLONE;
1083
1084 return type | DT_MASKS_NON_CLONE;
1085}
1086
1087//---------------------------------------------------------------------------------
1088// GUI callbacks
1089//---------------------------------------------------------------------------------
1090
1091static void rt_colorpick_color_set_callback(GtkColorButton *widget, dt_iop_module_t *self)
1092{
1093 if(dt_gui_widgets_suppressed()) return;
1095 // turn off the other color picker
1097
1098 GdkRGBA c
1099 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
1100 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(widget), &c);
1101 p->fill_color[0] = c.red;
1102 p->fill_color[1] = c.green;
1103 p->fill_color[2] = c.blue;
1104
1105 const int index = rt_get_selected_shape_index(self->dev, p);
1106 if(index >= 0)
1107 {
1108 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1109 {
1110 p->rt_forms[index].fill_color[0] = p->fill_color[0];
1111 p->rt_forms[index].fill_color[1] = p->fill_color[1];
1112 p->rt_forms[index].fill_color[2] = p->fill_color[2];
1113 }
1114 }
1115
1116 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1117}
1118
1119// wavelet decompose bar
1120#define RT_WDBAR_INSET 0.2f
1121#define lw DT_PIXEL_APPLY_DPI(1.0f)
1122
1124{
1125 char text[256];
1126
1127 snprintf(text, sizeof(text), "%i", p->curr_scale);
1128 gtk_label_set_text(g->lbl_curr_scale, text);
1129
1130 snprintf(text, sizeof(text), "%i", p->num_scales);
1131 gtk_label_set_text(g->lbl_num_scales, text);
1132
1133 snprintf(text, sizeof(text), "%i", p->merge_from_scale);
1134 gtk_label_set_text(g->lbl_merge_from_scale, text);
1135}
1136
1137static void rt_num_scales_update(const int _num_scales, dt_iop_module_t *self)
1138{
1139 if(dt_gui_widgets_suppressed()) return;
1140
1143
1144 const int num_scales = CLAMP(_num_scales, 0, RETOUCH_MAX_SCALES);
1145 if(p->num_scales == num_scales) return;
1146
1147 p->num_scales = num_scales;
1148
1149 if(p->num_scales < p->merge_from_scale) p->merge_from_scale = p->num_scales;
1150
1152
1153 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1154}
1155
1156static void rt_curr_scale_update(const int _curr_scale, dt_iop_module_t *self)
1157{
1158 if(dt_gui_widgets_suppressed()) return;
1159
1162
1163 const int curr_scale = CLAMP(_curr_scale, 0, RETOUCH_MAX_SCALES + 1);
1164 if(p->curr_scale == curr_scale) return;
1165
1166 p->curr_scale = curr_scale;
1167
1169
1170 // compute auto levels only the first time display wavelet scale is used,
1171 // only if levels values are the default
1172 // and a detail scale is displayed
1174 if(g->displayed_wavelet_scale == 0 && p->preview_levels[0] == RETOUCH_PREVIEW_LVL_MIN
1175 && p->preview_levels[1] == 0.f && p->preview_levels[2] == RETOUCH_PREVIEW_LVL_MAX
1176 && g->preview_auto_levels == 0 && p->curr_scale > 0 && p->curr_scale <= p->num_scales)
1177 {
1178 g->preview_auto_levels = 1;
1179 g->displayed_wavelet_scale = 1;
1180 }
1182
1184
1185 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1186}
1187
1188static void rt_merge_from_scale_update(const int _merge_from_scale, dt_iop_module_t *self)
1189{
1190 if(dt_gui_widgets_suppressed()) return;
1191
1194
1195 const int merge_from_scale = CLAMP(_merge_from_scale, 0, p->num_scales);
1196 if(p->merge_from_scale == merge_from_scale) return;
1197
1198 p->merge_from_scale = merge_from_scale;
1199
1201
1202 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1203}
1204
1205static gboolean rt_wdbar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
1206{
1208
1209 g->wdbar_mouse_x = g->wdbar_mouse_y = -1;
1210 g->curr_scale = -1;
1211 g->lower_cursor = g->upper_cursor = FALSE;
1212 g->lower_margin = g->upper_margin = FALSE;
1213
1214 gtk_widget_queue_draw(g->wd_bar);
1215 return TRUE;
1216}
1217
1218static gboolean rt_wdbar_button_press(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1219{
1220 if(dt_gui_widgets_suppressed()) return TRUE;
1221
1223
1225 GtkAllocation allocation;
1226 gtk_widget_get_allocation(widget, &allocation);
1227 const int inset = round(RT_WDBAR_INSET * allocation.height);
1228 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1229
1230 if(event->button == 1)
1231 {
1232 if(g->lower_margin) // bottom slider
1233 {
1234 if(g->lower_cursor) // is over the arrow?
1235 g->is_dragging = DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM;
1236 else
1237 rt_num_scales_update(g->wdbar_mouse_x / box_w, self);
1238 }
1239 else if(g->upper_margin) // top slider
1240 {
1241 if(g->upper_cursor) // is over the arrow?
1242 g->is_dragging = DT_IOP_RETOUCH_WDBAR_DRAG_TOP;
1243 else
1244 rt_merge_from_scale_update(g->wdbar_mouse_x / box_w, self);
1245 }
1246 else if (g->curr_scale >= 0)
1247 rt_curr_scale_update(g->curr_scale, self);
1248 }
1249
1250 gtk_widget_queue_draw(g->wd_bar);
1251 return TRUE;
1252}
1253
1254static gboolean rt_wdbar_button_release(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1255{
1257
1258 if(event->button == 1) g->is_dragging = 0;
1259
1260 gtk_widget_queue_draw(g->wd_bar);
1261 return TRUE;
1262}
1263
1264static gboolean rt_wdbar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
1265{
1266 if(dt_gui_widgets_suppressed()) return TRUE;
1267
1270
1272
1273 int delta_y;
1274 if(dt_gui_get_scroll_unit_deltas(event, NULL, &delta_y))
1275 {
1276 if(g->lower_margin) // bottom slider
1277 rt_num_scales_update(p->num_scales - delta_y, self);
1278 else if(g->upper_margin) // top slider
1279 rt_merge_from_scale_update(p->merge_from_scale - delta_y, self);
1280 else if (g->curr_scale >= 0)
1281 rt_curr_scale_update(p->curr_scale - delta_y, self);
1282 }
1283
1284 gtk_widget_queue_draw(g->wd_bar);
1285 return TRUE;
1286}
1287
1288static gboolean rt_wdbar_motion_notify(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
1289{
1292
1293 GtkAllocation allocation;
1294 gtk_widget_get_allocation(widget, &allocation);
1295 const int inset = round(RT_WDBAR_INSET * allocation.height);
1296 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1297 const float sh = 3.0f * lw + inset;
1298
1299
1300 /* record mouse position within control */
1301 g->wdbar_mouse_x = CLAMP(event->x - inset, 0, allocation.width - 2.0f * inset - 1.0f);
1302 g->wdbar_mouse_y = event->y;
1303
1304 g->curr_scale = g->wdbar_mouse_x / box_w;
1305 g->lower_cursor = g->upper_cursor = FALSE;
1306 g->lower_margin = g->upper_margin = FALSE;
1307 if(g->wdbar_mouse_y <= sh)
1308 {
1309 g->upper_margin = TRUE;
1310 float middle = box_w * (0.5f + (float)p->merge_from_scale);
1311 g->upper_cursor = (g->wdbar_mouse_x >= (middle - inset)) && (g->wdbar_mouse_x <= (middle + inset));
1312 if (!(g->is_dragging)) g->curr_scale = -1;
1313 }
1314 else if (g->wdbar_mouse_y >= allocation.height - sh)
1315 {
1316 g->lower_margin = TRUE;
1317 float middle = box_w * (0.5f + (float)p->num_scales);
1318 g->lower_cursor = (g->wdbar_mouse_x >= (middle - inset)) && (g->wdbar_mouse_x <= (middle + inset));
1319 if (!(g->is_dragging)) g->curr_scale = -1;
1320 }
1321
1322 if(g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM)
1323 rt_num_scales_update(g->curr_scale, self);
1324
1325 if(g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_TOP)
1326 rt_merge_from_scale_update(g->curr_scale, self);
1327
1328 gtk_widget_queue_draw(g->wd_bar);
1329 return TRUE;
1330}
1331
1332static int rt_scale_has_shapes(dt_iop_retouch_params_t *p, const int scale)
1333{
1334 int has_shapes = 0;
1335
1336 for(int i = 0; i < RETOUCH_NO_FORMS && has_shapes == 0; i++)
1337 has_shapes = (p->rt_forms[i].formid != 0 && p->rt_forms[i].scale == scale);
1338
1339 return has_shapes;
1340}
1341
1342static gboolean rt_wdbar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
1343{
1346
1347
1348 GdkRGBA border = {0.066, 0.066, 0.066, 1};
1349 GdkRGBA original = {.1, .1, .1, 1};
1350 GdkRGBA inactive = {.15, .15, .15, 1};
1351 GdkRGBA active = {.35, .35, .35, 1};
1352 GdkRGBA merge_from = {.5, .5, .5, 1};
1353 GdkRGBA residual = {.8, .8, .8, 1};
1354 GdkRGBA shapes = {.75, .5, .0, 1};
1355 GdkRGBA color;
1356
1357 float middle;
1358 const int first_scale_visible = (g->first_scale_visible > 0) ? g->first_scale_visible : RETOUCH_MAX_SCALES;
1359
1360 GtkAllocation allocation;
1361 gtk_widget_get_allocation(widget, &allocation);
1362
1363 cairo_surface_t *cst = dt_cairo_image_surface_create(CAIRO_FORMAT_ARGB32, allocation.width, allocation.height);
1364 cairo_t *cr = cairo_create(cst);
1365
1366 // clear background
1367 gdk_cairo_set_source_rgba(cr, &inactive);
1368 cairo_paint(cr);
1369 cairo_save(cr);
1370
1371 // geometry
1372 const int inset = round(RT_WDBAR_INSET * allocation.height);
1373 const int mk = 2 * inset;
1374 const float sh = 3.0f * lw + inset;
1375 const float box_w = (allocation.width - 2.0f * inset) / (float)RETOUCH_NO_SCALES;
1376 const float box_h = allocation.height - 2.0f * sh;
1377
1378 // render the boxes
1379 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
1380 for(int i = 0; i < RETOUCH_NO_SCALES; i++)
1381 {
1382 // draw box background
1383 if(i == 0)
1384 color = original;
1385 else if(i == p->num_scales + 1)
1386 color = residual;
1387 else if(i >= p->merge_from_scale && i <= p->num_scales && p->merge_from_scale > 0)
1388 color = merge_from;
1389 else if(i <= p->num_scales)
1390 color = active;
1391 else
1392 color = inactive;
1393
1394 gdk_cairo_set_source_rgba(cr, &color);
1395 cairo_rectangle(cr, box_w * i + inset, sh, box_w, box_h);
1396 cairo_fill(cr);
1397
1398 // if detail scale is visible at current zoom level inform it
1399 if(i >= first_scale_visible && i <= p->num_scales)
1400 {
1401 gdk_cairo_set_source_rgba(cr, &merge_from);
1402 cairo_rectangle(cr, box_w * i + inset, lw, box_w, 2.0f * lw);
1403 cairo_fill(cr);
1404 }
1405
1406 // if the scale has shapes inform it
1407 if(rt_scale_has_shapes(p, i))
1408 {
1409 cairo_set_line_width(cr, lw);
1410 gdk_cairo_set_source_rgba(cr, &shapes);
1411 cairo_rectangle(cr, box_w * i + inset + lw / 2.0f, allocation.height - sh, box_w - lw, 2.0f * lw);
1412 cairo_fill(cr);
1413 }
1414
1415 // draw the border
1416 cairo_set_line_width(cr, lw);
1417 gdk_cairo_set_source_rgba(cr, &border);
1418 cairo_rectangle(cr, box_w * i + inset, sh, box_w, box_h);
1419 cairo_stroke(cr);
1420 }
1421
1422 cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT);
1423 cairo_restore(cr);
1424
1425 // dot for the current scale
1426 if(p->curr_scale >= p->merge_from_scale && p->curr_scale <= p->num_scales && p->merge_from_scale > 0)
1427 color = active;
1428 else
1429 color = merge_from;
1430
1431 if(p->curr_scale >= 0 && p->curr_scale < RETOUCH_NO_SCALES)
1432 {
1433 cairo_set_line_width(cr, lw);
1434 gdk_cairo_set_source_rgba(cr, &color);
1435 middle = box_w * (0.5f + (float)p->curr_scale);
1436 cairo_arc(cr, middle + inset, 0.5f * box_h + sh, 0.5f * inset, 0, 2.0f * M_PI);
1437 cairo_fill(cr);
1438 cairo_stroke(cr);
1439 }
1440
1441 // mouse hover on a scale
1442 if(g->curr_scale >= 0)
1443 {
1444 cairo_set_line_width(cr, lw);
1445 if(g->curr_scale == p->num_scales + 1) color = inactive;
1446 else color = residual;
1447 gdk_cairo_set_source_rgba(cr, &color);
1448 cairo_rectangle(cr, box_w * g->curr_scale + inset + lw, sh + lw, box_w - 2.0f * lw, box_h - 2.0f * lw);
1449 cairo_stroke(cr);
1450 }
1451
1452 /* render control points handles */
1453
1454 // draw number of scales arrow (bottom arrow)
1455 middle = box_w * (0.5f + (float)p->num_scales);
1456 if(g->lower_cursor || g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM)
1457 {
1458 cairo_set_source_rgb(cr, 0.67, 0.67, 0.67);
1459 dtgtk_cairo_paint_solid_triangle(cr, middle, box_h + 5.0f * lw, mk, mk, CPF_DIRECTION_UP, NULL);
1460 }
1461 else
1462 {
1463 cairo_set_source_rgb(cr, 0.54, 0.54, 0.54);
1464 dtgtk_cairo_paint_triangle(cr, middle, box_h + 5.0f * lw, mk, mk, CPF_DIRECTION_UP, NULL);
1465 }
1466
1467 // draw merge scales arrow (top arrow)
1468 middle = box_w * (0.5f + (float)p->merge_from_scale);
1469 if(g->upper_cursor || g->is_dragging == DT_IOP_RETOUCH_WDBAR_DRAG_TOP)
1470 {
1471 cairo_set_source_rgb(cr, 0.67, 0.67, 0.67);
1472 dtgtk_cairo_paint_solid_triangle(cr, middle, 3.0f * lw, mk, mk, CPF_DIRECTION_DOWN, NULL);
1473 }
1474 else
1475 {
1476 cairo_set_source_rgb(cr, 0.54, 0.54, 0.54);
1477 dtgtk_cairo_paint_triangle(cr, middle, 3.0f * lw, mk, mk, CPF_DIRECTION_DOWN, NULL);
1478 }
1479
1480 /* push mem surface into widget */
1481 cairo_destroy(cr);
1482 cairo_set_source_surface(crf, cst, 0, 0);
1483 cairo_paint(crf);
1484 cairo_surface_destroy(cst);
1485
1486 return TRUE;
1487}
1488
1489static float rt_gslider_scale_callback(GtkWidget *self, float inval, int dir)
1490{
1491 float outval;
1492 switch(dir)
1493 {
1496 break;
1499 break;
1500 default:
1501 outval = inval;
1502 }
1503 return outval;
1504}
1505
1506
1508{
1510
1511 double dlevels[3];
1512
1513 if(dt_gui_widgets_suppressed()) return;
1514
1516
1517 for (int i = 0; i < 3; i++) p->preview_levels[i] = dlevels[i];
1518
1519 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1520
1521}
1522
1523
1525{
1528
1529 if(fabsf(p->fill_color[0] - self->picked_output_color[0]) < 0.0001f
1530 && fabsf(p->fill_color[1] - self->picked_output_color[1]) < 0.0001f
1531 && fabsf(p->fill_color[2] - self->picked_output_color[2]) < 0.0001f)
1532 {
1533 // interrupt infinite loops
1534 return;
1535 }
1536
1537 p->fill_color[0] = self->picked_output_color[0];
1538 p->fill_color[1] = self->picked_output_color[1];
1539 p->fill_color[2] = self->picked_output_color[2];
1540
1541 const int index = rt_get_selected_shape_index(self->dev, p);
1542 if(index >= 0)
1543 {
1544 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
1545 {
1546 p->rt_forms[index].fill_color[0] = p->fill_color[0];
1547 p->rt_forms[index].fill_color[1] = p->fill_color[1];
1548 p->rt_forms[index].fill_color[2] = p->fill_color[2];
1549 }
1550 }
1551
1553
1554 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1555}
1556
1557static gboolean rt_copypaste_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1558{
1559 if(dt_gui_widgets_suppressed()) return TRUE;
1560
1562
1563 int scale_copied = 0;
1564 const int active = !gtk_toggle_button_get_active(togglebutton);
1567
1568 if(togglebutton == (GtkToggleButton *)g->bt_copy_scale)
1569 {
1570 g->copied_scale = (active) ? p->curr_scale : -1;
1571 }
1572 else if(togglebutton == (GtkToggleButton *)g->bt_paste_scale)
1573 {
1574 rt_paste_forms_from_scale(p, g->copied_scale, p->curr_scale);
1576
1577 scale_copied = 1;
1578 g->copied_scale = -1;
1579 }
1580
1581 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_copy_scale), g->copied_scale >= 0);
1582 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_paste_scale), g->copied_scale >= 0);
1583 gtk_widget_set_sensitive(g->bt_paste_scale, g->copied_scale >= 0);
1584
1586
1587 if(scale_copied) dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1588
1589 return TRUE;
1590}
1591
1592// Whether some OTHER source (the generic blend-mask display) currently occupies
1593// request_mask_display, as opposed to one of retouch's own preview toggles below.
1595{
1596 return module->request_mask_display && !g->mask_display && !g->display_wavelet_scale;
1597}
1598
1599// Whether any of retouch's own preview toggles needs the pipeline cache bypassed for this
1600// module. Combines all three (not just the caller's own flag), or clearing one would drop a
1601// bypass another still needs -- bypass_cache is a single shared field, unlike
1602// request_mask_display below, so no cross-module ownership concern applies here.
1604{
1605 return g->mask_display || g->display_wavelet_scale || g->suppress_mask;
1606}
1607
1608// Syncs bypass_cache (whether the pipe needs to bypass at all) and bypass_cache_variant (which
1609// combination of the three preview toggles is active) together. bypass_cache alone cannot
1610// distinguish e.g. "wavelet scale only" from "wavelet scale + suppress": both keep it TRUE, so
1611// toggling suppress on top of an already-active wavelet-scale preview would not change the
1612// pipeline hash and the display would keep showing the pre-toggle (still-retouched) frame even
1613// though process_internal()/process_cl() had already recomputed the correct, retouch-free one.
1614// bypass_cache_variant closes that gap -- see dt_iop_module_t.bypass_cache_variant.
1616{
1617 const int variant = (g->mask_display ? 1 : 0) | (g->display_wavelet_scale ? 2 : 0) | (g->suppress_mask ? 4 : 0);
1618 dt_iop_set_cache_bypass_variant(module, variant);
1620}
1621
1622// Mirrors mask_display/display_wavelet_scale into the pipeline cache key
1623// (request_mask_display) -- otherwise toggling either back off keeps serving whatever was last
1624// cached instead of the real image, since these GUI-only flags are invisible to the pipeline
1625// hash on their own. Keep them on separate bits (mirroring the MASK-vs-PASSTHRU split
1626// process_internal already applies to pipe->mask_display) rather than collapsing to one value,
1627// or "show mask" and "show wavelet scale" would hash identically whenever curr_scale/params
1628// haven't changed and one preview mode would reuse the other's cached buffer.
1629//
1630// Only call this once rt_blend_mask_conflicts() has already been checked and passed: unlike
1631// bypass_cache, request_mask_display can belong to the generic blend-mask display instead of to
1632// retouch, and must not be blindly overwritten. rt_suppress_callback has no such guard, so it
1633// only calls rt_sync_bypass_cache() (bypass_cache/bypass_cache_variant), never this.
1635{
1637 if(g->mask_display) display_request |= DT_DEV_PIXELPIPE_DISPLAY_MASK;
1638 if(g->display_wavelet_scale) display_request |= DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
1639 module->request_mask_display = display_request;
1640}
1641
1642static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1643{
1644 if(dt_gui_widgets_suppressed()) return TRUE;
1645
1648
1649 // if blend module is displaying mask do not display wavelet scales
1650 if(rt_blend_mask_conflicts(self, g))
1651 {
1652 dt_control_log(_("cannot display scales when the blending mask is displayed"));
1653
1655 gtk_toggle_button_set_active(togglebutton, FALSE);
1657 return TRUE;
1658 }
1659
1660 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1662
1663 g->display_wavelet_scale = !gtk_toggle_button_get_active(togglebutton);
1665 rt_sync_bypass_cache(self, g);
1666
1668
1669 // compute auto levels only the first time display wavelet scale is used,
1670 // only if levels values are the default
1671 // and a detail scale is displayed
1673 if(g->displayed_wavelet_scale == 0 && p->preview_levels[0] == RETOUCH_PREVIEW_LVL_MIN
1674 && p->preview_levels[1] == 0.f && p->preview_levels[2] == RETOUCH_PREVIEW_LVL_MAX
1675 && g->preview_auto_levels == 0 && p->curr_scale > 0 && p->curr_scale <= p->num_scales)
1676 {
1677 g->preview_auto_levels = 1;
1678 g->displayed_wavelet_scale = 1;
1679 }
1681
1683
1684 gtk_toggle_button_set_active(togglebutton, g->display_wavelet_scale);
1685 return TRUE;
1686}
1687
1688static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
1689{
1690 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
1693
1694 // FIXME: this doesn't seems the right place to update params and GUI ...
1695 // update auto levels
1697 if(g->preview_auto_levels == 2)
1698 {
1699 g->preview_auto_levels = -1;
1700
1702
1703 for(int i = 0; i < 3; i++) p->preview_levels[i] = g->preview_levels[i];
1704
1705 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1706
1708
1709 // update the gradient slider
1710 double dlevels[3];
1711 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
1712
1714 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
1716
1717 g->preview_auto_levels = 0;
1718 }
1720
1721 // just in case zoom level has changed
1722 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
1723}
1724
1725static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
1726{
1727 if(dt_gui_widgets_suppressed()) return FALSE;
1728
1730
1731 if(self->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(self->gui->off), 1);
1733
1735 if(g->preview_auto_levels == 0)
1736 {
1737 g->preview_auto_levels = 1;
1738 }
1740
1742
1743 return TRUE;
1744}
1745
1747{
1748 if(dt_gui_widgets_suppressed()) return;
1749
1750 const int shape_id = rt_get_selected_shape_id(self);
1751
1752 if(shape_id > 0)
1753 {
1754 const float opacity = dt_bauhaus_slider_get(slider);
1755 rt_masks_form_change_opacity(self, shape_id, opacity);
1756 }
1757
1758 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1759}
1760
1762 cairo_t *cr,
1763 int32_t width,
1764 int32_t height,
1765 int32_t pointerx,
1766 int32_t pointery)
1767{
1769 if(IS_NULL_PTR(g)) return;
1770
1771 const int shape_id = rt_get_selected_shape_id(self);
1772
1773 if(shape_id > 0)
1774 {
1776 dt_bauhaus_slider_set(g->sl_mask_opacity, rt_masks_form_get_opacity(self, shape_id));
1778 }
1779}
1780
1781static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
1782{
1783 if(dt_gui_widgets_suppressed()) return FALSE;
1784
1785 // if we don't have the focus, request for it and quit, gui_focus() do the rest
1786 if(self->dev->gui_module != self)
1787 {
1789 return FALSE;
1790 }
1791
1794
1795 //hide all shapes and free if some are in creation
1796 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
1797 dt_masks_change_form_gui(self->dev, NULL);
1798
1800
1801 if(event->button == 1)
1802 {
1804
1806
1808 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
1809 {
1810 const gboolean control_button_pressed = dt_modifier_is(event->state, DT_PRIMARY_MASK);
1811
1812 switch(bd->masks_shown)
1813 {
1814 case DT_MASKS_EDIT_FULL:
1815 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_OFF;
1816 break;
1817
1819 bd->masks_shown = !control_button_pressed ? DT_MASKS_EDIT_FULL : DT_MASKS_EDIT_OFF;
1820 break;
1821
1822 default:
1823 case DT_MASKS_EDIT_OFF:
1824 bd->masks_shown = control_button_pressed ? DT_MASKS_EDIT_RESTRICTED : DT_MASKS_EDIT_FULL;
1825 break;
1826 }
1827 }
1828 else
1830
1832
1833 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
1834 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
1835
1837
1838 return TRUE;
1839 }
1840
1841 return TRUE;
1842}
1843
1846{
1847 return ((from == DT_IOP_RETOUCH_CLONE && to == DT_IOP_RETOUCH_HEAL)
1848 || (from == DT_IOP_RETOUCH_HEAL && to == DT_IOP_RETOUCH_CLONE)
1849 || (from == DT_IOP_RETOUCH_BLUR && to == DT_IOP_RETOUCH_FILL)
1850 || (from == DT_IOP_RETOUCH_FILL && to == DT_IOP_RETOUCH_BLUR));
1851}
1852
1853static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e,
1854 dt_iop_module_t *self)
1855{
1856 if(dt_gui_widgets_suppressed()) return FALSE;
1857
1859
1862
1864
1865 if(togglebutton == (GtkToggleButton *)g->bt_blur)
1866 new_algo = DT_IOP_RETOUCH_BLUR;
1867 else if(togglebutton == (GtkToggleButton *)g->bt_clone)
1868 new_algo = DT_IOP_RETOUCH_CLONE;
1869 else if(togglebutton == (GtkToggleButton *)g->bt_heal)
1870 new_algo = DT_IOP_RETOUCH_HEAL;
1871 else if(togglebutton == (GtkToggleButton *)g->bt_fill)
1872 new_algo = DT_IOP_RETOUCH_FILL;
1873
1874 // check if we have to do something
1875 gboolean accept = TRUE;
1876
1877 const int index = rt_get_selected_shape_index(self->dev, p);
1878 if(index >= 0 && dt_modifier_is(e->state, DT_PRIMARY_MASK))
1879 {
1880 const dt_iop_retouch_algo_type_t current_algo = p->rt_forms[index].algorithm;
1881 accept = (new_algo != current_algo && !rt_algo_pair_compatible(current_algo, new_algo));
1882 }
1883
1884 if(accept) p->algorithm = new_algo;
1885
1886 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), (p->algorithm == DT_IOP_RETOUCH_CLONE));
1887 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), (p->algorithm == DT_IOP_RETOUCH_HEAL));
1888 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), (p->algorithm == DT_IOP_RETOUCH_BLUR));
1889 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), (p->algorithm == DT_IOP_RETOUCH_FILL));
1890
1892
1893 if(!accept)
1894 {
1896 return FALSE;
1897 }
1898
1899 if(index >= 0 && dt_modifier_is(e->state, DT_PRIMARY_MASK))
1900 {
1901 if(p->algorithm != p->rt_forms[index].algorithm)
1902 {
1903 p->rt_forms[index].algorithm = p->algorithm;
1905 }
1906 }
1907 else if(self->dev->form_gui->creation && (self->dev->form_gui->creation_module == self))
1908 {
1910
1912 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_polygon)))
1914 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_circle)))
1916 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_ellipse)))
1918 else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g->bt_brush)))
1920
1921 dt_masks_type_t masks_type = DT_MASKS_NONE;
1922 if(p->algorithm == DT_IOP_RETOUCH_CLONE || p->algorithm == DT_IOP_RETOUCH_HEAL)
1923 masks_type = (type | DT_MASKS_CLONE);
1924 else
1925 masks_type = (type | DT_MASKS_NON_CLONE);
1926
1927 dt_masks_creation_mode_enter(self->dev, self, masks_type);
1928
1930 }
1931
1933
1934 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
1935
1936 // if we have the shift key pressed, we set it as default
1937 if(dt_modifier_is(e->state, GDK_SHIFT_MASK))
1938 {
1939 dt_conf_set_int("plugins/darkroom/retouch/default_algo", p->algorithm);
1940 // and we show a toat msg to confirm
1941 if(p->algorithm == DT_IOP_RETOUCH_CLONE)
1942 dt_control_log(N_("default tool changed to %s"), N_("cloning"));
1943 else if(p->algorithm == DT_IOP_RETOUCH_HEAL)
1944 dt_control_log(N_("default tool changed to %s"), N_("healing"));
1945 else if(p->algorithm == DT_IOP_RETOUCH_FILL)
1946 dt_control_log(N_("default tool changed to %s"), N_("blur"));
1947 else if(p->algorithm == DT_IOP_RETOUCH_BLUR)
1948 dt_control_log(N_("default tool changed to %s"), N_("fill"));
1949 }
1950
1951 return TRUE;
1952}
1953
1954static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1955{
1956 if(dt_gui_widgets_suppressed()) return TRUE;
1957
1959
1960 // if blend module is displaying mask do not display it here
1961 if(rt_blend_mask_conflicts(module, g))
1962 {
1963 dt_control_log(_("cannot display masks when the blending mask is displayed"));
1964
1965 gtk_toggle_button_set_active(togglebutton, FALSE);
1966 return TRUE;
1967 }
1968
1969 g->mask_display = !gtk_toggle_button_get_active(togglebutton);
1971 rt_sync_bypass_cache(module, g);
1972
1973 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), 1);
1974 dt_iop_request_focus(module);
1975
1977
1978 gtk_toggle_button_set_active(togglebutton, g->mask_display);
1979 return TRUE;
1980}
1981
1982static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
1983{
1984 if(dt_gui_widgets_suppressed()) return TRUE;
1985
1987 g->suppress_mask = !gtk_toggle_button_get_active(togglebutton);
1988 // No rt_blend_mask_conflicts() guard on this button, so only bypass_cache/bypass_cache_variant
1989 // are synced here -- request_mask_display may currently belong to the blend-mask display, not
1990 // to retouch.
1991 rt_sync_bypass_cache(module, g);
1992
1993 if(module->gui->off) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(module->gui->off), 1);
1994 dt_iop_request_focus(module);
1995
1997
1998 gtk_toggle_button_set_active(togglebutton, g->suppress_mask);
1999 return TRUE;
2000}
2001
2002void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
2003{
2006
2007 if(w == g->cmb_fill_mode)
2008 {
2012 }
2013 else
2014 {
2015 const int index = rt_get_selected_shape_index(self->dev, p);
2016 if(index >= 0)
2017 {
2018 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR)
2019 {
2020 p->rt_forms[index].blur_type = p->blur_type;
2021 p->rt_forms[index].blur_radius = p->blur_radius;
2022 }
2023 else if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
2024 {
2025 p->rt_forms[index].fill_mode = p->fill_mode;
2026 p->rt_forms[index].fill_brightness = p->fill_brightness;
2027 }
2028 }
2029 }
2030}
2031
2032//--------------------------------------------------------------------------------------------------
2033// GUI
2034//--------------------------------------------------------------------------------------------------
2035
2036void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
2037{
2039 if(IS_NULL_PTR(g)) return;
2040
2042 rt_load_shape_algo_in_gui(self, form_selected_id);
2044}
2045
2047{
2048 dt_iop_default_init(module);
2049
2050 dt_iop_retouch_params_t *d = module->default_params;
2051
2053 d->preview_levels[1] = 0.f;
2054 d->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
2055 d->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2056}
2057
2059{
2060 const int program = 21; // retouch.cl, from programs.conf
2062 module->data = gd;
2063 gd->kernel_retouch_clear_alpha = dt_opencl_create_kernel(program, "retouch_clear_alpha");
2064 gd->kernel_retouch_copy_alpha = dt_opencl_create_kernel(program, "retouch_copy_alpha");
2065 gd->kernel_retouch_copy_buffer_to_buffer = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer");
2066 gd->kernel_retouch_copy_buffer_to_image = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_image");
2067 gd->kernel_retouch_fill = dt_opencl_create_kernel(program, "retouch_fill");
2069 = dt_opencl_create_kernel(program, "retouch_copy_image_to_buffer_masked");
2071 = dt_opencl_create_kernel(program, "retouch_copy_buffer_to_buffer_masked");
2072 gd->kernel_retouch_image_rgb2lab = dt_opencl_create_kernel(program, "retouch_image_rgb2lab");
2073 gd->kernel_retouch_image_lab2rgb = dt_opencl_create_kernel(program, "retouch_image_lab2rgb");
2074 gd->kernel_retouch_copy_mask_to_alpha = dt_opencl_create_kernel(program, "retouch_copy_mask_to_alpha");
2075}
2076
2094
2095void gui_focus(struct dt_iop_module_t *self, gboolean in)
2096{
2097 if(self->enabled)
2098 {
2100
2101 if(in)
2102 {
2104 //only show shapes if shapes exist
2106 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
2107 {
2108 // got focus, show all shapes
2111
2113
2114 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
2116 && (self->dev->gui_module == self));
2117 }
2118 }
2119 else
2120 {
2121 // lost focus, hide all shapes and free if some are in creation
2122 if(self->dev->form_gui->creation && self->dev->form_gui->creation_module == self)
2123 dt_masks_change_form_gui(self->dev, NULL);
2124
2126 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2127
2129 }
2130
2131 // if we are switching between display modes we have to reprocess the main image
2132 if(g->display_wavelet_scale || g->mask_display || g->suppress_mask)
2134 }
2135}
2136
2140{
2143 {
2144 rt_resynch_params(self, &synced_params, pipe->forms);
2145 }
2146 else
2147 {
2149 rt_resynch_params(self, &synced_params, self->dev->forms);
2151 }
2152
2153 memcpy(piece->data, &synced_params, sizeof(dt_iop_retouch_params_t));
2154}
2155
2157{
2159 const float require = 2.0f;
2160 const float require_cl = 1.0f // in_retouch
2161 + ((p->num_scales > 0) ? 4.0f : 2.0f); // dwt_wavelet_decompose_cl requires 4 buffers, otherwise 2.0f is enough
2162 // FIXME the above are worst case values, we might iterate through the dt_iop_retouch_form_data_t to get
2163 // the largest bounding box
2164
2165 tiling->factor = 2.0f + require; // input & output buffers + internal requirements
2166 tiling->factor_cl = 2.0f + require_cl;
2167 tiling->maxbuf = 1.0f;
2168 tiling->maxbuf_cl = 1.0f;
2169 tiling->overhead = 0;
2170 tiling->overlap = 0;
2171 tiling->xalign = 1;
2172 tiling->yalign = 1;
2173}
2174
2180
2182{
2183 dt_free_align(piece->data);
2184 piece->data = NULL;
2185}
2186
2188{
2191
2192 // check if there is new or deleted forms
2194 rt_resynch_params(self, p, self->dev->forms);
2196
2197 // update clones count
2198 const dt_masks_form_t *grp = dt_masks_get_from_id(self->dev, self->blend_params->mask_id);
2199 guint nb = 0;
2200 if(grp && (grp->type & DT_MASKS_GROUP)) nb = g_list_length(grp->points);
2201 gchar *str = g_strdup_printf("%d", nb);
2202 gtk_label_set_text(g->label_form, str);
2203 dt_free(str);
2204
2205 // update wavelet decompose labels
2207
2208 // update selected shape label
2210
2211 // show the shapes for the current scale
2213
2214 // enable/disable algorithm toolbar
2215 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_clone), p->algorithm == DT_IOP_RETOUCH_CLONE);
2216 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_blur), p->algorithm == DT_IOP_RETOUCH_BLUR);
2217 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_heal), p->algorithm == DT_IOP_RETOUCH_HEAL);
2218 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_fill), p->algorithm == DT_IOP_RETOUCH_FILL);
2219
2220 // enable/disable shapes toolbar
2221 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_circle), rt_shape_is_being_added(self, DT_MASKS_CIRCLE));
2222 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_polygon), rt_shape_is_being_added(self, DT_MASKS_POLYGON));
2223 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_ellipse), rt_shape_is_being_added(self, DT_MASKS_ELLIPSE));
2224 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_brush), rt_shape_is_being_added(self, DT_MASKS_BRUSH));
2225
2226 // update the rest of the fields
2227 gtk_widget_queue_draw(GTK_WIDGET(g->wd_bar));
2228
2229 dt_bauhaus_combobox_set(g->cmb_blur_type, p->blur_type);
2230 dt_bauhaus_slider_set(g->sl_blur_radius, p->blur_radius);
2231 dt_bauhaus_slider_set(g->sl_fill_brightness, p->fill_brightness);
2232 dt_bauhaus_combobox_set(g->cmb_fill_mode, p->fill_mode);
2233 dt_bauhaus_combobox_set(g->cmb_heal_algorithm, p->heal_algorithm);
2234
2236
2237 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_display_wavelet_scale), g->display_wavelet_scale);
2238 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_copy_scale), g->copied_scale >= 0);
2239 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_paste_scale), g->copied_scale >= 0);
2240 gtk_widget_set_sensitive(g->bt_paste_scale, g->copied_scale >= 0);
2241
2242 // show/hide some fields
2244
2245 // update edit shapes status
2247
2248 //only toggle shape show button if shapes exist
2249 if(!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP) && grp->points)
2250 {
2251 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks),
2252 (bd->masks_shown != DT_MASKS_EDIT_OFF) && (self->dev->gui_module == self));
2253 }
2254 else
2255 {
2256 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g->bt_edit_masks), FALSE);
2257 }
2258
2259 // update the gradient slider
2260 double dlevels[3];
2261 for(int i = 0; i < 3; i++) dlevels[i] = p->preview_levels[i];
2262 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, dlevels);
2263}
2264
2266{
2268 if(!IS_NULL_PTR(g))
2269 {
2270 g->copied_scale = -1;
2271 g->mask_display = 0;
2272 g->suppress_mask = 0;
2273 g->display_wavelet_scale = 0;
2274 g->displayed_wavelet_scale = 0;
2275 g->first_scale_visible = RETOUCH_MAX_SCALES + 1;
2276
2277 g->preview_auto_levels = 0;
2278 g->preview_levels[0] = RETOUCH_PREVIEW_LVL_MIN;
2279 g->preview_levels[1] = 0.f;
2280 g->preview_levels[2] = RETOUCH_PREVIEW_LVL_MAX;
2281
2282 g->is_dragging = 0;
2283 g->wdbar_mouse_x = g->wdbar_mouse_y = -1;
2284 g->curr_scale = -1;
2285 g->lower_cursor = g->upper_cursor = FALSE;
2286 g->lower_margin = g->upper_margin = FALSE;
2287 }
2288}
2289
2291{
2294
2295 change_image(self);
2296
2297 // shapes toolbar
2298 GtkWidget *hbox_shapes = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2299
2300 gtk_box_pack_start(GTK_BOX(hbox_shapes), dt_ui_label_new(_("shapes:")), FALSE, TRUE, 0);
2301 g->label_form = GTK_LABEL(gtk_label_new("-1"));
2302 gtk_box_pack_start(GTK_BOX(hbox_shapes), GTK_WIDGET(g->label_form), FALSE, TRUE, DT_PIXEL_APPLY_DPI(5));
2303 gtk_widget_set_tooltip_text(hbox_shapes,
2304 _("To add a shape select an algorithm and a shape type and click on the image.\n"
2305 "Shapes are added to the current scale"));
2306
2307 g->bt_edit_masks = dt_iop_togglebutton_new(self, N_("editing"), N_("Show and edit shapes on the current scale"),
2308 N_("show and edit shapes in restricted mode"),
2309 G_CALLBACK(rt_edit_masks_callback), TRUE, 0, 0,
2310 dtgtk_cairo_paint_masks_edit, hbox_shapes);
2311
2312 GtkWidget *shape_buttons[DEVELOP_MASKS_NB_SHAPES] = { 0 };
2313 const dt_masks_shape_buttons_config_t shape_buttons_config = {
2314 .dev = self->dev,
2315 .owner_module = self,
2316 .creation_module = self,
2317 .buttons = shape_buttons,
2318 .types = NULL,
2319 .action_section = N_("shapes"),
2320 .flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT, // All shapes minus gradient
2321 .register_flags = DT_MASKS_SHAPE_BUTTONS_ALL & ~DT_MASKS_SHAPE_BUTTONS_GRADIENT,
2322 .local = TRUE,
2323 .user_data = NULL,
2324 .can_start = rt_shape_buttons_can_start,
2325 .form_type = rt_shape_buttons_form_type,
2326 .started = NULL,
2327 .exited = NULL,
2328 };
2329 GtkWidget *shape_buttons_box = dt_masks_shape_buttons_create(&shape_buttons_config);
2330 gtk_box_pack_start(GTK_BOX(hbox_shapes), shape_buttons_box, FALSE, FALSE, 0);
2331 g->bt_circle = shape_buttons[DT_MASKS_SHAPE_INDEX_CIRCLE];
2332 g->bt_ellipse = shape_buttons[DT_MASKS_SHAPE_INDEX_ELLIPSE];
2333 g->bt_polygon = shape_buttons[DT_MASKS_SHAPE_INDEX_POLYGON];
2334 g->bt_brush = shape_buttons[DT_MASKS_SHAPE_INDEX_BRUSH];
2335
2336 // algorithm toolbar
2337 GtkWidget *hbox_algo = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2338
2339 gtk_box_pack_start(GTK_BOX(hbox_algo), dt_ui_label_new(_("algorithms:")), FALSE, TRUE, 0);
2340
2341 g->bt_blur = dt_iop_togglebutton_new(
2342 self, N_("tools"), N_("Activate blur tool"), N_("change algorithm for current form"),
2343 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_blur, hbox_algo);
2344
2345 g->bt_fill = dt_iop_togglebutton_new(
2346 self, N_("tools"), N_("Activate fill tool"), N_("change algorithm for current form"),
2347 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_fill, hbox_algo);
2348
2349 g->bt_clone = dt_iop_togglebutton_new(
2350 self, N_("tools"), N_("Activate cloning tool"), N_("change algorithm for current form"),
2351 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_clone, hbox_algo);
2352
2353 g->bt_heal = dt_iop_togglebutton_new(
2354 self, N_("tools"), N_("Activate healing tool"), N_("change algorithm for current form"),
2355 G_CALLBACK(rt_select_algorithm_callback), TRUE, 0, 0, dtgtk_cairo_paint_tool_heal, hbox_algo);
2356
2357 // overwrite tooltip ourself to handle shift+click
2358 gchar *tt2 = g_strdup_printf("%s\n%s", _("Ctrl+click to change tool for current form"),
2359 _("Shift+click to set the tool as default"));
2360 gchar *tt = g_strdup_printf("%s\n%s", _("Activate blur tool"), tt2);
2361 gtk_widget_set_tooltip_text(g->bt_blur, tt);
2362 dt_free(tt);
2363 tt = g_strdup_printf("%s\n%s", _("Activate fill tool"), tt2);
2364 gtk_widget_set_tooltip_text(g->bt_fill, tt);
2365 dt_free(tt);
2366 tt = g_strdup_printf("%s\n%s", _("Activate cloning tool"), tt2);
2367 gtk_widget_set_tooltip_text(g->bt_clone, tt);
2368 dt_free(tt);
2369 tt = g_strdup_printf("%s\n%s", _("Activate healing tool"), tt2);
2370 gtk_widget_set_tooltip_text(g->bt_heal, tt);
2371 dt_free(tt);
2372 dt_free(tt2);
2373
2374 // wavelet decompose bar labels
2375 GtkWidget *grid_wd_labels = gtk_grid_new();
2376 gtk_grid_set_column_homogeneous(GTK_GRID(grid_wd_labels), FALSE);
2377
2378 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("scales:")), 0, 0, 1, 1);
2379 g->lbl_num_scales = GTK_LABEL(dt_ui_label_new(NULL));
2380 gtk_label_set_width_chars(g->lbl_num_scales, 2);
2381 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_num_scales), 1, 0, 1, 1);
2382
2383 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("current:")), 0, 1, 1, 1);
2384 g->lbl_curr_scale = GTK_LABEL(dt_ui_label_new(NULL));
2385 gtk_label_set_width_chars(g->lbl_curr_scale, 2);
2386 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_curr_scale), 1, 1, 1, 1);
2387
2388 gtk_grid_attach(GTK_GRID(grid_wd_labels), dt_ui_label_new(_("merge from:")), 0, 2, 1, 1);
2389 g->lbl_merge_from_scale = GTK_LABEL(dt_ui_label_new(NULL));
2390 gtk_label_set_width_chars(g->lbl_merge_from_scale, 2);
2391 gtk_grid_attach(GTK_GRID(grid_wd_labels), GTK_WIDGET(g->lbl_merge_from_scale), 1, 2, 1, 1);
2392
2393 // wavelet decompose bar
2394 g->wd_bar = gtk_drawing_area_new();
2395
2396 gtk_widget_set_tooltip_text(g->wd_bar, _("Top slider adjusts where the merge scales start\n"
2397 "Bottom slider adjusts the number of scales\n"
2398 "Dot indicates the current scale\n"
2399 "Top line indicates that the scale is visible at current zoom level\n"
2400 "Bottom line indicates that the scale has shapes on it"));
2401 g_signal_connect(G_OBJECT(g->wd_bar), "draw", G_CALLBACK(rt_wdbar_draw), self);
2402 g_signal_connect(G_OBJECT(g->wd_bar), "motion-notify-event", G_CALLBACK(rt_wdbar_motion_notify), self);
2403 g_signal_connect(G_OBJECT(g->wd_bar), "leave-notify-event", G_CALLBACK(rt_wdbar_leave_notify), self);
2404 g_signal_connect(G_OBJECT(g->wd_bar), "button-press-event", G_CALLBACK(rt_wdbar_button_press), self);
2405 g_signal_connect(G_OBJECT(g->wd_bar), "button-release-event", G_CALLBACK(rt_wdbar_button_release), self);
2406 g_signal_connect(G_OBJECT(g->wd_bar), "scroll-event", G_CALLBACK(rt_wdbar_scrolled), self);
2407 gtk_widget_add_events(GTK_WIDGET(g->wd_bar), GDK_POINTER_MOTION_MASK
2408 | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
2409 | GDK_LEAVE_NOTIFY_MASK | dt_widget_scroll_mask());
2410 gtk_widget_set_size_request(g->wd_bar, -1, DT_PIXEL_APPLY_DPI(40));
2411
2412 // toolbar display current scale / cut&paste / suppress&display masks
2413 GtkWidget *hbox_scale = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2414
2415 // display & suppress masks
2416 g->bt_showmask = dt_iop_togglebutton_new(self, N_("editing"), N_("Display masks"), NULL,
2417 G_CALLBACK(rt_showmask_callback), TRUE, 0, 0,
2418 dtgtk_cairo_paint_showmask, hbox_scale);
2419
2420
2421 g->bt_suppress = dt_iop_togglebutton_new(self, N_("editing"), N_("Temporarily switch off shapes"), NULL,
2422 G_CALLBACK(rt_suppress_callback), TRUE, 0, 0,
2423 dtgtk_cairo_paint_eye_toggle, hbox_scale);
2424
2425
2426 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2427
2428 // copy/paste shapes
2429 g->bt_paste_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("Paste cut shapes to current scale"), NULL,
2430 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2431 dtgtk_cairo_paint_paste_forms, hbox_scale);
2432
2433 g->bt_copy_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("Cut shapes from current scale"), NULL,
2434 G_CALLBACK(rt_copypaste_scale_callback), TRUE, 0, 0,
2435 dtgtk_cairo_paint_cut_forms, hbox_scale);
2436
2437 gtk_box_pack_end(GTK_BOX(hbox_scale), gtk_grid_new(), TRUE, TRUE, 0);
2438
2439 // display final image/current scale
2440 g->bt_display_wavelet_scale = dt_iop_togglebutton_new(self, N_("editing"), N_("Display wavelet scale"), NULL,
2441 G_CALLBACK(rt_display_wavelet_scale_callback), TRUE, 0, 0,
2443
2444
2445 // preview single scale
2446 g->vbox_preview_scale = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2447
2448 GtkWidget *lbl_psc = dt_ui_section_label_new(_("preview single scale"));
2449 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), lbl_psc, FALSE, TRUE, 0);
2450
2451 GtkWidget *prev_lvl = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2452
2453 // gradient slider
2454 #define NEUTRAL_GRAY 0.5
2455 static const GdkRGBA _gradient_L[]
2456 = { { 0, 0, 0, 1.0 }, { NEUTRAL_GRAY, NEUTRAL_GRAY, NEUTRAL_GRAY, 1.0 } };
2457 g->preview_levels_gslider = DTGTK_GRADIENT_SLIDER_MULTIVALUE(
2459 gtk_widget_set_tooltip_text(GTK_WIDGET(g->preview_levels_gslider), _("Adjust preview levels"));
2463 (g->preview_levels_gslider)->scale_callback = rt_gslider_scale_callback;
2465 dtgtk_gradient_slider_multivalue_set_values(g->preview_levels_gslider, vdefault);
2466 dtgtk_gradient_slider_multivalue_set_resetvalues(g->preview_levels_gslider, vdefault);
2467 (g->preview_levels_gslider)->markers_type = PROPORTIONAL_MARKERS;
2468 (g->preview_levels_gslider)->min_spacing = 0.05;
2469 g_signal_connect(G_OBJECT(g->preview_levels_gslider), "value-changed", G_CALLBACK(rt_gslider_changed), self);
2470
2471 gtk_box_pack_start(GTK_BOX(prev_lvl), GTK_WIDGET(g->preview_levels_gslider), TRUE, TRUE, 0);
2472
2473 // auto-levels button
2474 g->bt_auto_levels = dt_iop_togglebutton_new(self, N_("editing"), N_("Auto levels"), NULL,
2475 G_CALLBACK(rt_auto_levels_callback), TRUE, 0, 0,
2477
2478 gtk_box_pack_start(GTK_BOX(g->vbox_preview_scale), prev_lvl, TRUE, TRUE, 0);
2479
2480 // shapes selected (label)
2481 GtkWidget *hbox_shape_sel = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2482 GtkWidget *label1 = gtk_label_new(_("shape selected:"));
2483 gtk_label_set_ellipsize(GTK_LABEL(label1), PANGO_ELLIPSIZE_START);
2484 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), label1, FALSE, TRUE, 0);
2485 g->label_form_selected = GTK_LABEL(gtk_label_new("-1"));
2486 gtk_widget_set_tooltip_text(hbox_shape_sel,
2487 _("Click on a shape to select it,\nto unselect click on an empty space"));
2488 gtk_box_pack_start(GTK_BOX(hbox_shape_sel), GTK_WIDGET(g->label_form_selected), FALSE, TRUE, 0);
2489
2490 // fill properties
2491 g->vbox_fill = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2492
2493 g->cmb_fill_mode = dt_bauhaus_combobox_from_params(self, "fill_mode");
2494 gtk_widget_set_tooltip_text(g->cmb_fill_mode, _("Erase the detail or fills with chosen color"));
2495
2496 // color for fill algorithm
2497 GdkRGBA color
2498 = (GdkRGBA){.red = p->fill_color[0], .green = p->fill_color[1], .blue = p->fill_color[2], .alpha = 1.0 };
2499
2500 g->hbox_color_pick = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
2501 GtkWidget *lbl_fill_color = dt_ui_label_new(_("fill color: "));
2502 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), lbl_fill_color, FALSE, TRUE, 0);
2503
2504 g->colorpick = gtk_color_button_new_with_rgba(&color);
2505 gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(g->colorpick), FALSE);
2506 gtk_color_button_set_title(GTK_COLOR_BUTTON(g->colorpick), _("Select fill color"));
2507 gtk_widget_set_tooltip_text(g->colorpick, _("Select fill color"));
2508 g_signal_connect(G_OBJECT(g->colorpick), "color-set", G_CALLBACK(rt_colorpick_color_set_callback), self);
2509 gtk_box_pack_start(GTK_BOX(g->hbox_color_pick), GTK_WIDGET(g->colorpick), TRUE, TRUE, 0);
2510
2511 g->colorpicker = dt_color_picker_new(self, DT_COLOR_PICKER_POINT, g->hbox_color_pick);
2512 gtk_widget_set_tooltip_text(g->colorpicker, _("Pick fill color from image"));
2513
2514 gtk_box_pack_start(GTK_BOX(g->vbox_fill), g->hbox_color_pick, TRUE, TRUE, 0);
2515
2516 g->sl_fill_brightness = dt_bauhaus_slider_from_params(self, "fill_brightness");
2517 dt_bauhaus_slider_set_digits(g->sl_fill_brightness, 4);
2518 dt_bauhaus_slider_set_format(g->sl_fill_brightness, "%");
2519 gtk_widget_set_tooltip_text(g->sl_fill_brightness,
2520 _("Adjusts color brightness to fine-tune it. Works with erase as well"));
2521
2522 // blur properties
2523 g->vbox_blur = self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2524
2525 g->cmb_blur_type = dt_bauhaus_combobox_from_params(self, "blur_type");
2526 gtk_widget_set_tooltip_text(g->cmb_blur_type, _("Type for the blur algorithm"));
2527
2528 g->sl_blur_radius = dt_bauhaus_slider_from_params(self, "blur_radius");
2529 dt_bauhaus_slider_set_format(g->sl_blur_radius, " px");
2530 gtk_widget_set_tooltip_text(g->sl_blur_radius, _("Radius of the selected blur type"));
2531
2532 // mask opacity
2533 g->sl_mask_opacity = dt_bauhaus_slider_new_with_range(dt_bauhaus_get_global(), DT_GUI_MODULE(self), 0.0, 1.0, 0, 1., 3);
2534 dt_bauhaus_widget_set_label(g->sl_mask_opacity, N_("Mask opacity"));
2535 dt_bauhaus_slider_set_format(g->sl_mask_opacity, "%");
2536 gtk_widget_set_tooltip_text(g->sl_mask_opacity, _("Set the opacity on the selected shape"));
2537 g_signal_connect(G_OBJECT(g->sl_mask_opacity), "value-changed", G_CALLBACK(rt_mask_opacity_callback), self);
2538
2539 // start building top level widget
2540 self->gui->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, DT_GUI_BOX_SPACING);
2541
2542 GtkWidget *lbl_rt_tools = dt_ui_section_label_new(_("retouch tools"));
2543 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_rt_tools, FALSE, TRUE, 0);
2544
2545 // shapes toolbar
2546 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_shapes, TRUE, TRUE, 0);
2547 // algorithms toolbar
2548 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_algo, TRUE, TRUE, 0);
2549
2550 g->cmb_heal_algorithm = dt_bauhaus_combobox_from_params(self, "heal_algorithm");
2551 gtk_widget_set_tooltip_text(g->cmb_heal_algorithm,
2552 _("- Linear: the source keeps its own noise and texture amplitude,\n"
2553 " a source brighter than the target makes it noisier\n"
2554 "- Square root: the source noise is scaled to the brightness of the target"));
2555
2556 // wavelet decompose
2557 GtkWidget *lbl_wd = dt_ui_section_label_new(_("wavelet decompose"));
2558 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_wd, FALSE, TRUE, 0);
2559
2560 // wavelet decompose bar & labels
2561 gtk_box_pack_start(GTK_BOX(self->gui->widget), grid_wd_labels, TRUE, TRUE, 0);
2562 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->wd_bar, TRUE, TRUE, DT_PIXEL_APPLY_DPI(3));
2563
2564 // preview scale & cut/paste scale
2565 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_scale, TRUE, TRUE, 0);
2566
2567 // preview single scale
2568 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_preview_scale, TRUE, TRUE, 0);
2569
2570 // shapes
2571 GtkWidget *lbl_shapes = dt_ui_section_label_new(_("shapes"));
2572 gtk_box_pack_start(GTK_BOX(self->gui->widget), lbl_shapes, FALSE, TRUE, 0);
2573
2574 // shape selected
2575 gtk_box_pack_start(GTK_BOX(self->gui->widget), hbox_shape_sel, TRUE, TRUE, 0);
2576 // blur radius
2577 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_blur, TRUE, TRUE, 0);
2578 // fill color
2579 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->vbox_fill, TRUE, TRUE, 0);
2580 // mask (shape) opacity
2581 gtk_box_pack_start(GTK_BOX(self->gui->widget), g->sl_mask_opacity, TRUE, TRUE, 0);
2582
2583 /* add signal handler for preview pipe finish to redraw the preview */
2585 G_CALLBACK(rt_develop_ui_pipe_finished_callback), self);
2586}
2587
2588void gui_reset(struct dt_iop_module_t *self)
2589{
2590 // hide the previous masks
2592 // set the algo to the default one
2594 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2595}
2596
2598{
2599 // set the algo to the default one
2601 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2602}
2603
2610
2612 struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
2613 const dt_iop_roi_t *roi_in)
2614{
2615 *roi_out = *roi_in;
2616}
2617
2618// The input rectangle retouch grows while planning, as left/top/right/bottom edges.
2619typedef struct rt_roi_bounds_t
2620{
2621 int x;
2622 int y;
2623 int r;
2624 int b;
2626
2627// Grow the bounds to cover [left, right] x [top, bottom].
2628static void rt_roi_bounds_include(rt_roi_bounds_t *const bounds, const float left, const float top,
2629 const float right, const float bottom)
2630{
2631 bounds->y = fminf(top, bounds->y);
2632 bounds->x = fminf(left, bounds->x);
2633 bounds->r = fmaxf(right, bounds->r);
2634 bounds->b = fmaxf(bottom, bounds->b);
2635}
2636
2637// The members of the module's drawn mask, resolved in the run's snapshot. NULL when it has none.
2639{
2640 const dt_develop_blend_params_t *bp = (const dt_develop_blend_params_t *)piece->blendop_data;
2642 return (!IS_NULL_PTR(grp) && (grp->type & DT_MASKS_GROUP)) ? grp->points : NULL;
2643}
2644
2645// A group member's shape, with its id and its slot in rt_forms[]. NULL when the member names no
2646// shape, when the params do not know the shape yet, or when the snapshot does not hold it.
2648 const GList *member, int *formid, int *index)
2649{
2650 const dt_masks_form_group_t *grpt = (const dt_masks_form_group_t *)member->data;
2651 *formid = IS_NULL_PTR(grpt) ? 0 : grpt->formid;
2652 *index = rt_get_index_from_formid(p, *formid);
2653 if(*index < 0) return NULL;
2654 return dt_masks_get_from_id_in_pipe(pipe, *formid);
2655}
2656
2658{
2659 return algo == DT_IOP_RETOUCH_HEAL || algo == DT_IOP_RETOUCH_CLONE;
2660}
2661
2662// Grow the bounds to cover an area shifted by (dx, dy).
2663static void rt_roi_bounds_include_area(rt_roi_bounds_t *const bounds, const dt_masks_area_t *area, const float dx,
2664 const float dy)
2665{
2666 rt_roi_bounds_include(bounds, area->x + dx, area->y + dy, (area->x + area->width) + dx,
2667 (area->y + area->height) + dy);
2668}
2669
2670// Blur reads `overlap` pixels around the area: grow the bounds towards it on each side it
2671// reaches past, never beyond the area itself.
2672static void rt_roi_bounds_grow_for_blur(rt_roi_bounds_t *const bounds, const dt_masks_area_t *area,
2673 const int overlap)
2674{
2675 if(bounds->y > area->y) bounds->y = MAX(bounds->y - overlap, area->y);
2676 if(bounds->x > area->x) bounds->x = MAX(bounds->x - overlap, area->x);
2677 if(bounds->r < area->x + area->width) bounds->r = MAX(bounds->r + overlap, area->x + area->width);
2678 if(bounds->b < area->y + area->height) bounds->b = MAX(bounds->b + overlap, area->y + area->height);
2679}
2680
2681// The part of a rasterisation's identity that is not the shape: the transformation chain above
2682// this node, the sensor dimensions the masks code works in, and the rasterisation step a brush
2683// honours. The last two reach no hash of the pipeline's own. INVALID disables the memo.
2685{
2686 static const char cache_tag[] = "retouch:geometry";
2688
2689 uint64_t hash = dt_hash(piece->upstream_hash, cache_tag, sizeof(cache_tag));
2690 /* This module's own place in the pipe: it is the bound the back-transform stops at, and
2691 * upstream_hash only describes what sits before it. */
2692 hash = dt_hash(hash, (const char *)&piece->module->iop_order, sizeof(int));
2693 hash = dt_hash(hash, (const char *)&pipe->iwidth, sizeof(pipe->iwidth));
2694 hash = dt_hash(hash, (const char *)&pipe->iheight, sizeof(pipe->iheight));
2695 const int raster_step = pipe->mask_rasterization_step;
2696 hash = dt_hash(hash, (const char *)&raster_step, sizeof(raster_step));
2697 return hash;
2698}
2699
2700// The same, plus the shape's own geometry.
2702{
2704
2705 /* A nested group would need its members folded in through the run's form list. It cannot
2706 * reach here -- a group has no get_area(), so dt_masks_get_area() refuses it and the member is
2707 * skipped -- and a partial hash is worse than no memo, so refuse rather than assume. */
2709 return dt_masks_form_get_own_hash(base, NULL, form);
2710}
2711
2712/* What a shape's rasterisation needs before a single pixel is touched: its own bounding box and
2713 * the box of its clone source, both in sensor coordinates. `rt_compute_roi_in()` and
2714 * `rt_extend_roi_in_*()` ask for these on every ROI planning pass, the second pair once per
2715 * ordered PAIR of shapes, and for a brush or a polygon each answer regenerates the whole
2716 * outline. They depend on the shape and on the chain above the module, never on the viewport or
2717 * on a pixel, so one memo entry per shape serves every pass and every pipe. */
2719{
2720 dt_masks_area_t area; // the shape itself
2721 dt_masks_area_t source; // its clone source, meaningful only when `has_source`
2722 int32_t has_area;
2723 int32_t has_source;
2725
2726// Which of the two boxes a call needs.
2732
2735{
2736 *out = (rt_shape_geometry_t){ { 0 } };
2737 out->has_area = (dt_masks_get_area(ctx->self, ctx->pipe, ctx->piece, form, &out->area) == DT_MASKS_RASTER_OK);
2738 /* A shape that is not a clone has no source area -- an absence, not a failure. */
2739 out->has_source
2740 = (dt_masks_get_source_area(ctx->self, ctx->pipe, ctx->piece, form, &out->source) == DT_MASKS_RASTER_OK);
2741}
2742
2743// The one box a caller with no memo asked for. Computing the other as well would be a straight
2744// loss here: nothing would read it, and for a brush it is a second outline generation.
2745static gboolean rt_compute_shape_box(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form,
2746 const rt_box_t want, dt_masks_area_t *out)
2747{
2748 return ((want == RT_BOX_SOURCE) ? dt_masks_get_source_area(ctx->self, ctx->pipe, ctx->piece, form, out)
2749 : dt_masks_get_area(ctx->self, ctx->pipe, ctx->piece, form, out))
2751}
2752
2753// One of the shape's boxes, from the memo when there is one. FALSE when the shape has no such box.
2754static gboolean rt_shape_box(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form,
2755 const uint64_t shape_hash, const rt_box_t want, dt_masks_area_t *out)
2756{
2757 if(shape_hash == DT_PIXELPIPE_CACHE_HASH_INVALID)
2758 {
2759 if(ctx->stats) ctx->stats->boxes_computed++;
2760 return rt_compute_shape_box(ctx, form, want, out);
2761 }
2762
2763 static const char cache_tag[] = "boxes";
2764 const uint64_t hash = dt_hash(shape_hash, cache_tag, sizeof(cache_tag));
2765
2766 void *data = NULL;
2767 struct dt_pixel_cache_entry_t *entry = NULL;
2768 const int created = dt_dev_pixelpipe_cache_get(hash, sizeof(rt_shape_geometry_t), "retouch shape boxes",
2769 ctx->pipe->type, TRUE, &data, &entry);
2770 if(IS_NULL_PTR(data) || IS_NULL_PTR(entry))
2771 {
2772 if(!IS_NULL_PTR(entry))
2773 {
2776 }
2777 if(ctx->stats) ctx->stats->boxes_computed++;
2778 return rt_compute_shape_box(ctx, form, want, out);
2779 }
2780
2782 if(ctx->stats) { if(created) ctx->stats->boxes_computed++; else ctx->stats->boxes_hit++; }
2783 if(created)
2784 {
2785 // both, once: the other box is asked for by another pass over the same shapes
2789 }
2790 else
2791 {
2795 }
2797
2798 *out = (want == RT_BOX_SOURCE) ? boxes.source : boxes.area;
2799 return (want == RT_BOX_SOURCE) ? boxes.has_source : boxes.has_area;
2800}
2801
2802static void rt_compute_roi_in(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi_in,
2803 rt_roi_bounds_t *const bounds)
2804{
2805 const dt_iop_retouch_params_t *p = (const dt_iop_retouch_params_t *)ctx->piece->data;
2806 const uint64_t base_hash = rt_geometry_base_hash(ctx->pipe, ctx->piece);
2807
2808 for(const GList *members = rt_pipe_group_members(ctx->pipe, ctx->piece); members;
2810 {
2811 int formid = 0;
2812 int index = -1;
2813 dt_masks_form_t *form = rt_pipe_member_form(ctx->pipe, p, members, &formid, &index);
2814 if(IS_NULL_PTR(form) || p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL) continue;
2815
2816 // the area of the form, skipped when outside the roi
2817 dt_masks_area_t area;
2818 if(!rt_shape_box(ctx, form, rt_shape_geometry_hash(base_hash, form), RT_BOX_AREA, &area))
2819 continue;
2820 dt_masks_area_scale(&area, roi_in->scale);
2821 if(!dt_masks_area_intersects(&area, roi_in)) continue;
2822
2823 const dt_iop_retouch_form_data_t *data = &p->rt_forms[index];
2824
2825 // heal needs the entire area
2826 if(data->algorithm == DT_IOP_RETOUCH_HEAL) rt_roi_bounds_include_area(bounds, &area, 0.f, 0.f);
2827
2828 // blur needs an overlap of 4 * radius (scaled)
2829 if(data->algorithm == DT_IOP_RETOUCH_BLUR)
2830 rt_roi_bounds_grow_for_blur(bounds, &area, ceilf(4 * (data->blur_radius * roi_in->scale)));
2831
2832 // heal and clone need both source and destination areas
2833 float dx = 0.f;
2834 float dy = 0.f;
2835 if(rt_algo_needs_source(data->algorithm)
2836 && rt_masks_get_delta_to_destination(ctx, roi_in, form, &dx, &dy, data->distort_mode))
2837 rt_roi_bounds_include_area(bounds, &area, -dx, -dy);
2838 }
2839}
2840
2841// for a given form, if a previous clone/heal destination intersects the source area,
2842// include that area in roi_in too
2844 const int formid_src, const dt_masks_area_t *src,
2845 rt_roi_bounds_t *const bounds)
2846{
2847 const dt_iop_retouch_params_t *p = (const dt_iop_retouch_params_t *)ctx->piece->data;
2848 const uint64_t base_hash = rt_geometry_base_hash(ctx->pipe, ctx->piece);
2849
2850 for(const GList *members = rt_pipe_group_members(ctx->pipe, ctx->piece); members;
2852 {
2853 int formid = 0;
2854 int index = -1;
2855 dt_masks_form_t *form = rt_pipe_member_form(ctx->pipe, p, members, &formid, &index);
2856
2857 // just need the previous forms
2858 if(formid == formid_src) break;
2859 if(IS_NULL_PTR(form) || !rt_algo_needs_source(p->rt_forms[index].algorithm)) continue;
2860
2861 // the source area
2862 dt_masks_area_t area;
2864 continue;
2865 dt_masks_area_scale(&area, roi_in->scale);
2866
2867 // the destination area
2868 float dx = 0.f;
2869 float dy = 0.f;
2870 if(!rt_masks_get_delta_to_destination(ctx, roi_in, form, &dx, &dy, p->rt_forms[index].distort_mode))
2871 continue;
2872
2873 const int ft_dest = area.y + dy;
2874 const int fl_dest = area.x + dx;
2875
2876 // does the destination of this form intersect the source of formid_src?
2877 const int intersects = !(ft_dest + area.height < src->y || src->y + src->height < ft_dest
2878 || fl_dest + area.width < src->x || src->x + src->width < fl_dest);
2879 if(intersects)
2880 {
2881 // both source and destination areas
2882 rt_roi_bounds_include_area(bounds, &area, 0.f, 0.f);
2883 rt_roi_bounds_include_area(bounds, &area, dx, dy);
2884 }
2885 }
2886}
2887
2888// for clone and heal, if the source area is the destination from another clone/heal,
2889// we also need the area from that previous clone/heal
2890static void rt_extend_roi_in_for_clone(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi_in,
2891 rt_roi_bounds_t *const bounds)
2892{
2893 const dt_iop_retouch_params_t *p = (const dt_iop_retouch_params_t *)ctx->piece->data;
2894 const uint64_t base_hash = rt_geometry_base_hash(ctx->pipe, ctx->piece);
2895
2896 for(const GList *members = rt_pipe_group_members(ctx->pipe, ctx->piece); members;
2898 {
2899 int formid = 0;
2900 int index = -1;
2901 dt_masks_form_t *form = rt_pipe_member_form(ctx->pipe, p, members, &formid, &index);
2902 if(IS_NULL_PTR(form) || !rt_algo_needs_source(p->rt_forms[index].algorithm)) continue;
2903
2904 // the source area
2905 dt_masks_area_t src;
2907 continue;
2908 dt_masks_area_scale(&src, roi_in->scale);
2909
2910 // we only want to process forms already in roi_in
2911 const int intersects = !(bounds->b < src.y || src.y + src.height < bounds->y || bounds->r < src.x
2912 || src.x + src.width < bounds->x);
2913 if(intersects) rt_extend_roi_in_from_source_clones(ctx, roi_in, formid, &src, bounds);
2914 }
2915}
2916
2917// needed if mask dest is in roi and mask src is not
2918void modify_roi_in(struct dt_iop_module_t *self, const struct dt_dev_pixelpipe_t *pipe,
2919 struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
2920 dt_iop_roi_t *roi_in)
2921{
2922 *roi_in = *roi_out;
2923
2924 rt_roi_bounds_t bounds = { .x = roi_in->x, .y = roi_in->y,
2925 .r = roi_in->width + roi_in->x, .b = roi_in->height + roi_in->y };
2926
2927 /* Every pass of the loop below walks the members once per member -- O(shapes squared) source
2928 * areas -- so this is where a shape-heavy image spends its planning, and the pipeline's own
2929 * `processed` line does not cover any of it. See rt_memo_stats_t. */
2930 rt_memo_stats_t counters = { 0 };
2931 const gboolean counting = rt_perf_enabled();
2932 const rt_masks_ctx_t ctx = { self, pipe, piece, counting ? &counters : NULL };
2933 const double planning_start = counting ? dt_get_wtime() : 0.0;
2934 int passes = 0;
2935
2936 rt_compute_roi_in(&ctx, roi_in, &bounds);
2937
2938 rt_roi_bounds_t previous = { -1, -1, -1, -1 };
2939 while(memcmp(&bounds, &previous, sizeof(bounds)))
2940 {
2941 previous = bounds;
2942 passes++;
2943 rt_extend_roi_in_for_clone(&ctx, roi_in, &bounds);
2944 }
2945
2946 if(counting)
2948 "[retouch] %-8s modify_roi_in: %d stabilisation pass(es), boxes %d (%d memo / %d rasterised),"
2949 " %.3f s\n",
2950 dt_pixelpipe_name(pipe->type), passes, counters.boxes_hit + counters.boxes_computed,
2951 counters.boxes_hit, counters.boxes_computed, dt_get_wtime() - planning_start);
2952
2953 // now we set the values
2954 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
2955 roi_in->x = CLAMP(bounds.x, 0, scwidth - 1);
2956 roi_in->y = CLAMP(bounds.y, 0, scheight - 1);
2957 roi_in->width = CLAMP(bounds.r - roi_in->x, 1, scwidth + .5f - roi_in->x);
2958 roi_in->height = CLAMP(bounds.b - roi_in->y, 1, scheight + .5f - roi_in->y);
2959}
2960
2961//--------------------------------------------------------------------------------------------------
2962// process
2963//--------------------------------------------------------------------------------------------------
2964
2965static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
2966{
2967 const int stride = width * height * ch;
2969 for(int i = 0; i < stride; i += ch)
2970 {
2972
2975 }
2976}
2977
2978static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
2979{
2980 const int stride = width * height * ch;
2982 for(int i = 0; i < stride; i += ch)
2983 {
2985
2988 }
2989}
2990
2993 float *const img_src, const int width, const int height, const int ch, float levels[3])
2994{
2995 const int size = width * height * ch;
2996 float l_max = -INFINITY;
2997 float l_min = INFINITY;
2998 float l_sum = 0.f;
2999 int count = 0;
3002 for(int i = 0; i < size; i += ch)
3003 {
3004 dt_aligned_pixel_t Lab = { 0 };
3005
3006 if(work_profile)
3007 {
3009 work_profile->lut_in, work_profile->unbounded_coeffs_in,
3010 work_profile->lutsize, work_profile->nonlinearlut);
3011 }
3012 else
3013 {
3017 }
3018
3019 l_max = MAX(l_max, Lab[0]);
3020 l_min = MIN(l_min, Lab[0]);
3021 l_sum += Lab[0];
3022 count++;
3023 }
3024
3025 levels[0] = l_min / 100.f;
3026 levels[2] = l_max / 100.f;
3027 levels[1] = (l_sum / (float)count) / 100.f;
3028}
3029
3032 float *img_src, const int width, const int height, const int ch,
3033 const float levels[3])
3034{
3035 const int size = width * height * ch;
3037
3038 const float left = levels[0];
3039 const float middle = levels[1];
3040 const float right = levels[2];
3041
3042 if(left == RETOUCH_PREVIEW_LVL_MIN && middle == 0.f && right == RETOUCH_PREVIEW_LVL_MAX) return;
3043
3044 const float delta = (right - left) / 2.0f;
3045 const float mid = left + delta;
3046 const float tmp = (middle - mid) / delta;
3047 const float in_inv_gamma = powf(10, tmp);
3049 for(int i = 0; i < size; i += ch)
3050 {
3051 // The RGB<->Lab/XYZ conversions below store through 4-wide SIMD (dt_apply_transposed_color_matrix
3052 // et al.), which unconditionally overwrites all 4 lanes of img_src+i even though the color math
3053 // itself is 3-channel -- the 4th lane ends up holding leftover matrix-multiply output, not the
3054 // original value. For most pipeline buffers that 4th channel is meaningless padding, but here it
3055 // is retouch's own mask-display alpha (painted a few lines up the call chain via
3056 // rt_copy_mask_to_alpha), so silently clobbering it left every "mask display" pixel looking
3057 // fully transparent whenever a single wavelet scale was also being previewed. Save and restore it
3058 // around the round trip.
3059 const float preserved_alpha = img_src[i + 3];
3060
3061 if(!IS_NULL_PTR(work_profile))
3062 {
3064 work_profile->lut_in, work_profile->unbounded_coeffs_in,
3065 work_profile->lutsize, work_profile->nonlinearlut);
3066 }
3067 else
3068 {
3070
3073 }
3074
3075 for(int c = 0; c < 1; c++)
3076 {
3077 const float L_in = img_src[i + c] / 100.0f;
3078
3079 if(L_in <= left)
3080 {
3081 img_src[i + c] = 0.f;
3082 }
3083 else
3084 {
3085 const float percentage = (L_in - left) / (right - left);
3086 img_src[i + c] = 100.0f * powf(percentage, in_inv_gamma);
3087 }
3088 }
3089
3090 if(!IS_NULL_PTR(work_profile))
3091 {
3093 work_profile->lut_out, work_profile->unbounded_coeffs_out,
3094 work_profile->lutsize, work_profile->nonlinearlut);;
3095 }
3096 else
3097 {
3099
3102 }
3103
3104 img_src[i + 3] = preserved_alpha;
3105 }
3106}
3107
3108#undef RT_WDBAR_INSET
3109
3110#undef RETOUCH_NO_FORMS
3111#undef RETOUCH_MAX_SCALES
3112#undef RETOUCH_NO_SCALES
3113
3114#undef RETOUCH_PREVIEW_LVL_MIN
3115#undef RETOUCH_PREVIEW_LVL_MAX
3116
3117static void rt_intersect_2_rois(const dt_iop_roi_t *const roi_1, const dt_iop_roi_t *const roi_2, const int dx,
3118 const int dy,
3119 const int padding, dt_iop_roi_t *roi_dest)
3120{
3121 const int x_from = MAX(MAX((roi_1->x + 1 - padding), roi_2->x), (roi_2->x + dx));
3122 const int x_to
3123 = MIN(MIN((roi_1->x + roi_1->width + 1 + padding), roi_2->x + roi_2->width), (roi_2->x + roi_2->width + dx));
3124
3125 const int y_from = MAX(MAX((roi_1->y + 1 - padding), roi_2->y), (roi_2->y + dy));
3126 const int y_to = MIN(MIN((roi_1->y + roi_1->height + 1 + padding), (roi_2->y + roi_2->height)),
3127 (roi_2->y + roi_2->height + dy));
3128
3129 roi_dest->x = x_from;
3130 roi_dest->y = y_from;
3131 roi_dest->width = x_to - x_from;
3132 roi_dest->height = y_to - y_from;
3133}
3134
3136static void rt_copy_in_to_out(const float *const in, const struct dt_iop_roi_t *const roi_in, float *const out,
3137 const struct dt_iop_roi_t *const roi_out, const int ch, const int dx, const int dy)
3138{
3139 const size_t rowsize = sizeof(float) * ch * MIN(roi_out->width, roi_in->width);
3140 const int xoffs = roi_out->x - roi_in->x - dx;
3141 const int yoffs = roi_out->y - roi_in->y - dy;
3142 const int y_to = MIN(roi_out->height, roi_in->height);
3144 for(int y = 0; y < y_to; y++)
3145 {
3146 const size_t iindex = ((size_t)(y + yoffs) * roi_in->width + xoffs) * ch;
3147 const size_t oindex = (size_t)y * roi_out->width * ch;
3148 float *in1 = (float *)in + iindex;
3149 float *out1 = (float *)out + oindex;
3150
3152 }
3153}
3154
3155/* Where a shape's mask lands in the layer, and how big it is there. Pure arithmetic over the
3156 * shape's full-resolution area and the layer's ROI -- no rasterisation -- which is what lets the
3157 * memo below size its lookup before deciding whether it has to rasterise anything at all. An
3158 * empty result (width or height < 1) means the shape draws nothing in this layer. */
3159static void rt_scaled_mask_roi(const dt_masks_area_t *const area, const dt_iop_roi_t *const roi_in, const int dx,
3160 const int dy, const int algo, dt_iop_roi_t *roi_mask_scaled)
3161{
3162 const int padding = (algo == DT_IOP_RETOUCH_HEAL) ? 1 : 0;
3163
3164 dt_iop_roi_t roi_mask = { .x = area->x, .y = area->y, .width = area->width, .height = area->height, .scale = 1.f };
3165
3166 *roi_mask_scaled = roi_mask;
3167 roi_mask_scaled->x = roi_mask.x * roi_in->scale;
3168 roi_mask_scaled->y = roi_mask.y * roi_in->scale;
3169 roi_mask_scaled->width = ((roi_mask.width * roi_in->scale) + .5f);
3170 roi_mask_scaled->height = ((roi_mask.height * roi_in->scale) + .5f);
3171 roi_mask_scaled->scale = roi_in->scale;
3172
3173 rt_intersect_2_rois(roi_mask_scaled, roi_in, dx, dy, padding, roi_mask_scaled);
3174}
3175
3176/* Resample the full-resolution mask into `mask_scaled`, which the caller sized from
3177 * rt_scaled_mask_roi() for the same area and ROI. Writes every pixel of it. */
3178static void rt_fill_scaled_mask(const float *const mask, const dt_iop_roi_t *const roi_mask,
3179 float *const mask_scaled, const dt_iop_roi_t *const roi_mask_scaled,
3180 const dt_iop_roi_t *const roi_in)
3181{
3182 const int x_to = roi_mask_scaled->width + roi_mask_scaled->x;
3183 const int y_to = roi_mask_scaled->height + roi_mask_scaled->y;
3184
3187 for(int yy = roi_mask_scaled->y; yy < y_to; yy++)
3188 {
3189 const int mask_index = ((int)(yy / roi_in->scale)) - roi_mask->y;
3190 if(mask_index < 0 || mask_index >= roi_mask->height) continue;
3191
3192 const int mask_scaled_index = (yy - roi_mask_scaled->y) * roi_mask_scaled->width;
3193
3194 const float *m = mask + mask_index * roi_mask->width;
3195 float *ms = mask_scaled + mask_scaled_index;
3196
3197 for(int xx = roi_mask_scaled->x; xx < x_to; xx++, ms++)
3198 {
3199 const int mx = ((int)(xx / roi_in->scale)) - roi_mask->x;
3200 if(mx < 0 || mx >= roi_mask->width) continue;
3201
3202 *ms = m[mx];
3203 }
3204 }
3205}
3206
3207// img_src and mask_scaled must have the same roi
3208static void rt_copy_image_masked(float *const img_src, float *img_dest, dt_iop_roi_t *const roi_dest,
3209 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3210 const float opacity)
3211{
3213 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3214 {
3215 const int mask_index = yy * roi_mask_scaled->width;
3216 const int src_index = 4 * mask_index;
3217 const int dest_index
3218 = 4 * (((yy + roi_mask_scaled->y - roi_dest->y) * roi_dest->width) + (roi_mask_scaled->x - roi_dest->x));
3219
3220 const float *s = img_src + src_index;
3221 const float *m = mask_scaled + mask_index;
3222 float *d = img_dest + dest_index;
3223
3224 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3225 {
3226 const float f = m[xx] * opacity;
3227 const float f1 = (1.0f - f);
3228
3230 {
3231 d[4*xx + c] = d[4*xx + c] * f1 + s[4*xx + c] * f;
3232 }
3233 }
3234 }
3235}
3236
3237static void rt_copy_mask_to_alpha(float *const img, dt_iop_roi_t *const roi_img, const int ch,
3238 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3239 const float opacity)
3240{
3242 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3243 {
3244 const int mask_index = yy * roi_mask_scaled->width;
3245 const int dest_index
3246 = (((yy + roi_mask_scaled->y - roi_img->y) * roi_img->width) + (roi_mask_scaled->x - roi_img->x)) * ch;
3247
3248 float *d = img + dest_index;
3249 const float *m = mask_scaled + mask_index;
3250
3251 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3252 {
3253 const float f = (*m) * opacity;
3254 if(f > d[3]) d[3] = f;
3255 }
3256 }
3257}
3258
3259static void _retouch_fill(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3260 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const float *const fill_color)
3261{
3263 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3264 {
3265 const int mask_index = yy * roi_mask_scaled->width;
3266 const int dest_index
3267 = (((yy + roi_mask_scaled->y - roi_in->y) * roi_in->width) + (roi_mask_scaled->x - roi_in->x)) * 4;
3268
3269 float *d = in + dest_index;
3270 const float *m = mask_scaled + mask_index;
3271
3272 for(int xx = 0; xx < roi_mask_scaled->width; xx++)
3273 {
3274 const float f = m[xx] * opacity;
3275
3276 for_each_channel(c,aligned(d,fill_color))
3277 d[4*xx + c] = d[4*xx + c] * (1.0f - f) + fill_color[c] * f;
3278 }
3279 }
3280}
3281
3282static int _retouch_clone(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3283 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity)
3284{
3285 // alloc temp image to avoid issues when areas self-intersects
3287 if(IS_NULL_PTR(img_src))
3288 {
3289 fprintf(stderr, "retouch_clone: error allocating memory for cloning\n");
3290 return 1;
3291 }
3292
3293 // copy source image to tmp
3294 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3295
3296 // clone it
3298
3300 return 0;
3301}
3302
3303static int _retouch_blur(dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, float *const in,
3304 dt_iop_roi_t *const roi_in, float *const mask_scaled,
3305 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type,
3306 const float blur_radius, const int use_sse)
3307{
3308 if(fabsf(blur_radius) <= 0.1f) return 0;
3309
3310 const float sigma = blur_radius * roi_in->scale;
3311
3312 float *img_dest = NULL;
3313
3314 // alloc temp image to blur
3317 {
3318 fprintf(stderr, "retouch_blur: error allocating memory for blurring\n");
3319 return 1;
3320 }
3321
3322 // copy source image so we blur just the mask area (at least the smallest rect that covers it)
3323 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3324
3325 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3326 {
3327 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
3328 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3329
3332 if(!IS_NULL_PTR(g))
3333 {
3336 }
3337 }
3338 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
3339 {
3340 const float sigma_r = 100.0f; // does not depend on scale
3341 const float sigma_s = sigma;
3342 const float detail = -1.0f; // we want the bilateral base layer
3343
3345 if(b)
3346 {
3347 int converted_cst;
3349
3350 if(!IS_NULL_PTR(work_profile))
3353 work_profile);
3354 else
3355 image_rgb2lab(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3356
3361
3362 if(!IS_NULL_PTR(work_profile))
3365 work_profile);
3366 else
3367 image_lab2rgb(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, 4, use_sse);
3368 }
3369 }
3370
3371 // copy blurred (temp) image to destination image
3373
3375 return 0;
3376}
3377
3378// Only the image itself (scale 0) and the wavelet residual carry a light level. Detail scales are
3379// signed, zero-mean differences, which the square-root domain cannot represent: they heal linearly
3380// whatever the algorithm.
3381static dt_heal_domain_t rt_heal_domain(const dt_iop_retouch_params_t *const p, const int scale, const int scales)
3382{
3383 const gboolean has_level = (scale == 0 || scale == scales + 1);
3385}
3386
3387static int _retouch_heal(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3388 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity,
3389 const int max_iter, const dt_heal_domain_t domain)
3390{
3391 float *img_src = NULL;
3392 float *img_dest = NULL;
3393 int err = 0;
3394
3395 // alloc temp images for source and destination
3399 {
3400 fprintf(stderr, "retouch_heal: error allocating memory for healing\n");
3401 err = 1;
3402 goto cleanup;
3403 }
3404
3405 // copy source and destination to temp images
3406 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, 4, dx, dy);
3407 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, 4, 0, 0);
3408
3409 // heal it
3411 (dt_heal_solver_t){ .max_iter = max_iter, .domain = domain });
3412
3413 // copy healed (temp) image to destination image
3415
3416cleanup:
3419 return err;
3420}
3421
3422/* ==============================================================================================
3423 * Geometric memo: a shape's area, and its mask resampled into the layer.
3424 *
3425 * Neither reads a pixel. Both are pure functions of the shape's own geometry and of the
3426 * distortion chain above this module, so dragging ONE shape leaves every other shape's mask
3427 * bit-identical -- yet each of them was rasterised again on every frame, twice over: once for
3428 * the area (`dt_masks_form_is_in_roi()`) and once more inside `dt_masks_get_mask()`, which for a
3429 * brush or a polygon regenerates the whole outline each time. And it rasterises at full sensor
3430 * resolution whatever the zoom (`_circle_get_area()` works in `pipe->iwidth`/`iheight`), so a
3431 * 500 px shape back-transforms 250 000 points through every distorting module above, to then be
3432 * thrown away at one pixel in a hundred by `rt_fill_scaled_mask()` at fit zoom.
3433 *
3434 * Both results are memoised in the shared pixelpipe cache, keyed on `piece->upstream_hash` --
3435 * the transformation chain above this node, which by construction does NOT move when the
3436 * module's own parameters do. A miss costs only the work that was being done anyway, so
3437 * eviction and memory pressure need no handling of their own here.
3438 *
3439 * Measured on a 141-shape image, exported at 2000 px: the mask rasterisation this module asks
3440 * for is 3.5 s, and 3.1 s of it is `modify_roi_in()` -- `rt_extend_roi_in_for_clone()` is
3441 * O(shapes squared) and calls `dt_masks_get_source_area()` in its inner loop, which for a brush
3442 * regenerates the whole outline. The box memo below is what turns that inner loop into
3443 * arithmetic; the mask memo is the smaller half.
3444 * ============================================================================================ */
3445
3446// Why one group member is not applied at this scale.
3448{
3449 RT_SHAPE_READY = 0, // apply it
3450 RT_SHAPE_SKIP, // another scale, outside the layer, nothing to draw: go on with the next one
3451 RT_SHAPE_ERROR // out of memory: abort the whole decomposition
3453
3454// A shape of the module's drawn mask, ready to be applied at one wavelet scale.
3456{
3457 int index; // slot in rt_forms[]
3458 float opacity; // the shape's opacity in the group
3460 float *mask; // mask resampled into the layer, READ-ONLY (it may be shared)
3461 dt_iop_roi_t roi_mask; // where it lands in the layer
3462 float dx; // offset from the source to the destination
3463 float dy;
3464 // The memo entry `mask` belongs to, NULL when it is this shape's own allocation. Either way
3465 // rt_release_shape() is what hands it back.
3468
3469// Rasterise the shape and resample it into `dst`, which the caller sized from `area` through
3470// rt_scaled_mask_roi().
3472 const dt_masks_area_t *const area,
3473 const dt_iop_roi_t *const roi_layer,
3474 const dt_iop_roi_t *const roi_mask_scaled, float *const dst)
3475{
3476 float *mask = NULL;
3477 dt_masks_area_t rastered = { 0 };
3478 if(ctx->stats) ctx->stats->masks_rasterised++;
3479 dt_masks_get_mask(ctx->self, ctx->pipe, ctx->piece, form, &mask, &rastered);
3480 if(IS_NULL_PTR(mask))
3481 {
3482 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
3483 return RT_SHAPE_SKIP;
3484 }
3485
3486 /* `dst` was sized from dt_masks_get_area(), which every shape type reaching here implements
3487 * with the very function its get_mask() uses for its own bounding box. Should the two ever
3488 * disagree, this buffer is not the one that mask needs: say so and draw nothing rather than
3489 * write past it. */
3490 if(rastered.x != area->x || rastered.y != area->y || rastered.width != area->width
3491 || rastered.height != area->height)
3492 {
3494 "rt_process_forms: the rasterised area %ix%i+%i+%i disagrees with the announced %ix%i+%i+%i\n",
3495 rastered.width, rastered.height, rastered.x, rastered.y, area->width, area->height, area->x,
3496 area->y);
3498 return RT_SHAPE_SKIP;
3499 }
3500
3501 const dt_iop_roi_t roi_mask
3502 = { .x = area->x, .y = area->y, .width = area->width, .height = area->height, .scale = 1.f };
3505 return RT_SHAPE_READY;
3506}
3507
3508/* One memo entry per shape holds the mask AND, in a header ahead of it, the area it was
3509 * rasterised from. The area is what sizes the lookup, so storing it with the mask is what lets a
3510 * hit answer without calling dt_masks_get_area() -- which for a brush or a polygon is the whole
3511 * outline generation, i.e. most of what the memo exists to avoid. The header is a cache line
3512 * wide so the pixels keep the alignment the resampling loop was written for. */
3513#define RT_MASK_MEMO_HEADER 64u
3514
3515// Is `area` something the arithmetic below can be trusted with? A memo hit reads it from a line
3516// resolved by hash alone, so it is checked before it sizes anything.
3517static gboolean rt_area_is_sane(const dt_masks_area_t *const area)
3518{
3519 return area->width > 0 && area->height > 0 && area->width < (1 << 20) && area->height < (1 << 20);
3520}
3521
3522// Everything but the shape's own area that the memoised mask depends on.
3524 const int dy, const dt_iop_retouch_algo_type_t algo)
3525{
3526 static const char cache_tag[] = "scaled-mask";
3528
3530 hash = dt_hash(hash, (const char *)roi_layer, sizeof(dt_iop_roi_t));
3531 hash = dt_hash(hash, (const char *)&dx, sizeof(dx));
3532 hash = dt_hash(hash, (const char *)&dy, sizeof(dy));
3533 hash = dt_hash(hash, (const char *)&algo, sizeof(algo));
3534 return hash;
3535}
3536
3537// every algorithm needs a mask larger than 2x2, and clone and heal need a source offset
3538static gboolean rt_shape_has_effect(const rt_prepared_shape_t *const shape, const int dx, const int dy)
3539{
3540 return (dx != 0 || dy != 0 || shape->algo == DT_IOP_RETOUCH_BLUR || shape->algo == DT_IOP_RETOUCH_FILL)
3541 && shape->roi_mask.width > 2 && shape->roi_mask.height > 2;
3542}
3543
3544// Whether a memo line answered.
3546{
3547 RT_MEMO_TAKEN = 0, // `shape` now holds the line, with its reference and its read lock
3548 RT_MEMO_ABSENT, // no line to be had: the caller must rasterise
3549 RT_MEMO_REFUSED // there is a line and it is not this mask, or rasterising it failed
3551
3552/* Does this line hold the mask this shape wants? Read under the line's read lock, and fills
3553 * `shape->roi_mask` from the area stored in its header on the way -- which is the point of the
3554 * header: neither dt_masks_get_area() nor dt_masks_get_mask() runs on a hit. It touches neither
3555 * the reference nor the lock, so its caller alone decides what is released and what is handed
3556 * on. TAKEN means the pixels are this shape's. */
3558 const struct dt_pixel_cache_entry_t *const entry,
3559 const dt_iop_roi_t *const roi_layer, const int dx, const int dy,
3560 rt_prepared_shape_t *shape)
3561{
3562 dt_masks_area_t area;
3563 memcpy(&area, data, sizeof(dt_masks_area_t));
3564 if(!rt_area_is_sane(&area)) return RT_MEMO_ABSENT;
3565
3566 rt_scaled_mask_roi(&area, roi_layer, dx, dy, shape->algo, &shape->roi_mask);
3567 if(!rt_shape_has_effect(shape, dx, dy)) return RT_MEMO_ABSENT;
3568
3569 /* A hash identifies content, never a size. A line that cannot hold this mask is not this
3570 * mask: refuse it rather than read past its end. */
3571 const size_t needed
3572 = RT_MASK_MEMO_HEADER + (size_t)shape->roi_mask.width * shape->roi_mask.height * sizeof(float);
3574
3575 return RT_MEMO_TAKEN;
3576}
3577
3578/* The memoised mask, if there is one.
3579 *
3580 * `dt_dev_pixelpipe_cache_ref_entry_by_hash()` takes the reference BEFORE it knows whether the
3581 * line has a buffer, and answers TRUE for one that has none yet. Every exit from here therefore
3582 * releases what it took, except the single one that hands it to the shape -- a reference left
3583 * behind is not a leak that shows up as a crash, it is a line the cache can never evict again. */
3585 const dt_iop_roi_t *const roi_layer, const int dx, const int dy,
3586 rt_prepared_shape_t *shape)
3587{
3589
3590 void *data = NULL;
3591 struct dt_pixel_cache_entry_t *entry = NULL;
3593 return RT_MEMO_ABSENT;
3594
3595 if(IS_NULL_PTR(data))
3596 {
3598 return RT_MEMO_ABSENT;
3599 }
3600
3602 const rt_memo_result_t result = rt_scaled_mask_memo_holds(data, entry, roi_layer, dx, dy, shape);
3603
3604 if(result == RT_MEMO_TAKEN)
3605 {
3606 // the reference and the read lock travel with it, until rt_release_shape()
3607 shape->mask = (float *)((char *)data + RT_MASK_MEMO_HEADER);
3608 shape->mask_entry = entry;
3609 if(ctx->stats) ctx->stats->masks_hit++;
3610 return RT_MEMO_TAKEN;
3611 }
3612
3615 return result;
3616}
3617
3618/* Rasterise the mask into a memo line and hand that line to the shape. ABSENT when no line can
3619 * be had, which is not a failure: the caller rasterises into a buffer of its own instead. */
3621 const uint64_t hash, const dt_masks_area_t *const area,
3622 const dt_iop_roi_t *const roi_layer, rt_prepared_shape_t *shape)
3623{
3625
3626 const size_t bytes = RT_MASK_MEMO_HEADER
3627 + (size_t)shape->roi_mask.width * shape->roi_mask.height * sizeof(float);
3628
3629 void *data = NULL;
3630 struct dt_pixel_cache_entry_t *entry = NULL;
3631 const int created
3632 = dt_dev_pixelpipe_cache_get(hash, bytes, "retouch shape mask", ctx->pipe->type, TRUE, &data, &entry);
3633
3634 if(IS_NULL_PTR(data) || IS_NULL_PTR(entry))
3635 {
3636 if(!IS_NULL_PTR(entry))
3637 {
3638 // Created without a buffer: flagged while still held, so the release removes it rather than
3639 // leave a line a later get() would allocate and hand back unwritten.
3640 if(created)
3641 {
3644 }
3646 }
3647 return RT_MEMO_ABSENT;
3648 }
3649
3650 /* Not created means another thread published this line between the lookup and here. Its
3651 * content is this same mask, so take it rather than rasterise a second copy. */
3652 if(created)
3653 {
3654 memcpy(data, area, sizeof(dt_masks_area_t));
3656 (float *)((char *)data + RT_MASK_MEMO_HEADER));
3657 if(status != RT_SHAPE_READY)
3658 {
3659 // Flagged while still held and before the write lock goes, so the release removes it.
3663 return RT_MEMO_REFUSED;
3664 }
3666 }
3667
3668 /* Read-locked for as long as the caller holds it, so a reader can never overlap the writer
3669 * that is still filling a line it found already created. */
3671 shape->mask = (float *)((char *)data + RT_MASK_MEMO_HEADER);
3672 shape->mask_entry = entry;
3673 if(!created && ctx->stats) ctx->stats->masks_hit++; // published by another thread, not by us
3674 return RT_MEMO_TAKEN;
3675}
3676
3677// Fill in `shape->roi_mask` and `shape->mask`, rasterising only when the memo has no answer.
3678// `shape->dx`, `shape->dy` and `shape->algo` must already be set: the resampling depends on them.
3680 const uint64_t shape_hash, const dt_iop_roi_t *const roi_layer,
3681 rt_prepared_shape_t *shape)
3682{
3683 const int dx = (int)shape->dx;
3684 const int dy = (int)shape->dy;
3685 const uint64_t hash = rt_scaled_mask_hash(shape_hash, roi_layer, dx, dy, shape->algo);
3686
3687 switch(rt_scaled_mask_take_memo(ctx, hash, roi_layer, dx, dy, shape))
3688 {
3689 case RT_MEMO_TAKEN: return RT_SHAPE_READY;
3690 case RT_MEMO_REFUSED: return RT_SHAPE_SKIP;
3691 case RT_MEMO_ABSENT: break;
3692 }
3693
3694 dt_masks_area_t area;
3695 if(!rt_shape_box(ctx, form, shape_hash, RT_BOX_AREA, &area)) return RT_SHAPE_SKIP;
3696 if(!rt_area_is_sane(&area)) return RT_SHAPE_SKIP;
3697
3698 rt_scaled_mask_roi(&area, roi_layer, dx, dy, shape->algo, &shape->roi_mask);
3699 if(!rt_shape_has_effect(shape, dx, dy)) return RT_SHAPE_SKIP;
3700
3701 switch(rt_scaled_mask_publish(ctx, form, hash, &area, roi_layer, shape))
3702 {
3703 case RT_MEMO_TAKEN: return RT_SHAPE_READY;
3704 case RT_MEMO_REFUSED: return RT_SHAPE_SKIP;
3705 case RT_MEMO_ABSENT: break;
3706 }
3707
3708 // no memo line available: the previous behaviour, one buffer per shape per frame
3709 const size_t elements = (size_t)shape->roi_mask.width * shape->roi_mask.height;
3710 float *buffer = dt_pixelpipe_cache_alloc_align_float_cache(elements, 0);
3711 if(IS_NULL_PTR(buffer))
3712 {
3713 fprintf(stderr, "rt_shape_scaled_mask: error allocating memory\n");
3714 return RT_SHAPE_ERROR;
3715 }
3716
3718 = rt_rasterize_scaled_mask(ctx, form, &area, roi_layer, &shape->roi_mask, buffer);
3719 if(status != RT_SHAPE_READY)
3720 {
3722 return status;
3723 }
3724
3725 shape->mask = buffer;
3726 shape->mask_entry = NULL;
3727 return RT_SHAPE_READY;
3728}
3729
3730// Hand back whatever rt_prepare_shape() took. Safe on a shape it returned SKIP or ERROR for.
3732{
3733 if(!IS_NULL_PTR(shape->mask_entry))
3734 {
3737 shape->mask_entry = NULL;
3738 shape->mask = NULL;
3739 return;
3740 }
3742 shape->mask = NULL;
3743}
3744
3745// Everything the CPU and OpenCL callbacks need before applying one group member at `scale`: the
3746// member resolved in the run's snapshot, its offset to the source, and its mask laid out in the
3747// layer. Kept in one place so the two paths cannot drift; every outcome but RT_SHAPE_READY
3748// leaves nothing to release.
3750 const int scale, const uint64_t base_hash,
3752{
3753 const dt_iop_retouch_params_t *p = (const dt_iop_retouch_params_t *)ctx->piece->data;
3754 int formid = 0;
3755 int index = -1;
3756 dt_masks_form_t *form = rt_pipe_member_form(ctx->pipe, p, member, &formid, &index);
3757
3758 *shape = (rt_prepared_shape_t){ 0 };
3759
3760 // only process current scale
3761 if(index >= 0 && p->rt_forms[index].scale != scale) return RT_SHAPE_SKIP;
3762 if(IS_NULL_PTR(form))
3763 {
3764 // an unknown index is expected right after going back in history: the group still lists the
3765 // shape while rt_forms[] no longer does
3766 fprintf(stderr, "rt_process_forms: missing form=%i from %s\n", formid, index < 0 ? "array" : "masks");
3767 return RT_SHAPE_SKIP;
3768 }
3769
3771 shape->index = index;
3772 shape->opacity = grpt->opacity;
3773 shape->algo = p->rt_forms[index].algorithm;
3774
3775 // search the delta with the source
3776 if(shape->algo != DT_IOP_RETOUCH_BLUR && shape->algo != DT_IOP_RETOUCH_FILL
3777 && !rt_masks_get_delta_to_destination(ctx, roi_layer, form, &shape->dx, &shape->dy,
3778 p->rt_forms[index].distort_mode))
3779 return RT_SHAPE_SKIP;
3780
3781 /* No separate "is the shape in this layer" test: rt_scaled_mask_roi() intersects the shape's
3782 * area with the layer, source offset included, and a shape that draws nothing there comes out
3783 * of it too small to have an effect. That is the same answer dt_masks_form_is_in_roi() gave,
3784 * reached without rasterising the area a second time. */
3786}
3787
3788static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
3789{
3790 int scale = scale1;
3792 dt_iop_module_t *self = usr_d->self;
3793 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
3794 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
3795
3796 // if preview a single scale, just process that scale and original image
3797 // unless merge is activated
3798 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0) return 0;
3799 // do not process the reconstructed image
3800 if(scale > wt_p->scales + 1) return 0;
3801
3803 dt_iop_roi_t *roi_layer = &usr_d->roi;
3804 // In single-scale preview (return_layer > 0), the filter above already restricted us to scale 0
3805 // and the previewed scale itself -- both are exactly what this preview shows, regardless of
3806 // which scale curr_scale/display_scale currently points at (scale 0's shapes always apply here,
3807 // even while a *different* scale is being previewed). Requiring scale == display_scale on top,
3808 // like plain mask display does, would only match when curr_scale happens to equal 0, silently
3809 // dropping the highlight for a scale-0 shape as soon as the user previews any other scale --
3810 // even though that same shape's effect keeps being applied and visible in "mask display" alone.
3811 const int mask_display
3812 = usr_d->mask_display && (wt_p->return_layer > 0 || scale == usr_d->display_scale);
3813
3814 // when the requested scales is grather than max scales the residual image index will be different from the one
3815 // defined by the user,
3816 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
3817 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
3818 {
3819 scale = p->num_scales + 1;
3820 }
3821
3822 if(usr_d->suppress_mask) return 0;
3823
3824 const rt_masks_ctx_t ctx = { self, pipe, piece, usr_d->stats };
3826
3827 // Iterate through all forms, resolved in the run's snapshot (see dt_masks_get_from_id_in_pipe()).
3828 for(const GList *forms = rt_pipe_group_members(pipe, piece); forms; forms = g_list_next(forms))
3829 {
3830 rt_prepared_shape_t shape;
3831 const rt_shape_status_t status = rt_prepare_shape(&ctx, forms, scale, base_hash, roi_layer, &shape);
3832 if(status == RT_SHAPE_ERROR) return 1;
3833 if(status != RT_SHAPE_READY) continue;
3834
3835 const int index = shape.index;
3836 const float form_opacity = shape.opacity;
3837 const dt_iop_retouch_algo_type_t algo = shape.algo;
3838 float *mask_scaled = shape.mask;
3840 const int dx = (int)shape.dx;
3841 const int dy = (int)shape.dy;
3842
3843 const double algo_start = usr_d->stats ? dt_get_wtime() : 0.0;
3844
3845 if(algo == DT_IOP_RETOUCH_CLONE)
3846 {
3848 {
3849 rt_release_shape(&shape);
3850 return 1;
3851 }
3852 }
3853 else if(algo == DT_IOP_RETOUCH_HEAL)
3854 {
3855 if(_retouch_heal(layer, roi_layer, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity, p->max_heal_iter,
3856 rt_heal_domain(p, scale1, wt_p->scales)) != 0)
3857 {
3858 rt_release_shape(&shape);
3859 return 1;
3860 }
3861 }
3862 else if(algo == DT_IOP_RETOUCH_BLUR)
3863 {
3865 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, wt_p->use_sse) != 0)
3866 {
3867 rt_release_shape(&shape);
3868 return 1;
3869 }
3870 }
3871 else if(algo == DT_IOP_RETOUCH_FILL)
3872 {
3873 // add a brightness to the color so it can be fine-adjusted by the user
3874 dt_aligned_pixel_t fill_color;
3875
3876 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
3877 {
3878 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
3879 }
3880 else
3881 {
3882 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
3883 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
3884 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
3885 }
3886 fill_color[3] = 0.0f;
3887
3889 }
3890 else
3891 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
3892
3893 if(usr_d->stats)
3894 {
3895 usr_d->stats->shapes_applied++;
3896 usr_d->stats->algo_seconds += dt_get_wtime() - algo_start;
3897 }
3898
3899 if(mask_display)
3901
3902 rt_release_shape(&shape);
3903 }
3904
3905 return 0;
3906}
3907
3908/* One line per render, after every wavelet scale has been through the callback. Together with
3909 * the modify_roi_in() line it accounts for the whole of what this module costs a frame, which is
3910 * the question a shape drag asks. */
3911static void rt_report_render(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *const roi,
3912 const rt_memo_stats_t *const stats, const double start, const char *const path)
3913{
3915 "[retouch] %-8s process %s %dx%d: %d shape(s), masks %d (%d memo / %d rasterised),"
3916 " boxes %d (%d memo / %d rasterised), algorithms %.3f s, total %.3f s\n",
3917 dt_pixelpipe_name(pipe->type), path, roi->width, roi->height, stats->shapes_applied,
3918 stats->masks_hit + stats->masks_rasterised, stats->masks_hit, stats->masks_rasterised,
3919 stats->boxes_hit + stats->boxes_computed, stats->boxes_hit, stats->boxes_computed,
3920 stats->algo_seconds, dt_get_wtime() - start);
3921}
3922
3925 const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3926 void *const ovoid, const dt_iop_roi_t *const roi_in,
3927 const dt_iop_roi_t *const roi_out, const int use_sse)
3928{
3929
3932
3933 float *in_retouch = NULL;
3934 int err = 0;
3935
3936 dt_iop_roi_t roi_retouch = *roi_in;
3938
3941 rt_memo_stats_t counters = { 0 };
3942 const gboolean counting = rt_perf_enabled();
3943 const double render_start = counting ? dt_get_wtime() : 0.0;
3944
3945 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
3946 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
3947
3948 // we will do all the clone, heal, etc on the input image,
3949 // this way the source for one algorithm can be the destination from a previous one
3950 in_retouch = dt_pixelpipe_cache_alloc_align_float_cache((size_t)4 * roi_rt->width * roi_rt->height, 0);
3952 {
3953 err = 1;
3954 goto cleanup;
3955 }
3956
3958
3959 // user data passed from the decompose routine to the one that process each scale
3960 usr_data.self = self;
3961 usr_data.pipe = pipe;
3962 usr_data.piece = piece;
3963 usr_data.roi = *roi_rt;
3965 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
3966 && (pipe == self->dev->pipe));
3967 usr_data.display_scale = p->curr_scale;
3968 usr_data.stats = counting ? &counters : NULL;
3969
3970 // init the decompose routine
3971 dwt_p = dt_dwt_init(in_retouch, roi_rt->width, roi_rt->height, 4, p->num_scales,
3972 (!display_wavelet_scale || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
3973 p->merge_from_scale, &usr_data,
3974 roi_in->scale, use_sse);
3975 if(IS_NULL_PTR(dwt_p))
3976 {
3977 err = 1;
3978 goto cleanup;
3979 }
3980
3981 // check if this module should expose mask.
3983 && (g->mask_display || display_wavelet_scale) && self->dev->gui_attached
3984 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
3985 {
3986 for(size_t j = 0; j < (size_t)roi_rt->width * roi_rt->height * 4; j += 4) in_retouch[j + 3] = 0.f;
3987
3989 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
3990 usr_data.mask_display = 1;
3991 }
3992
3994 {
3995 // check if the image support this number of scales
3996 if(gui_active)
3997 {
3998 const int max_scales = dwt_get_max_scale(dwt_p);
3999 if(dwt_p->scales > max_scales)
4000 {
4001 dt_control_log(_("max scale is %i for this image size"), max_scales);
4002 }
4003 }
4004 // get first scale visible at this zoom level
4005 if(g) g->first_scale_visible = dt_dwt_first_scale_visible(dwt_p);
4006 }
4007
4008 // decompose it
4010 {
4011 err = 1;
4012 goto cleanup;
4013 }
4014
4016
4017 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
4018
4019 // process auto levels
4021 {
4023 if(g->preview_auto_levels == 1 && !dt_gui_widgets_suppressed())
4024 {
4025 g->preview_auto_levels = -1;
4026
4028
4029 levels[0] = levels[1] = levels[2] = 0;
4030 rt_process_stats(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
4032
4033 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
4034
4036 g->preview_auto_levels = 2;
4037 }
4039 }
4040
4041 // if user wants to preview a detail scale adjust levels
4042 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
4043 {
4044 rt_adjust_levels(self, pipe, in_retouch, roi_rt->width, roi_rt->height, 4, levels);
4045 }
4046
4047 // copy alpha channel if needed
4048 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && !IS_NULL_PTR(g) && !g->mask_display)
4049 {
4050 dt_iop_alpha_copy(ivoid, in_retouch, roi_rt->width, roi_rt->height);
4051 }
4052
4053 // return final image
4054 rt_copy_in_to_out(in_retouch, roi_rt, ovoid, roi_out, 4, 0, 0);
4055
4056cleanup:
4058 if(dwt_p) dt_dwt_free(dwt_p);
4059 return err;
4060}
4061
4062int process(struct dt_iop_module_t *self, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
4063 void *const ovoid)
4064{
4065 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4066 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4067 return process_internal(self, pipe, piece, ivoid, ovoid, roi_in, roi_out, 0);
4068}
4069
4071 const float *const in, float *const out, const dt_iop_roi_t *const roi_in,
4072 const dt_iop_roi_t *const roi_out)
4073{
4074 (void)pipe;
4075 rt_copy_in_to_out(in, roi_in, out, roi_out, 1, 0, 0);
4076}
4077
4078#ifdef HAVE_OPENCL
4079
4081 const int devid, cl_mem dev_img, const int width, const int height,
4082 float levels[3])
4083{
4084 cl_int err = CL_SUCCESS;
4085
4086 const int ch = 4;
4087
4088 float *src_buffer = NULL;
4089
4092 {
4093 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
4095 goto cleanup;
4096 }
4097
4099 (size_t)width * height * ch * sizeof(float), CL_TRUE);
4100 if(err != CL_SUCCESS)
4101 {
4102 goto cleanup;
4103 }
4104
4105 // just call the CPU version for now
4107
4108 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
4109 if(err != CL_SUCCESS)
4110 {
4111 goto cleanup;
4112 }
4113
4114cleanup:
4116
4117 return err;
4118}
4119
4121 const int devid, cl_mem dev_img, const int width, const int height,
4122 const float levels[3])
4123{
4124 cl_int err = CL_SUCCESS;
4125
4126 const int ch = 4;
4127
4128 float *src_buffer = NULL;
4129
4132 {
4133 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
4135 goto cleanup;
4136 }
4137
4139 (size_t)width * height * ch * sizeof(float), CL_TRUE);
4140 if(err != CL_SUCCESS)
4141 {
4142 goto cleanup;
4143 }
4144
4145 // just call the CPU version for now
4147
4148 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, CL_TRUE);
4149 if(err != CL_SUCCESS)
4150 {
4151 goto cleanup;
4152 }
4153
4154cleanup:
4156
4157 return err;
4158}
4159
4160static cl_int rt_copy_in_to_out_cl(const int devid, cl_mem dev_in, const struct dt_iop_roi_t *const roi_in,
4161 cl_mem dev_out, const struct dt_iop_roi_t *const roi_out, const int dx,
4162 const int dy, const int kernel)
4163{
4164 cl_int err = CL_SUCCESS;
4165
4166 const int xoffs = roi_out->x - roi_in->x - dx;
4167 const int yoffs = roi_out->y - roi_in->y - dy;
4168
4169 cl_mem dev_roi_in = NULL;
4170 cl_mem dev_roi_out = NULL;
4171
4172 const size_t sizes[]
4173 = { ROUNDUPDWD(MIN(roi_out->width, roi_in->width), devid), ROUNDUPDHT(MIN(roi_out->height, roi_in->height), devid), 1 };
4174
4175 dev_roi_in = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_in);
4176 dev_roi_out = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_out);
4178 {
4179 fprintf(stderr, "rt_copy_in_to_out_cl error 1\n");
4181 goto cleanup;
4182 }
4183
4184 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
4185 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_in);
4186 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_out);
4187 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_out);
4188 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), (void *)&xoffs);
4189 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), (void *)&yoffs);
4191 if(err != CL_SUCCESS)
4192 {
4193 fprintf(stderr, "rt_copy_in_to_out_cl error 2\n");
4194 goto cleanup;
4195 }
4196
4197cleanup:
4200
4201 return err;
4202}
4203
4204// Upload a prepared shape's mask to the device. The host copy stays valid -- it belongs to the
4205// shape, and _retouch_heal_cl() still needs it, heal itself running on the CPU.
4206static cl_int rt_upload_scaled_mask_cl(const int devid, const rt_prepared_shape_t *const shape,
4207 cl_mem *p_dev_mask_scaled)
4208{
4209 const size_t bytes = sizeof(float) * shape->roi_mask.width * shape->roi_mask.height;
4210
4211 const cl_mem dev_mask_scaled = dt_opencl_alloc_device_buffer(devid, bytes);
4213 {
4214 fprintf(stderr, "rt_upload_scaled_mask_cl: error allocating the device buffer\n");
4216 }
4217
4218 const cl_int err = dt_opencl_write_buffer_to_device(devid, shape->mask, dev_mask_scaled, 0, bytes, CL_TRUE);
4219 if(err != CL_SUCCESS)
4220 {
4221 fprintf(stderr, "rt_upload_scaled_mask_cl: error writing the mask to the device\n");
4223 return err;
4224 }
4225
4227 return CL_SUCCESS;
4228}
4229
4230static cl_int rt_copy_image_masked_cl(const int devid, cl_mem dev_src, cl_mem dev_dest,
4231 dt_iop_roi_t *const roi_dest, cl_mem dev_mask_scaled,
4232 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int kernel)
4233{
4234 cl_int err = CL_SUCCESS;
4235
4236 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
4237
4238 const cl_mem dev_roi_dest =
4240
4241 const cl_mem dev_roi_mask_scaled
4243
4245 {
4247 goto cleanup;
4248 }
4249
4250 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_src);
4251 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_dest);
4252 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_roi_dest);
4253 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_mask_scaled);
4254 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
4255 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&opacity);
4257 if(err != CL_SUCCESS) goto cleanup;
4258
4259cleanup:
4262
4263 return err;
4264}
4265
4266static cl_int rt_copy_mask_to_alpha_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
4268 const float opacity, dt_iop_retouch_global_data_t *gd)
4269{
4270 cl_int err = CL_SUCCESS;
4271
4272 // fill it
4273 const int kernel = gd->kernel_retouch_copy_mask_to_alpha;
4274 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
4275
4276 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
4277 const cl_mem dev_roi_mask_scaled
4280 {
4282 goto cleanup;
4283 }
4284
4285 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4286 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
4287 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
4288 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
4289 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
4291 if(err != CL_SUCCESS) goto cleanup;
4292
4293
4294cleanup:
4297
4298 return err;
4299}
4300
4301static cl_int _retouch_clone_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
4302 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
4303 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
4304{
4305 cl_int err = CL_SUCCESS;
4306
4307 const int ch = 4;
4308
4309 // alloc source temp image to avoid issues when areas self-intersects
4310 const cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
4311 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4312 if(IS_NULL_PTR(dev_src))
4313 {
4314 fprintf(stderr, "retouch_clone_cl error 2\n");
4316 goto cleanup;
4317 }
4318
4319 // copy source image to tmp
4320 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
4321 gd->kernel_retouch_copy_buffer_to_buffer);
4322 if(err != CL_SUCCESS)
4323 {
4324 fprintf(stderr, "retouch_clone_cl error 4\n");
4325 goto cleanup;
4326 }
4327
4328 // clone it
4330 gd->kernel_retouch_copy_buffer_to_buffer_masked);
4331 if(err != CL_SUCCESS)
4332 {
4333 fprintf(stderr, "retouch_clone_cl error 5\n");
4334 goto cleanup;
4335 }
4336
4337cleanup:
4339
4340 return err;
4341}
4342
4343static cl_int _retouch_fill_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
4344 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
4346{
4347 cl_int err = CL_SUCCESS;
4348
4349 // fill it
4350 const int kernel = gd->kernel_retouch_fill;
4351 const size_t sizes[] = { ROUNDUPDWD(roi_mask_scaled->width, devid), ROUNDUPDHT(roi_mask_scaled->height, devid), 1 };
4352
4353 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
4354 const cl_mem dev_roi_mask_scaled
4357 {
4359 goto cleanup;
4360 }
4361
4362 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4363 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
4364 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
4365 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
4366 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
4367 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&(color[0]));
4368 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), (void *)&(color[1]));
4369 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), (void *)&(color[2]));
4371 if(err != CL_SUCCESS) goto cleanup;
4372
4373
4374cleanup:
4377
4378 return err;
4379}
4380
4381static cl_int _retouch_blur_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
4382 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
4383 const int blur_type, const float blur_radius, dt_iop_retouch_global_data_t *gd)
4384{
4385 cl_int err = CL_SUCCESS;
4386
4387 if(fabsf(blur_radius) <= 0.1f) return err;
4388
4389 const float sigma = blur_radius * roi_layer->scale;
4390 const int ch = 4;
4391
4392 const cl_mem dev_dest =
4393 dt_opencl_alloc_device(devid, roi_mask_scaled->width, roi_mask_scaled->height, sizeof(float) * ch);
4394 if(IS_NULL_PTR(dev_dest))
4395 {
4396 fprintf(stderr, "retouch_blur_cl error 2\n");
4398 goto cleanup;
4399 }
4400
4401 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
4402 {
4403 const int kernel = gd->kernel_retouch_image_rgb2lab;
4404 size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
4405
4406 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4407 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
4408 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
4410 if(err != CL_SUCCESS) goto cleanup;
4411 }
4412
4413 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
4414 gd->kernel_retouch_copy_buffer_to_image);
4415 if(err != CL_SUCCESS)
4416 {
4417 fprintf(stderr, "retouch_blur_cl error 4\n");
4418 goto cleanup;
4419 }
4420
4421 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
4422 {
4423 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
4424 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
4425
4428 if(g)
4429 {
4430 err = dt_gaussian_blur_cl(g, dev_dest, dev_dest);
4432 if(err != CL_SUCCESS) goto cleanup;
4433 }
4434 }
4435 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
4436 {
4437 const float sigma_r = 100.0f; // does not depend on scale
4438 const float sigma_s = sigma;
4439 const float detail = -1.0f; // we want the bilateral base layer
4440
4443 if(b)
4444 {
4445 err = dt_bilateral_splat_cl(b, dev_dest);
4447 if(err == CL_SUCCESS) err = dt_bilateral_slice_cl(b, dev_dest, dev_dest, detail);
4448
4450 }
4451 }
4452
4453 // copy blurred (temp) image to destination image
4455 gd->kernel_retouch_copy_image_to_buffer_masked);
4456 if(err != CL_SUCCESS)
4457 {
4458 fprintf(stderr, "retouch_blur_cl error 5\n");
4459 goto cleanup;
4460 }
4461
4462 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
4463 {
4464 const int kernel = gd->kernel_retouch_image_lab2rgb;
4465 const size_t sizes[] = { ROUNDUPDWD(roi_layer->width, devid), ROUNDUPDHT(roi_layer->height, devid), 1 };
4466
4467 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4468 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
4469 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
4471 if(err != CL_SUCCESS) goto cleanup;
4472 }
4473
4474cleanup:
4476
4477 return err;
4478}
4479
4480static cl_int _retouch_heal_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, float *mask_scaled,
4481 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
4482 const int dy, const float opacity, const dt_iop_retouch_global_data_t *const gd,
4483 const int max_iter, const dt_heal_domain_t domain)
4484{
4485 cl_int err = CL_SUCCESS;
4486
4487 const int ch = 4;
4488
4489 cl_mem dev_dest = NULL;
4490 cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
4491 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4492 if(IS_NULL_PTR(dev_src))
4493 {
4494 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4496 goto cleanup;
4497 }
4498
4499 dev_dest = dt_opencl_alloc_device_buffer(devid,
4500 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4501 if(IS_NULL_PTR(dev_dest))
4502 {
4503 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4505 goto cleanup;
4506 }
4507
4508 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
4509 gd->kernel_retouch_copy_buffer_to_buffer);
4510 if(err != CL_SUCCESS)
4511 {
4512 fprintf(stderr, "retouch_heal_cl error 4\n");
4513 goto cleanup;
4514 }
4515
4516 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
4517 gd->kernel_retouch_copy_buffer_to_buffer);
4518 if(err != CL_SUCCESS)
4519 {
4520 fprintf(stderr, "retouch_heal_cl error 4\n");
4521 goto cleanup;
4522 }
4523
4524 // heal it
4526 if(hp)
4527 {
4528 err = dt_heal_cl(hp, dev_src, dev_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height,
4529 (dt_heal_solver_t){ .max_iter = max_iter, .domain = domain });
4531
4533 dev_src = NULL;
4534
4535 if(err != CL_SUCCESS) goto cleanup;
4536 }
4537
4538 // copy healed (temp) image to destination image
4540 gd->kernel_retouch_copy_buffer_to_buffer_masked);
4541 if(err != CL_SUCCESS)
4542 {
4543 fprintf(stderr, "retouch_heal_cl error 6\n");
4544 goto cleanup;
4545 }
4546
4547cleanup:
4550
4551 return err;
4552}
4553
4554static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
4555{
4556 cl_int err = CL_SUCCESS;
4557
4558 int scale = scale1;
4560 dt_iop_module_t *self = usr_d->self;
4561 const dt_dev_pixelpipe_t *pipe = usr_d->pipe;
4562 const dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
4563
4564 // if preview a single scale, just process that scale and original image
4565 // unless merge is activated
4566 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0)
4567 return err;
4568 // do not process the reconstructed image
4569 if(scale > wt_p->scales + 1) return err;
4570
4573 const int devid = pipe->devid;
4574 dt_iop_roi_t *roi_layer = &usr_d->roi;
4575 // See the CPU rt_process_forms() for why return_layer > 0 bypasses the display_scale match.
4576 const int mask_display
4577 = usr_d->mask_display && (wt_p->return_layer > 0 || scale == usr_d->display_scale);
4578
4579 // when the requested scales is grather than max scales the residual image index will be different from the one
4580 // defined by the user,
4581 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
4582 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
4583 {
4584 scale = p->num_scales + 1;
4585 }
4586
4587 const rt_masks_ctx_t ctx = { self, pipe, piece, usr_d->stats };
4589
4590 // Iterate through all forms, resolved in the same snapshot as the CPU rt_process_forms().
4591 if(!usr_d->suppress_mask)
4592 {
4593 for(const GList *forms = rt_pipe_group_members(pipe, piece); forms && err == CL_SUCCESS;
4594 forms = g_list_next(forms))
4595 {
4596 rt_prepared_shape_t shape;
4598 = rt_prepare_shape(&ctx, forms, scale, base_hash, roi_layer, &shape);
4599 if(status == RT_SHAPE_ERROR)
4600 {
4602 break;
4603 }
4604 if(status != RT_SHAPE_READY) continue;
4605
4606 const int index = shape.index;
4607 const float form_opacity = shape.opacity;
4608 const dt_iop_retouch_algo_type_t algo = shape.algo;
4609 float *mask_scaled = shape.mask;
4611 const int dx = (int)shape.dx;
4612 const int dy = (int)shape.dy;
4613
4614 cl_mem dev_mask_scaled = NULL;
4615 err = rt_upload_scaled_mask_cl(devid, &shape, &dev_mask_scaled);
4616
4617 /* On the device this measures the upload and the enqueue, not the kernels -- except for
4618 * heal, which reads back, solves on the CPU and writes again, and is therefore genuinely
4619 * synchronous here. */
4620 const double algo_start = usr_d->stats ? dt_get_wtime() : 0.0;
4621
4622 if(err == CL_SUCCESS)
4623 {
4624 if(algo == DT_IOP_RETOUCH_CLONE)
4625 {
4627 form_opacity, gd);
4628 }
4629 else if(algo == DT_IOP_RETOUCH_HEAL)
4630 {
4632 dy, form_opacity, gd, p->max_heal_iter, rt_heal_domain(p, scale1, wt_p->scales));
4633 }
4634 else if(algo == DT_IOP_RETOUCH_BLUR)
4635 {
4637 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, gd);
4638 }
4639 else if(algo == DT_IOP_RETOUCH_FILL)
4640 {
4641 // add a brightness to the color so it can be fine-adjusted by the user
4642 dt_aligned_pixel_t fill_color;
4643
4644 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
4645 {
4646 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
4647 }
4648 else
4649 {
4650 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
4651 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
4652 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
4653 }
4654
4656 fill_color, gd);
4657 }
4658 else
4659 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
4660
4661 if(mask_display)
4663 gd);
4664 }
4665
4666 if(usr_d->stats)
4667 {
4668 usr_d->stats->shapes_applied++;
4669 usr_d->stats->algo_seconds += dt_get_wtime() - algo_start;
4670 }
4671
4672 rt_release_shape(&shape);
4674 }
4675 }
4676
4677 return err;
4678}
4679
4680int 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)
4681{
4682 const dt_iop_roi_t *const roi_in = &piece->roi_in;
4683 const dt_iop_roi_t *const roi_out = &piece->roi_out;
4687
4688 cl_int err = CL_SUCCESS;
4689 const int devid = pipe->devid;
4690
4691 dt_iop_roi_t roi_retouch = *roi_in;
4693
4694 const int ch = piece->dsc_in.channels;
4696 rt_memo_stats_t counters = { 0 };
4697 const gboolean counting = rt_perf_enabled();
4698 const double render_start = counting ? dt_get_wtime() : 0.0;
4700
4701 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
4702 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
4703
4704 // we will do all the clone, heal, etc on the input image,
4705 // this way the source for one algorithm can be the destination from a previous one
4706 const cl_mem in_retouch = dt_opencl_alloc_device_buffer(devid, sizeof(float) * ch * roi_rt->width * roi_rt->height);
4708 {
4709 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error allocating memory for wavelet decompose on device %d\n", devid);
4711 goto cleanup;
4712 }
4713
4714 // copy input image to the new buffer
4715 {
4716 size_t origin[] = { 0, 0, 0 };
4717 size_t region[] = { roi_rt->width, roi_rt->height, 1 };
4718 err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, in_retouch, origin, region, 0);
4719 if(err != CL_SUCCESS) goto cleanup;
4720 }
4721
4722 // user data passed from the decompose routine to the one that process each scale
4723 usr_data.self = self;
4724 usr_data.pipe = pipe;
4725 usr_data.piece = piece;
4726 usr_data.roi = *roi_rt;
4728 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
4729 && (pipe == self->dev->pipe));
4730 usr_data.display_scale = p->curr_scale;
4731 usr_data.stats = counting ? &counters : NULL;
4732
4733 // init the decompose routine
4734 dwt_p = dt_dwt_init_cl(devid, in_retouch, roi_rt->width, roi_rt->height, p->num_scales,
4735 (!display_wavelet_scale
4736 || pipe->type != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
4737 p->merge_from_scale, &usr_data,
4738 roi_in->scale);
4739 if(IS_NULL_PTR(dwt_p))
4740 {
4741 dt_print(DT_DEBUG_OPENCL, "[retouch process_cl] error initializing wavelet decompose on device %d\n", devid);
4743 goto cleanup;
4744 }
4745
4746 // check if this module should expose mask.
4747 // Must mirror process_internal()'s CPU condition exactly: g->mask_display OR display_wavelet_scale,
4748 // not g->mask_display alone. Missing the wavelet-scale branch meant a wavelet-only OpenCL preview
4749 // never cleared alpha, never set pipe->mask_display, and so never made the downstream color-pipeline
4750 // modules bypass -- they ran their normal processing on the wavelet-domain buffer instead of getting
4751 // skipped, corrupting the preview.
4752 if(pipe->type == DT_DEV_PIXELPIPE_FULL && g && (g->mask_display || display_wavelet_scale)
4753 && self->dev->gui_attached
4754 && (self == self->dev->gui_module) && (pipe == self->dev->pipe))
4755 {
4756 const int kernel = gd->kernel_retouch_clear_alpha;
4757 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4758
4759 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&in_retouch);
4760 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_rt->width));
4761 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->height));
4763 if(err != CL_SUCCESS) goto cleanup;
4764
4766 ((dt_dev_pixelpipe_t *)pipe)->bypass_blendif = 1;
4767 usr_data.mask_display = 1;
4768 }
4769
4771 {
4772 // check if the image support this number of scales
4773 if(gui_active)
4774 {
4776 if(dwt_p->scales > max_scales)
4777 {
4778 dt_control_log(_("max scale is %i for this image size"), max_scales);
4779 }
4780 }
4781 // get first scale visible at this zoom level
4782 if(g) g->first_scale_visible = dt_dwt_first_scale_visible_cl(dwt_p);
4783 }
4784
4785 // decompose it
4787 if(err != CL_SUCCESS) goto cleanup;
4788
4790
4791 dt_aligned_pixel_t levels = { p->preview_levels[0], p->preview_levels[1], p->preview_levels[2] };
4792
4793 // process auto levels
4794 if(g && pipe->type == DT_DEV_PIXELPIPE_FULL)
4795 {
4797 if(g->preview_auto_levels == 1 && !dt_gui_widgets_suppressed())
4798 {
4799 g->preview_auto_levels = -1;
4800
4802
4803 levels[0] = levels[1] = levels[2] = 0;
4804 err = rt_process_stats_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4805 if(err != CL_SUCCESS) goto cleanup;
4806
4808
4809 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
4810
4812 g->preview_auto_levels = 2;
4813 }
4815 }
4816
4817 // if user wants to preview a detail scale adjust levels
4818 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
4819 {
4820 err = rt_adjust_levels_cl(self, pipe, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4821 if(err != CL_SUCCESS) goto cleanup;
4822 }
4823
4824 // copy alpha channel if needed
4825 if((pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && g && !g->mask_display)
4826 {
4827 const int kernel = gd->kernel_retouch_copy_alpha;
4828 const size_t sizes[] = { ROUNDUPDWD(roi_rt->width, devid), ROUNDUPDHT(roi_rt->height, devid), 1 };
4829
4830 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
4831 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&in_retouch);
4832 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->width));
4833 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&(roi_rt->height));
4835 if(err != CL_SUCCESS) goto cleanup;
4836 }
4837
4838 // return final image
4839 err = rt_copy_in_to_out_cl(devid, in_retouch, roi_in, dev_out, roi_out, 0, 0,
4840 gd->kernel_retouch_copy_buffer_to_image);
4841
4842cleanup:
4844
4846
4847 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_retouch] couldn't enqueue kernel! %d\n", err);
4848
4849 return (err == CL_SUCCESS) ? TRUE : FALSE;
4850}
4851#endif
4852
4855static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
4856{
4857 dt_iop_module_t *self = (dt_iop_module_t *)user_data;
4859
4860 const int formid = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "formid"));
4861 const int algo = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "algo"));
4862 const int index = rt_get_index_from_formid(p, formid);
4863 if(index < 0) return;
4865 if(p->rt_forms[index].algorithm == algo) return;
4866 dt_masks_form_t *form = dt_masks_get_from_id(self->dev, formid);
4867 if(IS_NULL_PTR(form)) return;
4868
4869 // Apply the new algorithm to the form
4870 p->rt_forms[index].algorithm = algo;
4871
4872 // Switch the clone type of the form
4873 dt_masks_type_t masks_type = form->type;
4874 if(algo == DT_IOP_RETOUCH_CLONE || algo == DT_IOP_RETOUCH_HEAL)
4875 {
4876 masks_type |= DT_MASKS_CLONE;
4877 masks_type &= ~DT_MASKS_NON_CLONE;
4878 }
4879 else
4880 {
4881 masks_type &= ~DT_MASKS_CLONE;
4882 masks_type |= DT_MASKS_NON_CLONE;
4883 }
4884 form->type = masks_type;
4885
4886 // Update GUI
4887 rt_load_shape_algo_in_gui(self, formid);
4888
4889 dt_dev_add_history_item(self->dev, self, TRUE, TRUE);
4890}
4891
4892int populate_masks_context_menu(struct dt_iop_module_t *self, GtkWidget *menu, const int formid,const float pzx, const float pzy)
4893{
4895 const int index = rt_get_index_from_formid(p, formid);
4896 if(index == -1)
4897 {
4898 fprintf(stderr, "populate_masks_context_menu: missing form=%i from array\n", formid);
4899 return FALSE;
4900 }
4901
4902 GtkWidget *menu_item = ctx_gtk_menu_item_new_with_markup(_("Retouch correction"), menu, NULL, NULL);
4905
4906 static const struct
4907 {
4909 const char *name;
4910 } algo_entries[] = {{ DT_IOP_RETOUCH_CLONE, N_("Clone") },
4911 { DT_IOP_RETOUCH_HEAL, N_("Heal") },
4912 { DT_IOP_RETOUCH_BLUR, N_("Blur") },
4913 { DT_IOP_RETOUCH_FILL, N_("Fill") },};
4914
4915 for(size_t i = 0; i < G_N_ELEMENTS(algo_entries); i++)
4916 {
4917 const gboolean is_selected = (p->rt_forms[index].algorithm == algo_entries[i].algo);
4918 const char *const label = _(algo_entries[i].name);
4922
4925 }
4926
4927 return TRUE;
4928}
4929// clang-format off
4930// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4931// vim: shiftwidth=2 expandtab tabstop=2 cindent
4932// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4933// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:170
int levels(struct dt_imageio_module_data_t *data)
Definition avif.c:641
#define m
Definition basecurve.c:283
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3346
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3283
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3334
void dt_bauhaus_combobox_set(GtkWidget *widget, const int pos)
Definition bauhaus.c:2093
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1635
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3410
void dt_bilateral_free(dt_bilateral_t *b)
Definition bilateral.c:432
__DT_CLONE_TARGETS__ void dt_bilateral_splat(const dt_bilateral_t *b, const float *const in)
Definition bilateral.c:183
dt_bilateral_t * dt_bilateral_init(const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateral.c:157
__DT_CLONE_TARGETS__ void dt_bilateral_slice(const dt_bilateral_t *const b, const float *const in, float *out, const float detail)
Definition bilateral.c:356
void dt_bilateral_blur(const dt_bilateral_t *b)
Definition bilateral.c:341
float sigma_s
Definition bilateral.h:3
float sigma_r
Definition bilateral.h:3
void dt_bilateral_free_cl(dt_bilateral_cl_t *b)
Definition bilateralcl.c:60
cl_int dt_bilateral_slice_cl(dt_bilateral_cl_t *b, cl_mem in, cl_mem out, const float detail)
dt_bilateral_cl_t * dt_bilateral_init_cl(const int devid, const int width, const int height, const float sigma_s, const float sigma_r)
Definition bilateralcl.c:91
cl_int dt_bilateral_blur_cl(dt_bilateral_cl_t *b)
cl_int dt_bilateral_splat_cl(dt_bilateral_cl_t *b, cl_mem in)
const dt_iop_gui_blendif_colorstop_t _gradient_L[]
Definition blend_gui.c:95
The blending panel's GUI state and API, cut out of develop/blend.h.
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
@ IOP_CS_RGB
@ IOP_CS_LAB
void dt_iop_color_picker_reset(dt_iop_module_t *module, gboolean keep)
GtkWidget * dt_color_picker_new(dt_iop_module_t *module, dt_iop_color_picker_kind_t kind, GtkWidget *w)
@ DT_COLOR_PICKER_POINT
void dt_colorspaces_apply_profile(const char *const op_name, const char *const instance_name, const float *const image_in, float *const image_out, const int width, const int height, const int cst_from, const int cst_to, int *converted_cst, const dt_iop_order_iccprofile_info_t *const profile_info)
Convert a 4-channel float buffer between RGB and Lab through one profile.
const float f
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
dt_Lab_to_XYZ(Lab, XYZ)
static const float const float const float min
static dt_aligned_pixel_t XYZ
const float max
static dt_aligned_pixel_t Lab
const dt_colormatrix_t dt_aligned_pixel_t out
const float top
dt_XYZ_to_Lab(XYZ, Lab)
const float delta
void dt_conf_set_float(const char *name, float val)
void dt_conf_set_int(const char *name, int val)
int dt_conf_get_int(const char *name)
Integer for name, clamped to the bounds declared in the XML.
void dt_control_log(const char *msg,...)
Definition control.c:839
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:970
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:649
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_iop_params_t
Definition dev_history.h:43
#define dt_dev_pixelpipe_update_history_main(dev)
dt_iop_order_iccprofile_info_t * dt_ioppr_get_pipe_work_profile_info(const struct dt_dev_pixelpipe_t *pipe)
int dt_dev_distort_transform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1864
dt_dev_pixelpipe_display_mask_t
Definition develop.h:121
@ DT_DEV_PIXELPIPE_DISPLAY_MASK
Definition develop.h:123
@ DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU
Definition develop.h:141
@ DT_DEV_PIXELPIPE_DISPLAY_NONE
Definition develop.h:122
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
int dt_dwt_first_scale_visible_cl(dwt_params_cl_t *p)
Definition dwt.c:610
int dwt_decompose(dwt_params_t *p, _dwt_layer_func layer_func)
Definition dwt.c:380
int dwt_get_max_scale(dwt_params_t *p)
Definition dwt.c:92
void dt_dwt_free(dwt_params_t *p)
Definition dwt.c:64
dwt_params_cl_t * dt_dwt_init_cl(const int devid, cl_mem image, const int width, const int height, const int scales, const int return_layer, const int merge_from_scale, void *user_data, const float preview_scale)
Definition dwt.c:577
void dt_dwt_free_cl(dwt_params_cl_t *p)
Definition dwt.c:599
dwt_params_t * dt_dwt_init(float *image, const int width, const int height, const int ch, const int scales, const int return_layer, const int merge_from_scale, void *user_data, const float preview_scale, const int use_sse)
Definition dwt.c:43
cl_int dwt_decompose_cl(dwt_params_cl_t *p, _dwt_layer_func_cl layer_func)
Definition dwt.c:925
int dwt_get_max_scale_cl(dwt_params_cl_t *p)
Definition dwt.c:605
int dt_dwt_first_scale_visible(dwt_params_t *p)
Definition dwt.c:116
void dt_gaussian_free(dt_gaussian_t *g)
Definition gaussian.c:335
void dt_gaussian_free_cl(dt_gaussian_cl_t *g)
Definition gaussian.c:365
cl_int dt_gaussian_blur_cl(dt_gaussian_cl_t *g, cl_mem dev_in, cl_mem dev_out)
Definition gaussian.c:453
void dt_gaussian_blur_4c(dt_gaussian_t *g, const float *const in, float *const out)
Definition gaussian.c:330
dt_gaussian_cl_t * dt_gaussian_init_cl(const int devid, const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:376
dt_gaussian_t * dt_gaussian_init(const int width, const int height, const int channels, const float *max, const float *min, const float sigma, const int order)
Definition gaussian.c:127
@ DT_IOP_GAUSSIAN_ZERO
Definition gaussian.h:34
GdkRGBA color[]
Definition geotagging.c:541
void dtgtk_gradient_slider_multivalue_set_values(GtkDarktableGradientSlider *gslider, gdouble *values)
void dtgtk_gradient_slider_multivalue_get_values(GtkDarktableGradientSlider *gslider, gdouble *values)
GtkWidget * dtgtk_gradient_slider_multivalue_new_with_color_and_name(GdkRGBA start, GdkRGBA end, gint positions, gchar *name)
void dtgtk_gradient_slider_multivalue_set_marker(GtkDarktableGradientSlider *gslider, gint mark, gint pos)
void dtgtk_gradient_slider_multivalue_set_resetvalues(GtkDarktableGradientSlider *gslider, gdouble *values)
#define DTGTK_GRADIENT_SLIDER_MULTIVALUE(obj)
@ GRADIENT_SLIDER_MARKER_LOWER_OPEN_BIG
@ GRADIENT_SLIDER_MARKER_LOWER_FILLED_BIG
@ PROPORTIONAL_MARKERS
@ GRADIENT_SLIDER_GET
@ GRADIENT_SLIDER_SET
#define DT_GUI_MODULE(x)
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
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 dt_heal_solver_t solver)
Definition heal.c:410
void dt_heal_free_cl(heal_params_cl_t *p)
Definition heal.c:477
heal_params_cl_t * dt_heal_init_cl(const int devid)
Definition heal.c:465
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 dt_heal_solver_t solver)
Definition heal.c:483
dt_heal_domain_t
Definition heal.h:35
@ DT_HEAL_DOMAIN_SQRT
Definition heal.h:37
@ DT_HEAL_DOMAIN_LINEAR
Definition heal.h:36
float dt_boundingbox_t[4]
Definition image.h:83
__DT_CLONE_TARGETS__ void dt_iop_image_fill(float *const buf, const float fill_value, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.c:218
static void dt_iop_image_copy_by_size(float *const __restrict__ out, const float *const __restrict__ in, const size_t width, const size_t height, const size_t ch)
Definition imagebuf.h:91
void dt_iop_set_cache_bypass_variant(dt_iop_module_t *module, int variant)
Definition imageop.c:1702
void dt_iop_default_init(dt_iop_module_t *module)
Definition imageop.c:308
const char ** dt_iop_set_description(dt_iop_module_t *module, const char *main_text, const char *purpose, const char *input, const char *process, const char *output)
Definition imageop.c:1909
void dt_iop_set_cache_bypass(dt_iop_module_t *module, gboolean state)
Definition imageop.c:1685
void dt_iop_gui_leave_critical_section(dt_iop_module_t *const module)
Release what dt_iop_gui_enter_critical_section() took. Also a no-op headless.
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_INTERNAL_MASKS
Definition imageop.h:198
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:194
void dt_iop_gui_enter_critical_section(dt_iop_module_t *const module)
Take the module's GUI lock, serialising access to its dt_iop_gui_data_t.
@ IOP_GROUP_REPAIR
Definition imageop.h:159
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
GtkWidget * dt_bauhaus_slider_from_params(dt_iop_module_t *self, const char *param)
GtkWidget * dt_bauhaus_combobox_from_params(dt_iop_module_t *self, const char *param)
#define IOP_GUI_FREE
Definition imageop_gui.h:96
static dt_iop_gui_data_t * dt_iop_gui_data(const struct dt_iop_module_t *m)
The module's GUI data blob, NULL-safe for headless callers: IOP process() implementations read it for...
Definition imageop_gui.h:81
#define IOP_GUI_ALLOC(module)
Definition imageop_gui.h:93
void *const ovoid
static float kernel(const float *x, const float *y)
GtkWidget * dt_ui_section_label_new(const gchar *str)
Definition label.c:114
GtkWidget * dt_ui_label_new(const gchar *str)
Definition label.c:125
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
hash
Definition layers.h:90
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_OPENCL
Definition logging.h:57
@ DT_DEBUG_PERF
Definition logging.h:55
int32_t dt_get_debug_flags(void)
Definition darktable.c:2094
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t const size_t ch
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid, const float opacity, dt_masks_member_t *out)
Set a group member's opacity.
Definition masks.c:2048
dt_masks_raster_result_t dt_masks_get_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, dt_masks_area_t *area)
Definition masks.c:1647
dt_masks_form_t * dt_masks_get_from_id_in_pipe(const dt_dev_pixelpipe_t *pipe, int id)
Definition masks.c:933
@ DT_MASKS_RASTER_OK
Definition masks.h:314
uint64_t dt_masks_form_get_own_hash(uint64_t hash, GList *masks, const dt_masks_form_t *form)
Definition masks.c:1460
dt_masks_raster_result_t dt_masks_get_area(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, dt_masks_area_t *area)
Definition masks.c:464
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:901
void dt_masks_area_scale(dt_masks_area_t *area, const double scale)
Definition masks.c:450
gboolean dt_masks_area_intersects(const dt_masks_area_t *area, const dt_iop_roi_t *roi)
Definition masks.c:458
dt_masks_form_t * dt_masks_get_from_id_ext(GList *forms, int id)
Definition masks.c:915
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
dt_masks_raster_result_t dt_masks_get_source_area(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, dt_masks_area_t *area)
Definition masks.c:486
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_group_ungroup(dt_develop_t *dev, dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
Definition masks_gui.c:5356
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
dt_masks_form_group_t * dt_masks_form_get_selected_group_live(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Resolve a "live" selected group entry, even if GUI selection is stale.
Definition masks_gui.c:1539
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:113
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
Definition masks_gui.c:308
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4894
gboolean dt_masks_creation_mode_enter(dt_develop_t *dev, dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
Definition masks_gui.c:5762
dt_masks_form_group_t * dt_masks_group_add_form_with_state(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form, const int parentid, const dt_masks_state_t state, const float opacity)
Definition masks_gui.c:5317
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
@ DT_MASKS_SHAPE_INDEX_BRUSH
Definition masks_gui.h:480
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks_gui.h:478
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks_gui.h:479
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks_gui.h:477
@ DT_MASKS_SHAPE_BUTTONS_ALL
Definition masks_gui.h:498
@ DT_MASKS_EDIT_RESTRICTED
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
@ DT_MASKS_STATE_USE
Definition masks_types.h:89
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_BRUSH
Definition masks_types.h:70
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_NONE
Definition masks_types.h:63
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
dt_masks_result_t
What a request that may change the masks model did.
@ DT_MASKS_OK
@ DT_MASKS_UNCHANGED
#define DEVELOP_MASKS_NB_SHAPES
Definition masks_types.h:58
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GtkWidget * ctx_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:170
GtkWidget * ctx_gtk_check_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data, const gboolean checked, const gboolean show_checkbox)
Definition menu.c:241
uint32_t width
Definition mipmap_cache.c:0
dt_imageio_retval_t status
Definition mipmap_cache.c:8
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define o
#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE)
DT_MODULE() for a module whose params struct is introspected.
int dt_opencl_enqueue_kernel_2d(const int dev, const int kernel, const size_t *sizes)
Definition opencl.c:2612
void * dt_opencl_alloc_device_buffer(const int devid, const size_t size)
Definition opencl.c:3028
void * dt_opencl_alloc_device(const int devid, const int width, const int height, const int bpp)
Definition opencl.c:2952
int dt_opencl_create_kernel(const int prog, const char *name)
Definition opencl.c:2506
void * dt_opencl_copy_host_to_device_constant(const int devid, const size_t size, void *host)
Definition opencl.c:2808
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:2796
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:2785
void dt_opencl_free_kernel(const int kernel)
Definition opencl.c:2549
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:2603
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:2748
void dt_opencl_release_mem_object(cl_mem mem)
Definition opencl.c:2863
#define ROUNDUPDHT(a, b)
Definition opencl.h:86
#define DT_OPENCL_SYSMEM_ALLOCATION
Definition opencl.h:62
#define ROUNDUPDWD(a, b)
Definition opencl.h:85
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const char * dt_pixelpipe_name(dt_dev_pixelpipe_type_t pipe)
Definition pixelpipe.c:32
@ DT_DEV_PIXELPIPE_FULL
Definition pixelpipe.h:43
void dt_dev_pixelpipe_cache_wrlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the write lock on the entry.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Take or release one reference on a cache line. A referenced line is never evicted,...
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
int dt_dev_pixelpipe_cache_get(const uint64_t hash, const size_t size, const char *name, const int id, const gboolean alloc, void **data, dt_pixel_cache_entry_t **entry)
Get a cache line from the cache.
size_t dt_pixel_cache_entry_get_size(const dt_pixel_cache_entry_t *entry)
gboolean dt_dev_pixelpipe_cache_ref_entry_by_hash(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cache entry by hash.
void dt_dev_pixelpipe_cache_flag_auto_destroy(dt_pixel_cache_entry_t *cache_entry)
Flag a cache line as disposable (auto_destroy), for short-lived lines nothing will need again: the re...
Pixelpipe cache for storing intermediate results in the pixelpipe.
#define DT_PIXELPIPE_CACHE_HASH_INVALID
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
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:1051
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:1078
dt_iop_retouch_fill_modes_t
Definition retouch.c:102
@ DT_IOP_RETOUCH_FILL_COLOR
Definition retouch.c:104
@ DT_IOP_RETOUCH_FILL_ERASE
Definition retouch.c:103
static float rt_gslider_scale_callback(GtkWidget *self, float inval, int dir)
Definition retouch.c:1489
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:3237
void init(dt_iop_module_t *module)
Definition retouch.c:2046
static void rt_num_scales_update(const int _num_scales, dt_iop_module_t *self)
Definition retouch.c:1137
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:4070
static void rt_resynch_params(struct dt_iop_module_t *self, dt_iop_retouch_params_t *p, GList *forms_list)
Definition retouch.c:744
const char ** description(struct dt_iop_module_t *self)
Definition retouch.c:263
static rt_shape_status_t rt_rasterize_scaled_mask(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, const dt_masks_area_t *const area, const dt_iop_roi_t *const roi_layer, const dt_iop_roi_t *const roi_mask_scaled, float *const dst)
Definition retouch.c:3471
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:4266
int default_group()
Definition retouch.c:272
static float rt_get_shape_opacity(dt_iop_module_t *self, const int formid)
Definition retouch.c:476
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:3282
static rt_memo_result_t rt_scaled_mask_take_memo(const rt_masks_ctx_t *const ctx, const uint64_t hash, const dt_iop_roi_t *const roi_layer, const int dx, const int dy, rt_prepared_shape_t *shape)
Definition retouch.c:3584
static cl_int rt_upload_scaled_mask_cl(const int devid, const rt_prepared_shape_t *const shape, cl_mem *p_dev_mask_scaled)
Definition retouch.c:4206
static rt_shape_status_t rt_shape_scaled_mask(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, const uint64_t shape_hash, const dt_iop_roi_t *const roi_layer, rt_prepared_shape_t *shape)
Definition retouch.c:3679
static void rt_report_render(const dt_dev_pixelpipe_t *pipe, const dt_iop_roi_t *const roi, const rt_memo_stats_t *const stats, const double start, const char *const path)
Definition retouch.c:3911
#define RETOUCH_NO_FORMS
Definition retouch.c:90
static void rt_menu_select_algorithm_callback(GtkWidget *widget, gpointer user_data)
Definition retouch.c:4855
static gboolean rt_select_algorithm_callback(GtkToggleButton *togglebutton, GdkEventButton *e, dt_iop_module_t *self)
Definition retouch.c:1853
static gboolean rt_area_is_sane(const dt_masks_area_t *const area)
Definition retouch.c:3517
dt_iop_retouch_algo_type_t
Definition retouch.c:119
@ DT_IOP_RETOUCH_BLUR
Definition retouch.c:123
@ DT_IOP_RETOUCH_NONE
Definition retouch.c:120
@ DT_IOP_RETOUCH_HEAL
Definition retouch.c:122
@ DT_IOP_RETOUCH_FILL
Definition retouch.c:124
@ DT_IOP_RETOUCH_CLONE
Definition retouch.c:121
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:2138
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:4381
static const GList * rt_pipe_group_members(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2638
void masks_selection_changed(struct dt_iop_module_t *self, const int form_selected_id)
Definition retouch.c:2036
void gui_update(dt_iop_module_t *self)
Refresh GUI controls from current params and configuration.
Definition retouch.c:2187
static void rt_extend_roi_in_from_source_clones(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi_in, const int formid_src, const dt_masks_area_t *src, rt_roi_bounds_t *const bounds)
Definition retouch.c:2843
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:4080
dt_iop_retouch_drag_types_t
Definition retouch.c:97
@ DT_IOP_RETOUCH_WDBAR_DRAG_BOTTOM
Definition retouch.c:99
@ DT_IOP_RETOUCH_WDBAR_DRAG_TOP
Definition retouch.c:98
dt_iop_retouch_heal_algorithm_t
Definition retouch.c:109
@ DT_IOP_RETOUCH_HEAL_SQRT
Definition retouch.c:111
@ DT_IOP_RETOUCH_HEAL_LINEAR
Definition retouch.c:110
static float rt_masks_form_get_opacity(dt_iop_module_t *self, int formid)
Definition retouch.c:645
#define RT_MASK_MEMO_HEADER
Definition retouch.c:3513
static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
Definition retouch.c:2978
struct dt_iop_retouch_params_t dt_iop_retouch_data_t
Definition retouch.c:234
const char * aliases()
Definition retouch.c:257
static uint64_t rt_shape_geometry_hash(const uint64_t base, dt_masks_form_t *const form)
Definition retouch.c:2701
static void rt_merge_from_scale_update(const int _merge_from_scale, dt_iop_module_t *self)
Definition retouch.c:1188
static void rt_sync_mask_display_request(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1634
rt_memo_result_t
Definition retouch.c:3546
@ RT_MEMO_ABSENT
Definition retouch.c:3548
@ RT_MEMO_TAKEN
Definition retouch.c:3547
@ RT_MEMO_REFUSED
Definition retouch.c:3549
rt_shape_status_t
Definition retouch.c:3448
@ RT_SHAPE_ERROR
Definition retouch.c:3451
@ RT_SHAPE_READY
Definition retouch.c:3449
@ RT_SHAPE_SKIP
Definition retouch.c:3450
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:4230
void gui_focus(struct dt_iop_module_t *self, gboolean in)
Definition retouch.c:2095
#define RETOUCH_MAX_SCALES
Definition retouch.c:91
void init_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2175
static gboolean rt_edit_masks_callback(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1781
static int rt_get_selected_shape_id(const dt_iop_module_t *self)
Definition retouch.c:439
const char * name()
Definition retouch.c:252
#define RT_WDBAR_INSET
Definition retouch.c:1120
static dt_masks_form_group_t * rt_get_mask_point_group(dt_iop_module_t *self, int formid)
Definition retouch.c:452
static gboolean rt_wdbar_motion_notify(GtkWidget *widget, GdkEventMotion *event, dt_iop_module_t *self)
Definition retouch.c:1288
void gui_reset(struct dt_iop_module_t *self)
Definition retouch.c:2588
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:4301
static dt_heal_domain_t rt_heal_domain(const dt_iop_retouch_params_t *const p, const int scale, const int scales)
Definition retouch.c:3381
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:4160
static void rt_develop_ui_pipe_finished_callback(gpointer instance, gpointer user_data)
Definition retouch.c:1688
static void rt_compute_shape_geometry(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, rt_shape_geometry_t *out)
Definition retouch.c:2733
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, const dt_iop_retouch_global_data_t *const gd, const int max_iter, const dt_heal_domain_t domain)
Definition retouch.c:4480
static gboolean rt_any_preview_active(dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1603
void gui_init(dt_iop_module_t *self)
Definition retouch.c:2290
#define lw
Definition retouch.c:1121
static rt_shape_status_t rt_prepare_shape(const rt_masks_ctx_t *const ctx, const GList *member, const int scale, const uint64_t base_hash, const dt_iop_roi_t *roi_layer, rt_prepared_shape_t *shape)
Definition retouch.c:3749
void change_image(struct dt_iop_module_t *self)
Definition retouch.c:2265
static gboolean rt_shape_has_effect(const rt_prepared_shape_t *const shape, const int dx, const int dy)
Definition retouch.c:3538
static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
Definition retouch.c:2965
static void rt_display_selected_shapes_lbl(dt_develop_t *dev, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:538
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:3924
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:3031
void gui_changed(dt_iop_module_t *self, GtkWidget *w, void *previous)
Definition retouch.c:2002
static gboolean rt_perf_enabled(void)
Definition retouch.c:898
static uint64_t rt_scaled_mask_hash(const uint64_t shape_hash, const dt_iop_roi_t *const roi_layer, const int dx, const int dy, const dt_iop_retouch_algo_type_t algo)
Definition retouch.c:3523
#define RETOUCH_NO_SCALES
Definition retouch.c:92
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:2156
static void rt_roi_bounds_include(rt_roi_bounds_t *const bounds, const float left, const float top, const float right, const float bottom)
Definition retouch.c:2628
static int rt_shape_is_being_added(dt_iop_module_t *self, const int shape_type)
Definition retouch.c:1025
static gboolean rt_wdbar_draw(GtkWidget *widget, cairo_t *crf, dt_iop_module_t *self)
Definition retouch.c:1342
static rt_memo_result_t rt_scaled_mask_memo_holds(const void *const data, const struct dt_pixel_cache_entry_t *const entry, const dt_iop_roi_t *const roi_layer, const int dx, const int dy, rt_prepared_shape_t *shape)
Definition retouch.c:3557
static rt_memo_result_t rt_scaled_mask_publish(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, const uint64_t hash, const dt_masks_area_t *const area, const dt_iop_roi_t *const roi_layer, rt_prepared_shape_t *shape)
Definition retouch.c:3620
void gui_cleanup(dt_iop_module_t *self)
Definition retouch.c:2604
rt_box_t
Definition retouch.c:2728
@ RT_BOX_SOURCE
Definition retouch.c:2730
@ RT_BOX_AREA
Definition retouch.c:2729
static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
Definition retouch.c:4554
dt_iop_retouch_blur_types_t
Definition retouch.c:114
@ DT_IOP_RETOUCH_BLUR_GAUSSIAN
Definition retouch.c:115
@ DT_IOP_RETOUCH_BLUR_BILATERAL
Definition retouch.c:116
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:3136
void cleanup_global(dt_iop_module_so_t *module)
Definition retouch.c:2077
void reload_defaults(dt_iop_module_t *self)
Definition retouch.c:2597
static void rt_compute_roi_in(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi_in, rt_roi_bounds_t *const bounds)
Definition retouch.c:2802
static gboolean rt_wdbar_scrolled(GtkWidget *widget, GdkEventScroll *event, dt_iop_module_t *self)
Definition retouch.c:1264
void post_history_commit(dt_iop_module_t *self)
Definition retouch.c:814
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:1844
static gboolean rt_suppress_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1982
static void rt_scaled_mask_roi(const dt_masks_area_t *const area, const dt_iop_roi_t *const roi_in, const int dx, const int dy, const int algo, dt_iop_roi_t *roi_mask_scaled)
Definition retouch.c:3159
int default_colorspace(dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:282
static int rt_get_index_from_formid(const dt_iop_retouch_params_t *p, const int formid)
Definition retouch.c:423
static void rt_gslider_changed(GtkDarktableGradientSlider *gslider, dt_iop_module_t *self)
Definition retouch.c:1507
int flags()
Definition retouch.c:277
#define RETOUCH_PREVIEW_LVL_MIN
Definition retouch.c:94
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:1761
static void rt_show_forms_for_current_scale(dt_iop_module_t *self)
Definition retouch.c:677
static void rt_show_hide_controls(const dt_iop_module_t *self)
Definition retouch.c:493
static void rt_colorpick_color_set_callback(GtkColorButton *widget, dt_iop_module_t *self)
Definition retouch.c:1091
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:3259
static int rt_masks_get_delta_to_destination(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi, dt_masks_form_t *form, float *dx, float *dy, const int distort_mode)
Definition retouch.c:942
static int rt_get_selected_shape_index(dt_develop_t *dev, const dt_iop_retouch_params_t *p)
Definition retouch.c:550
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:3208
static dt_masks_form_t * rt_pipe_member_form(const dt_dev_pixelpipe_t *pipe, const dt_iop_retouch_params_t *p, const GList *member, int *formid, int *index)
Definition retouch.c:2647
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:4343
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:855
static gboolean rt_wdbar_button_release(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1254
static void rt_sync_bypass_cache(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1615
static int rt_scale_has_shapes(dt_iop_retouch_params_t *p, const int scale)
Definition retouch.c:1332
static int rt_masks_point_calc_delta(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi, const float *target, const float *source, float *dx, float *dy, const int distort_mode)
Definition retouch.c:903
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:4062
static gboolean rt_wdbar_leave_notify(GtkWidget *widget, GdkEventCrossing *event, dt_iop_module_t *self)
Definition retouch.c:1205
static void rt_extend_roi_in_for_clone(const rt_masks_ctx_t *const ctx, const dt_iop_roi_t *roi_in, rt_roi_bounds_t *const bounds)
Definition retouch.c:2890
static uint64_t rt_geometry_base_hash(const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2684
static gboolean rt_blend_mask_conflicts(dt_iop_module_t *module, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1594
static int rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
Definition retouch.c:3788
void cleanup_pipe(struct dt_iop_module_t *self, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece)
Definition retouch.c:2181
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, const dt_heal_domain_t domain)
Definition retouch.c:3387
static gboolean rt_showmask_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *module)
Definition retouch.c:1954
static void rt_load_shape_algo_in_gui(dt_iop_module_t *self, const int form_selected_id)
Definition retouch.c:556
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:4120
static gboolean rt_algo_needs_source(const dt_iop_retouch_algo_type_t algo)
Definition retouch.c:2657
static void rt_roi_bounds_include_area(rt_roi_bounds_t *const bounds, const dt_masks_area_t *area, const float dx, const float dy)
Definition retouch.c:2663
static void rt_curr_scale_update(const int _curr_scale, dt_iop_module_t *self)
Definition retouch.c:1156
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:2992
static gboolean rt_shape_box(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, const uint64_t shape_hash, const rt_box_t want, dt_masks_area_t *out)
Definition retouch.c:2754
static void rt_update_wd_bar_labels(dt_iop_retouch_params_t *p, dt_iop_retouch_gui_data_t *g)
Definition retouch.c:1123
void init_global(dt_iop_module_so_t *module)
Definition retouch.c:2058
static void rt_release_shape(rt_prepared_shape_t *shape)
Definition retouch.c:3731
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:3303
static void rt_roi_bounds_grow_for_blur(rt_roi_bounds_t *const bounds, const dt_masks_area_t *area, const int overlap)
Definition retouch.c:2672
static gboolean rt_auto_levels_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1725
static void rt_mask_opacity_callback(GtkWidget *slider, dt_iop_module_t *self)
Definition retouch.c:1746
static void rt_display_selected_fill_color(dt_iop_retouch_gui_data_t *g, dt_iop_retouch_params_t *p)
Definition retouch.c:486
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:4680
static gboolean rt_copypaste_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1557
static gboolean rt_wdbar_button_press(GtkWidget *widget, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1218
static void rt_fill_scaled_mask(const float *const mask, const dt_iop_roi_t *const roi_mask, float *const mask_scaled, const dt_iop_roi_t *const roi_mask_scaled, const dt_iop_roi_t *const roi_in)
Definition retouch.c:3178
static int rt_allow_create_form(dt_iop_module_t *self)
Definition retouch.c:665
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:4892
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:1524
static void rt_intersect_2_rois(const dt_iop_roi_t *const roi_1, const 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:3117
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:2918
static gboolean rt_display_wavelet_scale_callback(GtkToggleButton *togglebutton, GdkEventButton *event, dt_iop_module_t *self)
Definition retouch.c:1642
#define NEUTRAL_GRAY
#define RETOUCH_PREVIEW_LVL_MAX
Definition retouch.c:95
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:2611
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:291
static void rt_masks_form_change_opacity(dt_iop_module_t *self, int formid, float opacity)
Definition retouch.c:625
static void rt_paste_forms_from_scale(dt_iop_retouch_params_t *p, const int source_scale, const int dest_scale)
Definition retouch.c:654
static gboolean rt_compute_shape_box(const rt_masks_ctx_t *const ctx, dt_masks_form_t *form, const rt_box_t want, dt_masks_area_t *out)
Definition retouch.c:2745
static cairo_surface_t * dt_cairo_image_surface_create(cairo_format_t format, int width, int height)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:411
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:619
@ 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:183
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:400
DT_ALIGNED_PIXEL float dt_aligned_pixel_t[4]
Definition simd.h:53
#define for_each_channel(_var,...)
Definition simd.h:87
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
const float sigma
unsigned __int64 uint64_t
Definition strptime.c:75
int mask_rasterization_step
Rasterisation step for drawn masks, in image pixels. Always >= 1.
dt_dev_pixelpipe_type_t type
int32_t gui_attached
Definition develop.h:167
struct dt_iop_module_t * gui_module
Definition develop.h:170
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
dt_pthread_rwlock_t masks_mutex
Definition develop.h:316
struct dt_dev_pixelpipe_t * pipe
Definition develop.h:214
GList * forms
Definition develop.h:304
GtkWidget * widget
Definition imageop_gui.h:47
dt_iop_global_data_t * data
Definition imageop.h:238
dt_iop_params_t * default_params
Definition imageop.h:333
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
char multi_name[128]
Definition imageop.h:387
struct dt_develop_t * dev
Definition imageop.h:311
dt_iop_global_data_t * global_data
Definition imageop.h:337
dt_aligned_pixel_t picked_output_color
Definition imageop.h:289
gboolean enabled
Definition imageop.h:313
dt_dev_operation_t op
Definition imageop.h:259
dt_iop_params_t * params
Definition imageop.h:333
A profile reduced to the arithmetic the pixel loop can run: two matrices and six tone-curve LUTs,...
int nonlinearlut
Non-zero when the profile has tone curves at all; tested as a boolean everywhere, but it is really th...
int lutsize
Entry count of each of the six LUTs. Always 65536 in practice: both callers of dt_ioppr_init_profile_...
float * lut_out[3]
Per-channel linear -> encoded tone curve, same convention as lut_in.
float * lut_in[3]
Per-channel encoded -> linear tone curve, lutsize entries each, sampled over [0,1]....
dt_iop_retouch_blur_types_t blur_type
Definition retouch.c:133
dt_iop_retouch_fill_modes_t fill_mode
Definition retouch.c:136
dt_iop_retouch_algo_type_t algorithm
Definition retouch.c:131
int kernel_retouch_copy_buffer_to_buffer_masked
Definition retouch.c:244
int kernel_retouch_copy_image_to_buffer_masked
Definition retouch.c:243
GtkDarktableGradientSlider * preview_levels_gslider
Definition retouch.c:214
GtkWidget * bt_showmask
Definition retouch.c:193
GtkWidget * cmb_blur_type
Definition retouch.c:219
GtkWidget * bt_suppress
Definition retouch.c:193
GtkWidget * bt_paste_scale
Definition retouch.c:210
GtkWidget * sl_fill_brightness
Definition retouch.c:229
GtkWidget * hbox_color_pick
Definition retouch.c:223
GtkWidget * cmb_fill_mode
Definition retouch.c:227
GtkWidget * sl_blur_radius
Definition retouch.c:220
GtkWidget * vbox_preview_scale
Definition retouch.c:212
GtkWidget * sl_mask_opacity
Definition retouch.c:231
GtkWidget * bt_edit_masks
Definition retouch.c:191
GtkLabel * label_form_selected
Definition retouch.c:190
GtkWidget * bt_display_wavelet_scale
Definition retouch.c:207
GtkWidget * bt_auto_levels
Definition retouch.c:216
GtkWidget * colorpicker
Definition retouch.c:225
GtkLabel * lbl_num_scales
Definition retouch.c:196
GtkLabel * lbl_curr_scale
Definition retouch.c:197
GtkWidget * bt_copy_scale
Definition retouch.c:209
GtkWidget * cmb_heal_algorithm
Definition retouch.c:228
GtkLabel * lbl_merge_from_scale
Definition retouch.c:198
dt_iop_retouch_blur_types_t blur_type
Definition retouch.c:168
dt_iop_retouch_fill_modes_t fill_mode
Definition retouch.c:171
dt_iop_retouch_algo_type_t algorithm
Definition retouch.c:160
dt_iop_retouch_heal_algorithm_t heal_algorithm
Definition retouch.c:175
dt_iop_retouch_form_data_t rt_forms[300]
Definition retouch.c:158
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
dt_masks_type_t type
Definition masks.h:254
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
One shape's membership of one group, BY VALUE.
struct dt_develop_t * dev
Definition masks_gui.h:514
void * data
dt_atomic_uint64 age
dt_iop_module_t * self
Definition retouch.c:144
struct rt_memo_stats_t * stats
Definition retouch.c:153
dt_iop_roi_t roi
Definition retouch.c:147
const dt_dev_pixelpipe_t * pipe
Definition retouch.c:145
const dt_dev_pixelpipe_iop_t * piece
Definition retouch.c:146
const dt_dev_pixelpipe_iop_t * piece
Definition retouch.c:893
rt_memo_stats_t * stats
Definition retouch.c:894
const dt_iop_module_t * self
Definition retouch.c:891
const dt_dev_pixelpipe_t * pipe
Definition retouch.c:892
double algo_seconds
Definition retouch.c:883
int masks_rasterised
Definition retouch.c:881
struct dt_pixel_cache_entry_t * mask_entry
Definition retouch.c:3466
dt_iop_retouch_algo_type_t algo
Definition retouch.c:3459
dt_iop_roi_t roi_mask
Definition retouch.c:3461
dt_masks_area_t source
Definition retouch.c:2721
dt_masks_area_t area
Definition retouch.c:2720
#define __DT_CLONE_TARGETS__
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static double dt_get_wtime(void)
Definition times.h:43
gboolean dt_gui_get_scroll_unit_deltas(const GdkEventScroll *event, int *delta_x, int *delta_y)
GdkEventMask dt_widget_scroll_mask(void)
gboolean dt_gui_widgets_suppressed(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
#define dt_gui_freeze_begin()
#define dt_gui_freeze_end()
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_paste_forms(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_showmask(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_cut_forms(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_heal(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_blur(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_auto_levels(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_clone(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_solid_triangle(cairo_t *cr, gint x, int y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_eye_toggle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_display_wavelet_scale(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_tool_fill(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_edit(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
@ CPF_DIRECTION_UP
@ CPF_DIRECTION_DOWN