Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
group.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013 Aldric Renaudin.
4 Copyright (C) 2013 Simon Spannagel.
5 Copyright (C) 2013-2016 Tobias Ellinghaus.
6 Copyright (C) 2013-2014, 2019 Ulrich Pegelow.
7 Copyright (C) 2014, 2016 Roman Lebedev.
8 Copyright (C) 2016, 2019-2021 Pascal Obry.
9 Copyright (C) 2018 Edgardo Hoszowski.
10 Copyright (C) 2018 johannes hanika.
11 Copyright (C) 2019 Andreas Schneider.
12 Copyright (C) 2019 Heiko Bauke.
13 Copyright (C) 2020 GrahamByrnes.
14 Copyright (C) 2020 Hubert Kowalski.
15 Copyright (C) 2020-2021 Ralf Brown.
16 Copyright (C) 2022 Martin Bařinka.
17 Copyright (C) 2022 Miloš Komarčević.
18 Copyright (C) 2023, 2025-2026 Aurélien PIERRE.
19 Copyright (C) 2025-2026 Guillaume Stutin.
20
21 darktable is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation, either version 3 of the License, or
24 (at your option) any later version.
25
26 darktable is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
30
31 You should have received a copy of the GNU General Public License
32 along with darktable. If not, see <http://www.gnu.org/licenses/>.
33*/
34#include "common/darktable.h"
35#include "gui/gdkkeys.h"
36#include "common/debug.h"
37#include "control/conf.h"
38#include"control/control.h"
39#include "develop/blend.h"
40#include "develop/imageop.h"
41#include "develop/masks.h"
42
43/* Shape handlers receive widget-space coordinates, while normalized output-image
44 * coordinates come from `gui->rel_pos` and absolute output-image
45 * coordinates come from `gui->pos`. */
46// Centralize group child lookup so all dispatchers and expose code resolve the same
47// selected child the same way.
48static dt_masks_form_t *_group_get_child_at(dt_masks_form_t *form, const int group_index,
49 dt_masks_form_group_t **group_entry)
50{
51 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)g_list_nth_data(form->points, group_index);
52 if(IS_NULL_PTR(entry)) return NULL;
53 if(group_entry) *group_entry = entry;
55}
56
58 dt_masks_form_group_t **group_entry)
59{
60 if(gui->group_selected < 0) return NULL;
61 return _group_get_child_at(form, gui->group_selected, group_entry);
62}
63
64static int _group_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
65 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
66 int unused, dt_masks_interaction_t interaction)
67{
68 return 0;
69}
70
71static gboolean _group_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
72 double pressure, int which, int type, uint32_t state,
73 dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
74{
75 return FALSE;
76}
77
78static int _group_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
79 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
80 int unused2)
81{
82 return 0;
83}
84
85static int _group_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
86{
87 if(IS_NULL_PTR(form)) return 0;
88
89 gboolean return_value = FALSE;
90 guint key = dt_keys_mainpad_alternatives(event->keyval);
91
92 // Global key bindings for groups
93 if(!return_value)
94 {
95 switch(key)
96 {
97 case GDK_KEY_Escape:
98 {
99 return_value = dt_masks_form_exit_creation(module, gui);
100 break;
101 }
102 case GDK_KEY_Delete:
103 {
104 if(gui->group_selected >= 0)
105 {
106 // Remove shape from current group
107 dt_masks_form_group_t *group_entry = NULL;
108 dt_masks_form_t *selected_form = _group_get_selected_child(form, gui, &group_entry);
109 if(IS_NULL_PTR(selected_form)) return 0;
110 return_value = dt_masks_gui_remove(module, selected_form, gui, group_entry->parentid);
111 break;
112 }
113 }
114 }
115 }
116
117 return return_value;
118}
119
120static int _group_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure,
121 int which, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
122 int unused2)
123{
124 return 0;
125}
126
127static void _group_events_post_expose_draw(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
128 dt_masks_form_gui_t *gui, int pos)
129{
130 dt_masks_form_t *selected_form = _group_get_child_at(form, pos, NULL);
131 if(selected_form && selected_form->functions && selected_form->functions->post_expose)
132 {
133 gui->type = selected_form->type;
134 selected_form->functions->post_expose(cr, zoom_scale, gui, pos, g_list_length(selected_form->points));
135 }
136}
137
138void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
140{
141 int pos = 0;
142 // draw the selected form last so it's drawn on top of the others.
143 // we loop over all forms and skip the selected one
144 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
145 {
146 // skip drawing for the selected one
147 if(gui->group_selected != pos)
148 _group_events_post_expose_draw(cr, zoom_scale, form, gui, pos);
149
150 pos++;
151 }
152 // now draw the selected one on top, if any
153 if(gui->group_selected >= 0)
154 _group_events_post_expose_draw(cr, zoom_scale, form, gui, gui->group_selected);
155}
156
157static int _inverse_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
158 dt_masks_form_t *const form,
159 float **buffer, int *width, int *height, int *posx, int *posy)
160{
161 // we create a new buffer
162 const int wt = piece->iwidth;
163 const int ht = piece->iheight;
164 float *buf = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ht * wt, 0);
165 if(IS_NULL_PTR(buf)) return 1;
166
167 // we fill this buffer
168 const int posx_ = *posx;
169 const int posy_ = *posy;
170 const int width_ = *width;
171 const int height_ = *height;
172 const float *const src = *buffer;
173 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
174 for(int yy = 0; yy < MIN(posy_, ht); yy++)
175 {
176 float *const row = buf + (size_t)yy * wt;
177 for(int xx = 0; xx < wt; xx++) row[xx] = 1.0f;
178 }
179 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
180 for(int yy = MAX(posy_, 0); yy < MIN(ht, posy_ + height_); yy++)
181 {
182 float *const row = buf + (size_t)yy * wt;
183 for(int xx = 0; xx < MIN(posx_, wt); xx++) row[xx] = 1.0f;
184 const int xstart = MAX(posx_, 0);
185 const int xend = MIN(wt, posx_ + width_);
186 const float *const src_row = src + (size_t)(yy - posy_) * width_;
187 for(int xx = xstart; xx < xend; xx++)
188 row[xx] = 1.0f - src_row[xx - posx_];
189 for(int xx = MAX(posx_ + width_, 0); xx < wt; xx++) row[xx] = 1.0f;
190 }
191 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
192 for(int yy = MAX(posy_ + height_, 0); yy < ht; yy++)
193 {
194 float *const row = buf + (size_t)yy * wt;
195 for(int xx = 0; xx < wt; xx++) row[xx] = 1.0f;
196 }
197
198 // we free the old buffer
200 (*buffer) = buf;
201
202 // we return correct values for positions;
203 *posx = *posy = 0;
204 *width = wt;
205 *height = ht;
206 return 0;
207}
208
209static int _group_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe,
210 const dt_dev_pixelpipe_iop_t *const piece,
211 dt_masks_form_t *const form,
212 float **buffer, int *width, int *height, int *posx, int *posy)
213{
214 // we allocate buffers and values
215 const guint nb = g_list_length(form->points);
216 if(nb == 0)
217 {
218 *buffer = NULL;
219 *width = *height = *posx = *posy = 0;
220 return 0;
221 }
222 float **bufs = calloc(nb, sizeof(float *));
223 int *w = malloc(sizeof(int) * nb);
224 int *h = malloc(sizeof(int) * nb);
225 int *px = malloc(sizeof(int) * nb);
226 int *py = malloc(sizeof(int) * nb);
227 int *states = malloc(sizeof(int) * nb);
228 float *op = malloc(sizeof(float) * nb);
229 if(IS_NULL_PTR(bufs) || IS_NULL_PTR(w) || IS_NULL_PTR(h) || IS_NULL_PTR(px) || IS_NULL_PTR(py) || IS_NULL_PTR(states) || IS_NULL_PTR(op))
230 {
231 dt_free(op);
232 dt_free(states);
233 dt_free(py);
234 dt_free(px);
235 dt_free(h);
236 dt_free(w);
237 dt_free(bufs);
238 return 1;
239 }
240
241 // and we get all masks
242 int pos = 0;
243 int nb_ok = 0;
244 int err = 0;
245 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
246 {
247 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
248 dt_masks_form_t *sel = dt_masks_get_from_id(module->dev, fpt->formid);
249 if(sel)
250 {
251 if(dt_masks_get_mask(module, pipe, piece, sel, &bufs[pos], &w[pos], &h[pos], &px[pos], &py[pos]) != 0)
252 {
253 err = 1;
254 break;
255 }
257 {
258 const double start = dt_get_wtime();
259 if(_inverse_mask(module, piece, sel, &bufs[pos], &w[pos], &h[pos], &px[pos], &py[pos]) != 0)
260 {
261 err = 1;
262 break;
263 }
265 dt_print(DT_DEBUG_MASKS, "[masks %s] inverse took %0.04f sec\n", sel->name, dt_get_wtime() - start);
266 }
267 op[pos] = fpt->opacity;
268 states[pos] = fpt->state;
269 nb_ok++;
270 }
271 pos++;
272 }
273 if(err) goto cleanup;
274 if(nb_ok == 0)
275 {
276 *buffer = NULL;
277 *width = *height = *posx = *posy = 0;
278 goto cleanup;
279 }
280
281 // now we get the min, max, width, height of the final mask
282 int l = INT_MAX, r = INT_MIN, t = INT_MAX, b = INT_MIN;
283 for(int i = 0; i < nb; i++)
284 {
285 l = MIN(l, px[i]);
286 t = MIN(t, py[i]);
287 r = MAX(r, px[i] + w[i]);
288 b = MAX(b, py[i] + h[i]);
289 }
290 *posx = l;
291 *posy = t;
292 *width = r - l;
293 *height = b - t;
294
295 // we allocate the buffer
296 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)(r - l) * (b - t), 0);
297 if(IS_NULL_PTR(*buffer))
298 {
299 err = 1;
300 goto cleanup;
301 }
302
303 // and we copy each buffer inside, row by row
304 const int dst_w = r - l;
305 const int dst_h = b - t;
306 float *const dst = *buffer;
307 for(int i = 0; i < nb; i++)
308 {
309 const double start = dt_get_wtime();
310 const int wi = w[i];
311 const int hi = h[i];
312 const int ox = px[i] - l;
313 const int oy = py[i] - t;
314 const float opacity = op[i];
315 const float *const src = bufs[i];
316 if(states[i] & DT_MASKS_STATE_UNION)
317 {
318 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
319 for(int y = 0; y < hi; y++)
320 {
321 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
322 const float *const src_row = src + (size_t)y * wi;
323 for(int x = 0; x < wi; x++)
324 {
325 const float v = src_row[x] * opacity;
326 if(v > dst_row[x]) dst_row[x] = v;
327 }
328 }
329 }
330 else if(states[i] & DT_MASKS_STATE_INTERSECTION)
331 {
332 const int x0 = MAX(px[i], l);
333 const int y0 = MAX(py[i], t);
334 const int x1 = MIN(px[i] + wi, r);
335 const int y1 = MIN(py[i] + hi, b);
336 if(x0 >= x1 || y0 >= y1)
337 {
338 memset(dst, 0, (size_t)dst_w * dst_h * sizeof(float));
339 }
340 else
341 {
342 const int row_start = y0 - t;
343 const int row_end = y1 - t;
344 const int col_start = x0 - l;
345 const int col_end = x1 - l;
346 const int src_x_offset = x0 - px[i];
347 const int src_y_offset = t - py[i];
348 __OMP_PARALLEL_FOR__(if((size_t)dst_w * dst_h > 10000))
349 for(int y = 0; y < dst_h; y++)
350 {
351 float *const dst_row = dst + (size_t)y * dst_w;
352 if(y < row_start || y >= row_end)
353 {
354 memset(dst_row, 0, (size_t)dst_w * sizeof(float));
355 continue;
356 }
357
358 const int src_y = y + src_y_offset;
359 const float *const src_row = src + (size_t)src_y * wi + src_x_offset;
360 float *const dst_mid = dst_row + col_start;
361 const int mid_w = col_end - col_start;
362 for(int x = 0; x < mid_w; x++)
363 {
364 const float b1 = dst_mid[x];
365 const float b2 = src_row[x];
366 if(b1 > 0.0f && b2 > 0.0f)
367 dst_mid[x] = fminf(b1, b2 * opacity);
368 else
369 dst_mid[x] = 0.0f;
370 }
371
372 if(col_start > 0) memset(dst_row, 0, (size_t)col_start * sizeof(float));
373 if(col_end < dst_w) memset(dst_row + col_end, 0, (size_t)(dst_w - col_end) * sizeof(float));
374 }
375 }
376 }
377 else if(states[i] & DT_MASKS_STATE_DIFFERENCE)
378 {
379 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
380 for(int y = 0; y < hi; y++)
381 {
382 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
383 const float *const src_row = src + (size_t)y * wi;
384 for(int x = 0; x < wi; x++)
385 {
386 const float b1 = dst_row[x];
387 const float b2 = src_row[x] * opacity;
388 if(b1 > 0.0f && b2 > 0.0f) dst_row[x] = b1 * (1.0f - b2);
389 }
390 }
391 }
392 else if(states[i] & DT_MASKS_STATE_EXCLUSION)
393 {
394 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
395 for(int y = 0; y < hi; y++)
396 {
397 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
398 const float *const src_row = src + (size_t)y * wi;
399 for(int x = 0; x < wi; x++)
400 {
401 const float b1 = dst_row[x];
402 const float b2 = src_row[x] * opacity;
403 if(b1 > 0.0f && b2 > 0.0f)
404 dst_row[x] = fmaxf((1.0f - b1) * b2, b1 * (1.0f - b2));
405 else
406 dst_row[x] = fmaxf(dst_row[x], b2);
407 }
408 }
409 }
410 else // if we are here, this mean that we just have to copy the shape and null other parts
411 {
412 const int x0 = MAX(px[i], l);
413 const int y0 = MAX(py[i], t);
414 const int x1 = MIN(px[i] + wi, r);
415 const int y1 = MIN(py[i] + hi, b);
416 if(x0 >= x1 || y0 >= y1)
417 {
418 memset(dst, 0, (size_t)dst_w * dst_h * sizeof(float));
419 }
420 else
421 {
422 const int row_start = y0 - t;
423 const int row_end = y1 - t;
424 const int col_start = x0 - l;
425 const int col_end = x1 - l;
426 const int src_x_offset = x0 - px[i];
427 const int src_y_offset = t - py[i];
428 __OMP_PARALLEL_FOR__(if((size_t)dst_w * dst_h > 10000))
429 for(int y = 0; y < dst_h; y++)
430 {
431 float *const dst_row = dst + (size_t)y * dst_w;
432 if(y < row_start || y >= row_end)
433 {
434 memset(dst_row, 0, (size_t)dst_w * sizeof(float));
435 continue;
436 }
437
438 const int src_y = y + src_y_offset;
439 const float *const src_row = src + (size_t)src_y * wi + src_x_offset;
440 float *const dst_mid = dst_row + col_start;
441 const int mid_w = col_end - col_start;
442 for(int x = 0; x < mid_w; x++)
443 {
444 dst_mid[x] = src_row[x] * opacity;
445 }
446
447 if(col_start > 0) memset(dst_row, 0, (size_t)col_start * sizeof(float));
448 if(col_end < dst_w) memset(dst_row + col_end, 0, (size_t)(dst_w - col_end) * sizeof(float));
449 }
450 }
451 }
452
454 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", i, dt_get_wtime() - start);
455 }
456
457cleanup:
458 dt_free(op);
459 dt_free(states);
460 dt_free(py);
461 dt_free(px);
462 dt_free(h);
463 dt_free(w);
464 for(int i = 0; i < nb; i++) dt_pixelpipe_cache_free_align(bufs[i]);
465 dt_free(bufs);
466 return err;
467}
468
469static void _combine_masks_union(float *const restrict dest, float *const restrict newmask, const size_t npixels,
470 const float opacity, const int inverted)
471{
472 if (inverted)
473 {
474 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
475 for(int index = 0; index < npixels; index++)
476 {
477 const float mask = opacity * (1.0f - newmask[index]);
478 dest[index] = MAX(dest[index], mask);
479 }
480 }
481 else
482 {
483 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
484 for(int index = 0; index < npixels; index++)
485 {
486 const float mask = opacity * newmask[index];
487 dest[index] = MAX(dest[index], mask);
488 }
489 }
490}
491
492static void _combine_masks_intersect(float *const restrict dest, float *const restrict newmask, const size_t npixels,
493 const float opacity, const int inverted)
494{
495 if (inverted)
496 {
497 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
498 for(int index = 0; index < npixels; index++)
499 {
500 const float mask = opacity * (1.0f - newmask[index]);
501 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
502 }
503 }
504 else
505 {
506 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
507 for(int index = 0; index < npixels; index++)
508 {
509 const float mask = opacity * newmask[index];
510 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
511 }
512 }
513}
514
516static inline int both_positive(const float val1, const float val2)
517{
518 // this needs to be a separate inline function to convince the compiler to vectorize
519 return (val1 > 0.0f) && (val2 > 0.0f);
520}
521
522static void _combine_masks_difference(float *const restrict dest, float *const restrict newmask, const size_t npixels,
523 const float opacity, const int inverted)
524{
525 if (inverted)
526 {
527 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
528 for(int index = 0; index < npixels; index++)
529 {
530 const float mask = opacity * (1.0f - newmask[index]);
531 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
532 }
533 }
534 else
535 {
536__OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000)
537)
538 for(int index = 0; index < npixels; index++)
539 {
540 const float mask = opacity * newmask[index];
541 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
542 }
543 }
544}
545
546static void _combine_masks_exclusion(float *const restrict dest, float *const restrict newmask, const size_t npixels,
547 const float opacity, const int inverted)
548{
549 if (inverted)
550 {
551__OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000)
552)
553 for(int index = 0; index < npixels; index++)
554 {
555 const float mask = opacity * (1.0f - newmask[index]);
556 const float pos = both_positive(dest[index], mask);
557 const float neg = (1.0f - pos);
558 const float b1 = dest[index];
559 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
560 }
561 }
562 else
563 {
564 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
565 for(int index = 0; index < npixels; index++)
566 {
567 const float mask = opacity * newmask[index];
568 const float pos = both_positive(dest[index], mask);
569 const float neg = (1.0f - pos);
570 const float b1 = dest[index];
571 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
572 }
573 }
574}
575
576static int _group_get_mask_roi(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe,
577 const dt_dev_pixelpipe_iop_t *const restrict piece,
578 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
579 float *const restrict buffer)
580{
581 double start = dt_get_wtime();
582 if(IS_NULL_PTR(form->points)) return 0;
583 int nb_ok = 0;
584
585 const int width = roi->width;
586 const int height = roi->height;
587 const size_t npixels = (size_t)width * height;
588
589 // we need to allocate a zeroed temporary buffer for intermediate creation of individual shapes
590 float *const restrict bufs = dt_pixelpipe_cache_alloc_align_float_cache(npixels, 0);
591 if(IS_NULL_PTR(bufs)) return 1;
592 int err = 0;
593
594 int i = 0;
595 // and we get all masks
596 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
597 {
598 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
599 dt_masks_form_t *sel = dt_masks_get_from_id(module->dev, fpt->formid);
600
601 if(sel)
602 {
603 // ensure that we start with a zeroed buffer regardless of what was previously written into 'bufs'
604 memset(bufs, 0, npixels*sizeof(float));
605 const int err_child = dt_masks_get_mask_roi(module, pipe, piece, sel, roi, bufs);
606 const float op = fpt->opacity;
607 // Add a foolproof to ensure that the first shape is no-op
608 const int no_op_state = fpt->state & ~(DT_MASKS_STATE_IS_COMBINE_OP) ;
609 const int state = (i == 0) ? no_op_state : fpt->state;
610 if(err_child != 0)
611 {
612 err = 1;
613 break;
614 }
615 if(err_child == 0)
616 {
617 // first see if we need to invert this shape
618 const int inverted = (state & DT_MASKS_STATE_INVERSE);
619
621 {
622 _combine_masks_union(buffer, bufs, npixels, op, inverted);
623 }
625 {
626 _combine_masks_intersect(buffer, bufs, npixels, op, inverted);
627 }
629 {
630 _combine_masks_difference(buffer, bufs, npixels, op, inverted);
631 }
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 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer, bufs : 64) if(npixels > 10000))
639 for(int index = 0; index < npixels; index++)
640 {
641 buffer[index] = op * (inverted ? (1.0f - bufs[index]) : bufs[index]);
642 }
643 }
644
646 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", nb_ok, dt_get_wtime() - start);
647 start = dt_get_wtime();
648
649 nb_ok++;
650 }
651 }
652 i++;
653 }
654 // and we free the intermediate buffer
656
657 if(nb_ok == 0)
658 memset(buffer, 0, npixels * sizeof(float));
659
660 return err;
661}
662
664 const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form,
665 const dt_iop_roi_t *roi, float *buffer)
666{
667 const double start = dt_get_wtime();
668 if(IS_NULL_PTR(form)) return 0;
669
670 const int err = dt_masks_get_mask_roi(module, pipe, piece, form, roi, buffer);
671
673 dt_print(DT_DEBUG_MASKS, "[masks] render all masks took %0.04f sec\n", dt_get_wtime() - start);
674 return err;
675}
676
677static void _group_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base,
678 dt_masks_form_t *const dest)
679{
680 for(GList *pts = base->points; pts; pts = g_list_next(pts))
681 {
684
685 npt->formid = dt_masks_form_duplicate(dev, pt->formid);
686 npt->parentid = dest->formid;
687 npt->state = pt->state;
688 npt->opacity = pt->opacity;
689 dest->points = g_list_append(dest->points, npt);
690 }
691}
692
693static gboolean _group_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
694{
695 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
696
697 float sum_x = 0.0f;
698 float sum_y = 0.0f;
699 float sum_w = 0.0f;
700 int count = 0;
701
702 for(const GList *l = form->points; l; l = g_list_next(l))
703 {
704 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)l->data;
705 if(IS_NULL_PTR(pt)) continue;
707 if(IS_NULL_PTR(child)) continue;
708
709 float child_center[2] = { 0.0f, 0.0f };
710 float child_area = 0.0f;
711 if(!dt_masks_form_get_gravity_center(child, child_center, &child_area)) continue;
712
713 const float w = (child_area > 0.0f) ? child_area : 1.0f;
714 sum_x += child_center[0] * w;
715 sum_y += child_center[1] * w;
716 sum_w += w;
717 count++;
718 }
719
720 if(count == 0)
721 {
722 center[0] = 0.0f;
723 center[1] = 0.0f;
724 *area = 0.0f;
725 return FALSE;
726 }
727
728 if(sum_w <= 0.0f)
729 {
730 center[0] = sum_x / (float)count;
731 center[1] = sum_y / (float)count;
732 *area = 0.0f;
733 }
734 else
735 {
736 center[0] = sum_x / sum_w;
737 center[1] = sum_y / sum_w;
738 *area = sum_w;
739 }
740
741 return TRUE;
742}
743
744// The function table for groups. This must be public, i.e. no "static" keyword.
747 .sanitize_config = NULL,
748 .set_form_name = NULL,
749 .set_hint_message = NULL,
750 .duplicate_points = _group_duplicate_points,
751 .initial_source_pos = NULL,
752 .get_distance = NULL,
753 .get_points = NULL,
754 .get_points_border = NULL,
755 .get_mask = _group_get_mask,
756 .get_mask_roi = _group_get_mask_roi,
757 .get_area = NULL,
758 .get_source_area = NULL,
759 .get_gravity_center = _group_get_gravity_center,
760 .get_interaction_value = NULL,
761 .set_interaction_value = NULL,
762 .update_hover = NULL,
763 .mouse_moved = _group_events_mouse_moved,
764 .mouse_scrolled = _group_events_mouse_scrolled,
765 .button_pressed = _group_events_button_pressed,
766 .button_released = _group_events_button_released,
767 .key_pressed = _group_events_key_pressed,
768//TODO: .post_expose = _group_events_post_expose
769 .draw_shape = NULL
770};
771
772
773// clang-format off
774// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
775// vim: shiftwidth=2 expandtab tabstop=2 cindent
776// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
777// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void cleanup(dt_imageio_module_format_t *self)
Definition avif.c:164
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
static const int row
char * key
int type
darktable_t darktable
Definition darktable.c:181
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1542
@ DT_DEBUG_PERF
Definition darktable.h:719
@ DT_DEBUG_MASKS
Definition darktable.h:727
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
Definition darktable.h:447
#define dt_free(ptr)
Definition darktable.h:456
#define __OMP_DECLARE_SIMD__(...)
Definition darktable.h:263
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:453
#define __OMP_PARALLEL_FOR__(...)
Definition darktable.h:258
static double dt_get_wtime(void)
Definition darktable.h:914
#define __OMP_FOR_SIMD__(...)
Definition darktable.h:260
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition darktable.h:259
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition darktable.h:281
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
static int _group_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
Definition group.c:120
static dt_masks_form_t * _group_get_child_at(dt_masks_form_t *form, const int group_index, dt_masks_form_group_t **group_entry)
Definition group.c:48
int dt_masks_group_render_roi(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form, const dt_iop_roi_t *roi, float *buffer)
Definition group.c:663
static int _group_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event, dt_masks_form_t *form, int parentid, dt_masks_form_gui_t *gui, int index)
Definition group.c:85
static int _group_events_button_released(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
Definition group.c:78
static void _group_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base, dt_masks_form_t *const dest)
Definition group.c:677
static gboolean _group_get_gravity_center(const dt_masks_form_t *form, float center[2], float *area)
Definition group.c:693
const dt_masks_functions_t dt_masks_functions_group
Definition group.c:745
static void _group_events_post_expose_draw(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, int pos)
Definition group.c:127
static void _combine_masks_union(float *const restrict dest, float *const restrict newmask, const size_t npixels, const float opacity, const int inverted)
Definition group.c:469
static void _combine_masks_exclusion(float *const restrict dest, float *const restrict newmask, const size_t npixels, const float opacity, const int inverted)
Definition group.c:546
static gboolean _group_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure, int which, int type, uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
Definition group.c:71
static int _group_get_mask_roi(const dt_iop_module_t *const restrict module, dt_dev_pixelpipe_t *pipe, 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)
Definition group.c:576
static void _combine_masks_difference(float *const restrict dest, float *const restrict newmask, const size_t npixels, const float opacity, const int inverted)
Definition group.c:522
static int _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)
Definition group.c:157
static int both_positive(const float val1, const float val2)
Definition group.c:516
static dt_masks_form_t * _group_get_selected_child(dt_masks_form_t *form, dt_masks_form_gui_t *gui, dt_masks_form_group_t **group_entry)
Definition group.c:57
static int _group_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition group.c:209
void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Definition group.c:138
static void _combine_masks_intersect(float *const restrict dest, float *const restrict newmask, const size_t npixels, const float opacity, const int inverted)
Definition group.c:492
static int _group_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow, uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused, dt_masks_interaction_t interaction)
Definition group.c:64
static const float x
const int t
const float v
gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *form, dt_masks_form_gui_t *gui, const int parentid)
remove a mask shape or node form from the GUI. This function is used with a popupmenu "Delete" action...
@ DT_MASKS_STATE_DIFFERENCE
Definition masks.h:173
@ DT_MASKS_STATE_INVERSE
Definition masks.h:170
@ DT_MASKS_STATE_INTERSECTION
Definition masks.h:172
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks.h:177
@ DT_MASKS_STATE_EXCLUSION
Definition masks.h:174
@ DT_MASKS_STATE_UNION
Definition masks.h:171
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *gui)
dt_masks_interaction_t
Definition masks.h:302
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
static int dt_masks_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition masks.h:853
static int dt_masks_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer)
Definition masks.h:862
int dt_masks_form_duplicate(dt_develop_t *dev, int formid)
gboolean dt_masks_form_get_gravity_center(const struct dt_masks_form_t *form, float center[2], float *area)
const float uint32_t state[4]
const float r
int32_t unmuted
Definition darktable.h:760
struct dt_develop_t * develop
Definition darktable.h:770
struct dt_develop_t * dev
Definition imageop.h:296
Region of interest passed through the pixelpipe.
Definition imageop.h:72
dt_masks_type_t type
Definition masks.h:428
const dt_masks_functions_t * functions
Definition masks.h:379
dt_masks_type_t type
Definition masks.h:378
char name[128]
Definition masks.h:396
GList * points
Definition masks.h:377
void(* post_expose)(cairo_t *cr, float zoom_scale, struct dt_masks_form_gui_t *gui, int index, int num_points)
Definition masks.h:366
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29