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 dt_masks_area_t area;
298 const dt_masks_raster_result_t child = dt_masks_get_mask(module, pipe, piece, sel, &bufs[nb_ok], &area);
299 w[nb_ok] = area.width, h[nb_ok] = area.height, px[nb_ok] = area.x, py[nb_ok] = area.y;
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
693/* Publish `fold` under `prefix`, unless a line already answers to it. dt_dev_pixelpipe_cache_get()
694 * hands back a reference, plus the write lock when it created the line, whether or not the buffer
695 * could be allocated: both go back on every path. */
696static void _group_publish_prefix(const uint64_t prefix, const float *const fold, const size_t npixels,
698{
699 void *slot = NULL;
700 dt_pixel_cache_entry_t *entry = NULL;
701 const int created = dt_dev_pixelpipe_cache_get(prefix, npixels * sizeof(float), "masks group prefix",
702 IS_NULL_PTR(pipe) ? -1 : pipe->type, TRUE, &slot, &entry);
703 if(IS_NULL_PTR(entry)) return;
704
705 if(!created)
706 {
707 // already published: nothing to write, only the reference to hand back
709 return;
710 }
711
712 // Created without a buffer: nothing a later render could resume from. Flagged while still held, so
713 // the release removes it.
714 if(IS_NULL_PTR(slot))
716 else
717 memcpy(slot, fold, npixels * sizeof(float));
720}
721
723 const dt_dev_pixelpipe_iop_t *const restrict piece,
724 dt_masks_form_t *const form, const dt_iop_roi_t *const roi,
725 float *const restrict buffer, dt_iop_roi_t *touched)
726{
727 double start = dt_get_wtime();
728 dt_masks_touched_none(touched);
729 if(IS_NULL_PTR(form->points)) return DT_MASKS_RASTER_EMPTY;
730 int nb_ok = 0;
731
732 const int width = roi->width;
733 const int height = roi->height;
734 const size_t npixels = (size_t)width * height;
735
736 /* Resume from the longest cached prefix.
737 *
738 * Without this every shape is rasterised and combined from scratch on every render, so drawing
739 * the tenth stroke costs ten shapes and drawing the first costs one -- measured on #1158 at
740 * ~9 ms to rasterise and ~21 ms to combine per shape, on the preview pipe and the full pipe
741 * both, growing `render all masks' from 205 ms to 260 ms across one session. Appending a stroke
742 * leaves every earlier prefix identical, so the work that actually has to be redone is one
743 * shape's.
744 *
745 * Shapes are walked newest-first: the append case hits on the first probe. The forms come from
746 * pipe->forms, the refcounted snapshot this run owns -- never dev->forms, which the GUI thread
747 * is mutating while this runs on a worker. */
748 GList *const masks = !IS_NULL_PTR(pipe) && !IS_NULL_PTR(pipe->forms) ? pipe->forms : module->dev->forms;
749 const int shape_count = g_list_length(form->points);
750 int resume_from = 0;
751
752 for(int count = shape_count - 1; count > 0; count--)
753 {
754 const uint64_t prefix = _group_prefix_hash(form, masks, piece, roi, count);
755 if(prefix == 0) break;
756
757 /* Retained, not peeked. A prefix sits at refcount 0 between renders, so any thread's eviction
758 * may free it at any moment: dt_dev_pixelpipe_cache_peek() takes no reference, and the entry
759 * it returns can be gone before the read lock below is taken. The lookup and the reference
760 * are therefore one step, under the cache lock, and the reference is what the release at the
761 * end of this block hands back -- releasing after a peek drops one this code never took. */
762 void *cached = NULL;
763 dt_pixel_cache_entry_t *entry = NULL;
764 if(dt_dev_pixelpipe_cache_ref_host_entry_by_hash(prefix, &cached, &entry))
765 {
766 // A hash identifies content, never a size: refuse a line too short for this ROI.
767 if(dt_pixel_cache_entry_get_size(entry) < npixels * sizeof(float))
768 {
770 continue;
771 }
773 memcpy(buffer, cached, npixels * sizeof(float));
776 resume_from = count;
777 nb_ok = count;
779 dt_print(DT_DEBUG_MASKS, "[masks] group fold resumed from cached prefix of %d/%d shapes\n", count,
780 shape_count);
781 break;
782 }
783 }
784
785 // we need to allocate a zeroed temporary buffer for intermediate creation of individual shapes
786 float *const restrict bufs = dt_pixelpipe_cache_alloc_align_float_cache(npixels, 0);
787 if(IS_NULL_PTR(bufs)) return DT_MASKS_RASTER_ERROR;
789
790 /* Somewhere to hold the fold minus its last shape. Only needed when there is something new to
791 * publish; a failure to allocate simply means this render publishes nothing. */
792 float *publishable = NULL;
793 if(shape_count > 1 && resume_from < shape_count - 1)
794 publishable = dt_pixelpipe_cache_alloc_align_float_cache(npixels, 0);
795
796 if(resume_from == 0) memset(buffer, 0, npixels * sizeof(float));
797 gboolean bufs_zeroed = FALSE;
798 dt_iop_roi_t child_touched;
799 dt_masks_touched_none(&child_touched);
800 dt_iop_roi_t group_touched;
801 dt_masks_touched_none(&group_touched);
802 if(resume_from > 0) dt_masks_touched_full(&group_touched, width, height); // the prefix is opaque to us
803
804 int i = 0;
805 // and we get all masks
806 for(GList *fpts = form->points; fpts; fpts = g_list_next(fpts))
807 {
808 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
810
811 if(sel && i < resume_from)
812 {
813 // already folded into `buffer' by the cached prefix above
814 i++;
815 continue;
816 }
817
818 if(sel)
819 {
820 /* Snapshot the fold before the LAST shape goes in: that is what gets published, and it is
821 * the only state a later render can start from without redoing the shape being edited. */
822 if(i == shape_count - 1 && !IS_NULL_PTR(publishable))
823 memcpy(publishable, buffer, npixels * sizeof(float));
824
825 /* `bufs' must be zero wherever the child will not write. Clearing only the PREVIOUS child's
826 * reported box keeps that invariant at the cost of that box, not of the whole ROI per shape. */
827 if(!bufs_zeroed)
828 {
829 memset(bufs, 0, npixels * sizeof(float));
830 bufs_zeroed = TRUE;
831 }
832 else
833 dt_masks_touched_clear(bufs, width, &child_touched);
834 const dt_masks_raster_result_t child_result
835 = dt_masks_get_mask_roi(module, pipe, piece, sel, roi, bufs, &child_touched);
836 const float op = fpt->opacity;
837 // Add a foolproof to ensure that the first shape is no-op
838 const int no_op_state = fpt->state & ~(DT_MASKS_STATE_IS_COMBINE_OP) ;
839 const int state = (i == 0) ? no_op_state : fpt->state;
840 if(child_result == DT_MASKS_RASTER_ERROR)
841 {
842 /* Undefined buffer: what this child left in `bufs' cannot be folded in, and the fold as
843 * a whole must not be published. */
845 break;
846 }
847 /* EMPTY combines exactly like OK, because `bufs' is fully zeroed before every child (the
848 * first child memsets it, each later one clears the previous child's reported box), so a
849 * shape that drew nothing IS an all-zero child. That distinction matters: an EMPTY child
850 * must still be intersected with -- a brush lying outside this ROI genuinely has no
851 * coverage here, and skipping the combine would leave the previous fold standing where
852 * the mask should have gone to zero.
853 *
854 * What EMPTY changes is only that it is no longer an ERROR. A NULL points list used to
855 * abort the entire group for a circle and fold as zeros for an ellipse: the same
856 * degenerate data killed or spared the whole mask depending only on which shape carried
857 * it. Both now fold as zeros. */
858 else
859 {
860 // first see if we need to invert this shape
861 const int inverted = (state & DT_MASKS_STATE_INVERSE);
862
863 /* Union and plain copy leave `buffer' untouched outside the child's box: max(dest,0) is
864 * dest, and the copy writes op * 0 = 0 over an already-zero buffer. Those two run over the
865 * box alone. Intersection/difference/exclusion rewrite the outside too, and an inverted
866 * child is non-zero outside its box, so those keep the full-ROI pass. */
867 const gboolean box_only = !inverted && !dt_masks_touched_is_empty(&child_touched)
871
873 {
874 if(box_only)
875 _combine_masks_union_box(buffer, bufs, width, &child_touched, op);
876 else
877 _combine_masks_union(buffer, bufs, npixels, op, inverted);
878 }
880 {
881 _combine_masks_intersect(buffer, bufs, npixels, op, inverted);
882 }
884 {
885 _combine_masks_difference(buffer, bufs, npixels, op, inverted);
886 }
888 {
889 _combine_masks_exclusion(buffer, bufs, npixels, op, inverted);
890 }
891 else if(box_only) // plain copy: op * child inside the box, zero everywhere else
892 {
893 if(i != 0) memset(buffer, 0, npixels * sizeof(float)); // a later copy discards the fold so far
894 for(int y = child_touched.y; y < child_touched.y + child_touched.height; y++)
895 {
896 float *const restrict dest_row = buffer + (size_t)y * width + child_touched.x;
897 const float *const restrict src_row = bufs + (size_t)y * width + child_touched.x;
898 for(int x = 0; x < child_touched.width; x++) dest_row[x] = op * src_row[x];
899 }
900 }
901 else
902 {
903 __OMP_PARALLEL_FOR_SIMD__(aligned(buffer, bufs : 64) if(npixels > 10000))
904 for(int index = 0; index < npixels; index++)
905 {
906 buffer[index] = op * (inverted ? (1.0f - bufs[index]) : bufs[index]);
907 }
908 }
909
910 if(box_only && (state & DT_MASKS_STATE_UNION))
911 dt_masks_touched_union(&group_touched, &child_touched);
912 else if(box_only)
913 group_touched = child_touched; // the copy discarded everything outside this box
914 else
915 dt_masks_touched_full(&group_touched, width, height);
916
918 dt_print(DT_DEBUG_MASKS, "[masks %d] combine took %0.04f sec\n", nb_ok, dt_get_wtime() - start);
919 start = dt_get_wtime();
920
921 nb_ok++;
922 }
923 }
924 i++;
925 }
926 // and we free the intermediate buffer
928
929
930 if(nb_ok == 0)
931 memset(buffer, 0, npixels * sizeof(float));
932 else if(!IS_NULL_PTR(touched))
933 *touched = group_touched;
934
935 /* Publish the fold WITHOUT the last shape, never the complete one.
936 *
937 * The last shape is the one being edited: while a stroke is dragged its content changes on every
938 * frame, so the complete fold's key changes on every frame too. Publishing that would leave one
939 * full-ROI cacheline per rendered frame behind, each dead the moment it is written -- memory
940 * pressure that evicts the pipeline's own outputs and costs more than the fold ever saved.
941 * Measured: publishing the complete fold flattened `render all masks' as intended (205-260 ms
942 * down to 32-100 ms) while the darkroom redraw grew from 5 ms to 416 ms over one session.
943 *
944 * The fold minus its last shape is stable across every frame of a stroke, so it is published
945 * once and hit on every later frame -- and when the next shape is appended the resume covers all
946 * but two. Nothing is published when the resume already came from that prefix: it is what we
947 * would be writing back.
948 *
949 * Only on a complete, error-free fold: a prefix that describes fewer shapes than its key claims
950 * is not a wrong-looking mask, it is a mask. */
951 if(err == DT_MASKS_RASTER_OK && shape_count > 1 && nb_ok == shape_count && resume_from < shape_count - 1
952 && !IS_NULL_PTR(publishable))
953 {
954 const uint64_t prefix = _group_prefix_hash(form, masks, piece, roi, shape_count - 1);
955 if(prefix != 0) _group_publish_prefix(prefix, publishable, npixels, pipe);
956 }
957
959
960 return err;
961}
962
965 const dt_iop_roi_t *roi, float *buffer)
966{
967 const double start = dt_get_wtime();
968 if(IS_NULL_PTR(form)) return DT_MASKS_RASTER_EMPTY;
969
970 const dt_masks_raster_result_t err = dt_masks_get_mask_roi(module, pipe, piece, form, roi, buffer, NULL);
971
973 dt_print(DT_DEBUG_MASKS, "[masks] render all masks took %0.04f sec\n", dt_get_wtime() - start);
974 return err;
975}
976
977static void _group_duplicate_points(dt_develop_t *const dev, dt_masks_form_t *const base,
978 dt_masks_form_t *const dest)
979{
980 for(GList *pts = base->points; pts; pts = g_list_next(pts))
981 {
984
985 npt->formid = dt_masks_form_duplicate(dev, pt->formid);
986 npt->parentid = dest->formid;
987 npt->state = pt->state;
988 npt->opacity = pt->opacity;
989 dest->points = g_list_append(dest->points, npt);
990 }
991}
992
993static gboolean _group_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *form, float center[2], float *area)
994{
995 if(IS_NULL_PTR(form) || IS_NULL_PTR(form->points) || IS_NULL_PTR(center) || IS_NULL_PTR(area)) return FALSE;
996
997 float sum_x = 0.0f;
998 float sum_y = 0.0f;
999 float sum_w = 0.0f;
1000 int count = 0;
1001
1002 for(const GList *l = form->points; l; l = g_list_next(l))
1003 {
1004 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)l->data;
1005 if(IS_NULL_PTR(pt)) continue;
1006 dt_masks_form_t *child = dt_masks_get_from_id(dev, pt->formid);
1007 if(IS_NULL_PTR(child)) continue;
1008
1009 float child_center[2] = { 0.0f, 0.0f };
1010 float child_area = 0.0f;
1011 if(!dt_masks_form_get_gravity_center(dev, child, child_center, &child_area)) continue;
1012
1013 const float w = (child_area > 0.0f) ? child_area : 1.0f;
1014 sum_x += child_center[0] * w;
1015 sum_y += child_center[1] * w;
1016 sum_w += w;
1017 count++;
1018 }
1019
1020 if(count == 0)
1021 {
1022 center[0] = 0.0f;
1023 center[1] = 0.0f;
1024 *area = 0.0f;
1025 return FALSE;
1026 }
1027
1028 if(sum_w <= 0.0f)
1029 {
1030 center[0] = sum_x / (float)count;
1031 center[1] = sum_y / (float)count;
1032 *area = 0.0f;
1033 }
1034 else
1035 {
1036 center[0] = sum_x / sum_w;
1037 center[1] = sum_y / sum_w;
1038 *area = sum_w;
1039 }
1040
1041 return TRUE;
1042}
1043
1044// The function table for groups. This must be public, i.e. no "static" keyword.
1046 .point_struct_size = sizeof(struct dt_masks_form_group_t),
1047 .sanitize_config = NULL,
1048 .set_form_name = NULL,
1049 .set_hint_message = NULL,
1050 .duplicate_points = _group_duplicate_points,
1051 .initial_source_pos = NULL,
1052 .get_distance = NULL,
1053 .get_points = NULL,
1054 .get_points_border = NULL,
1055 .get_mask = _group_get_mask,
1056 .get_mask_roi = _group_get_mask_roi,
1057 .get_area = NULL,
1058 .get_source_area = NULL,
1059 .get_gravity_center = _group_get_gravity_center,
1060 .get_interaction_value = NULL,
1061 .set_interaction_value = NULL,
1062 .update_hover = NULL,
1063 .mouse_moved = _group_events_mouse_moved,
1064 .mouse_scrolled = _group_events_mouse_scrolled,
1065 .button_pressed = _group_events_button_pressed,
1066 .button_released = _group_events_button_released,
1067 .key_pressed = _group_events_key_pressed,
1068//TODO: .post_expose = _group_events_post_expose
1069 .draw_shape = NULL
1070};
1071
1072
1073// clang-format off
1074// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
1075// vim: shiftwidth=2 expandtab tabstop=2 cindent
1076// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
1077// 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:511
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 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 dt_masks_raster_result_t _group_get_mask(const dt_iop_module_t *const module, const 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_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:977
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:1045
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
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:993
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 void _group_publish_prefix(const uint64_t prefix, const float *const fold, const size_t npixels, const dt_dev_pixelpipe_t *pipe)
Definition group.c:696
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_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
static dt_masks_raster_result_t _group_get_mask_roi(const dt_iop_module_t *const restrict module, const 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:722
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
dt_masks_raster_result_t dt_masks_group_render_roi(dt_iop_module_t *module, const 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:963
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
static const dt_dev_pixelpipe_t * pipe
Definition layers.h:49
static const dt_dev_pixelpipe_t const dt_dev_pixelpipe_iop_t * piece
Definition layers.h:50
hash
Definition layers.h:90
_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:2094
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, const 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:1661
dt_masks_raster_result_t dt_masks_get_mask(const dt_iop_module_t *const module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, dt_masks_area_t *area)
Definition masks.c:1647
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:1460
dt_masks_form_t * dt_masks_get_from_id_ext(GList *forms, int id)
Definition masks.c:915
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:925
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)
Take or release one reference on a cache line. A referenced line is never evicted,...
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.
size_t dt_pixel_cache_entry_get_size(const dt_pixel_cache_entry_t *entry)
void dt_dev_pixelpipe_cache_flag_auto_destroy(dt_pixel_cache_entry_t *cache_entry)
Flag a cache line as disposable (auto_destroy), for short-lived lines nothing will need again: the re...
gboolean dt_dev_pixelpipe_cache_ref_host_entry_by_hash(const uint64_t hash, void **data, dt_pixel_cache_entry_t **entry)
Resolve and retain an existing cacheline that already holds HOST pixels.
#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