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