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/hash.h" // dt_hash()
35#include "system/macros.h"
36#include "system/openmp.h"
37#include "system/mem_alloc.h"
38#include "common/logging.h"
39#include "common/times.h"
41#include "widgets/gdkkeys.h"
42#include "develop/imageop.h"
43#include "develop/masks.h"
44#include "develop/masks_gui.h"
47
48/* Shape handlers receive widget-space coordinates, while normalized output-image
49 * coordinates come from `gui->rel_pos` and absolute output-image
50 * coordinates come from `gui->pos`. */
51// Centralize group child lookup so all dispatchers and expose code resolve the same
52// selected child the same way.
53static dt_masks_form_t *_group_get_child_at(dt_develop_t *dev, dt_masks_form_t *form, const int group_index,
54 dt_masks_form_group_t **group_entry)
55{
56 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)g_list_nth_data(form->points, group_index);
57 if(IS_NULL_PTR(entry)) return NULL;
58 if(group_entry) *group_entry = entry;
59 return dt_masks_get_from_id(dev, entry->formid);
60}
61
63 dt_masks_form_group_t **group_entry)
64{
65 if(gui->group_selected < 0) return NULL;
66 return _group_get_child_at(gui->dev, form, gui->group_selected, group_entry);
67}
68
69static int _group_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int up, const int flow,
70 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
71 int unused, dt_masks_interaction_t interaction)
72{
73 return 0;
74}
75
76static gboolean _group_events_button_pressed(struct dt_iop_module_t *module, double x, double y,
77 double pressure, int which, int type, uint32_t state,
78 dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui, int unused2)
79{
80 return FALSE;
81}
82
83static int _group_events_button_released(struct dt_iop_module_t *module, double x, double y, int which,
84 uint32_t state, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
85 int unused2)
86{
87 return 0;
88}
89
90static 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)
91{
92 if(IS_NULL_PTR(form)) return 0;
93
94 gboolean return_value = FALSE;
95 guint key = dt_keys_mainpad_alternatives(event->keyval);
96
97 // Global key bindings for groups
98 if(!return_value)
99 {
100 switch(key)
101 {
102 case GDK_KEY_Escape:
103 {
104 return_value = dt_masks_form_exit_creation(module, gui);
105 break;
106 }
107 case GDK_KEY_Delete:
108 {
109 if(gui->group_selected >= 0)
110 {
111 // Remove shape from current group
112 dt_masks_form_group_t *group_entry = NULL;
113 dt_masks_form_t *selected_form = _group_get_selected_child(form, gui, &group_entry);
114 if(IS_NULL_PTR(selected_form)) return 0;
115 return_value = dt_masks_gui_remove(module, selected_form, gui, group_entry->parentid);
116 break;
117 }
118 }
119 }
120 }
121
122 return return_value;
123}
124
125static int _group_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure,
126 int which, dt_masks_form_t *form, int unused1, dt_masks_form_gui_t *gui,
127 int unused2)
128{
129 return 0;
130}
131
132static void _group_events_post_expose_draw(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
133 dt_masks_form_gui_t *gui, int pos)
134{
135 dt_masks_form_t *selected_form = _group_get_child_at(gui->dev, form, pos, NULL);
136 if(selected_form && selected_form->functions && selected_form->functions->post_expose)
137 {
138 /* Timed per shape, with the size of what is being stroked.
139 *
140 * The overlay redraw is the expensive half of a mask-heavy darkroom and nothing measured it:
141 * on the #1158 logs the darkroom expose runs 270-390 ms with only ~15 ms of it accounted for
142 * by the outline rebuilds around it, and the unaccounted time falls BETWEEN consecutive
143 * shapes -- which is this call. Whether that is the cairo stroking, and whether it scales
144 * with the point count or with the node count (dt_masks_draw_path_seg_by_seg() strokes once
145 * per node), is exactly what these two numbers separate. */
146 const dt_times_t start = { 0 };
147 dt_get_times((dt_times_t *)&start);
148
149 gui->type = selected_form->type;
150 selected_form->functions->post_expose(cr, zoom_scale, gui, pos, g_list_length(selected_form->points));
151
153 {
154 const dt_masks_form_gui_points_t *const gui_points
155 = (const dt_masks_form_gui_points_t *)g_list_nth_data(gui->points, pos);
156 dt_show_times_f(&start, "[masks]", "shape %d (%s) drawn: %d outline points, %d border points, %d nodes",
157 pos, selected_form->name, IS_NULL_PTR(gui_points) ? -1 : gui_points->points_count,
158 IS_NULL_PTR(gui_points) ? -1 : gui_points->border_count,
159 g_list_length(selected_form->points));
160 }
161 }
162}
163
164void dt_group_events_post_expose_except(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
165 dt_masks_form_gui_t *gui, const int except_pos)
166{
167 int pos = 0;
168 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts), pos++)
169 if(pos != except_pos) _group_events_post_expose_draw(cr, zoom_scale, form, gui, pos);
170}
171
172void dt_group_events_post_expose_only(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
173 dt_masks_form_gui_t *gui, const int pos)
174{
175 if(pos < 0 || pos >= (int)g_list_length(form->points)) return;
176 _group_events_post_expose_draw(cr, zoom_scale, form, gui, pos);
177}
178
179void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form,
181{
182 int pos = 0;
183 // draw the selected form last so it's drawn on top of the others.
184 // we loop over all forms and skip the selected one
185 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
186 {
187 // skip drawing for the selected one
188 if(gui->group_selected != pos)
189 _group_events_post_expose_draw(cr, zoom_scale, form, gui, pos);
190
191 pos++;
192 }
193 // now draw the selected one on top, if any
194 if(gui->group_selected >= 0)
195 _group_events_post_expose_draw(cr, zoom_scale, form, gui, gui->group_selected);
196}
197
198static int _inverse_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_iop_t *const piece,
199 dt_masks_form_t *const form,
200 float **buffer, int *width, int *height, int *posx, int *posy)
201{
202 // we create a new buffer
203 const int wt = piece->iwidth;
204 const int ht = piece->iheight;
205 float *buf = dt_pixelpipe_cache_alloc_align_float_cache((size_t)ht * wt, 0);
206 if(IS_NULL_PTR(buf)) return 1;
207
208 // we fill this buffer
209 const int posx_ = *posx;
210 const int posy_ = *posy;
211 const int width_ = *width;
212 const int height_ = *height;
213 const float *const src = *buffer;
214 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
215 for(int yy = 0; yy < MIN(posy_, ht); yy++)
216 {
217 float *const row = buf + (size_t)yy * wt;
218 for(int xx = 0; xx < wt; xx++) row[xx] = 1.0f;
219 }
220 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
221 for(int yy = MAX(posy_, 0); yy < MIN(ht, posy_ + height_); yy++)
222 {
223 float *const row = buf + (size_t)yy * wt;
224 for(int xx = 0; xx < MIN(posx_, wt); xx++) row[xx] = 1.0f;
225 const int xstart = MAX(posx_, 0);
226 const int xend = MIN(wt, posx_ + width_);
227 const float *const src_row = src + (size_t)(yy - posy_) * width_;
228 for(int xx = xstart; xx < xend; xx++)
229 row[xx] = 1.0f - src_row[xx - posx_];
230 for(int xx = MAX(posx_ + width_, 0); xx < wt; xx++) row[xx] = 1.0f;
231 }
232 __OMP_PARALLEL_FOR__(if(wt * ht > 50000))
233 for(int yy = MAX(posy_ + height_, 0); yy < ht; yy++)
234 {
235 float *const row = buf + (size_t)yy * wt;
236 for(int xx = 0; xx < wt; xx++) row[xx] = 1.0f;
237 }
238
239 // we free the old buffer
241 (*buffer) = buf;
242
243 // we return correct values for positions;
244 *posx = *posy = 0;
245 *width = wt;
246 *height = ht;
247 return 0;
248}
249
251 const dt_dev_pixelpipe_iop_t *const piece,
252 dt_masks_form_t *const form,
253 float **buffer, int *width, int *height, int *posx, int *posy)
254{
255 *buffer = NULL;
256 *width = 0;
257 *height = 0;
258 *posx = 0;
259 *posy = 0;
260
261 // we allocate buffers and values
262 const guint nb = g_list_length(form->points);
263 if(nb == 0) return DT_MASKS_RASTER_EMPTY;
264 float **bufs = dt_calloc_align(nb * sizeof(float *));
265 int *w = dt_alloc_align(sizeof(int) * nb);
266 int *h = dt_alloc_align(sizeof(int) * nb);
267 int *px = dt_alloc_align(sizeof(int) * nb);
268 int *py = dt_alloc_align(sizeof(int) * nb);
269 int *states = dt_alloc_align(sizeof(int) * nb);
270 float *op = dt_alloc_align_float(nb);
271 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))
272 {
273 dt_free_align(op);
274 dt_free_align(states);
275 dt_free_align(py);
276 dt_free_align(px);
277 dt_free_align(h);
278 dt_free_align(w);
279 dt_free_align(bufs);
281 }
282
283 /* Slots are filled DENSELY: `nb_ok' is both the count of usable children and the index of the
284 * next one. A child that resolves to nothing -- an id that is gone, or a shape with no geometry
285 * -- simply does not take a slot. That matters because the bounding-box and copy loops below
286 * read every slot they iterate: indexing by list position instead left a hole whose px/py/w/h
287 * were never written (the arrays are malloc'd, not calloc'd), and one unresolved id was enough
288 * to drag the group's bounding box to garbage. */
289 int nb_ok = 0;
291 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
292 {
293 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
294 dt_masks_form_t *sel = dt_masks_get_from_id(module->dev, fpt->formid);
295 if(sel)
296 {
297 const dt_masks_raster_result_t child
298 = dt_masks_get_mask(module, pipe, piece, sel, &bufs[nb_ok], &w[nb_ok], &h[nb_ok],
299 &px[nb_ok], &py[nb_ok]);
300 if(child == DT_MASKS_RASTER_ERROR)
301 {
303 break;
304 }
305 /* A shape with nothing to draw contributes nothing and must NOT stop the fold: the other
306 * members of the group are still theirs to render. It takes no slot, so the loops below
307 * never see it. (dt_masks_get_mask() has already zeroed this slot's out-parameters.) */
308 if(child == DT_MASKS_RASTER_EMPTY) continue;
310 {
311 const double start = dt_get_wtime();
312 if(_inverse_mask(module, piece, sel, &bufs[nb_ok], &w[nb_ok], &h[nb_ok], &px[nb_ok], &py[nb_ok]) != 0)
313 {
315 break;
316 }
318 dt_print(DT_DEBUG_MASKS, "[masks %s] inverse took %0.04f sec\n", sel->name, dt_get_wtime() - start);
319 }
320 op[nb_ok] = fpt->opacity;
321 states[nb_ok] = fpt->state;
322 nb_ok++;
323 }
324 }
325 if(err) goto cleanup;
326 if(nb_ok == 0)
327 {
328 *buffer = NULL;
329 *width = 0;
330 *height = 0;
331 *posx = 0;
332 *posy = 0;
333 goto cleanup;
334 }
335
336 // now we get the min, max, width, height of the final mask
337 int l = INT_MAX, r = INT_MIN, t = INT_MAX, b = INT_MIN;
338 for(int i = 0; i < nb_ok; i++)
339 {
340 l = MIN(l, px[i]);
341 t = MIN(t, py[i]);
342 r = MAX(r, px[i] + w[i]);
343 b = MAX(b, py[i] + h[i]);
344 }
345 *posx = l;
346 *posy = t;
347 *width = r - l;
348 *height = b - t;
349
350 // we allocate the buffer
351 *buffer = dt_pixelpipe_cache_alloc_align_float_cache((size_t)(r - l) * (b - t), 0);
352 if(IS_NULL_PTR(*buffer))
353 {
355 goto cleanup;
356 }
357
358 // and we copy each buffer inside, row by row
359 const int dst_w = r - l;
360 const int dst_h = b - t;
361 float *const dst = *buffer;
362 for(int i = 0; i < nb_ok; i++)
363 {
364 const double start = dt_get_wtime();
365 const int wi = w[i];
366 const int hi = h[i];
367 const int ox = px[i] - l;
368 const int oy = py[i] - t;
369 const float opacity = op[i];
370 const float *const src = bufs[i];
371 if(states[i] & DT_MASKS_STATE_UNION)
372 {
373 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
374 for(int y = 0; y < hi; y++)
375 {
376 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
377 const float *const src_row = src + (size_t)y * wi;
378 for(int x = 0; x < wi; x++)
379 {
380 const float v = src_row[x] * opacity;
381 if(v > dst_row[x]) dst_row[x] = v;
382 }
383 }
384 }
385 else if(states[i] & DT_MASKS_STATE_INTERSECTION)
386 {
387 const int x0 = MAX(px[i], l);
388 const int y0 = MAX(py[i], t);
389 const int x1 = MIN(px[i] + wi, r);
390 const int y1 = MIN(py[i] + hi, b);
391 if(x0 >= x1 || y0 >= y1)
392 {
393 memset(dst, 0, (size_t)dst_w * dst_h * sizeof(float));
394 }
395 else
396 {
397 const int row_start = y0 - t;
398 const int row_end = y1 - t;
399 const int col_start = x0 - l;
400 const int col_end = x1 - l;
401 const int src_x_offset = x0 - px[i];
402 const int src_y_offset = t - py[i];
403 __OMP_PARALLEL_FOR__(if((size_t)dst_w * dst_h > 10000))
404 for(int y = 0; y < dst_h; y++)
405 {
406 float *const dst_row = dst + (size_t)y * dst_w;
407 if(y < row_start || y >= row_end)
408 {
409 memset(dst_row, 0, (size_t)dst_w * sizeof(float));
410 continue;
411 }
412
413 const int src_y = y + src_y_offset;
414 const float *const src_row = src + (size_t)src_y * wi + src_x_offset;
415 float *const dst_mid = dst_row + col_start;
416 const int mid_w = col_end - col_start;
417 for(int x = 0; x < mid_w; x++)
418 {
419 const float b1 = dst_mid[x];
420 const float b2 = src_row[x];
421 if(b1 > 0.0f && b2 > 0.0f)
422 dst_mid[x] = fminf(b1, b2 * opacity);
423 else
424 dst_mid[x] = 0.0f;
425 }
426
427 if(col_start > 0) memset(dst_row, 0, (size_t)col_start * sizeof(float));
428 if(col_end < dst_w) memset(dst_row + col_end, 0, (size_t)(dst_w - col_end) * sizeof(float));
429 }
430 }
431 }
432 else if(states[i] & DT_MASKS_STATE_DIFFERENCE)
433 {
434 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
435 for(int y = 0; y < hi; y++)
436 {
437 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
438 const float *const src_row = src + (size_t)y * wi;
439 for(int x = 0; x < wi; x++)
440 {
441 const float b1 = dst_row[x];
442 const float b2 = src_row[x] * opacity;
443 if(b1 > 0.0f && b2 > 0.0f) dst_row[x] = b1 * (1.0f - b2);
444 }
445 }
446 }
447 else if(states[i] & DT_MASKS_STATE_EXCLUSION)
448 {
449 __OMP_PARALLEL_FOR__(if((size_t)wi * hi > 10000))
450 for(int y = 0; y < hi; y++)
451 {
452 float *const dst_row = dst + (size_t)(oy + y) * dst_w + ox;
453 const float *const src_row = src + (size_t)y * wi;
454 for(int x = 0; x < wi; x++)
455 {
456 const float b1 = dst_row[x];
457 const float b2 = src_row[x] * opacity;
458 if(b1 > 0.0f && b2 > 0.0f)
459 dst_row[x] = fmaxf((1.0f - b1) * b2, b1 * (1.0f - b2));
460 else
461 dst_row[x] = fmaxf(dst_row[x], b2);
462 }
463 }
464 }
465 else // if we are here, this mean that we just have to copy the shape and null other parts
466 {
467 const int x0 = MAX(px[i], l);
468 const int y0 = MAX(py[i], t);
469 const int x1 = MIN(px[i] + wi, r);
470 const int y1 = MIN(py[i] + hi, b);
471 if(x0 >= x1 || y0 >= y1)
472 {
473 memset(dst, 0, (size_t)dst_w * dst_h * sizeof(float));
474 }
475 else
476 {
477 const int row_start = y0 - t;
478 const int row_end = y1 - t;
479 const int col_start = x0 - l;
480 const int col_end = x1 - l;
481 const int src_x_offset = x0 - px[i];
482 const int src_y_offset = t - py[i];
483 __OMP_PARALLEL_FOR__(if((size_t)dst_w * dst_h > 10000))
484 for(int y = 0; y < dst_h; y++)
485 {
486 float *const dst_row = dst + (size_t)y * dst_w;
487 if(y < row_start || y >= row_end)
488 {
489 memset(dst_row, 0, (size_t)dst_w * sizeof(float));
490 continue;
491 }
492
493 const int src_y = y + src_y_offset;
494 const float *const src_row = src + (size_t)src_y * wi + src_x_offset;
495 float *const dst_mid = dst_row + col_start;
496 const int mid_w = col_end - col_start;
497 for(int x = 0; x < mid_w; x++)
498 {
499 dst_mid[x] = src_row[x] * opacity;
500 }
501
502 if(col_start > 0) memset(dst_row, 0, (size_t)col_start * sizeof(float));
503 if(col_end < dst_w) memset(dst_row + col_end, 0, (size_t)(dst_w - col_end) * sizeof(float));
504 }
505 }
506 }
507
509 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", i, dt_get_wtime() - start);
510 }
511
512cleanup:
513 dt_free_align(op);
514 dt_free_align(states);
515 dt_free_align(py);
516 dt_free_align(px);
517 dt_free_align(h);
518 dt_free_align(w);
519 for(int i = 0; i < nb; i++) dt_pixelpipe_cache_free_align(bufs[i]);
520 dt_free_align(bufs);
521 return err;
522}
523
524static void _combine_masks_union(float *const restrict dest, float *const restrict newmask, const size_t npixels,
525 const float opacity, const int inverted)
526{
527 if (inverted)
528 {
529 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
530 for(int index = 0; index < npixels; index++)
531 {
532 const float mask = opacity * (1.0f - newmask[index]);
533 dest[index] = MAX(dest[index], mask);
534 }
535 }
536 else
537 {
538 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
539 for(int index = 0; index < npixels; index++)
540 {
541 const float mask = opacity * newmask[index];
542 dest[index] = MAX(dest[index], mask);
543 }
544 }
545}
546
548static void _combine_masks_union_box(float *const restrict dest, const float *const restrict newmask,
549 const int width, const dt_iop_roi_t *const box, const float opacity)
550{
551 for(int y = box->y; y < box->y + box->height; y++)
552 {
553 float *const restrict dest_row = dest + (size_t)y * width + box->x;
554 const float *const restrict src_row = newmask + (size_t)y * width + box->x;
555 for(int x = 0; x < box->width; x++)
556 {
557 const float mask = opacity * src_row[x];
558 dest_row[x] = MAX(dest_row[x], mask);
559 }
560 }
561}
562
563static void _combine_masks_intersect(float *const restrict dest, float *const restrict newmask, const size_t npixels,
564 const float opacity, const int inverted)
565{
566 if (inverted)
567 {
568 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
569 for(int index = 0; index < npixels; index++)
570 {
571 const float mask = opacity * (1.0f - newmask[index]);
572 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
573 }
574 }
575 else
576 {
577 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
578 for(int index = 0; index < npixels; index++)
579 {
580 const float mask = opacity * newmask[index];
581 dest[index] = MIN(MAX(dest[index], 0.0f), MAX(mask, 0.0f));
582 }
583 }
584}
585
587static inline int both_positive(const float val1, const float val2)
588{
589 // this needs to be a separate inline function to convince the compiler to vectorize
590 return (val1 > 0.0f) && (val2 > 0.0f);
591}
592
593static void _combine_masks_difference(float *const restrict dest, float *const restrict newmask, const size_t npixels,
594 const float opacity, const int inverted)
595{
596 if (inverted)
597 {
598 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
599 for(int index = 0; index < npixels; index++)
600 {
601 const float mask = opacity * (1.0f - newmask[index]);
602 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
603 }
604 }
605 else
606 {
607__OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000)
608)
609 for(int index = 0; index < npixels; index++)
610 {
611 const float mask = opacity * newmask[index];
612 dest[index] *= (1.0f - mask * both_positive(dest[index],mask));
613 }
614 }
615}
616
617static void _combine_masks_exclusion(float *const restrict dest, float *const restrict newmask, const size_t npixels,
618 const float opacity, const int inverted)
619{
620 if (inverted)
621 {
622__OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000)
623)
624 for(int index = 0; index < npixels; index++)
625 {
626 const float mask = opacity * (1.0f - newmask[index]);
627 const float pos = both_positive(dest[index], mask);
628 const float neg = (1.0f - pos);
629 const float b1 = dest[index];
630 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
631 }
632 }
633 else
634 {
635 __OMP_FOR_SIMD__(aligned(dest, newmask : 64) if(npixels > 10000))
636 for(int index = 0; index < npixels; index++)
637 {
638 const float mask = opacity * newmask[index];
639 const float pos = both_positive(dest[index], mask);
640 const float neg = (1.0f - pos);
641 const float b1 = dest[index];
642 dest[index] = pos * MAX((1.0f - b1) * mask, b1 * (1.0f - mask)) + neg * MAX(b1, mask);
643 }
644 }
645}
646
667static uint64_t _group_prefix_hash(const dt_masks_form_t *const form, GList *masks,
668 const dt_dev_pixelpipe_iop_t *const piece, const dt_iop_roi_t *const roi,
669 const int count)
670{
671 uint64_t hash = 5381;
672 hash = dt_hash(hash, (const char *)roi, sizeof(dt_iop_roi_t));
673 if(!IS_NULL_PTR(piece))
674 {
675 hash = dt_hash(hash, (const char *)&piece->buf_in, sizeof(dt_iop_roi_t));
676 hash = dt_hash(hash, (const char *)&piece->buf_out, sizeof(dt_iop_roi_t));
677 }
678
679 int i = 0;
680 for(const GList *fpts = form->points; fpts && i < count; fpts = g_list_next(fpts), i++)
681 {
682 const dt_masks_form_group_t *const fpt = (const dt_masks_form_group_t *)fpts->data;
683 const dt_masks_form_t *const sel = dt_masks_get_from_id_ext(masks, fpt->formid);
684 if(IS_NULL_PTR(sel)) return 0; // cannot describe this prefix; refuse to key on it
685
686 hash = dt_hash(hash, (const char *)&fpt->state, sizeof(int));
687 hash = dt_hash(hash, (const char *)&fpt->opacity, sizeof(float));
688 hash = dt_masks_form_get_own_hash(hash, masks, sel);
689 }
690 return hash ? hash : 1;
691}
692
694 const dt_dev_pixelpipe_iop_t *const restrict piece,
695 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
696 float *const restrict buffer, dt_iop_roi_t *touched)
697{
698 double start = dt_get_wtime();
699 dt_masks_touched_none(touched);
700 if(IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
701 int nb_ok = 0;
702
703 const int width = roi->width;
704 const int height = roi->height;
705 const size_t npixels = (size_t)width * height;
706
707 /* Resume from the longest cached prefix.
708 *
709 * Without this every shape is rasterised and combined from scratch on every render, so drawing
710 * the tenth stroke costs ten shapes and drawing the first costs one -- measured on #1158 at
711 * ~9 ms to rasterise and ~21 ms to combine per shape, on the preview pipe and the full pipe
712 * both, growing `render all masks' from 205 ms to 260 ms across one session. Appending a stroke
713 * leaves every earlier prefix identical, so the work that actually has to be redone is one
714 * shape's.
715 *
716 * Shapes are walked newest-first: the append case hits on the first probe. The forms come from
717 * pipe->forms, the refcounted snapshot this run owns -- never dev->forms, which the GUI thread
718 * is mutating while this runs on a worker. */
719 GList *const masks = !IS_NULL_PTR(pipe) && !IS_NULL_PTR(pipe->forms) ? pipe->forms : module->dev->forms;
720 const int shape_count = g_list_length(form->points);
721 int resume_from = 0;
722
723 for(int count = shape_count - 1; count > 0; count--)
724 {
725 const uint64_t prefix = _group_prefix_hash(form, masks, piece, roi, count);
726 if(prefix == 0) break;
727
728 void *cached = NULL;
729 dt_pixel_cache_entry_t *entry = NULL;
730 if(dt_dev_pixelpipe_cache_peek(prefix, &cached, &entry, -1, NULL) && !IS_NULL_PTR(cached))
731 {
733 memcpy(buffer, cached, npixels * sizeof(float));
736 resume_from = count;
737 nb_ok = count;
739 dt_print(DT_DEBUG_MASKS, "[masks] group fold resumed from cached prefix of %d/%d shapes\n", count,
740 shape_count);
741 break;
742 }
743 }
744
745 // we need to allocate a zeroed temporary buffer for intermediate creation of individual shapes
746 float *const restrict bufs = dt_pixelpipe_cache_alloc_align_float_cache(npixels, 0);
747 if(IS_NULL_PTR(bufs)) return DT_MASKS_RASTER_ERROR;
749
750 /* Somewhere to hold the fold minus its last shape. Only needed when there is something new to
751 * publish; a failure to allocate simply means this render publishes nothing. */
752 float *publishable = NULL;
753 if(shape_count > 1 && resume_from < shape_count - 1)
754 publishable = dt_pixelpipe_cache_alloc_align_float_cache(npixels, 0);
755
756 if(resume_from == 0) memset(buffer, 0, npixels * sizeof(float));
757 gboolean bufs_zeroed = FALSE;
758 dt_iop_roi_t child_touched;
759 dt_masks_touched_none(&child_touched);
760 dt_iop_roi_t group_touched;
761 dt_masks_touched_none(&group_touched);
762 if(resume_from > 0) dt_masks_touched_full(&group_touched, width, height); // the prefix is opaque to us
763
764 int i = 0;
765 // and we get all masks
766 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
767 {
768 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
770
771 if(sel && i < resume_from)
772 {
773 // already folded into `buffer' by the cached prefix above
774 i++;
775 continue;
776 }
777
778 if(sel)
779 {
780 /* Snapshot the fold before the LAST shape goes in: that is what gets published, and it is
781 * the only state a later render can start from without redoing the shape being edited. */
782 if(i == shape_count - 1 && !IS_NULL_PTR(publishable))
783 memcpy(publishable, buffer, npixels * sizeof(float));
784
785 /* `bufs' must be zero wherever the child will not write. Clearing only the PREVIOUS child's
786 * reported box keeps that invariant at the cost of that box, not of the whole ROI per shape. */
787 if(!bufs_zeroed)
788 {
789 memset(bufs, 0, npixels * sizeof(float));
790 bufs_zeroed = TRUE;
791 }
792 else
793 dt_masks_touched_clear(bufs, width, &child_touched);
794 const dt_masks_raster_result_t child_result
795 = dt_masks_get_mask_roi(module, pipe, piece, sel, roi, bufs, &child_touched);
796 const float op = fpt->opacity;
797 // Add a foolproof to ensure that the first shape is no-op
798 const int no_op_state = fpt->state & ~(DT_MASKS_STATE_IS_COMBINE_OP) ;
799 const int state = (i == 0) ? no_op_state : fpt->state;
800 if(child_result == DT_MASKS_RASTER_ERROR)
801 {
802 /* Undefined buffer: what this child left in `bufs' cannot be folded in, and the fold as
803 * a whole must not be published. */
805 break;
806 }
807 /* EMPTY combines exactly like OK, because `bufs' is fully zeroed before every child (the
808 * first child memsets it, each later one clears the previous child's reported box), so a
809 * shape that drew nothing IS an all-zero child. That distinction matters: an EMPTY child
810 * must still be intersected with -- a brush lying outside this ROI genuinely has no
811 * coverage here, and skipping the combine would leave the previous fold standing where
812 * the mask should have gone to zero.
813 *
814 * What EMPTY changes is only that it is no longer an ERROR. A NULL points list used to
815 * abort the entire group for a circle and fold as zeros for an ellipse: the same
816 * degenerate data killed or spared the whole mask depending only on which shape carried
817 * it. Both now fold as zeros. */
818 else
819 {
820 // first see if we need to invert this shape
821 const int inverted = (state & DT_MASKS_STATE_INVERSE);
822
823 /* Union and plain copy leave `buffer' untouched outside the child's box: max(dest,0) is
824 * dest, and the copy writes op * 0 = 0 over an already-zero buffer. Those two run over the
825 * box alone. Intersection/difference/exclusion rewrite the outside too, and an inverted
826 * child is non-zero outside its box, so those keep the full-ROI pass. */
827 const gboolean box_only = !inverted && !dt_masks_touched_is_empty(&child_touched)
831
833 {
834 if(box_only)
835 _combine_masks_union_box(buffer, bufs, width, &child_touched, op);
836 else
837 _combine_masks_union(buffer, bufs, npixels, op, inverted);
838 }
840 {
841 _combine_masks_intersect(buffer, bufs, npixels, op, inverted);
842 }
844 {
845 _combine_masks_difference(buffer, bufs, npixels, op, inverted);
846 }
848 {
849 _combine_masks_exclusion(buffer, bufs, npixels, op, inverted);
850 }
851 else if(box_only) // plain copy: op * child inside the box, zero everywhere else
852 {
853 if(i != 0) memset(buffer, 0, npixels * sizeof(float)); // a later copy discards the fold so far
854 for(int y = child_touched.y; y < child_touched.y + child_touched.height; y++)
855 {
856 float *const restrict dest_row = buffer + (size_t)y * width + child_touched.x;
857 const float *const restrict src_row = bufs + (size_t)y * width + child_touched.x;
858 for(int x = 0; x < child_touched.width; x++) dest_row[x] = op * src_row[x];
859 }
860 }
861 else
862 {
863 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer, bufs : 64) if(npixels > 10000))
864 for(int index = 0; index < npixels; index++)
865 {
866 buffer[index] = op * (inverted ? (1.0f - bufs[index]) : bufs[index]);
867 }
868 }
869
870 if(box_only && (state & DT_MASKS_STATE_UNION))
871 dt_masks_touched_union(&group_touched, &child_touched);
872 else if(box_only)
873 group_touched = child_touched; // the copy discarded everything outside this box
874 else
875 dt_masks_touched_full(&group_touched, width, height);
876
878 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", nb_ok, dt_get_wtime() - start);
879 start = dt_get_wtime();
880
881 nb_ok++;
882 }
883 }
884 i++;
885 }
886 // and we free the intermediate buffer
888
889
890 if(nb_ok == 0)
891 memset(buffer, 0, npixels * sizeof(float));
892 else if(!IS_NULL_PTR(touched))
893 *touched = group_touched;
894
895 /* Publish the fold WITHOUT the last shape, never the complete one.
896 *
897 * The last shape is the one being edited: while a stroke is dragged its content changes on every
898 * frame, so the complete fold's key changes on every frame too. Publishing that would leave one
899 * full-ROI cacheline per rendered frame behind, each dead the moment it is written -- memory
900 * pressure that evicts the pipeline's own outputs and costs more than the fold ever saved.
901 * Measured: publishing the complete fold flattened `render all masks' as intended (205-260 ms
902 * down to 32-100 ms) while the darkroom redraw grew from 5 ms to 416 ms over one session.
903 *
904 * The fold minus its last shape is stable across every frame of a stroke, so it is published
905 * once and hit on every later frame -- and when the next shape is appended the resume covers all
906 * but two. Nothing is published when the resume already came from that prefix: it is what we
907 * would be writing back.
908 *
909 * Only on a complete, error-free fold: a prefix that describes fewer shapes than its key claims
910 * is not a wrong-looking mask, it is a mask. */
911 if(err == DT_MASKS_RASTER_OK && shape_count > 1 && nb_ok == shape_count && resume_from < shape_count - 1
912 && !IS_NULL_PTR(publishable))
913 {
914 const uint64_t prefix = _group_prefix_hash(form, masks, piece, roi, shape_count - 1);
915 if(prefix != 0)
916 {
917 void *slot = NULL;
918 dt_pixel_cache_entry_t *entry = NULL;
919 const int created = dt_dev_pixelpipe_cache_get(prefix, npixels * sizeof(float), "masks group prefix",
920 IS_NULL_PTR(pipe) ? -1 : pipe->type, TRUE, &slot, &entry);
921 if(!IS_NULL_PTR(slot))
922 {
923 if(created)
924 {
925 memcpy(slot, publishable, npixels * sizeof(float));
927 }
929 }
930 }
931 }
932
934
935 return err;
936}
937
939 const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *form,
940 const dt_iop_roi_t *roi, float *buffer)
941{
942 const double start = dt_get_wtime();
943 if(IS_NULL_PTR(form)) return DT_MASKS_RASTER_EMPTY;
944
945 const dt_masks_raster_result_t err = dt_masks_get_mask_roi(module, pipe, piece, form, roi, buffer, NULL);
946
948 dt_print(DT_DEBUG_MASKS, "[masks] render all masks took %0.04f sec\n", dt_get_wtime() - start);
949 return err;
950}
951
952static void _group_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base,
953 dt_masks_form_t *const dest)
954{
955 for(GList *pts = base->points; pts; pts = g_list_next(pts))
956 {
959
960 npt->formid = dt_masks_form_duplicate(dev, pt->formid);
961 npt->parentid = dest->formid;
962 npt->state = pt->state;
963 npt->opacity = pt->opacity;
964 dest->points = g_list_append(dest->points, npt);
965 }
966}
967
968static gboolean _group_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
969{
970 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
971
972 float sum_x = 0.0f;
973 float sum_y = 0.0f;
974 float sum_w = 0.0f;
975 int count = 0;
976
977 for(const GList *l = form->points; l; l = g_list_next(l))
978 {
979 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)l->data;
980 if(IS_NULL_PTR(pt)) continue;
982 if(IS_NULL_PTR(child)) continue;
983
984 float child_center[2] = { 0.0f, 0.0f };
985 float child_area = 0.0f;
986 if(!dt_masks_form_get_gravity_center(dev, child, child_center, &child_area)) continue;
987
988 const float w = (child_area > 0.0f) ? child_area : 1.0f;
989 sum_x += child_center[0] * w;
990 sum_y += child_center[1] * w;
991 sum_w += w;
992 count++;
993 }
994
995 if(count == 0)
996 {
997 center[0] = 0.0f;
998 center[1] = 0.0f;
999 *area = 0.0f;
1000 return FALSE;
1001 }
1002
1003 if(sum_w <= 0.0f)
1004 {
1005 center[0] = sum_x / (float)count;
1006 center[1] = sum_y / (float)count;
1007 *area = 0.0f;
1008 }
1009 else
1010 {
1011 center[0] = sum_x / sum_w;
1012 center[1] = sum_y / sum_w;
1013 *area = sum_w;
1014 }
1015
1016 return TRUE;
1017}
1018
1019// The function table for groups. This must be public, i.e. no "static" keyword.
1021 .point_struct_size = sizeof(struct dt_masks_form_group_t),
1022 .sanitize_config = NULL,
1023 .set_form_name = NULL,
1024 .set_hint_message = NULL,
1025 .duplicate_points = _group_duplicate_points,
1026 .initial_source_pos = NULL,
1027 .get_distance = NULL,
1028 .get_points = NULL,
1029 .get_points_border = NULL,
1030 .get_mask = _group_get_mask,
1031 .get_mask_roi = _group_get_mask_roi,
1032 .get_area = NULL,
1033 .get_source_area = NULL,
1034 .get_gravity_center = _group_get_gravity_center,
1035 .get_interaction_value = NULL,
1036 .set_interaction_value = NULL,
1037 .update_hover = NULL,
1038 .mouse_moved = _group_events_mouse_moved,
1039 .mouse_scrolled = _group_events_mouse_scrolled,
1040 .button_pressed = _group_events_button_pressed,
1041 .button_released = _group_events_button_released,
1042 .key_pressed = _group_events_key_pressed,
1043//TODO: .post_expose = _group_events_post_expose
1044 .draw_shape = NULL
1045};
1046
1047
1048// clang-format off
1049// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1050// vim: shiftwidth=2 expandtab tabstop=2 cindent
1051// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1052// 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:170
static const float x
const int t
const float v
static const int row
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
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:125
static uint64_t _group_prefix_hash(const dt_masks_form_t *const form, GList *masks, const dt_dev_pixelpipe_iop_t *const piece, const dt_iop_roi_t *const roi, const int count)
Identity of the group mask after folding the first count shapes, at this ROI.
Definition group.c:667
static dt_masks_raster_result_t _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:250
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:90
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:83
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:952
static void _combine_masks_union_box(float *const restrict dest, const float *const restrict newmask, const int width, const dt_iop_roi_t *const box, const float opacity)
Definition group.c:548
const dt_masks_functions_t dt_masks_functions_group
Definition group.c:1020
void dt_group_events_post_expose_except(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, const int except_pos)
Definition group.c:164
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:132
dt_masks_raster_result_t 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:938
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:524
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:617
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:76
void dt_group_events_post_expose_only(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, const int pos)
Definition group.c:172
static gboolean _group_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
Definition group.c:968
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:593
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:198
static int both_positive(const float val1, const float val2)
Definition group.c:587
static dt_masks_raster_result_t _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, dt_iop_roi_t *touched)
Definition group.c:693
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:62
static dt_masks_form_t * _group_get_child_at(dt_develop_t *dev, dt_masks_form_t *form, const int group_index, dt_masks_form_group_t **group_entry)
Definition group.c:53
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:179
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:563
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:69
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#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 macros.h:96
dt_masks_raster_result_t 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, dt_iop_roi_t *touched)
Definition masks.c:1642
dt_masks_raster_result_t 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.c:1626
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_EMPTY
Definition masks.h:315
@ DT_MASKS_RASTER_OK
Definition masks.h:314
uint64_t dt_masks_form_get_own_hash(uint64_t hash, GList *masks, const dt_masks_form_t *form)
Definition masks.c:1439
dt_masks_form_t * dt_masks_get_from_id_ext(GList *forms, int id)
Definition masks.c:899
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
int dt_masks_form_duplicate(dt_develop_t *dev, int formid)
Definition masks.c:186
The per-shape function table, private to the masks implementation.
gboolean dt_masks_form_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
Definition masks_gui.c:5156
gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, const int parent_id)
remove a mask shape or node form from the GUI. This function is used with a popupmenu "Delete" action...
Definition masks_gui.c:2378
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2250
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
static int dt_masks_touched_is_empty(const dt_iop_roi_t *touched)
static void dt_masks_touched_full(dt_iop_roi_t *touched, const int width, const int height)
static void dt_masks_touched_clear(float *const buffer, const int width, const dt_iop_roi_t *touched)
static void dt_masks_touched_none(dt_iop_roi_t *touched)
static void dt_masks_touched_union(dt_iop_roi_t *into, const dt_iop_roi_t *other)
@ DT_MASKS_STATE_DIFFERENCE
Definition masks_types.h:94
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_INTERSECTION
Definition masks_types.h:93
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks_types.h:98
@ DT_MASKS_STATE_EXCLUSION
Definition masks_types.h:95
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
dt_masks_interaction_t
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
static void * dt_calloc_align(size_t size)
dt_alloc_align() followed by a zero fill.
Definition mem_alloc.h:225
static float * dt_alloc_align_float(size_t pixels)
Allocate pixels floats, cacheline-aligned and marked as such.
Definition mem_alloc.h:235
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
#define __OMP_DECLARE_SIMD__(...)
Definition openmp.h:100
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
#define __OMP_FOR_SIMD__(...)
Definition openmp.h:97
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
void dt_dev_pixelpipe_cache_wrlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the write lock on the entry.
void dt_dev_pixelpipe_cache_ref_count_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Increase/Decrease the reference count on the cache line as to prevent LRU item removal....
gboolean dt_dev_pixelpipe_cache_peek(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry, const int preferred_devid, void **cl_mem_output)
Non-owning lookup of an existing cache line.
void dt_dev_pixelpipe_cache_rdlock_entry(gboolean lock, dt_pixel_cache_entry_t *cache_entry)
Lock or release the read lock on the entry.
int dt_dev_pixelpipe_cache_get(const uint64_t hash, const size_t size, const char *name, const int id, const gboolean alloc, void **data, dt_pixel_cache_entry_t **entry)
Get a cache line from the cache.
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
const float uint32_t state[4]
const float r
unsigned __int64 uint64_t
Definition strptime.c:75
dt_dev_pixelpipe_type_t type
struct dt_develop_t * dev
Definition imageop.h:311
Region of interest passed through the pixelpipe.
Definition format.h:49
int width
Definition format.h:50
int height
Definition format.h:50
dt_masks_type_t type
Definition masks_gui.h:103
struct dt_develop_t * dev
Definition masks_gui.h:101
const dt_masks_functions_t * functions
Definition masks.h:255
dt_masks_type_t type
Definition masks.h:254
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
void(* post_expose)(cairo_t *cr, float zoom_scale, struct dt_masks_form_gui_t *gui, int index, int num_points)
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
static void dt_get_times(dt_times_t *t)
Definition times.h:50
static double dt_get_wtime(void)
Definition times.h:43
void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix,...) __attribute__((format(printf