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