1 /*
2 This file is part of darktable,
3 Copyright (C) 2013-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 #include "common/debug.h"
19 #include "control/conf.h"
20 #include "control/control.h"
21 #include "develop/blend.h"
22 #include "develop/imageop.h"
23 #include "develop/masks.h"
24
_group_events_mouse_scrolled(struct dt_iop_module_t * module,float pzx,float pzy,int up,uint32_t state,dt_masks_form_t * form,int unused1,dt_masks_form_gui_t * gui,int unused)25 static int _group_events_mouse_scrolled(struct dt_iop_module_t *module, float pzx, float pzy, int up,
26 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
27 int unused)
28 {
29 if(gui->group_edited >= 0)
30 {
31 // we get the form
32 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
33 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
34 if(sel && sel->functions)
35 return sel->functions->mouse_scrolled(module, pzx, pzy, up, state, sel, fpt->parentid, gui, gui->group_edited);
36 }
37 return 0;
38 }
39
_group_events_button_pressed(struct dt_iop_module_t * module,float pzx,float pzy,double pressure,int which,int type,uint32_t state,dt_masks_form_t * form,int unused1,dt_masks_form_gui_t * gui,int unused2)40 static int _group_events_button_pressed(struct dt_iop_module_t *module, float pzx, float pzy,
41 double pressure, int which, int type, uint32_t state,
42 dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
43 {
44 if(gui->group_edited != gui->group_selected)
45 {
46 // we set the selected form in edit mode
47 gui->group_edited = gui->group_selected;
48 // we initialise some variable
49 gui->dx = gui->dy = 0.0f;
50 gui->form_selected = gui->border_selected = gui->form_dragging = gui->form_rotating = FALSE;
51 gui->pivot_selected = FALSE;
52 gui->point_border_selected = gui->seg_selected = gui->point_selected = gui->feather_selected = -1;
53 gui->point_border_dragging = gui->seg_dragging = gui->feather_dragging = gui->point_dragging = -1;
54
55 dt_control_queue_redraw_center();
56 return 1;
57 }
58 if(gui->group_edited >= 0)
59 {
60 // we get the form
61 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
62 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
63 if(!sel) return 0;
64 if(sel->functions)
65 return sel->functions->button_pressed(module, pzx, pzy, pressure, which, type, state, sel,
66 fpt->parentid, gui, gui->group_edited);
67 }
68 return 0;
69 }
70
_group_events_button_released(struct dt_iop_module_t * module,float pzx,float pzy,int which,uint32_t state,dt_masks_form_t * form,int unused1,dt_masks_form_gui_t * gui,int unused2)71 static int _group_events_button_released(struct dt_iop_module_t *module, float pzx, float pzy, int which,
72 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
73 int unused2)
74 {
75 if(gui->group_edited >= 0)
76 {
77 // we get the form
78 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
79 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
80 if(sel && sel->functions)
81 return sel->functions->button_released(module, pzx, pzy, which, state, sel, fpt->parentid, gui,
82 gui->group_edited);
83 }
84 return 0;
85 }
86
_is_handling_form(dt_masks_form_gui_t * gui)87 static inline gboolean _is_handling_form(dt_masks_form_gui_t *gui)
88 {
89 return gui->form_dragging
90 || gui->source_dragging
91 || gui->gradient_toggling
92 || gui->form_rotating
93 || (gui->point_edited != -1)
94 || (gui->point_dragging != -1)
95 || (gui->feather_dragging != -1)
96 || (gui->point_border_dragging != -1)
97 || (gui->seg_dragging != -1);
98 }
99
_group_events_mouse_moved(struct dt_iop_module_t * module,float pzx,float pzy,double pressure,int which,dt_masks_form_t * form,int unused1,dt_masks_form_gui_t * gui,int unused2)100 static int _group_events_mouse_moved(struct dt_iop_module_t *module, float pzx, float pzy, double pressure,
101 int which, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
102 int unused2)
103 {
104 const dt_dev_zoom_t zoom = dt_control_get_dev_zoom();
105 const int closeup = dt_control_get_dev_closeup();
106 const float zoom_scale = dt_dev_get_zoom_scale(darktable.develop, zoom, 1<<closeup, 1);
107 const float pr_d = darktable.develop->preview_downsampling;
108 const float as = DT_PIXEL_APPLY_DPI(5) / (pr_d * zoom_scale); // transformed to backbuf dimensions
109
110 // we first don't do anything if we are inside a scrolling session
111
112 if(gui->scrollx != 0.0f && gui->scrolly != 0.0f)
113 {
114 const float as2 = 0.015f / zoom_scale;
115 if((gui->scrollx - pzx < as2 && gui->scrollx - pzx > -as2)
116 && (gui->scrolly - pzy < as2 && gui->scrolly - pzy > -as2))
117 return 1;
118 gui->scrollx = gui->scrolly = 0.0f;
119 }
120
121 // if a form is in edit mode and we are dragging, don't try to select another form
122 if(gui->group_edited >= 0 && _is_handling_form(gui))
123 {
124 // we get the form
125 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)g_list_nth_data(form->points, gui->group_edited);
126 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
127 if(!sel) return 0;
128 int rep = 0;
129 if(sel->functions)
130 rep = sel->functions->mouse_moved(module, pzx, pzy, pressure, which, sel, fpt->parentid, gui,
131 gui->group_edited);
132 if(rep) return 1;
133 // if a point is in state editing, then we don't want that another form can be selected
134 if(gui->point_edited >= 0) return 0;
135 }
136
137 // now we check if we are near a form
138 int pos = 0;
139 gui->form_selected = gui->border_selected = FALSE;
140 gui->source_selected = gui->source_dragging = FALSE;
141 gui->pivot_selected = FALSE;
142 gui->feather_selected = -1;
143 gui->point_edited = gui->point_selected = -1;
144 gui->seg_selected = -1;
145 gui->point_border_selected = -1;
146 gui->group_edited = gui->group_selected = -1;
147
148 dt_masks_form_t *sel = NULL;
149 dt_masks_point_group_t *sel_fpt = NULL;
150 int sel_pos = 0;
151 float sel_dist = FLT_MAX;
152
153 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
154 {
155 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
156 dt_masks_form_t *frm = dt_masks_get_from_id(darktable.develop, fpt->formid);
157 int inside, inside_border, near, inside_source;
158 float dist = FLT_MAX;
159 inside = inside_border = inside_source = 0;
160 near = -1;
161 const float xx = pzx * darktable.develop->preview_pipe->backbuf_width,
162 yy = pzy * darktable.develop->preview_pipe->backbuf_height;
163 if(frm->functions && frm->functions->get_distance)
164 frm->functions->get_distance(xx, yy, as, gui, pos, g_list_length(frm->points),
165 &inside, &inside_border, &near, &inside_source, &dist);
166
167 if(inside || inside_border || near >= 0 || inside_source)
168 {
169
170 if(sel_dist > dist)
171 {
172 sel = frm;
173 sel_dist = dist;
174 sel_pos = pos;
175 sel_fpt = fpt;
176 }
177 }
178 pos++;
179 }
180
181 if(sel && sel->functions)
182 {
183 gui->group_edited = gui->group_selected = sel_pos;
184 return sel->functions->mouse_moved(module, pzx, pzy, pressure, which, sel, sel_fpt->parentid, gui, gui->group_edited);
185 }
186
187 dt_control_queue_redraw_center();
188 return 0;
189 }
190
dt_group_events_post_expose(cairo_t * cr,float zoom_scale,dt_masks_form_t * form,dt_masks_form_gui_t * gui)191 void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
192 dt_masks_form_gui_t *gui)
193 {
194 int pos = 0;
195 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
196 {
197 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
198 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
199 if (!sel) return;
200 if(sel->functions)
201 sel->functions->post_expose(cr, zoom_scale, gui, pos, g_list_length(sel->points));
202 pos++;
203 }
204 }
205
_inverse_mask(const dt_iop_module_t * const module,const dt_dev_pixelpipe_iop_t * const piece,dt_masks_form_t * const form,float ** buffer,int * width,int * height,int * posx,int * posy)206 static void _inverse_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
207 dt_masks_form_t *const form,
208 float **buffer, int *width, int *height, int *posx, int *posy)
209 {
210 // we create a new buffer
211 const int wt = piece->iwidth;
212 const int ht = piece->iheight;
213 float *buf = dt_alloc_align_float((size_t)ht * wt);
214
215 // we fill this buffer
216 for(int yy = 0; yy < MIN(*posy, ht); yy++)
217 {
218 for(int xx = 0; xx < wt; xx++) buf[(size_t)yy * wt + xx] = 1.0f;
219 }
220
221 for(int yy = MAX(*posy, 0); yy < MIN(ht, (*posy) + (*height)); yy++)
222 {
223 for(int xx = 0; xx < MIN((*posx), wt); xx++) buf[(size_t)yy * wt + xx] = 1.0f;
224 for(int xx = MAX((*posx), 0); xx < MIN(wt, (*posx) + (*width)); xx++)
225 buf[(size_t)yy * wt + xx] = 1.0f - (*buffer)[((size_t)yy - (*posy)) * (*width) + xx - (*posx)];
226 for(int xx = MAX((*posx) + (*width), 0); xx < wt; xx++) buf[(size_t)yy * wt + xx] = 1.0f;
227 }
228
229 for(int yy = MAX((*posy) + (*height), 0); yy < ht; yy++)
230 {
231 for(int xx = 0; xx < wt; xx++) buf[(size_t)yy * wt + xx] = 1.0f;
232 }
233
234 // we free the old buffer
235 dt_free_align(*buffer);
236 (*buffer) = buf;
237
238 // we return correct values for positions;
239 *posx = *posy = 0;
240 *width = wt;
241 *height = ht;
242 }
243
_group_get_mask(const dt_iop_module_t * const module,const dt_dev_pixelpipe_iop_t * const piece,dt_masks_form_t * const form,float ** buffer,int * width,int * height,int * posx,int * posy)244 static int _group_get_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
245 dt_masks_form_t *const form,
246 float **buffer, int *width, int *height, int *posx, int *posy)
247 {
248 // we allocate buffers and values
249 const guint nb = g_list_length(form->points);
250 if(nb == 0) return 0;
251 float **bufs = calloc(nb, sizeof(float *));
252 int *w = malloc(sizeof(int) * nb);
253 int *h = malloc(sizeof(int) * nb);
254 int *px = malloc(sizeof(int) * nb);
255 int *py = malloc(sizeof(int) * nb);
256 int *ok = malloc(sizeof(int) * nb);
257 int *states = malloc(sizeof(int) * nb);
258 float *op = malloc(sizeof(float) * nb);
259
260 // and we get all masks
261 int pos = 0;
262 int nb_ok = 0;
263 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
264 {
265 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
266 dt_masks_form_t *sel = dt_masks_get_from_id(module->dev, fpt->formid);
267 if(sel)
268 {
269 ok[pos] = dt_masks_get_mask(module, piece, sel, &bufs[pos], &w[pos], &h[pos], &px[pos], &py[pos]);
270 if(fpt->state & DT_MASKS_STATE_INVERSE)
271 {
272 const double start = dt_get_wtime();
273 _inverse_mask(module, piece, sel, &bufs[pos], &w[pos], &h[pos], &px[pos], &py[pos]);
274 if(darktable.unmuted & DT_DEBUG_PERF)
275 dt_print(DT_DEBUG_MASKS, "[masks %s] inverse took %0.04f sec\n", sel->name, dt_get_wtime() - start);
276 }
277 op[pos] = fpt->opacity;
278 states[pos] = fpt->state;
279 if(ok[pos]) nb_ok++;
280 }
281 pos++;
282 }
283 if(nb_ok == 0) goto error;
284
285 // now we get the min, max, width, height of the final mask
286 int l = INT_MAX, r = INT_MIN, t = INT_MAX, b = INT_MIN;
287 for(int i = 0; i < nb; i++)
288 {
289 l = MIN(l, px[i]);
290 t = MIN(t, py[i]);
291 r = MAX(r, px[i] + w[i]);
292 b = MAX(b, py[i] + h[i]);
293 }
294 *posx = l;
295 *posy = t;
296 *width = r - l;
297 *height = b - t;
298
299 // we allocate the buffer
300 *buffer = dt_alloc_align_float((size_t)(r - l) * (b - t));
301
302 // and we copy each buffer inside, row by row
303 for(int i = 0; i < nb; i++)
304 {
305 const double start = dt_get_wtime();
306 if(states[i] & DT_MASKS_STATE_UNION)
307 {
308 for(int y = 0; y < h[i]; y++)
309 {
310 for(int x = 0; x < w[i]; x++)
311 {
312 (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l]
313 = fmaxf((*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l], bufs[i][y * w[i] + x] * op[i]);
314 }
315 }
316 }
317 else if(states[i] & DT_MASKS_STATE_INTERSECTION)
318 {
319 for(int y = 0; y < b - t; y++)
320 {
321 for(int x = 0; x < r - l; x++)
322 {
323 const float b1 = (*buffer)[y * (r - l) + x];
324 float b2 = 0.0f;
325 if(y + t - py[i] >= 0 && y + t - py[i] < h[i] && x + l - px[i] >= 0 && x + l - px[i] < w[i])
326 b2 = bufs[i][(y + t - py[i]) * w[i] + x + l - px[i]];
327 if(b1 > 0.0f && b2 > 0.0f)
328 (*buffer)[y * (r - l) + x] = fminf(b1, b2 * op[i]);
329 else
330 (*buffer)[y * (r - l) + x] = 0.0f;
331 }
332 }
333 }
334 else if(states[i] & DT_MASKS_STATE_DIFFERENCE)
335 {
336 for(int y = 0; y < h[i]; y++)
337 {
338 for(int x = 0; x < w[i]; x++)
339 {
340 const float b1 = (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l];
341 const float b2 = bufs[i][y * w[i] + x] * op[i];
342 if(b1 > 0.0f && b2 > 0.0f) (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l] = b1 * (1.0f - b2);
343 }
344 }
345 }
346 else if(states[i] & DT_MASKS_STATE_EXCLUSION)
347 {
348 for(int y = 0; y < h[i]; y++)
349 {
350 for(int x = 0; x < w[i]; x++)
351 {
352 const float b1 = (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l];
353 const float b2 = bufs[i][y * w[i] + x] * op[i];
354 if(b1 > 0.0f && b2 > 0.0f)
355 (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l] = fmaxf((1.0f - b1) * b2, b1 * (1.0f - b2));
356 else
357 (*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l]
358 = fmaxf((*buffer)[(py[i] + y - t) * (r - l) + px[i] + x - l], bufs[i][y * w[i] + x] * op[i]);
359 }
360 }
361 }
362 else // if we are here, this mean that we just have to copy the shape and null other parts
363 {
364 for(int y = 0; y < b - t; y++)
365 {
366 for(int x = 0; x < r - l; x++)
367 {
368 float b2 = 0.0f;
369 if(y + t - py[i] >= 0 && y + t - py[i] < h[i] && x + l - px[i] >= 0 && x + l - px[i] < w[i])
370 b2 = bufs[i][(y + t - py[i]) * w[i] + x + l - px[i]];
371 (*buffer)[y * (r - l) + x] = b2 * op[i];
372 }
373 }
374 }
375
376 if(darktable.unmuted & DT_DEBUG_PERF)
377 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", i, dt_get_wtime() - start);
378 }
379
380 free(op);
381 free(states);
382 free(ok);
383 free(py);
384 free(px);
385 free(h);
386 free(w);
387 for(int i = 0; i < nb; i++) dt_free_align(bufs[i]);
388 free(bufs);
389 return 1;
390
391 error:
392 free(op);
393 free(states);
394 free(ok);
395 free(py);
396 free(px);
397 free(h);
398 free(w);
399 for(int i = 0; i < nb; i++) dt_free_align(bufs[i]);
400 free(bufs);
401 return 0;
402 }
403
_combine_masks_union(float * const restrict dest,float * const restrict newmask,const size_t npixels,const float opacity,const int inverted)404 static void _combine_masks_union(float *const restrict dest, float *const restrict newmask, const size_t npixels,
405 const float opacity, const int inverted)
406 {
407 if (inverted)
408 {
409 #ifdef _OPENMP
410 #if !defined(__SUNOS__) && !defined(__NetBSD__)
411 #pragma omp parallel for simd default(none) \
412 dt_omp_firstprivate(npixels, opacity) \
413 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
414 schedule(simd:static)
415 #else
416 #pragma omp parallel for shared(dest, newmask)
417 #endif
418 #endif
419 for(int index = 0; index < npixels; index++)
420 {
421 const float mask = opacity * (1.0f - newmask[index]);
422 dest[index] = MAX(dest[index], mask);
423 }
424 }
425 else
426 {
427 #ifdef _OPENMP
428 #if !defined(__SUNOS__) && !defined(__NetBSD__)
429 #pragma omp parallel for simd default(none) \
430 dt_omp_firstprivate(npixels, opacity) \
431 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
432 schedule(simd:static)
433 #else
434 #pragma omp parallel for shared(dest, newmask)
435 #endif
436 #endif
437 for(int index = 0; index < npixels; index++)
438 {
439 const float mask = opacity * newmask[index];
440 dest[index] = MAX(dest[index], mask);
441 }
442 }
443 }
444
_combine_masks_intersect(float * const restrict dest,float * const restrict newmask,const size_t npixels,const float opacity,const int inverted)445 static void _combine_masks_intersect(float *const restrict dest, float *const restrict newmask, const size_t npixels,
446 const float opacity, const int inverted)
447 {
448 if (inverted)
449 {
450 #ifdef _OPENMP
451 #if !defined(__SUNOS__) && !defined(__NetBSD__)
452 #pragma omp parallel for simd default(none) \
453 dt_omp_firstprivate(npixels, opacity) \
454 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
455 schedule(simd:static)
456 #else
457 #pragma omp parallel for shared(dest, newmask)
458 #endif
459 #endif
460 for(int index = 0; index < npixels; index++)
461 {
462 const float mask = opacity * (1.0f - newmask[index]);
463 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
464 }
465 }
466 else
467 {
468 #ifdef _OPENMP
469 #if !defined(__SUNOS__) && !defined(__NetBSD__)
470 #pragma omp parallel for simd default(none) \
471 dt_omp_firstprivate(npixels, opacity) \
472 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
473 schedule(simd:static)
474 #else
475 #pragma omp parallel for shared(dest, newmask)
476 #endif
477 #endif
478 for(int index = 0; index < npixels; index++)
479 {
480 const float mask = opacity * newmask[index];
481 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
482 }
483 }
484 }
485
486 #ifdef _OPENMP
487 #pragma omp declare simd
488 #endif
both_positive(const float val1,const float val2)489 static inline int both_positive(const float val1, const float val2)
490 {
491 // this needs to be a separate inline function to convince the compiler to vectorize
492 return (val1 > 0.0f) && (val2 > 0.0f);
493 }
494
_combine_masks_difference(float * const restrict dest,float * const restrict newmask,const size_t npixels,const float opacity,const int inverted)495 static void _combine_masks_difference(float *const restrict dest, float *const restrict newmask, const size_t npixels,
496 const float opacity, const int inverted)
497 {
498 if (inverted)
499 {
500 #ifdef _OPENMP
501 #if !defined(__SUNOS__) && !defined(__NetBSD__)
502 #pragma omp parallel for simd default(none) \
503 dt_omp_firstprivate(npixels, opacity) \
504 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
505 schedule(simd:static)
506 #else
507 #pragma omp parallel for shared(dest, newmask)
508 #endif
509 #endif
510 for(int index = 0; index < npixels; index++)
511 {
512 const float mask = opacity * (1.0f - newmask[index]);
513 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
514 }
515 }
516 else
517 {
518 #ifdef _OPENMP
519 #if !defined(__SUNOS__) && !defined(__NetBSD__)
520 #pragma omp parallel for simd default(none) \
521 dt_omp_firstprivate(npixels, opacity) \
522 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
523 schedule(simd:static)
524 #else
525 #pragma omp parallel for shared(dest, newmask)
526 #endif
527 #endif
528 for(int index = 0; index < npixels; index++)
529 {
530 const float mask = opacity * newmask[index];
531 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
532 }
533 }
534 }
535
_combine_masks_exclusion(float * const restrict dest,float * const restrict newmask,const size_t npixels,const float opacity,const int inverted)536 static void _combine_masks_exclusion(float *const restrict dest, float *const restrict newmask, const size_t npixels,
537 const float opacity, const int inverted)
538 {
539 if (inverted)
540 {
541 #ifdef _OPENMP
542 #if !defined(__SUNOS__) && !defined(__NetBSD__)
543 #pragma omp parallel for simd default(none) \
544 dt_omp_firstprivate(npixels, opacity) \
545 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
546 schedule(simd:static)
547 #else
548 #pragma omp parallel for shared(dest, newmask)
549 #endif
550 #endif
551 for(int index = 0; index < npixels; index++)
552 {
553 const float mask = opacity * (1.0f - newmask[index]);
554 const float pos = both_positive(dest[index], mask);
555 const float neg = (1.0f - pos);
556 const float b1 = dest[index];
557 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
558 }
559 }
560 else
561 {
562 #ifdef _OPENMP
563 #if !defined(__SUNOS__) && !defined(__NetBSD__)
564 #pragma omp parallel for simd default(none) \
565 dt_omp_firstprivate(npixels, opacity) \
566 dt_omp_sharedconst(dest, newmask) aligned(dest, newmask : 64) \
567 schedule(simd:static)
568 #else
569 #pragma omp parallel for shared(dest, newmask)
570 #endif
571 #endif
572 for(int index = 0; index < npixels; index++)
573 {
574 const float mask = opacity * newmask[index];
575 const float pos = both_positive(dest[index], mask);
576 const float neg = (1.0f - pos);
577 const float b1 = dest[index];
578 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
579 }
580 }
581 }
582
_group_get_mask_roi(const dt_iop_module_t * const restrict module,const dt_dev_pixelpipe_iop_t * const restrict piece,dt_masks_form_t * const form,const dt_iop_roi_t * const roi,float * const restrict buffer)583 static int _group_get_mask_roi(const dt_iop_module_t *const restrict module,
584 const dt_dev_pixelpipe_iop_t *const restrict piece,
585 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
586 float *const restrict buffer)
587 {
588 double start = dt_get_wtime();
589 if(!form->points) return 0;
590 int nb_ok = 0;
591
592 const int width = roi->width;
593 const int height = roi->height;
594 const size_t npixels = (size_t)width * height;
595
596 // we need to allocate a temporary buffer for intermediate creation of individual shapes
597 float *const restrict bufs = dt_alloc_align_float(npixels);
598 if(bufs == NULL) return 0;
599
600 // empty the output buffer
601 memset(buffer, 0, sizeof(float) * npixels);
602
603 // and we get all masks
604 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
605 {
606 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
607 dt_masks_form_t *sel = dt_masks_get_from_id(module->dev, fpt->formid);
608
609 if(sel)
610 {
611 const int ok = dt_masks_get_mask_roi(module, piece, sel, roi, bufs);
612 const float op = fpt->opacity;
613 const int state = fpt->state;
614
615 if(ok)
616 {
617 // first see if we need to invert this shape
618 const int inverted = (state & DT_MASKS_STATE_INVERSE);
619
620 if(state & DT_MASKS_STATE_UNION)
621 {
622 _combine_masks_union(buffer, bufs, npixels, op, inverted);
623 }
624 else if(state & DT_MASKS_STATE_INTERSECTION)
625 {
626 _combine_masks_intersect(buffer, bufs, npixels, op, inverted);
627 }
628 else if(state & DT_MASKS_STATE_DIFFERENCE)
629 {
630 _combine_masks_difference(buffer, bufs, npixels, op, inverted);
631 }
632 else if(state & DT_MASKS_STATE_EXCLUSION)
633 {
634 _combine_masks_exclusion(buffer, bufs, npixels, op, inverted);
635 }
636 else // if we are here, this mean that we just have to copy the shape and null other parts
637 {
638 #ifdef _OPENMP
639 #if !defined(__SUNOS__) && !defined(__NetBSD__)
640 #pragma omp parallel for simd default(none) \
641 dt_omp_firstprivate(npixels, op, inverted) \
642 dt_omp_sharedconst(buffer, bufs) schedule(simd:static) aligned(buffer, bufs : 64)
643 #else
644 #pragma omp parallel for shared(bufs, buffer)
645 #endif
646 #endif
647 for(int index = 0; index < npixels; index++)
648 {
649 buffer[index] = op * (inverted ? (1.0f - bufs[index]) : bufs[index]);
650 }
651 }
652
653 if(darktable.unmuted & DT_DEBUG_PERF)
654 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", nb_ok, dt_get_wtime() - start);
655 start = dt_get_wtime();
656
657 nb_ok++;
658 }
659 }
660 }
661 // and we free the intermediate buffer
662 dt_free_align(bufs);
663
664 return nb_ok != 0;
665 }
666
dt_masks_group_render_roi(dt_iop_module_t * module,dt_dev_pixelpipe_iop_t * piece,dt_masks_form_t * form,const dt_iop_roi_t * roi,float * buffer)667 int dt_masks_group_render_roi(dt_iop_module_t *module, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form,
668 const dt_iop_roi_t *roi, float *buffer)
669 {
670 const double start = dt_get_wtime();
671 if(!form) return 0;
672
673 const int ok = dt_masks_get_mask_roi(module, piece, form, roi, buffer);
674
675 if(darktable.unmuted & DT_DEBUG_PERF)
676 dt_print(DT_DEBUG_MASKS, "[masks] render all masks took %0.04f sec\n", dt_get_wtime() - start);
677 return ok;
678 }
679
_group_setup_mouse_actions(const struct dt_masks_form_t * const form)680 static GSList *_group_setup_mouse_actions(const struct dt_masks_form_t *const form)
681 {
682 GSList *lm = NULL;
683 // initialize the mask of seen shapes to the set of flags which aren't actually shapes
684 dt_masks_type_t seen_types = (DT_MASKS_GROUP | DT_MASKS_CLONE | DT_MASKS_NON_CLONE);
685 // iterate over the shapes in the group, adding the mouse_action for each distinct type of shape
686 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
687 {
688 dt_masks_point_group_t *fpt = (dt_masks_point_group_t *)fpts->data;
689 dt_masks_form_t *sel = dt_masks_get_from_id(darktable.develop, fpt->formid);
690 if (!sel || (sel->type & ~seen_types) == 0)
691 continue;
692 if (sel && sel->functions && sel->functions->setup_mouse_actions)
693 {
694 GSList *new_actions = sel->functions->setup_mouse_actions(sel);
695 lm = g_slist_concat(lm, new_actions);
696 seen_types |= sel->type;
697 }
698 }
699 return lm;
700 }
701
_group_duplicate_points(dt_develop_t * const dev,dt_masks_form_t * const base,dt_masks_form_t * const dest)702 static void _group_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base,
703 dt_masks_form_t *const dest)
704 {
705 for(GList *pts = base->points; pts; pts = g_list_next(pts))
706 {
707 dt_masks_point_group_t *pt = (dt_masks_point_group_t *)pts->data;
708 dt_masks_point_group_t *npt = (dt_masks_point_group_t *)malloc(sizeof(dt_masks_point_group_t));
709
710 npt->formid = dt_masks_form_duplicate(dev, pt->formid);
711 npt->parentid = dest->formid;
712 npt->state = pt->state;
713 npt->opacity = pt->opacity;
714 dest->points = g_list_append(dest->points, npt);
715 }
716 }
717
718 // The function table for groups. This must be public, i.e. no "static" keyword.
719 const dt_masks_functions_t dt_masks_functions_group = {
720 .point_struct_size = sizeof(struct dt_masks_point_group_t),
721 .sanitize_config = NULL,
722 .setup_mouse_actions = _group_setup_mouse_actions,
723 .set_form_name = NULL,
724 .set_hint_message = NULL,
725 .duplicate_points = _group_duplicate_points,
726 .initial_source_pos = NULL,
727 .get_distance = NULL,
728 .get_points = NULL,
729 .get_points_border = NULL,
730 .get_mask = _group_get_mask,
731 .get_mask_roi = _group_get_mask_roi,
732 .get_area = NULL,
733 .get_source_area = NULL,
734 .mouse_moved = _group_events_mouse_moved,
735 .mouse_scrolled = _group_events_mouse_scrolled,
736 .button_pressed = _group_events_button_pressed,
737 .button_released = _group_events_button_released,
738 //TODO: .post_expose = _group_events_post_expose
739 };
740
741
742 // modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
743 // vim: shiftwidth=2 expandtab tabstop=2 cindent
744 // kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
745