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;
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 float points[4];
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 = dt_util_dstrcat(NULL, "%s\n%s", _("ctrl+click to change tool for current form"),
2183 _("shift+click to set the tool as default"));
2184 gchar *tt = dt_util_dstrcat(NULL, "%s\n%s", _("activate blur tool"), tt2);
2185 gtk_widget_set_tooltip_text(g->bt_blur, tt);
2186 g_free(tt);
2187 tt = dt_util_dstrcat(NULL, "%s\n%s", _("activate fill tool"), tt2);
2188 gtk_widget_set_tooltip_text(g->bt_fill, tt);
2189 g_free(tt);
2190 tt = dt_util_dstrcat(NULL, "%s\n%s", _("activate cloning tool"), tt2);
2191 gtk_widget_set_tooltip_text(g->bt_clone, tt);
2192 g_free(tt);
2193 tt = dt_util_dstrcat(NULL, "%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 gtk_widget_set_tooltip_text(g->sl_fill_brightness,
2340 _("adjusts color brightness to fine-tune it. works with erase as well"));
2341
2342 // blur properties
2343 g->vbox_blur = self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
2344
2345 g->cmb_blur_type = dt_bauhaus_combobox_from_params(self, "blur_type");
2346 gtk_widget_set_tooltip_text(g->cmb_blur_type, _("type for the blur algorithm"));
2347
2348 g->sl_blur_radius = dt_bauhaus_slider_from_params(self, "blur_radius");
2349 dt_bauhaus_slider_set_step(g->sl_blur_radius, 0.1);
2350 gtk_widget_set_tooltip_text(g->sl_blur_radius, _("radius of the selected blur type"));
2351
2352 // mask opacity
2353 g->sl_mask_opacity = dt_bauhaus_slider_new_with_range(self, 0.0, 1.0, 0.05, 1., 3);
2354 dt_bauhaus_widget_set_label(g->sl_mask_opacity, NULL, N_("mask opacity"));
2355 gtk_widget_set_tooltip_text(g->sl_mask_opacity, _("set the opacity on the selected shape"));
2356 g_signal_connect(G_OBJECT(g->sl_mask_opacity), "value-changed", G_CALLBACK(rt_mask_opacity_callback), self);
2357
2358 // start building top level widget
2359 self->widget = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
2360
2361 GtkWidget *lbl_rt_tools = dt_ui_section_label_new(_("retouch tools"));
2362 gtk_box_pack_start(GTK_BOX(self->widget), lbl_rt_tools, FALSE, TRUE, 0);
2363
2364 // shapes toolbar
2365 gtk_box_pack_start(GTK_BOX(self->widget), hbox_shapes, TRUE, TRUE, 0);
2366 // algorithms toolbar
2367 gtk_box_pack_start(GTK_BOX(self->widget), hbox_algo, TRUE, TRUE, 0);
2368
2369 // wavelet decompose
2370 GtkWidget *lbl_wd = dt_ui_section_label_new(_("wavelet decompose"));
2371 gtk_box_pack_start(GTK_BOX(self->widget), lbl_wd, FALSE, TRUE, 0);
2372
2373 // wavelet decompose bar & labels
2374 gtk_box_pack_start(GTK_BOX(self->widget), grid_wd_labels, TRUE, TRUE, 0);
2375 gtk_box_pack_start(GTK_BOX(self->widget), g->wd_bar, TRUE, TRUE, DT_PIXEL_APPLY_DPI(3));
2376
2377 // preview scale & cut/paste scale
2378 gtk_box_pack_start(GTK_BOX(self->widget), hbox_scale, TRUE, TRUE, 0);
2379
2380 // preview single scale
2381 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_preview_scale, TRUE, TRUE, 0);
2382
2383 // shapes
2384 GtkWidget *lbl_shapes = dt_ui_section_label_new(_("shapes"));
2385 gtk_box_pack_start(GTK_BOX(self->widget), lbl_shapes, FALSE, TRUE, 0);
2386
2387 // shape selected
2388 gtk_box_pack_start(GTK_BOX(self->widget), hbox_shape_sel, TRUE, TRUE, 0);
2389 // blur radius
2390 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_blur, TRUE, TRUE, 0);
2391 // fill color
2392 gtk_box_pack_start(GTK_BOX(self->widget), g->vbox_fill, TRUE, TRUE, 0);
2393 // mask (shape) opacity
2394 gtk_box_pack_start(GTK_BOX(self->widget), g->sl_mask_opacity, TRUE, TRUE, 0);
2395
2396 /* add signal handler for preview pipe finish to redraw the preview */
2397 DT_DEBUG_CONTROL_SIGNAL_CONNECT(darktable.signals, DT_SIGNAL_DEVELOP_UI_PIPE_FINISHED,
2398 G_CALLBACK(rt_develop_ui_pipe_finished_callback), self);
2399 }
2400
gui_reset(struct dt_iop_module_t * self)2401 void gui_reset(struct dt_iop_module_t *self)
2402 {
2403 // hide the previous masks
2404 dt_masks_reset_form_gui();
2405 // set the algo to the default one
2406 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)self->params;
2407 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2408 }
2409
reload_defaults(dt_iop_module_t * self)2410 void reload_defaults(dt_iop_module_t *self)
2411 {
2412 // set the algo to the default one
2413 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)self->default_params;
2414 p->algorithm = dt_conf_get_int("plugins/darkroom/retouch/default_algo");
2415 }
2416
gui_cleanup(dt_iop_module_t * self)2417 void gui_cleanup(dt_iop_module_t *self)
2418 {
2419 DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(darktable.signals, G_CALLBACK(rt_develop_ui_pipe_finished_callback), self);
2420
2421 IOP_GUI_FREE;
2422 }
2423
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)2424 void modify_roi_out(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, dt_iop_roi_t *roi_out,
2425 const dt_iop_roi_t *roi_in)
2426 {
2427 *roi_out = *roi_in;
2428 }
2429
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)2430 static void rt_compute_roi_in(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
2431 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2432 {
2433 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
2434 dt_develop_blend_params_t *bp = self->blend_params;
2435
2436 int roir = *_roir;
2437 int roib = *_roib;
2438 int roix = *_roix;
2439 int roiy = *_roiy;
2440
2441 // We iterate through all forms
2442 const dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id);
2443 if(grp && (grp->type & DT_MASKS_GROUP))
2444 {
2445 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2446 {
2447 const dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
2448 if(grpt)
2449 {
2450 const int formid = grpt->formid;
2451 const int index = rt_get_index_from_formid(p, formid);
2452 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_FILL)
2453 {
2454 continue;
2455 }
2456
2457 // we get the spot
2458 dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, formid);
2459 if(form)
2460 {
2461 // if the form is outside the roi, we just skip it
2462 // we get the area for the form
2463 int fl, ft, fw, fh;
2464 if(!dt_masks_get_area(self, piece, form, &fw, &fh, &fl, &ft))
2465 {
2466 continue;
2467 }
2468
2469 // is the form outside of the roi?
2470 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2471 if(ft >= roi_in->y + roi_in->height || ft + fh <= roi_in->y || fl >= roi_in->x + roi_in->width
2472 || fl + fw <= roi_in->x)
2473 {
2474 continue;
2475 }
2476
2477 // heal need the entire area
2478 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL)
2479 {
2480 // we enlarge the roi if needed
2481 roiy = fminf(ft, roiy);
2482 roix = fminf(fl, roix);
2483 roir = fmaxf(fl + fw, roir);
2484 roib = fmaxf(ft + fh, roib);
2485 }
2486 // blur need an overlap of 4 * radius (scaled)
2487 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_BLUR)
2488 {
2489 if(index >= 0)
2490 {
2491 const int overlap = ceilf(4 * (p->rt_forms[index].blur_radius * roi_in->scale / piece->iscale));
2492 if(roiy > ft) roiy = MAX(roiy - overlap, ft);
2493 if(roix > fl) roix = MAX(roix - overlap, fl);
2494 if(roir < fl + fw) roir = MAX(roir + overlap, fl + fw);
2495 if(roib < ft + fh) roib = MAX(roib + overlap, ft + fh);
2496 }
2497 }
2498 // heal and clone need both source and destination areas
2499 if(p->rt_forms[index].algorithm == DT_IOP_RETOUCH_HEAL
2500 || p->rt_forms[index].algorithm == DT_IOP_RETOUCH_CLONE)
2501 {
2502 int dx = 0, dy = 0;
2503 if(rt_masks_get_delta_to_destination(self, piece, roi_in, form, &dx, &dy,
2504 p->rt_forms[index].distort_mode))
2505 {
2506 roiy = fminf(ft - dy, roiy);
2507 roix = fminf(fl - dx, roix);
2508 roir = fmaxf(fl + fw - dx, roir);
2509 roib = fmaxf(ft + fh - dy, roib);
2510 }
2511 }
2512 }
2513 }
2514 }
2515 }
2516
2517 *_roir = roir;
2518 *_roib = roib;
2519 *_roix = roix;
2520 *_roiy = roiy;
2521 }
2522
2523 // for a given form, if a previous clone/heal destination intersects the source area,
2524 // 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)2525 static void rt_extend_roi_in_from_source_clones(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
2526 dt_iop_roi_t *roi_in, const int formid_src, const int fl_src,
2527 const int ft_src, const int fw_src, const int fh_src, int *_roir,
2528 int *_roib, int *_roix, int *_roiy)
2529 {
2530 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
2531 dt_develop_blend_params_t *bp = self->blend_params;
2532
2533 int roir = *_roir;
2534 int roib = *_roib;
2535 int roix = *_roix;
2536 int roiy = *_roiy;
2537
2538 // We iterate through all forms
2539 const dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id);
2540 if(grp && (grp->type & DT_MASKS_GROUP))
2541 {
2542 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2543 {
2544 const dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
2545 if(grpt)
2546 {
2547 const int formid = grpt->formid;
2548
2549 // just need the previous forms
2550 if(formid == formid_src) break;
2551
2552 const int index = rt_get_index_from_formid(p, formid);
2553
2554 // only process clone and heal
2555 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2556 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2557 {
2558 continue;
2559 }
2560
2561 // we get the spot
2562 dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, formid);
2563 if(form)
2564 {
2565 // we get the source area
2566 int fl, ft, fw, fh;
2567 if(!dt_masks_get_source_area(self, piece, form, &fw, &fh, &fl, &ft))
2568 {
2569 continue;
2570 }
2571 fw *= roi_in->scale, fh *= roi_in->scale, fl *= roi_in->scale, ft *= roi_in->scale;
2572
2573 // get the destination area
2574 int fl_dest, ft_dest;
2575 int dx = 0, dy = 0;
2576 if(!rt_masks_get_delta_to_destination(self, piece, roi_in, form, &dx, &dy,
2577 p->rt_forms[index].distort_mode))
2578 {
2579 continue;
2580 }
2581
2582 ft_dest = ft + dy;
2583 fl_dest = fl + dx;
2584
2585 // check if the destination of this form intersects the source of the formid_src
2586 const int intersects = !(ft_dest + fh < ft_src || ft_src + fh_src < ft_dest || fl_dest + fw < fl_src
2587 || fl_src + fw_src < fl_dest);
2588 if(intersects)
2589 {
2590 // we enlarge the roi if needed
2591 roiy = fminf(ft, roiy);
2592 roix = fminf(fl, roix);
2593 roir = fmaxf(fl + fw, roir);
2594 roib = fmaxf(ft + fh, roib);
2595
2596 // need both source and destination areas
2597 roiy = fminf(ft + dy, roiy);
2598 roix = fminf(fl + dx, roix);
2599 roir = fmaxf(fl + fw + dx, roir);
2600 roib = fmaxf(ft + fh + dy, roib);
2601 }
2602 }
2603 }
2604 }
2605 }
2606
2607 *_roir = roir;
2608 *_roib = roib;
2609 *_roix = roix;
2610 *_roiy = roiy;
2611 }
2612
2613 // for clone and heal, if the source area is the destination from another clone/heal,
2614 // 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)2615 static void rt_extend_roi_in_for_clone(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece,
2616 dt_iop_roi_t *roi_in, int *_roir, int *_roib, int *_roix, int *_roiy)
2617 {
2618 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
2619 dt_develop_blend_params_t *bp = self->blend_params;
2620
2621 int roir = *_roir;
2622 int roib = *_roib;
2623 int roix = *_roix;
2624 int roiy = *_roiy;
2625
2626 // go through all clone and heal forms
2627 const dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id);
2628 if(grp && (grp->type & DT_MASKS_GROUP))
2629 {
2630 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
2631 {
2632 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
2633 if(grpt)
2634 {
2635 const int formid = grpt->formid;
2636 const int index = rt_get_index_from_formid(p, formid);
2637
2638 if(p->rt_forms[index].algorithm != DT_IOP_RETOUCH_HEAL
2639 && p->rt_forms[index].algorithm != DT_IOP_RETOUCH_CLONE)
2640 {
2641 continue;
2642 }
2643
2644 // we get the spot
2645 dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, formid);
2646 if(form == NULL)
2647 {
2648 continue;
2649 }
2650
2651 // get the source area
2652 int fl_src, ft_src, fw_src, fh_src;
2653 if(!dt_masks_get_source_area(self, piece, form, &fw_src, &fh_src, &fl_src, &ft_src))
2654 {
2655 continue;
2656 }
2657
2658 fw_src *= roi_in->scale, fh_src *= roi_in->scale, fl_src *= roi_in->scale, ft_src *= roi_in->scale;
2659
2660 // we only want to process forms already in roi_in
2661 const int intersects
2662 = !(roib < ft_src || ft_src + fh_src < roiy || roir < fl_src || fl_src + fw_src < roix);
2663 if(intersects)
2664 rt_extend_roi_in_from_source_clones(self, piece, roi_in, formid, fl_src, ft_src, fw_src, fh_src, &roir,
2665 &roib, &roix, &roiy);
2666 }
2667 }
2668 }
2669
2670 *_roir = roir;
2671 *_roib = roib;
2672 *_roix = roix;
2673 *_roiy = roiy;
2674 }
2675
2676 // 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)2677 void modify_roi_in(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, const dt_iop_roi_t *roi_out,
2678 dt_iop_roi_t *roi_in)
2679 {
2680 *roi_in = *roi_out;
2681
2682 int roir = roi_in->width + roi_in->x;
2683 int roib = roi_in->height + roi_in->y;
2684 int roix = roi_in->x;
2685 int roiy = roi_in->y;
2686
2687 rt_compute_roi_in(self, piece, roi_in, &roir, &roib, &roix, &roiy);
2688
2689 int roir_prev = -1, roib_prev = -1, roix_prev = -1, roiy_prev = -1;
2690
2691 while(roir != roir_prev || roib != roib_prev || roix != roix_prev || roiy != roiy_prev)
2692 {
2693 roir_prev = roir;
2694 roib_prev = roib;
2695 roix_prev = roix;
2696 roiy_prev = roiy;
2697
2698 rt_extend_roi_in_for_clone(self, piece, roi_in, &roir, &roib, &roix, &roiy);
2699 }
2700
2701 // now we set the values
2702 const float scwidth = piece->buf_in.width * roi_in->scale, scheight = piece->buf_in.height * roi_in->scale;
2703 roi_in->x = CLAMP(roix, 0, scwidth - 1);
2704 roi_in->y = CLAMP(roiy, 0, scheight - 1);
2705 roi_in->width = CLAMP(roir - roi_in->x, 1, scwidth + .5f - roi_in->x);
2706 roi_in->height = CLAMP(roib - roi_in->y, 1, scheight + .5f - roi_in->y);
2707 }
2708
2709 //--------------------------------------------------------------------------------------------------
2710 // process
2711 //--------------------------------------------------------------------------------------------------
2712
2713 #ifdef __SSE2__
2714 /** uses D50 white point. */
2715 // see http://www.brucelindbloom.com/Eqn_RGB_XYZ_Matrix.html for the transformation matrices
dt_XYZ_to_RGB_sse2(__m128 XYZ)2716 static inline __m128 dt_XYZ_to_RGB_sse2(__m128 XYZ)
2717 {
2718 // XYZ -> sRGB matrix, D65
2719 const __m128 xyz_to_srgb_0 = _mm_setr_ps(3.1338561f, -0.9787684f, 0.0719453f, 0.0f);
2720 const __m128 xyz_to_srgb_1 = _mm_setr_ps(-1.6168667f, 1.9161415f, -0.2289914f, 0.0f);
2721 const __m128 xyz_to_srgb_2 = _mm_setr_ps(-0.4906146f, 0.0334540f, 1.4052427f, 0.0f);
2722
2723 __m128 rgb
2724 = _mm_add_ps(_mm_mul_ps(xyz_to_srgb_0, _mm_shuffle_ps(XYZ, XYZ, _MM_SHUFFLE(0, 0, 0, 0))),
2725 _mm_add_ps(_mm_mul_ps(xyz_to_srgb_1, _mm_shuffle_ps(XYZ, XYZ, _MM_SHUFFLE(1, 1, 1, 1))),
2726 _mm_mul_ps(xyz_to_srgb_2, _mm_shuffle_ps(XYZ, XYZ, _MM_SHUFFLE(2, 2, 2, 2)))));
2727
2728 return rgb;
2729 }
2730
dt_RGB_to_XYZ_sse2(__m128 rgb)2731 static inline __m128 dt_RGB_to_XYZ_sse2(__m128 rgb)
2732 {
2733 // sRGB -> XYZ matrix, D65
2734 const __m128 srgb_to_xyz_0 = _mm_setr_ps(0.4360747f, 0.2225045f, 0.0139322f, 0.0f);
2735 const __m128 srgb_to_xyz_1 = _mm_setr_ps(0.3850649f, 0.7168786f, 0.0971045f, 0.0f);
2736 const __m128 srgb_to_xyz_2 = _mm_setr_ps(0.1430804f, 0.0606169f, 0.7141733f, 0.0f);
2737
2738 __m128 XYZ
2739 = _mm_add_ps(_mm_mul_ps(srgb_to_xyz_0, _mm_shuffle_ps(rgb, rgb, _MM_SHUFFLE(0, 0, 0, 0))),
2740 _mm_add_ps(_mm_mul_ps(srgb_to_xyz_1, _mm_shuffle_ps(rgb, rgb, _MM_SHUFFLE(1, 1, 1, 1))),
2741 _mm_mul_ps(srgb_to_xyz_2, _mm_shuffle_ps(rgb, rgb, _MM_SHUFFLE(2, 2, 2, 2)))));
2742 return XYZ;
2743 }
2744 #endif
2745
dt_linearRGB_to_XYZ(const float * const linearRGB,float * XYZ)2746 static inline void dt_linearRGB_to_XYZ(const float *const linearRGB, float *XYZ)
2747 {
2748 const float srgb_to_xyz[3][3] = { { 0.4360747, 0.3850649, 0.1430804 },
2749 { 0.2225045, 0.7168786, 0.0606169 },
2750 { 0.0139322, 0.0971045, 0.7141733 } };
2751
2752 // sRGB -> XYZ
2753 XYZ[0] = XYZ[1] = XYZ[2] = 0.0;
2754 for(int r = 0; r < 3; r++)
2755 for(int c = 0; c < 3; c++) XYZ[r] += srgb_to_xyz[r][c] * linearRGB[c];
2756 }
2757
dt_XYZ_to_linearRGB(const float * const XYZ,float * linearRGB)2758 static inline void dt_XYZ_to_linearRGB(const float *const XYZ, float *linearRGB)
2759 {
2760 const float xyz_to_srgb_matrix[3][3] = { { 3.1338561, -1.6168667, -0.4906146 },
2761 { -0.9787684, 1.9161415, 0.0334540 },
2762 { 0.0719453, -0.2289914, 1.4052427 } };
2763
2764 // XYZ -> sRGB
2765 linearRGB[0] = linearRGB[1] = linearRGB[2] = 0.f;
2766 for(int r = 0; r < 3; r++)
2767 for(int c = 0; c < 3; c++) linearRGB[r] += xyz_to_srgb_matrix[r][c] * XYZ[c];
2768 }
2769
image_rgb2lab(float * img_src,const int width,const int height,const int ch,const int use_sse)2770 static void image_rgb2lab(float *img_src, const int width, const int height, const int ch, const int use_sse)
2771 {
2772 const int stride = width * height * ch;
2773
2774 #if defined(__SSE__)
2775 if(ch == 4 && use_sse)
2776 {
2777 #ifdef _OPENMP
2778 #pragma omp parallel for default(none) \
2779 dt_omp_firstprivate(ch, stride) \
2780 shared(img_src) \
2781 schedule(static)
2782 #endif
2783 for(int i = 0; i < stride; i += ch)
2784 {
2785 // RGB -> XYZ
2786 __m128 rgb = _mm_load_ps(img_src + i);
2787 __m128 XYZ = dt_RGB_to_XYZ_sse2(rgb);
2788 // XYZ -> Lab
2789 _mm_store_ps(img_src + i, dt_XYZ_to_Lab_sse2(XYZ));
2790 }
2791
2792 return;
2793 }
2794 #endif
2795
2796 #ifdef _OPENMP
2797 #pragma omp parallel for default(none) \
2798 dt_omp_firstprivate(ch, stride) \
2799 shared(img_src) \
2800 schedule(static)
2801 #endif
2802 for(int i = 0; i < stride; i += ch)
2803 {
2804 float DT_ALIGNED_PIXEL XYZ[4];
2805
2806 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2807 dt_XYZ_to_Lab(XYZ, img_src + i);
2808 }
2809 }
2810
image_lab2rgb(float * img_src,const int width,const int height,const int ch,const int use_sse)2811 static void image_lab2rgb(float *img_src, const int width, const int height, const int ch, const int use_sse)
2812 {
2813 const int stride = width * height * ch;
2814
2815 #if defined(__SSE__)
2816 if(ch == 4 && use_sse)
2817 {
2818 #ifdef _OPENMP
2819 #pragma omp parallel for default(none) \
2820 dt_omp_firstprivate(ch, stride) \
2821 shared(img_src) \
2822 schedule(static)
2823 #endif
2824 for(int i = 0; i < stride; i += ch)
2825 {
2826 // Lab -> XYZ
2827 __m128 Lab = _mm_load_ps(img_src + i);
2828 __m128 XYZ = dt_Lab_to_XYZ_sse2(Lab);
2829 // XYZ -> RGB
2830 _mm_store_ps(img_src + i, dt_XYZ_to_RGB_sse2(XYZ));
2831 }
2832
2833 return;
2834 }
2835 #endif
2836
2837 #ifdef _OPENMP
2838 #pragma omp parallel for default(none) \
2839 dt_omp_firstprivate(ch, stride) \
2840 shared(img_src) \
2841 schedule(static)
2842 #endif
2843 for(int i = 0; i < stride; i += ch)
2844 {
2845 float DT_ALIGNED_PIXEL XYZ[4];
2846
2847 dt_Lab_to_XYZ(img_src + i, XYZ);
2848 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2849 }
2850 }
2851
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],int use_sse)2852 static void rt_process_stats(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *const img_src,
2853 const int width, const int height, const int ch, float levels[3], int use_sse)
2854 {
2855 const int size = width * height * ch;
2856 float l_max = -INFINITY;
2857 float l_min = INFINITY;
2858 float l_sum = 0.f;
2859 int count = 0;
2860 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
2861
2862 #ifdef _OPENMP
2863 #pragma omp parallel for default(none) \
2864 dt_omp_firstprivate(ch, img_src, size, work_profile) \
2865 schedule(static) \
2866 reduction(+ : count, l_sum) \
2867 reduction(max : l_max) \
2868 reduction(min : l_min)
2869 #endif
2870 for(int i = 0; i < size; i += ch)
2871 {
2872 float Lab[3] = { 0 };
2873
2874 if(work_profile)
2875 {
2876 dt_ioppr_rgb_matrix_to_lab(img_src + i, Lab, work_profile->matrix_in,
2877 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2878 work_profile->lutsize, work_profile->nonlinearlut);
2879 }
2880 else
2881 {
2882 float DT_ALIGNED_PIXEL XYZ[4];
2883 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2884 dt_XYZ_to_Lab(XYZ, Lab);
2885 }
2886
2887 l_max = MAX(l_max, Lab[0]);
2888 l_min = MIN(l_min, Lab[0]);
2889 l_sum += Lab[0];
2890 count++;
2891 }
2892
2893 levels[0] = l_min / 100.f;
2894 levels[2] = l_max / 100.f;
2895 levels[1] = (l_sum / (float)count) / 100.f;
2896 }
2897
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],int use_sse)2898 static void rt_adjust_levels(dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, float *img_src, const int width,
2899 const int height, const int ch, const float levels[3], int use_sse)
2900 {
2901 const int size = width * height * ch;
2902 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
2903
2904 const float left = levels[0];
2905 const float middle = levels[1];
2906 const float right = levels[2];
2907
2908 if(left == RETOUCH_PREVIEW_LVL_MIN && middle == 0.f && right == RETOUCH_PREVIEW_LVL_MAX) return;
2909
2910 const float delta = (right - left) / 2.0f;
2911 const float mid = left + delta;
2912 const float tmp = (middle - mid) / delta;
2913 const float in_inv_gamma = powf(10, tmp);
2914
2915 #ifdef _OPENMP
2916 #pragma omp parallel for default(none) \
2917 dt_omp_firstprivate(ch, in_inv_gamma, left, right, size, work_profile) \
2918 shared(img_src) \
2919 schedule(static)
2920 #endif
2921 for(int i = 0; i < size; i += ch)
2922 {
2923 if(work_profile)
2924 {
2925 dt_ioppr_rgb_matrix_to_lab(img_src + i, img_src + i, work_profile->matrix_in,
2926 work_profile->lut_in, work_profile->unbounded_coeffs_in,
2927 work_profile->lutsize, work_profile->nonlinearlut);
2928 }
2929 else
2930 {
2931 float DT_ALIGNED_PIXEL XYZ[4];
2932
2933 dt_linearRGB_to_XYZ(img_src + i, XYZ);
2934 dt_XYZ_to_Lab(XYZ, img_src + i);
2935 }
2936
2937 for(int c = 0; c < 1; c++)
2938 {
2939 const float L_in = img_src[i + c] / 100.0f;
2940
2941 if(L_in <= left)
2942 {
2943 img_src[i + c] = 0.f;
2944 }
2945 else
2946 {
2947 const float percentage = (L_in - left) / (right - left);
2948 img_src[i + c] = 100.0f * powf(percentage, in_inv_gamma);
2949 }
2950 }
2951
2952 if(work_profile)
2953 {
2954 dt_ioppr_lab_to_rgb_matrix(img_src + i, img_src + i, work_profile->matrix_out,
2955 work_profile->lut_out, work_profile->unbounded_coeffs_out,
2956 work_profile->lutsize, work_profile->nonlinearlut);;
2957 }
2958 else
2959 {
2960 float DT_ALIGNED_PIXEL XYZ[4];
2961
2962 dt_Lab_to_XYZ(img_src + i, XYZ);
2963 dt_XYZ_to_linearRGB(XYZ, img_src + i);
2964 }
2965 }
2966 }
2967
2968 #undef RT_WDBAR_INSET
2969
2970 #undef RETOUCH_NO_FORMS
2971 #undef RETOUCH_MAX_SCALES
2972 #undef RETOUCH_NO_SCALES
2973
2974 #undef RETOUCH_PREVIEW_LVL_MIN
2975 #undef RETOUCH_PREVIEW_LVL_MAX
2976
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)2977 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,
2978 const int padding, dt_iop_roi_t *roi_dest)
2979 {
2980 const int x_from = MAX(MAX((roi_1->x + 1 - padding), roi_2->x), (roi_2->x + dx));
2981 const int x_to
2982 = MIN(MIN((roi_1->x + roi_1->width + 1 + padding), roi_2->x + roi_2->width), (roi_2->x + roi_2->width + dx));
2983
2984 const int y_from = MAX(MAX((roi_1->y + 1 - padding), roi_2->y), (roi_2->y + dy));
2985 const int y_to = MIN(MIN((roi_1->y + roi_1->height + 1 + padding), (roi_2->y + roi_2->height)),
2986 (roi_2->y + roi_2->height + dy));
2987
2988 roi_dest->x = x_from;
2989 roi_dest->y = y_from;
2990 roi_dest->width = x_to - x_from;
2991 roi_dest->height = y_to - y_from;
2992 }
2993
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)2994 static void rt_copy_in_to_out(const float *const in, const struct dt_iop_roi_t *const roi_in, float *const out,
2995 const struct dt_iop_roi_t *const roi_out, const int ch, const int dx, const int dy)
2996 {
2997 const size_t rowsize = sizeof(float) * ch * MIN(roi_out->width, roi_in->width);
2998 const int xoffs = roi_out->x - roi_in->x - dx;
2999 const int yoffs = roi_out->y - roi_in->y - dy;
3000 const int y_to = MIN(roi_out->height, roi_in->height);
3001
3002 #ifdef _OPENMP
3003 #pragma omp parallel for default(none) \
3004 dt_omp_firstprivate(ch, in, out, roi_in, roi_out, rowsize, xoffs, yoffs, \
3005 y_to) \
3006 schedule(static)
3007 #endif
3008 for(int y = 0; y < y_to; y++)
3009 {
3010 const size_t iindex = ((size_t)(y + yoffs) * roi_in->width + xoffs) * ch;
3011 const size_t oindex = (size_t)y * roi_out->width * ch;
3012 float *in1 = (float *)in + iindex;
3013 float *out1 = (float *)out + oindex;
3014
3015 memcpy(out1, in1, rowsize);
3016 }
3017 }
3018
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)3019 static void rt_build_scaled_mask(float *const mask, dt_iop_roi_t *const roi_mask, float **mask_scaled,
3020 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
3021 const int dy, const int algo)
3022 {
3023 float *mask_tmp = NULL;
3024
3025 const int padding = (algo == DT_IOP_RETOUCH_HEAL) ? 1 : 0;
3026
3027 *roi_mask_scaled = *roi_mask;
3028
3029 roi_mask_scaled->x = roi_mask->x * roi_in->scale;
3030 roi_mask_scaled->y = roi_mask->y * roi_in->scale;
3031 roi_mask_scaled->width = ((roi_mask->width * roi_in->scale) + .5f);
3032 roi_mask_scaled->height = ((roi_mask->height * roi_in->scale) + .5f);
3033 roi_mask_scaled->scale = roi_in->scale;
3034
3035 rt_intersect_2_rois(roi_mask_scaled, roi_in, dx, dy, padding, roi_mask_scaled);
3036 if(roi_mask_scaled->width < 1 || roi_mask_scaled->height < 1) goto cleanup;
3037
3038 const int x_to = roi_mask_scaled->width + roi_mask_scaled->x;
3039 const int y_to = roi_mask_scaled->height + roi_mask_scaled->y;
3040
3041 mask_tmp = dt_alloc_align_float((size_t)roi_mask_scaled->width * roi_mask_scaled->height);
3042 if(mask_tmp == NULL)
3043 {
3044 fprintf(stderr, "rt_build_scaled_mask: error allocating memory\n");
3045 goto cleanup;
3046 }
3047 dt_iop_image_fill(mask_tmp, 0.0f, roi_mask_scaled->width, roi_mask_scaled->height, 1);
3048
3049 #ifdef _OPENMP
3050 #pragma omp parallel for default(none) \
3051 dt_omp_firstprivate(mask, roi_in, roi_mask, x_to, y_to) \
3052 shared(mask_tmp, roi_mask_scaled) \
3053 schedule(static)
3054 #endif
3055 for(int yy = roi_mask_scaled->y; yy < y_to; yy++)
3056 {
3057 const int mask_index = ((int)(yy / roi_in->scale)) - roi_mask->y;
3058 if(mask_index < 0 || mask_index >= roi_mask->height) continue;
3059
3060 const int mask_scaled_index = (yy - roi_mask_scaled->y) * roi_mask_scaled->width;
3061
3062 const float *m = mask + mask_index * roi_mask->width;
3063 float *ms = mask_tmp + mask_scaled_index;
3064
3065 for(int xx = roi_mask_scaled->x; xx < x_to; xx++, ms++)
3066 {
3067 const int mx = ((int)(xx / roi_in->scale)) - roi_mask->x;
3068 if(mx < 0 || mx >= roi_mask->width) continue;
3069
3070 *ms = m[mx];
3071 }
3072 }
3073
3074 cleanup:
3075 *mask_scaled = mask_tmp;
3076 }
3077
3078 // 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,const int ch,float * const mask_scaled,dt_iop_roi_t * const roi_mask_scaled,const float opacity,const int use_sse)3079 static void rt_copy_image_masked(float *const img_src, float *img_dest, dt_iop_roi_t *const roi_dest, const int ch,
3080 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3081 const float opacity, const int use_sse)
3082 {
3083 #if defined(__SSE__)
3084 if(ch == 4 && use_sse)
3085 {
3086 #ifdef _OPENMP
3087 #pragma omp parallel for default(none) \
3088 dt_omp_firstprivate(ch, img_src, mask_scaled, opacity, roi_dest, roi_mask_scaled) \
3089 shared(img_dest) \
3090 schedule(static)
3091 #endif
3092 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3093 {
3094 const int mask_index = yy * roi_mask_scaled->width;
3095 const int src_index = mask_index * ch;
3096 const int dest_index
3097 = (((yy + roi_mask_scaled->y - roi_dest->y) * roi_dest->width) + (roi_mask_scaled->x - roi_dest->x))
3098 * ch;
3099
3100 const float *s = img_src + src_index;
3101 const float *m = mask_scaled + mask_index;
3102 float *d = img_dest + dest_index;
3103
3104 for(int xx = 0; xx < roi_mask_scaled->width; xx++, s += ch, d += ch, m++)
3105 {
3106 const float f = (*m) * opacity;
3107
3108 const __m128 val1_f = _mm_set1_ps(1.0f - f);
3109 const __m128 valf = _mm_set1_ps(f);
3110
3111 _mm_store_ps(d, _mm_add_ps(_mm_mul_ps(_mm_load_ps(d), val1_f), _mm_mul_ps(_mm_load_ps(s), valf)));
3112 }
3113 }
3114 }
3115 else
3116 #endif
3117 {
3118 const int ch1 = (ch == 4) ? ch - 1 : ch;
3119
3120 #ifdef _OPENMP
3121 #pragma omp parallel for default(none) \
3122 dt_omp_firstprivate(ch, ch1, img_src, mask_scaled, opacity, roi_dest, roi_mask_scaled) \
3123 shared(img_dest) \
3124 schedule(static)
3125 #endif
3126 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3127 {
3128 const int mask_index = yy * roi_mask_scaled->width;
3129 const int src_index = mask_index * ch;
3130 const int dest_index
3131 = (((yy + roi_mask_scaled->y - roi_dest->y) * roi_dest->width) + (roi_mask_scaled->x - roi_dest->x))
3132 * ch;
3133
3134 const float *s = img_src + src_index;
3135 const float *m = mask_scaled + mask_index;
3136 float *d = img_dest + dest_index;
3137
3138 for(int xx = 0; xx < roi_mask_scaled->width; xx++, s += ch, d += ch, m++)
3139 {
3140 const float f = (*m) * opacity;
3141
3142 for(int c = 0; c < ch1; c++)
3143 {
3144 d[c] = d[c] * (1.0f - f) + s[c] * f;
3145 }
3146 }
3147 }
3148 }
3149 }
3150
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)3151 static void rt_copy_mask_to_alpha(float *const img, dt_iop_roi_t *const roi_img, const int ch,
3152 float *const mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3153 const float opacity)
3154 {
3155 #ifdef _OPENMP
3156 #pragma omp parallel for default(none) \
3157 dt_omp_firstprivate(ch, img, mask_scaled, opacity, roi_img, roi_mask_scaled) \
3158 schedule(static)
3159 #endif
3160 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3161 {
3162 const int mask_index = yy * roi_mask_scaled->width;
3163 const int dest_index
3164 = (((yy + roi_mask_scaled->y - roi_img->y) * roi_img->width) + (roi_mask_scaled->x - roi_img->x)) * ch;
3165
3166 float *d = img + dest_index;
3167 const float *m = mask_scaled + mask_index;
3168
3169 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3170 {
3171 const float f = (*m) * opacity;
3172 if(f > d[3]) d[3] = f;
3173 }
3174 }
3175 }
3176
3177 #if defined(__SSE__)
retouch_fill_sse(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)3178 static void retouch_fill_sse(float *const in, dt_iop_roi_t *const roi_in, float *const mask_scaled,
3179 dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3180 const float *const fill_color)
3181 {
3182 const int ch = 4;
3183
3184 const float valf4_fill[4] = { fill_color[0], fill_color[1], fill_color[2], 0.f };
3185 const __m128 val_fill = _mm_load_ps(valf4_fill);
3186
3187 #ifdef _OPENMP
3188 #pragma omp parallel for default(none) \
3189 dt_omp_firstprivate(ch, in, mask_scaled, opacity, roi_in, roi_mask_scaled, val_fill) \
3190 schedule(static)
3191 #endif
3192 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3193 {
3194 const int mask_index = yy * roi_mask_scaled->width;
3195 const int dest_index
3196 = (((yy + roi_mask_scaled->y - roi_in->y) * roi_in->width) + (roi_mask_scaled->x - roi_in->x)) * ch;
3197
3198 float *d = in + dest_index;
3199 const float *m = mask_scaled + mask_index;
3200
3201 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3202 {
3203 const float f = (*m) * opacity;
3204
3205 const __m128 val1_f = _mm_set1_ps(1.0f - f);
3206 const __m128 valf = _mm_set1_ps(f);
3207
3208 _mm_store_ps(d, _mm_add_ps(_mm_mul_ps(_mm_load_ps(d), val1_f), _mm_mul_ps(val_fill, valf)));
3209 }
3210 }
3211 }
3212 #endif
3213
retouch_fill(float * const in,dt_iop_roi_t * const roi_in,const int ch,float * const mask_scaled,dt_iop_roi_t * const roi_mask_scaled,const float opacity,const float * const fill_color,const int use_sse)3214 static void retouch_fill(float *const in, dt_iop_roi_t *const roi_in, const int ch, float *const mask_scaled,
3215 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const float *const fill_color,
3216 const int use_sse)
3217 {
3218 #if defined(__SSE__)
3219 if(ch == 4 && use_sse)
3220 {
3221 retouch_fill_sse(in, roi_in, mask_scaled, roi_mask_scaled, opacity, fill_color);
3222 return;
3223 }
3224 #endif
3225 const int ch1 = (ch == 4) ? ch - 1 : ch;
3226
3227 #ifdef _OPENMP
3228 #pragma omp parallel for default(none) \
3229 dt_omp_firstprivate(ch, ch1, fill_color, in, mask_scaled, opacity, roi_in, roi_mask_scaled) \
3230 schedule(static)
3231 #endif
3232 for(int yy = 0; yy < roi_mask_scaled->height; yy++)
3233 {
3234 const int mask_index = yy * roi_mask_scaled->width;
3235 const int dest_index
3236 = (((yy + roi_mask_scaled->y - roi_in->y) * roi_in->width) + (roi_mask_scaled->x - roi_in->x)) * ch;
3237
3238 float *d = in + dest_index;
3239 const float *m = mask_scaled + mask_index;
3240
3241 for(int xx = 0; xx < roi_mask_scaled->width; xx++, d += ch, m++)
3242 {
3243 const float f = (*m) * opacity;
3244
3245 for(int c = 0; c < ch1; c++) d[c] = d[c] * (1.0f - f) + fill_color[c] * f;
3246 }
3247 }
3248 }
3249
retouch_clone(float * const in,dt_iop_roi_t * const roi_in,const int ch,float * const mask_scaled,dt_iop_roi_t * const roi_mask_scaled,const int dx,const int dy,const float opacity,const int use_sse)3250 static void retouch_clone(float *const in, dt_iop_roi_t *const roi_in, const int ch, float *const mask_scaled,
3251 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity,
3252 const int use_sse)
3253 {
3254 // alloc temp image to avoid issues when areas self-intersects
3255 float *img_src = dt_alloc_align_float((size_t)ch * roi_mask_scaled->width * roi_mask_scaled->height);
3256 if(img_src == NULL)
3257 {
3258 fprintf(stderr, "retouch_clone: error allocating memory for cloning\n");
3259 goto cleanup;
3260 }
3261
3262 // copy source image to tmp
3263 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, ch, dx, dy);
3264
3265 // clone it
3266 rt_copy_image_masked(img_src, in, roi_in, ch, mask_scaled, roi_mask_scaled, opacity, use_sse);
3267
3268 cleanup:
3269 if(img_src) dt_free_align(img_src);
3270 }
3271
retouch_blur(dt_iop_module_t * self,float * const in,dt_iop_roi_t * const roi_in,const int ch,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)3272 static void retouch_blur(dt_iop_module_t *self, float *const in, dt_iop_roi_t *const roi_in, const int ch, float *const mask_scaled,
3273 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int blur_type,
3274 const float blur_radius, dt_dev_pixelpipe_iop_t *piece, const int use_sse)
3275 {
3276 if(fabsf(blur_radius) <= 0.1f) return;
3277
3278 const float sigma = blur_radius * roi_in->scale / piece->iscale;
3279
3280 float *img_dest = NULL;
3281
3282 // alloc temp image to blur
3283 img_dest = dt_alloc_align_float((size_t)ch * roi_mask_scaled->width * roi_mask_scaled->height);
3284 if(img_dest == NULL)
3285 {
3286 fprintf(stderr, "retouch_blur: error allocating memory for blurring\n");
3287 goto cleanup;
3288 }
3289
3290 // copy source image so we blur just the mask area (at least the smallest rect that covers it)
3291 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, ch, 0, 0);
3292
3293 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
3294 {
3295 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
3296 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
3297
3298 dt_gaussian_t *g = dt_gaussian_init(roi_mask_scaled->width, roi_mask_scaled->height, ch, Labmax, Labmin, sigma,
3299 DT_IOP_GAUSSIAN_ZERO);
3300 if(g)
3301 {
3302 if(ch == 4)
3303 dt_gaussian_blur_4c(g, img_dest, img_dest);
3304 else
3305 dt_gaussian_blur(g, img_dest, img_dest);
3306 dt_gaussian_free(g);
3307 }
3308 }
3309 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
3310 {
3311 const float sigma_r = 100.0f; // does not depend on scale
3312 const float sigma_s = sigma;
3313 const float detail = -1.0f; // we want the bilateral base layer
3314
3315 dt_bilateral_t *b = dt_bilateral_init(roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
3316 if(b)
3317 {
3318 int converted_cst;
3319 const dt_iop_order_iccprofile_info_t *const work_profile = dt_ioppr_get_pipe_work_profile_info(piece->pipe);
3320
3321 if(work_profile)
3322 dt_ioppr_transform_image_colorspace(self, img_dest, img_dest, roi_mask_scaled->width,
3323 roi_mask_scaled->height, iop_cs_rgb, iop_cs_Lab, &converted_cst,
3324 work_profile);
3325 else
3326 image_rgb2lab(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, ch, use_sse);
3327
3328 dt_bilateral_splat(b, img_dest);
3329 dt_bilateral_blur(b);
3330 dt_bilateral_slice(b, img_dest, img_dest, detail);
3331 dt_bilateral_free(b);
3332
3333 if(work_profile)
3334 dt_ioppr_transform_image_colorspace(self, img_dest, img_dest, roi_mask_scaled->width,
3335 roi_mask_scaled->height, iop_cs_Lab, iop_cs_rgb, &converted_cst,
3336 work_profile);
3337 else
3338 image_lab2rgb(img_dest, roi_mask_scaled->width, roi_mask_scaled->height, ch, use_sse);
3339 }
3340 }
3341
3342 // copy blurred (temp) image to destination image
3343 rt_copy_image_masked(img_dest, in, roi_in, ch, mask_scaled, roi_mask_scaled, opacity, use_sse);
3344
3345 cleanup:
3346 if(img_dest) dt_free_align(img_dest);
3347 }
3348
retouch_heal(float * const in,dt_iop_roi_t * const roi_in,const int ch,float * const mask_scaled,dt_iop_roi_t * const roi_mask_scaled,const int dx,const int dy,const float opacity,int use_sse)3349 static void retouch_heal(float *const in, dt_iop_roi_t *const roi_in, const int ch, float *const mask_scaled,
3350 dt_iop_roi_t *const roi_mask_scaled, const int dx, const int dy, const float opacity,
3351 int use_sse)
3352 {
3353 float *img_src = NULL;
3354 float *img_dest = NULL;
3355
3356 // alloc temp images for source and destination
3357 img_src = dt_alloc_align_float((size_t)ch * roi_mask_scaled->width * roi_mask_scaled->height);
3358 img_dest = dt_alloc_align_float((size_t)ch * roi_mask_scaled->width * roi_mask_scaled->height);
3359 if((img_src == NULL) || (img_dest == NULL))
3360 {
3361 fprintf(stderr, "retouch_heal: error allocating memory for healing\n");
3362 goto cleanup;
3363 }
3364
3365 // copy source and destination to temp images
3366 rt_copy_in_to_out(in, roi_in, img_src, roi_mask_scaled, ch, dx, dy);
3367 rt_copy_in_to_out(in, roi_in, img_dest, roi_mask_scaled, ch, 0, 0);
3368
3369 // heal it
3370 dt_heal(img_src, img_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height, ch, use_sse);
3371
3372 // copy healed (temp) image to destination image
3373 rt_copy_image_masked(img_dest, in, roi_in, ch, mask_scaled, roi_mask_scaled, opacity, use_sse);
3374
3375 cleanup:
3376 if(img_src) dt_free_align(img_src);
3377 if(img_dest) dt_free_align(img_dest);
3378 }
3379
rt_process_forms(float * layer,dwt_params_t * const wt_p,const int scale1)3380 static void rt_process_forms(float *layer, dwt_params_t *const wt_p, const int scale1)
3381 {
3382 int scale = scale1;
3383 retouch_user_data_t *usr_d = (retouch_user_data_t *)wt_p->user_data;
3384 dt_iop_module_t *self = usr_d->self;
3385 dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
3386
3387 // if preview a single scale, just process that scale and original image
3388 // unless merge is activated
3389 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0) return;
3390 // do not process the reconstructed image
3391 if(scale > wt_p->scales + 1) return;
3392
3393 dt_develop_blend_params_t *bp = (dt_develop_blend_params_t *)piece->blendop_data;
3394 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
3395 dt_iop_roi_t *roi_layer = &usr_d->roi;
3396 const int mask_display = usr_d->mask_display && (scale == usr_d->display_scale);
3397
3398 // when the requested scales is grather than max scales the residual image index will be different from the one
3399 // defined by the user,
3400 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
3401 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
3402 {
3403 scale = p->num_scales + 1;
3404 }
3405
3406 // iterate through all forms
3407 if(!usr_d->suppress_mask)
3408 {
3409 const dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id);
3410 if(grp && (grp->type & DT_MASKS_GROUP))
3411 {
3412 for(const GList *forms = grp->points; forms; forms = g_list_next(forms))
3413 {
3414 const dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
3415 if(grpt == NULL)
3416 {
3417 fprintf(stderr, "rt_process_forms: invalid form\n");
3418 continue;
3419 }
3420 const int formid = grpt->formid;
3421 const float form_opacity = grpt->opacity;
3422 if(formid == 0)
3423 {
3424 fprintf(stderr, "rt_process_forms: form is null\n");
3425 continue;
3426 }
3427 const int index = rt_get_index_from_formid(p, formid);
3428 if(index == -1)
3429 {
3430 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
3431 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
3432 continue;
3433 }
3434
3435 // only process current scale
3436 if(p->rt_forms[index].scale != scale)
3437 {
3438 continue;
3439 }
3440
3441 // get the spot
3442 dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, formid);
3443 if(form == NULL)
3444 {
3445 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
3446 continue;
3447 }
3448
3449 // if the form is outside the roi, we just skip it
3450 if(!rt_masks_form_is_in_roi(self, piece, form, roi_layer, roi_layer))
3451 {
3452 continue;
3453 }
3454
3455 // get the mask
3456 float *mask = NULL;
3457 dt_iop_roi_t roi_mask = { 0 };
3458
3459 dt_masks_get_mask(self, piece, form, &mask, &roi_mask.width, &roi_mask.height, &roi_mask.x, &roi_mask.y);
3460 if(mask == NULL)
3461 {
3462 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
3463 continue;
3464 }
3465
3466 // search the delta with the source
3467 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
3468 int dx = 0, dy = 0;
3469
3470 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
3471 {
3472 if(!rt_masks_get_delta_to_destination(self, piece, roi_layer, form, &dx, &dy,
3473 p->rt_forms[index].distort_mode))
3474 {
3475 if(mask) dt_free_align(mask);
3476 continue;
3477 }
3478 }
3479
3480 // scale the mask
3481 float *mask_scaled = NULL;
3482 dt_iop_roi_t roi_mask_scaled = { 0 };
3483
3484 rt_build_scaled_mask(mask, &roi_mask, &mask_scaled, &roi_mask_scaled, roi_layer, dx, dy, algo);
3485
3486 // we don't need the original mask anymore
3487 if(mask)
3488 {
3489 dt_free_align(mask);
3490 mask = NULL;
3491 }
3492
3493 if(mask_scaled == NULL)
3494 {
3495 continue;
3496 }
3497
3498 if((dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
3499 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
3500 {
3501 if(algo == DT_IOP_RETOUCH_CLONE)
3502 {
3503 retouch_clone(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity,
3504 wt_p->use_sse);
3505 }
3506 else if(algo == DT_IOP_RETOUCH_HEAL)
3507 {
3508 retouch_heal(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, dx, dy, form_opacity,
3509 wt_p->use_sse);
3510 }
3511 else if(algo == DT_IOP_RETOUCH_BLUR)
3512 {
3513 retouch_blur(self, layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, form_opacity,
3514 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, piece, wt_p->use_sse);
3515 }
3516 else if(algo == DT_IOP_RETOUCH_FILL)
3517 {
3518 // add a brightness to the color so it can be fine-adjusted by the user
3519 float fill_color[3];
3520
3521 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
3522 {
3523 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
3524 }
3525 else
3526 {
3527 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
3528 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
3529 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
3530 }
3531
3532 retouch_fill(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, form_opacity, fill_color,
3533 wt_p->use_sse);
3534 }
3535 else
3536 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
3537
3538 if(mask_display)
3539 rt_copy_mask_to_alpha(layer, roi_layer, wt_p->ch, mask_scaled, &roi_mask_scaled, form_opacity);
3540 }
3541
3542 if(mask) dt_free_align(mask);
3543 if(mask_scaled) dt_free_align(mask_scaled);
3544 }
3545 }
3546 }
3547 }
3548
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)3549 static void process_internal(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3550 void *const ovoid, const dt_iop_roi_t *const roi_in,
3551 const dt_iop_roi_t *const roi_out, const int use_sse)
3552 {
3553 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
3554 dt_iop_retouch_gui_data_t *g = (dt_iop_retouch_gui_data_t *)self->gui_data;
3555
3556 float *in_retouch = NULL;
3557
3558 dt_iop_roi_t roi_retouch = *roi_in;
3559 dt_iop_roi_t *roi_rt = &roi_retouch;
3560
3561 const int ch = piece->colors;
3562 retouch_user_data_t usr_data = { 0 };
3563 dwt_params_t *dwt_p = NULL;
3564
3565 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
3566 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
3567
3568 // we will do all the clone, heal, etc on the input image,
3569 // this way the source for one algorithm can be the destination from a previous one
3570 in_retouch = dt_alloc_align_float((size_t)ch * roi_rt->width * roi_rt->height);
3571 if(in_retouch == NULL) goto cleanup;
3572
3573 dt_iop_image_copy_by_size(in_retouch, ivoid, roi_rt->width, roi_rt->height, ch);
3574
3575 // user data passed from the decompose routine to the one that process each scale
3576 usr_data.self = self;
3577 usr_data.piece = piece;
3578 usr_data.roi = *roi_rt;
3579 usr_data.mask_display = 0;
3580 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
3581 && (piece->pipe == self->dev->pipe));
3582 usr_data.display_scale = p->curr_scale;
3583
3584 // init the decompose routine
3585 dwt_p = dt_dwt_init(in_retouch, roi_rt->width, roi_rt->height, ch, p->num_scales,
3586 (!display_wavelet_scale || (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
3587 p->merge_from_scale, &usr_data,
3588 roi_in->scale / piece->iscale, use_sse);
3589 if(dwt_p == NULL) goto cleanup;
3590
3591 // check if this module should expose mask.
3592 if((piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL && g
3593 && (g->mask_display || display_wavelet_scale) && self->dev->gui_attached
3594 && (self == self->dev->gui_module) && (piece->pipe == self->dev->pipe))
3595 {
3596 for(size_t j = 0; j < (size_t)roi_rt->width * roi_rt->height * ch; j += ch) in_retouch[j + 3] = 0.f;
3597
3598 piece->pipe->mask_display = g->mask_display ? DT_DEV_PIXELPIPE_DISPLAY_MASK : DT_DEV_PIXELPIPE_DISPLAY_PASSTHRU;
3599 piece->pipe->bypass_blendif = 1;
3600 usr_data.mask_display = 1;
3601 }
3602
3603 if((piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
3604 {
3605 // check if the image support this number of scales
3606 if(gui_active)
3607 {
3608 const int max_scales = dwt_get_max_scale(dwt_p);
3609 if(dwt_p->scales > max_scales)
3610 {
3611 dt_control_log(_("max scale is %i for this image size"), max_scales);
3612 }
3613 }
3614 // get first scale visible at this zoom level
3615 if(g) g->first_scale_visible = dt_dwt_first_scale_visible(dwt_p);
3616 }
3617
3618 // decompose it
3619 dwt_decompose(dwt_p, rt_process_forms);
3620
3621 float levels[3] = { 0.f };
3622 levels[0] = p->preview_levels[0];
3623 levels[1] = p->preview_levels[1];
3624 levels[2] = p->preview_levels[2];
3625
3626 // process auto levels
3627 if(g && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
3628 {
3629 dt_iop_gui_enter_critical_section(self);
3630 if(g->preview_auto_levels == 1 && !darktable.gui->reset)
3631 {
3632 g->preview_auto_levels = -1;
3633
3634 dt_iop_gui_leave_critical_section(self);
3635
3636 levels[0] = levels[1] = levels[2] = 0;
3637 rt_process_stats(self, piece, in_retouch, roi_rt->width, roi_rt->height, ch, levels, use_sse);
3638 rt_clamp_minmax(levels, levels);
3639
3640 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
3641
3642 dt_iop_gui_enter_critical_section(self);
3643 g->preview_auto_levels = 2;
3644 dt_iop_gui_leave_critical_section(self);
3645 }
3646 else
3647 {
3648 dt_iop_gui_leave_critical_section(self);
3649 }
3650 }
3651
3652 // if user wants to preview a detail scale adjust levels
3653 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
3654 {
3655 rt_adjust_levels(self, piece, in_retouch, roi_rt->width, roi_rt->height, ch, levels, use_sse);
3656 }
3657
3658 // copy alpha channel if needed
3659 if((piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && g && !g->mask_display)
3660 {
3661 dt_iop_alpha_copy(ivoid, in_retouch, roi_rt->width, roi_rt->height);
3662 }
3663
3664 // return final image
3665 rt_copy_in_to_out(in_retouch, roi_rt, ovoid, roi_out, ch, 0, 0);
3666
3667 cleanup:
3668 if(in_retouch) dt_free_align(in_retouch);
3669 if(dwt_p) dt_dwt_free(dwt_p);
3670 }
3671
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)3672 void process(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3673 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
3674 {
3675 process_internal(self, piece, ivoid, ovoid, roi_in, roi_out, 0);
3676 }
3677
3678 #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)3679 void process_sse2(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, const void *const ivoid,
3680 void *const ovoid, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
3681 {
3682 process_internal(self, piece, ivoid, ovoid, roi_in, roi_out, 1);
3683 }
3684 #endif
3685
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)3686 void distort_mask(struct dt_iop_module_t *self, struct dt_dev_pixelpipe_iop_t *piece, const float *const in,
3687 float *const out, const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
3688 {
3689 rt_copy_in_to_out(in, roi_in, out, roi_out, 1, 0, 0);
3690 }
3691
3692 #ifdef HAVE_OPENCL
3693
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])3694 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,
3695 const int width, const int height, float levels[3])
3696 {
3697 cl_int err = CL_SUCCESS;
3698
3699 const int ch = 4;
3700
3701 float *src_buffer = NULL;
3702
3703 src_buffer = dt_alloc_align_float((size_t)ch * width * height);
3704 if(src_buffer == NULL)
3705 {
3706 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3707 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3708 goto cleanup;
3709 }
3710
3711 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3712 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3713 if(err != CL_SUCCESS)
3714 {
3715 goto cleanup;
3716 }
3717
3718 // just call the CPU version for now
3719 rt_process_stats(self, piece, src_buffer, width, height, ch, levels, 1);
3720
3721 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, TRUE);
3722 if(err != CL_SUCCESS)
3723 {
3724 goto cleanup;
3725 }
3726
3727 cleanup:
3728 if(src_buffer) dt_free_align(src_buffer);
3729
3730 return err;
3731 }
3732
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])3733 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,
3734 const int width, const int height, const float levels[3])
3735 {
3736 cl_int err = CL_SUCCESS;
3737
3738 const int ch = 4;
3739
3740 float *src_buffer = NULL;
3741
3742 src_buffer = dt_alloc_align_float((size_t)ch * width * height);
3743 if(src_buffer == NULL)
3744 {
3745 fprintf(stderr, "dt_heal_cl: error allocating memory for healing\n");
3746 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3747 goto cleanup;
3748 }
3749
3750 err = dt_opencl_read_buffer_from_device(devid, (void *)src_buffer, dev_img, 0,
3751 (size_t)width * height * ch * sizeof(float), CL_TRUE);
3752 if(err != CL_SUCCESS)
3753 {
3754 goto cleanup;
3755 }
3756
3757 // just call the CPU version for now
3758 rt_adjust_levels(self, piece, src_buffer, width, height, ch, levels, 1);
3759
3760 err = dt_opencl_write_buffer_to_device(devid, src_buffer, dev_img, 0, sizeof(float) * ch * width * height, TRUE);
3761 if(err != CL_SUCCESS)
3762 {
3763 goto cleanup;
3764 }
3765
3766 cleanup:
3767 if(src_buffer) dt_free_align(src_buffer);
3768
3769 return err;
3770 }
3771
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)3772 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,
3773 cl_mem dev_out, const struct dt_iop_roi_t *const roi_out, const int dx,
3774 const int dy, const int kernel)
3775 {
3776 cl_int err = CL_SUCCESS;
3777
3778 const int xoffs = roi_out->x - roi_in->x - dx;
3779 const int yoffs = roi_out->y - roi_in->y - dy;
3780
3781 cl_mem dev_roi_in = NULL;
3782 cl_mem dev_roi_out = NULL;
3783
3784 const size_t sizes[]
3785 = { ROUNDUPWD(MIN(roi_out->width, roi_in->width)), ROUNDUPHT(MIN(roi_out->height, roi_in->height)), 1 };
3786
3787 dev_roi_in = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_in);
3788 dev_roi_out = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_out);
3789 if(dev_roi_in == NULL || dev_roi_out == NULL)
3790 {
3791 fprintf(stderr, "rt_copy_in_to_out_cl error 1\n");
3792 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3793 goto cleanup;
3794 }
3795
3796 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
3797 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_in);
3798 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_out);
3799 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_out);
3800 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(int), (void *)&xoffs);
3801 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(int), (void *)&yoffs);
3802 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3803 if(err != CL_SUCCESS)
3804 {
3805 fprintf(stderr, "rt_copy_in_to_out_cl error 2\n");
3806 goto cleanup;
3807 }
3808
3809 cleanup:
3810 if(dev_roi_in) dt_opencl_release_mem_object(dev_roi_in);
3811 if(dev_roi_out) dt_opencl_release_mem_object(dev_roi_out);
3812
3813 return err;
3814 }
3815
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)3816 static cl_int rt_build_scaled_mask_cl(const int devid, float *const mask, dt_iop_roi_t *const roi_mask,
3817 float **mask_scaled, cl_mem *p_dev_mask_scaled,
3818 dt_iop_roi_t *roi_mask_scaled, dt_iop_roi_t *const roi_in, const int dx,
3819 const int dy, const int algo)
3820 {
3821 cl_int err = CL_SUCCESS;
3822
3823 rt_build_scaled_mask(mask, roi_mask, mask_scaled, roi_mask_scaled, roi_in, dx, dy, algo);
3824 if(*mask_scaled == NULL)
3825 {
3826 goto cleanup;
3827 }
3828
3829 const cl_mem dev_mask_scaled
3830 = dt_opencl_alloc_device_buffer(devid, sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height);
3831 if(dev_mask_scaled == NULL)
3832 {
3833 fprintf(stderr, "rt_build_scaled_mask_cl error 2\n");
3834 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3835 goto cleanup;
3836 }
3837
3838 err = dt_opencl_write_buffer_to_device(devid, *mask_scaled, dev_mask_scaled, 0,
3839 sizeof(float) * roi_mask_scaled->width * roi_mask_scaled->height, TRUE);
3840 if(err != CL_SUCCESS)
3841 {
3842 fprintf(stderr, "rt_build_scaled_mask_cl error 4\n");
3843 goto cleanup;
3844 }
3845
3846 *p_dev_mask_scaled = dev_mask_scaled;
3847
3848 cleanup:
3849 if(err != CL_SUCCESS) fprintf(stderr, "rt_build_scaled_mask_cl error\n");
3850
3851 return err;
3852 }
3853
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)3854 static cl_int rt_copy_image_masked_cl(const int devid, cl_mem dev_src, cl_mem dev_dest,
3855 dt_iop_roi_t *const roi_dest, cl_mem dev_mask_scaled,
3856 dt_iop_roi_t *const roi_mask_scaled, const float opacity, const int kernel)
3857 {
3858 cl_int err = CL_SUCCESS;
3859
3860 const size_t sizes[] = { ROUNDUPWD(roi_mask_scaled->width), ROUNDUPHT(roi_mask_scaled->height), 1 };
3861
3862 const cl_mem dev_roi_dest =
3863 dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_dest);
3864
3865 const cl_mem dev_roi_mask_scaled
3866 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3867
3868 if(dev_roi_dest == NULL || dev_roi_mask_scaled == NULL)
3869 {
3870 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3871 goto cleanup;
3872 }
3873
3874 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_src);
3875 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_dest);
3876 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_roi_dest);
3877 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_mask_scaled);
3878 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3879 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&opacity);
3880 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3881 if(err != CL_SUCCESS) goto cleanup;
3882
3883 cleanup:
3884 if(dev_roi_dest) dt_opencl_release_mem_object(dev_roi_dest);
3885 if(dev_roi_mask_scaled) dt_opencl_release_mem_object(dev_roi_mask_scaled);
3886
3887 return err;
3888 }
3889
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)3890 static cl_int rt_copy_mask_to_alpha_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3891 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled,
3892 const float opacity, dt_iop_retouch_global_data_t *gd)
3893 {
3894 cl_int err = CL_SUCCESS;
3895
3896 // fill it
3897 const int kernel = gd->kernel_retouch_copy_mask_to_alpha;
3898 const size_t sizes[] = { ROUNDUPWD(roi_mask_scaled->width), ROUNDUPHT(roi_mask_scaled->height), 1 };
3899
3900 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3901 const cl_mem dev_roi_mask_scaled
3902 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3903 if(dev_roi_layer == NULL || dev_roi_mask_scaled == NULL)
3904 {
3905 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3906 goto cleanup;
3907 }
3908
3909 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3910 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3911 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3912 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3913 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3914 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3915 if(err != CL_SUCCESS) goto cleanup;
3916
3917
3918 cleanup:
3919 dt_opencl_release_mem_object(dev_roi_layer);
3920 dt_opencl_release_mem_object(dev_roi_mask_scaled);
3921
3922 return err;
3923 }
3924
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)3925 static cl_int retouch_clone_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3926 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
3927 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
3928 {
3929 cl_int err = CL_SUCCESS;
3930
3931 const int ch = 4;
3932
3933 // alloc source temp image to avoid issues when areas self-intersects
3934 const cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
3935 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
3936 if(dev_src == NULL)
3937 {
3938 fprintf(stderr, "retouch_clone_cl error 2\n");
3939 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3940 goto cleanup;
3941 }
3942
3943 // copy source image to tmp
3944 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
3945 gd->kernel_retouch_copy_buffer_to_buffer);
3946 if(err != CL_SUCCESS)
3947 {
3948 fprintf(stderr, "retouch_clone_cl error 4\n");
3949 goto cleanup;
3950 }
3951
3952 // clone it
3953 err = rt_copy_image_masked_cl(devid, dev_src, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
3954 gd->kernel_retouch_copy_buffer_to_buffer_masked);
3955 if(err != CL_SUCCESS)
3956 {
3957 fprintf(stderr, "retouch_clone_cl error 5\n");
3958 goto cleanup;
3959 }
3960
3961 cleanup:
3962 if(dev_src) dt_opencl_release_mem_object(dev_src);
3963
3964 return err;
3965 }
3966
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)3967 static cl_int retouch_fill_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
3968 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
3969 float *color, dt_iop_retouch_global_data_t *gd)
3970 {
3971 cl_int err = CL_SUCCESS;
3972
3973 // fill it
3974 const int kernel = gd->kernel_retouch_fill;
3975 const size_t sizes[] = { ROUNDUPWD(roi_mask_scaled->width), ROUNDUPHT(roi_mask_scaled->height), 1 };
3976
3977 const cl_mem dev_roi_layer = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_layer);
3978 const cl_mem dev_roi_mask_scaled
3979 = dt_opencl_copy_host_to_device_constant(devid, sizeof(dt_iop_roi_t), (void *)roi_mask_scaled);
3980 if(dev_roi_layer == NULL || dev_roi_mask_scaled == NULL)
3981 {
3982 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
3983 goto cleanup;
3984 }
3985
3986 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
3987 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&dev_roi_layer);
3988 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(cl_mem), (void *)&dev_mask_scaled);
3989 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(cl_mem), (void *)&dev_roi_mask_scaled);
3990 dt_opencl_set_kernel_arg(devid, kernel, 4, sizeof(float), (void *)&opacity);
3991 dt_opencl_set_kernel_arg(devid, kernel, 5, sizeof(float), (void *)&(color[0]));
3992 dt_opencl_set_kernel_arg(devid, kernel, 6, sizeof(float), (void *)&(color[1]));
3993 dt_opencl_set_kernel_arg(devid, kernel, 7, sizeof(float), (void *)&(color[2]));
3994 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
3995 if(err != CL_SUCCESS) goto cleanup;
3996
3997
3998 cleanup:
3999 dt_opencl_release_mem_object(dev_roi_layer);
4000 dt_opencl_release_mem_object(dev_roi_mask_scaled);
4001
4002 return err;
4003 }
4004
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)4005 static cl_int retouch_blur_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer,
4006 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const float opacity,
4007 const int blur_type, const float blur_radius, dt_dev_pixelpipe_iop_t *piece,
4008 dt_iop_retouch_global_data_t *gd)
4009 {
4010 cl_int err = CL_SUCCESS;
4011
4012 if(fabsf(blur_radius) <= 0.1f) return err;
4013
4014 const float sigma = blur_radius * roi_layer->scale / piece->iscale;
4015 const int ch = 4;
4016
4017 const cl_mem dev_dest =
4018 dt_opencl_alloc_device(devid, roi_mask_scaled->width, roi_mask_scaled->height, sizeof(float) * ch);
4019 if(dev_dest == NULL)
4020 {
4021 fprintf(stderr, "retouch_blur_cl error 2\n");
4022 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4023 goto cleanup;
4024 }
4025
4026 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
4027 {
4028 const int kernel = gd->kernel_retouch_image_rgb2lab;
4029 size_t sizes[] = { ROUNDUPWD(roi_layer->width), ROUNDUPHT(roi_layer->height), 1 };
4030
4031 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4032 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
4033 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
4034 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4035 if(err != CL_SUCCESS) goto cleanup;
4036 }
4037
4038 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
4039 gd->kernel_retouch_copy_buffer_to_image);
4040 if(err != CL_SUCCESS)
4041 {
4042 fprintf(stderr, "retouch_blur_cl error 4\n");
4043 goto cleanup;
4044 }
4045
4046 if(blur_type == DT_IOP_RETOUCH_BLUR_GAUSSIAN && fabsf(blur_radius) > 0.1f)
4047 {
4048 float Labmax[] = { INFINITY, INFINITY, INFINITY, INFINITY };
4049 float Labmin[] = { -INFINITY, -INFINITY, -INFINITY, -INFINITY };
4050
4051 dt_gaussian_cl_t *g = dt_gaussian_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, ch, Labmax,
4052 Labmin, sigma, DT_IOP_GAUSSIAN_ZERO);
4053 if(g)
4054 {
4055 err = dt_gaussian_blur_cl(g, dev_dest, dev_dest);
4056 dt_gaussian_free_cl(g);
4057 if(err != CL_SUCCESS) goto cleanup;
4058 }
4059 }
4060 else if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL && fabsf(blur_radius) > 0.1f)
4061 {
4062 const float sigma_r = 100.0f; // does not depend on scale
4063 const float sigma_s = sigma;
4064 const float detail = -1.0f; // we want the bilateral base layer
4065
4066 dt_bilateral_cl_t *b
4067 = dt_bilateral_init_cl(devid, roi_mask_scaled->width, roi_mask_scaled->height, sigma_s, sigma_r);
4068 if(b)
4069 {
4070 err = dt_bilateral_splat_cl(b, dev_dest);
4071 if(err == CL_SUCCESS) err = dt_bilateral_blur_cl(b);
4072 if(err == CL_SUCCESS) err = dt_bilateral_slice_cl(b, dev_dest, dev_dest, detail);
4073
4074 dt_bilateral_free_cl(b);
4075 }
4076 }
4077
4078 // copy blurred (temp) image to destination image
4079 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
4080 gd->kernel_retouch_copy_image_to_buffer_masked);
4081 if(err != CL_SUCCESS)
4082 {
4083 fprintf(stderr, "retouch_blur_cl error 5\n");
4084 goto cleanup;
4085 }
4086
4087 if(blur_type == DT_IOP_RETOUCH_BLUR_BILATERAL)
4088 {
4089 const int kernel = gd->kernel_retouch_image_lab2rgb;
4090 const size_t sizes[] = { ROUNDUPWD(roi_layer->width), ROUNDUPHT(roi_layer->height), 1 };
4091
4092 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_layer);
4093 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_layer->width));
4094 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_layer->height));
4095 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4096 if(err != CL_SUCCESS) goto cleanup;
4097 }
4098
4099 cleanup:
4100 if(dev_dest) dt_opencl_release_mem_object(dev_dest);
4101
4102 return err;
4103 }
4104
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)4105 static cl_int retouch_heal_cl(const int devid, cl_mem dev_layer, dt_iop_roi_t *const roi_layer, float *mask_scaled,
4106 cl_mem dev_mask_scaled, dt_iop_roi_t *const roi_mask_scaled, const int dx,
4107 const int dy, const float opacity, dt_iop_retouch_global_data_t *gd)
4108 {
4109 cl_int err = CL_SUCCESS;
4110
4111 const int ch = 4;
4112
4113 cl_mem dev_dest = NULL;
4114 cl_mem dev_src = dt_opencl_alloc_device_buffer(devid,
4115 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4116 if(dev_src == NULL)
4117 {
4118 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4119 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4120 goto cleanup;
4121 }
4122
4123 dev_dest = dt_opencl_alloc_device_buffer(devid,
4124 sizeof(float) * ch * roi_mask_scaled->width * roi_mask_scaled->height);
4125 if(dev_dest == NULL)
4126 {
4127 fprintf(stderr, "retouch_heal_cl: error allocating memory for healing\n");
4128 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4129 goto cleanup;
4130 }
4131
4132 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_src, roi_mask_scaled, dx, dy,
4133 gd->kernel_retouch_copy_buffer_to_buffer);
4134 if(err != CL_SUCCESS)
4135 {
4136 fprintf(stderr, "retouch_heal_cl error 4\n");
4137 goto cleanup;
4138 }
4139
4140 err = rt_copy_in_to_out_cl(devid, dev_layer, roi_layer, dev_dest, roi_mask_scaled, 0, 0,
4141 gd->kernel_retouch_copy_buffer_to_buffer);
4142 if(err != CL_SUCCESS)
4143 {
4144 fprintf(stderr, "retouch_heal_cl error 4\n");
4145 goto cleanup;
4146 }
4147
4148 // heal it
4149 heal_params_cl_t *hp = dt_heal_init_cl(devid);
4150 if(hp)
4151 {
4152 err = dt_heal_cl(hp, dev_src, dev_dest, mask_scaled, roi_mask_scaled->width, roi_mask_scaled->height);
4153 dt_heal_free_cl(hp);
4154
4155 dt_opencl_release_mem_object(dev_src);
4156 dev_src = NULL;
4157
4158 if(err != CL_SUCCESS) goto cleanup;
4159 }
4160
4161 // copy healed (temp) image to destination image
4162 err = rt_copy_image_masked_cl(devid, dev_dest, dev_layer, roi_layer, dev_mask_scaled, roi_mask_scaled, opacity,
4163 gd->kernel_retouch_copy_buffer_to_buffer_masked);
4164 if(err != CL_SUCCESS)
4165 {
4166 fprintf(stderr, "retouch_heal_cl error 6\n");
4167 goto cleanup;
4168 }
4169
4170 cleanup:
4171 if(dev_src) dt_opencl_release_mem_object(dev_src);
4172 if(dev_dest) dt_opencl_release_mem_object(dev_dest);
4173
4174 return err;
4175 }
4176
rt_process_forms_cl(cl_mem dev_layer,dwt_params_cl_t * const wt_p,const int scale1)4177 static cl_int rt_process_forms_cl(cl_mem dev_layer, dwt_params_cl_t *const wt_p, const int scale1)
4178 {
4179 cl_int err = CL_SUCCESS;
4180
4181 int scale = scale1;
4182 retouch_user_data_t *usr_d = (retouch_user_data_t *)wt_p->user_data;
4183 dt_iop_module_t *self = usr_d->self;
4184 dt_dev_pixelpipe_iop_t *piece = usr_d->piece;
4185
4186 // if preview a single scale, just process that scale and original image
4187 // unless merge is activated
4188 if(wt_p->merge_from_scale == 0 && wt_p->return_layer > 0 && scale != wt_p->return_layer && scale != 0)
4189 return err;
4190 // do not process the reconstructed image
4191 if(scale > wt_p->scales + 1) return err;
4192
4193 dt_develop_blend_params_t *bp = (dt_develop_blend_params_t *)piece->blendop_data;
4194 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
4195 dt_iop_retouch_global_data_t *gd = (dt_iop_retouch_global_data_t *)self->global_data;
4196 const int devid = piece->pipe->devid;
4197 dt_iop_roi_t *roi_layer = &usr_d->roi;
4198 const int mask_display = usr_d->mask_display && (scale == usr_d->display_scale);
4199
4200 // when the requested scales is grather than max scales the residual image index will be different from the one
4201 // defined by the user,
4202 // so we need to adjust it here, otherwise we will be using the shapes from a scale on the residual image
4203 if(wt_p->scales < p->num_scales && wt_p->return_layer == 0 && scale == wt_p->scales + 1)
4204 {
4205 scale = p->num_scales + 1;
4206 }
4207
4208 // iterate through all forms
4209 if(!usr_d->suppress_mask)
4210 {
4211 dt_masks_form_t *grp = dt_masks_get_from_id_ext(piece->pipe->forms, bp->mask_id);
4212 if(grp && (grp->type & DT_MASKS_GROUP))
4213 {
4214 for(const GList *forms = grp->points; forms && err == CL_SUCCESS; forms = g_list_next(forms))
4215 {
4216 dt_masks_point_group_t *grpt = (dt_masks_point_group_t *)forms->data;
4217 if(grpt == NULL)
4218 {
4219 fprintf(stderr, "rt_process_forms: invalid form\n");
4220 continue;
4221 }
4222 const int formid = grpt->formid;
4223 const float form_opacity = grpt->opacity;
4224 if(formid == 0)
4225 {
4226 fprintf(stderr, "rt_process_forms: form is null\n");
4227 continue;
4228 }
4229 const int index = rt_get_index_from_formid(p, formid);
4230 if(index == -1)
4231 {
4232 // FIXME: we get this error when user go back in history, so forms are the same but the array has changed
4233 fprintf(stderr, "rt_process_forms: missing form=%i from array\n", formid);
4234 continue;
4235 }
4236
4237 // only process current scale
4238 if(p->rt_forms[index].scale != scale)
4239 {
4240 continue;
4241 }
4242
4243 // get the spot
4244 dt_masks_form_t *form = dt_masks_get_from_id_ext(piece->pipe->forms, formid);
4245 if(form == NULL)
4246 {
4247 fprintf(stderr, "rt_process_forms: missing form=%i from masks\n", formid);
4248 continue;
4249 }
4250
4251 // if the form is outside the roi, we just skip it
4252 if(!rt_masks_form_is_in_roi(self, piece, form, roi_layer, roi_layer))
4253 {
4254 continue;
4255 }
4256
4257 // get the mask
4258 float *mask = NULL;
4259 dt_iop_roi_t roi_mask = { 0 };
4260
4261 dt_masks_get_mask(self, piece, form, &mask, &roi_mask.width, &roi_mask.height, &roi_mask.x, &roi_mask.y);
4262 if(mask == NULL)
4263 {
4264 fprintf(stderr, "rt_process_forms: error retrieving mask\n");
4265 continue;
4266 }
4267
4268 int dx = 0, dy = 0;
4269
4270 // search the delta with the source
4271 const dt_iop_retouch_algo_type_t algo = p->rt_forms[index].algorithm;
4272 if(algo != DT_IOP_RETOUCH_BLUR && algo != DT_IOP_RETOUCH_FILL)
4273 {
4274 if(!rt_masks_get_delta_to_destination(self, piece, roi_layer, form, &dx, &dy,
4275 p->rt_forms[index].distort_mode))
4276 {
4277 if(mask) dt_free_align(mask);
4278 continue;
4279 }
4280 }
4281
4282 // scale the mask
4283 cl_mem dev_mask_scaled = NULL;
4284 float *mask_scaled = NULL;
4285 dt_iop_roi_t roi_mask_scaled = { 0 };
4286
4287 err = rt_build_scaled_mask_cl(devid, mask, &roi_mask, &mask_scaled, &dev_mask_scaled, &roi_mask_scaled,
4288 roi_layer, dx, dy, algo);
4289
4290 // only heal needs mask scaled
4291 if(algo != DT_IOP_RETOUCH_HEAL && mask_scaled != NULL)
4292 {
4293 dt_free_align(mask_scaled);
4294 mask_scaled = NULL;
4295 }
4296
4297 // we don't need the original mask anymore
4298 if(mask)
4299 {
4300 dt_free_align(mask);
4301 mask = NULL;
4302 }
4303
4304 if(mask_scaled == NULL && algo == DT_IOP_RETOUCH_HEAL)
4305 {
4306 if(dev_mask_scaled) dt_opencl_release_mem_object(dev_mask_scaled);
4307 dev_mask_scaled = NULL;
4308 continue;
4309 }
4310
4311 if((err == CL_SUCCESS)
4312 && (dx != 0 || dy != 0 || algo == DT_IOP_RETOUCH_BLUR || algo == DT_IOP_RETOUCH_FILL)
4313 && ((roi_mask_scaled.width > 2) && (roi_mask_scaled.height > 2)))
4314 {
4315 if(algo == DT_IOP_RETOUCH_CLONE)
4316 {
4317 err = retouch_clone_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, dx, dy,
4318 form_opacity, gd);
4319 }
4320 else if(algo == DT_IOP_RETOUCH_HEAL)
4321 {
4322 err = retouch_heal_cl(devid, dev_layer, roi_layer, mask_scaled, dev_mask_scaled, &roi_mask_scaled, dx,
4323 dy, form_opacity, gd);
4324 }
4325 else if(algo == DT_IOP_RETOUCH_BLUR)
4326 {
4327 err = retouch_blur_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4328 p->rt_forms[index].blur_type, p->rt_forms[index].blur_radius, piece, gd);
4329 }
4330 else if(algo == DT_IOP_RETOUCH_FILL)
4331 {
4332 // add a brightness to the color so it can be fine-adjusted by the user
4333 float fill_color[3];
4334
4335 if(p->rt_forms[index].fill_mode == DT_IOP_RETOUCH_FILL_ERASE)
4336 {
4337 fill_color[0] = fill_color[1] = fill_color[2] = p->rt_forms[index].fill_brightness;
4338 }
4339 else
4340 {
4341 fill_color[0] = p->rt_forms[index].fill_color[0] + p->rt_forms[index].fill_brightness;
4342 fill_color[1] = p->rt_forms[index].fill_color[1] + p->rt_forms[index].fill_brightness;
4343 fill_color[2] = p->rt_forms[index].fill_color[2] + p->rt_forms[index].fill_brightness;
4344 }
4345
4346 err = retouch_fill_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4347 fill_color, gd);
4348 }
4349 else
4350 fprintf(stderr, "rt_process_forms: unknown algorithm %i\n", algo);
4351
4352 if(mask_display)
4353 rt_copy_mask_to_alpha_cl(devid, dev_layer, roi_layer, dev_mask_scaled, &roi_mask_scaled, form_opacity,
4354 gd);
4355 }
4356
4357 if(mask) dt_free_align(mask);
4358 if(mask_scaled) dt_free_align(mask_scaled);
4359 if(dev_mask_scaled) dt_opencl_release_mem_object(dev_mask_scaled);
4360 }
4361 }
4362 }
4363
4364 return err;
4365 }
4366
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)4367 int process_cl(struct dt_iop_module_t *self, dt_dev_pixelpipe_iop_t *piece, cl_mem dev_in, cl_mem dev_out,
4368 const dt_iop_roi_t *const roi_in, const dt_iop_roi_t *const roi_out)
4369 {
4370 dt_iop_retouch_params_t *p = (dt_iop_retouch_params_t *)piece->data;
4371 dt_iop_retouch_global_data_t *gd = (dt_iop_retouch_global_data_t *)self->global_data;
4372 dt_iop_retouch_gui_data_t *g = (dt_iop_retouch_gui_data_t *)self->gui_data;
4373
4374 cl_int err = CL_SUCCESS;
4375 const int devid = piece->pipe->devid;
4376
4377 dt_iop_roi_t roi_retouch = *roi_in;
4378 dt_iop_roi_t *roi_rt = &roi_retouch;
4379
4380 const int ch = piece->colors;
4381 retouch_user_data_t usr_data = { 0 };
4382 dwt_params_cl_t *dwt_p = NULL;
4383
4384 const int gui_active = (self->dev) ? (self == self->dev->gui_module) : 0;
4385 const int display_wavelet_scale = (g && gui_active) ? g->display_wavelet_scale : 0;
4386
4387 // we will do all the clone, heal, etc on the input image,
4388 // this way the source for one algorithm can be the destination from a previous one
4389 const cl_mem in_retouch = dt_opencl_alloc_device_buffer(devid, sizeof(float) * ch * roi_rt->width * roi_rt->height);
4390 if(in_retouch == NULL)
4391 {
4392 fprintf(stderr, "process_internal: error allocating memory for wavelet decompose\n");
4393 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4394 goto cleanup;
4395 }
4396
4397 // copy input image to the new buffer
4398 {
4399 size_t origin[] = { 0, 0, 0 };
4400 size_t region[] = { roi_rt->width, roi_rt->height, 1 };
4401 err = dt_opencl_enqueue_copy_image_to_buffer(devid, dev_in, in_retouch, origin, region, 0);
4402 if(err != CL_SUCCESS) goto cleanup;
4403 }
4404
4405 // user data passed from the decompose routine to the one that process each scale
4406 usr_data.self = self;
4407 usr_data.piece = piece;
4408 usr_data.roi = *roi_rt;
4409 usr_data.mask_display = 0;
4410 usr_data.suppress_mask = (g && g->suppress_mask && self->dev->gui_attached && (self == self->dev->gui_module)
4411 && (piece->pipe == self->dev->pipe));
4412 usr_data.display_scale = p->curr_scale;
4413
4414 // init the decompose routine
4415 dwt_p = dt_dwt_init_cl(devid, in_retouch, roi_rt->width, roi_rt->height, p->num_scales,
4416 (!display_wavelet_scale
4417 || (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) != DT_DEV_PIXELPIPE_FULL) ? 0 : p->curr_scale,
4418 p->merge_from_scale, &usr_data,
4419 roi_in->scale / piece->iscale);
4420 if(dwt_p == NULL)
4421 {
4422 fprintf(stderr, "process_internal: error initializing wavelet decompose\n");
4423 err = CL_MEM_OBJECT_ALLOCATION_FAILURE;
4424 goto cleanup;
4425 }
4426
4427 // check if this module should expose mask.
4428 if((piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL && g && g->mask_display && self->dev->gui_attached
4429 && (self == self->dev->gui_module) && (piece->pipe == self->dev->pipe))
4430 {
4431 const int kernel = gd->kernel_retouch_clear_alpha;
4432 const size_t sizes[] = { ROUNDUPWD(roi_rt->width), ROUNDUPHT(roi_rt->height), 1 };
4433
4434 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&in_retouch);
4435 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(int), (void *)&(roi_rt->width));
4436 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->height));
4437 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4438 if(err != CL_SUCCESS) goto cleanup;
4439
4440 piece->pipe->mask_display = DT_DEV_PIXELPIPE_DISPLAY_MASK;
4441 piece->pipe->bypass_blendif = 1;
4442 usr_data.mask_display = 1;
4443 }
4444
4445 if((piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
4446 {
4447 // check if the image support this number of scales
4448 if(gui_active)
4449 {
4450 const int max_scales = dwt_get_max_scale_cl(dwt_p);
4451 if(dwt_p->scales > max_scales)
4452 {
4453 dt_control_log(_("max scale is %i for this image size"), max_scales);
4454 }
4455 }
4456 // get first scale visible at this zoom level
4457 if(g) g->first_scale_visible = dt_dwt_first_scale_visible_cl(dwt_p);
4458 }
4459
4460 // decompose it
4461 err = dwt_decompose_cl(dwt_p, rt_process_forms_cl);
4462 if(err != CL_SUCCESS) goto cleanup;
4463
4464 float levels[3] = { 0.f };
4465 levels[0] = p->preview_levels[0];
4466 levels[1] = p->preview_levels[1];
4467 levels[2] = p->preview_levels[2];
4468
4469 // process auto levels
4470 if(g && (piece->pipe->type & DT_DEV_PIXELPIPE_FULL) == DT_DEV_PIXELPIPE_FULL)
4471 {
4472 dt_iop_gui_enter_critical_section(self);
4473 if(g->preview_auto_levels == 1 && !darktable.gui->reset)
4474 {
4475 g->preview_auto_levels = -1;
4476
4477 dt_iop_gui_leave_critical_section(self);
4478
4479 levels[0] = levels[1] = levels[2] = 0;
4480 err = rt_process_stats_cl(self, piece, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4481 if(err != CL_SUCCESS) goto cleanup;
4482
4483 rt_clamp_minmax(levels, levels);
4484
4485 for(int i = 0; i < 3; i++) g->preview_levels[i] = levels[i];
4486
4487 dt_iop_gui_enter_critical_section(self);
4488 g->preview_auto_levels = 2;
4489 dt_iop_gui_leave_critical_section(self);
4490 }
4491 else
4492 {
4493 dt_iop_gui_leave_critical_section(self);
4494 }
4495 }
4496
4497 // if user wants to preview a detail scale adjust levels
4498 if(dwt_p->return_layer > 0 && dwt_p->return_layer < dwt_p->scales + 1)
4499 {
4500 err = rt_adjust_levels_cl(self, piece, devid, in_retouch, roi_rt->width, roi_rt->height, levels);
4501 if(err != CL_SUCCESS) goto cleanup;
4502 }
4503
4504 // copy alpha channel if needed
4505 if((piece->pipe->mask_display & DT_DEV_PIXELPIPE_DISPLAY_MASK) && g && !g->mask_display)
4506 {
4507 const int kernel = gd->kernel_retouch_copy_alpha;
4508 const size_t sizes[] = { ROUNDUPWD(roi_rt->width), ROUNDUPHT(roi_rt->height), 1 };
4509
4510 dt_opencl_set_kernel_arg(devid, kernel, 0, sizeof(cl_mem), (void *)&dev_in);
4511 dt_opencl_set_kernel_arg(devid, kernel, 1, sizeof(cl_mem), (void *)&in_retouch);
4512 dt_opencl_set_kernel_arg(devid, kernel, 2, sizeof(int), (void *)&(roi_rt->width));
4513 dt_opencl_set_kernel_arg(devid, kernel, 3, sizeof(int), (void *)&(roi_rt->height));
4514 err = dt_opencl_enqueue_kernel_2d(devid, kernel, sizes);
4515 if(err != CL_SUCCESS) goto cleanup;
4516 }
4517
4518 // return final image
4519 err = rt_copy_in_to_out_cl(devid, in_retouch, roi_in, dev_out, roi_out, 0, 0,
4520 gd->kernel_retouch_copy_buffer_to_image);
4521
4522 cleanup:
4523 if(dwt_p) dt_dwt_free_cl(dwt_p);
4524
4525 if(in_retouch) dt_opencl_release_mem_object(in_retouch);
4526
4527 if(err != CL_SUCCESS) dt_print(DT_DEBUG_OPENCL, "[opencl_retouch] couldn't enqueue kernel! %d\n", err);
4528
4529 return (err == CL_SUCCESS) ? TRUE : FALSE;
4530 }
4531 #endif
4532
4533 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
4534 // vim: shiftwidth=2 expandtab tabstop=2 cindent
4535 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-space on;
4536