Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
masks.c
Go to the documentation of this file.
1/*
2 This file is part of darktable,
3 Copyright (C) 2013-2014, 2016, 2019-2021 Aldric Renaudin.
4 Copyright (C) 2013, 2016-2021 Pascal Obry.
5 Copyright (C) 2013-2016 Roman Lebedev.
6 Copyright (C) 2013 Simon Spannagel.
7 Copyright (C) 2013-2014, 2016-2018 Tobias Ellinghaus.
8 Copyright (C) 2013-2016, 2019-2020 Ulrich Pegelow.
9 Copyright (C) 2016, 2018 Matthieu Moy.
10 Copyright (C) 2017-2019 Edgardo Hoszowski.
11 Copyright (C) 2017, 2019 luzpaz.
12 Copyright (C) 2017 Peter Budai.
13 Copyright (C) 2018 johannes hanika.
14 Copyright (C) 2019-2020 Diederik Ter Rahe.
15 Copyright (C) 2019 Jacopo Guderzo.
16 Copyright (C) 2020 Chris Elston.
17 Copyright (C) 2020 GrahamByrnes.
18 Copyright (C) 2020 Heiko Bauke.
19 Copyright (C) 2020-2021 Hubert Kowalski.
20 Copyright (C) 2020-2021 Ralf Brown.
21 Copyright (C) 2021 darkelectron.
22 Copyright (C) 2021 Hanno Schwalm.
23 Copyright (C) 2021 Philipp Lutz.
24 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
25 Copyright (C) 2022 Martin Bařinka.
26 Copyright (C) 2023 Alynx Zhou.
27 Copyright (C) 2023 Luca Zulberti.
28 Copyright (C) 2025-2026 Guillaume Stutin.
29
30 darktable is free software: you can redistribute it and/or modify
31 it under the terms of the GNU General Public License as published by
32 the Free Software Foundation, either version 3 of the License, or
33 (at your option) any later version.
34
35 darktable is distributed in the hope that it will be useful,
36 but WITHOUT ANY WARRANTY; without even the implied warranty of
37 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
38 GNU General Public License for more details.
39
40 You should have received a copy of the GNU General Public License
41 along with darktable. If not, see <http://www.gnu.org/licenses/>.
42*/
43#include "system/macros.h"
46#include "system/openmp.h"
47#include "system/mem_alloc.h"
48#include "common/hash.h"
49#include "common/logging.h"
50#include "common/times.h"
51#include "common/glib_utils.h"
52#include "system/dtpthread.h"
54#include "develop/masks.h"
55#include "history/notify.h"
56#include "develop/blend_gui.h" // dt_iop_gui_blend_masks_update()
57#include "develop/masks_group.h"
60#include "develop/develop.h"
61#include "develop/supervisor.h"
62#include "math/math.h"
63#include "common/conf.h"
64#include "develop/blend.h"
66#include "develop/imageop.h"
67#include <stdint.h>
68
69
78{
79 if (IS_NULL_PTR(mask_form)) return NULL;
80
81 dt_masks_form_t *duplicate_form = malloc(sizeof(struct dt_masks_form_t));
82 memcpy(duplicate_form, mask_form, sizeof(struct dt_masks_form_t));
83
84 // The memcpy above copied the source's live refcount: a fresh clone must
85 // start with exactly one owner (whoever holds the returned pointer).
86 dt_atomic_set_int(&duplicate_form->refcount, 1);
87
88 // Duplicate the GList *points payloads into a new list.
89 GList *duplicated_points = NULL;
90
91 if(mask_form->points)
92 {
93 const int point_struct_size = (mask_form->functions) ? mask_form->functions->point_struct_size : 0;
94
95 if(point_struct_size != 0)
96 {
97 for(GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
98 {
99 void *point_copy = malloc(point_struct_size);
100 memcpy(point_copy, point_node->data, point_struct_size);
101 duplicated_points = g_list_prepend(duplicated_points, point_copy);
102 }
103 }
104 }
105
106 // The list was built in reverse order, so un-reverse it.
107 duplicate_form->points = g_list_reverse(duplicated_points);
108
109 return duplicate_form;
110}
111
113{
114 if(IS_NULL_PTR(data)) return;
115
117
122 dt_free(gui_points);
123}
124
126{
127 int new_form_id = 100;
128 for(GList *form_node = dev->forms; form_node; )
129 {
130 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
131 if(existing_form->formid == mask_form->formid)
132 {
133 mask_form->formid = new_form_id++;
134 form_node = dev->forms; // jump back to start of list
135 }
136 else
137 {
138 form_node = g_list_next(form_node); // advance to next form
139 }
140 }
141}
142
144{
145 if(IS_NULL_PTR(module)) return NULL;
146
147 gchar *module_multi_name = dt_dev_get_multi_name(module);
148 const gboolean has_multi_name = g_strcmp0(module_multi_name, "") != 0;
149 // fallback to module name if no multi name
150 gchar *module_label = has_multi_name ? g_strdup(module_multi_name) : dt_history_item_get_name(module);
151 gchar *group_name = g_strdup_printf("Group %s", module_label);
152 dt_free(module_label);
153 dt_free(module_multi_name);
154
155 return group_name;
156}
157
158/* The module-internal spelling, for the creation paths that hold the form before it is reachable
159 * by id. Callers OUTSIDE the module go through dt_masks_group_set_name_from_module() instead,
160 * which owns the copy-on-write. */
162{
163 gchar *group_name = dt_masks_group_name_for_module(module);
164 if(IS_NULL_PTR(group_name)) return;
165
166 g_strlcpy(group_form->name, group_name, sizeof(group_form->name));
167 dt_free(group_name);
168}
169
171{
172 dt_masks_form_t *group_form = dt_masks_create(group_type);
173 _set_group_name_from_module(module, group_form);
174 _check_id(develop, group_form);
175 dt_masks_append_form(develop, group_form);
176 module->blend_params->mask_id = group_form->formid;
177 return group_form;
178}
179
180// get the group form associated to the module, if any
185
186int dt_masks_form_duplicate(dt_develop_t *develop, int form_id)
187{
188 // we create a new empty form
189 dt_masks_form_t *base_form = dt_masks_get_from_id(develop, form_id);
190 if(IS_NULL_PTR(base_form)) return -1;
191 dt_masks_form_t *dest_form = dt_masks_create(base_form->type);
192 _check_id(develop, dest_form);
193
194 // we copy the base values
195 dest_form->source[0] = base_form->source[0];
196 dest_form->source[1] = base_form->source[1];
197 dest_form->version = base_form->version;
198 snprintf(dest_form->name, sizeof(dest_form->name), _("copy of %s"), base_form->name);
199
200 dt_masks_append_form(develop, dest_form);
201
202 // we copy all the points
203 if(base_form->functions)
204 base_form->functions->duplicate_points(develop, base_form, dest_form);
205
206 // and we return its id
207 return dest_form->formid;
208}
209
210int dt_masks_form_duplicate_in_group(dt_develop_t *develop, int group_id, int form_id)
211{
212 const int nid = dt_masks_form_duplicate(develop, form_id);
213 if(nid <= 0) return nid;
214
215 dt_masks_form_t *grp = (group_id > 0) ? dt_masks_get_from_id(develop, group_id) : NULL;
216 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return nid;
217
218 grp = dt_masks_cow_touch(develop, grp);
219
220 dt_masks_form_group_t *orig_entry = NULL;
221 for(GList *pts = grp->points; pts; pts = g_list_next(pts))
222 {
224 if(pt->formid == form_id) { orig_entry = pt; break; }
225 }
226
227 dt_masks_form_t *dup_form = dt_masks_get_from_id(develop, nid);
228 dt_masks_form_group_t *new_entry = dup_form ? dt_masks_group_add_form(develop, grp, dup_form) : NULL;
229 if(new_entry && orig_entry)
230 {
231 new_entry->state = orig_entry->state;
232 new_entry->opacity = orig_entry->opacity;
233 }
234
235 return nid;
236}
237
239 float **point_buffer, int *point_count,
240 float **border_buffer, int *border_count,
241 dt_masks_skip_range_t **border_skips, int *border_skip_count,
242 int source, dt_iop_module_t *module)
243{
244 if(!IS_NULL_PTR(border_skips)) *border_skips = NULL;
245 if(!IS_NULL_PTR(border_skip_count)) *border_skip_count = 0;
246
247 /* A shape type with no outline builder is a programming error, not an empty outline. */
248 if(IS_NULL_PTR(mask_form->functions) || IS_NULL_PTR(mask_form->functions->get_points_border))
250
252 = mask_form->functions->get_points_border(develop, mask_form, point_buffer, point_count,
253 border_buffer, border_count,
254 border_skips, border_skip_count, source, module);
255
256 /* THE OUTLINE BUFFERS HOLD FINITE GEOMETRY AND NOTHING ELSE.
257 *
258 * Everything a consumer must not draw travels beside the buffer, in border_skips. Nothing is
259 * encoded into the coordinates -- no NaN marking an excluded sample, no NaN,NaN ending a
260 * contour, and above all no index smuggled through a float y with a NaN x, which is what issue
261 * #1313 shipped and what the out-of-band ranges replaced. A dozen walkers downstream dropped
262 * their own NaN tests on the strength of this.
263 *
264 * It is not checked here, and that is deliberate. The invariant is STRUCTURAL: neither
265 * _brush_border_get_XY() nor _polygon_border_get_XY() can write a sentinel -- they return
266 * whether they produced a border point, and the recursions carry explicit have_* flags -- so
267 * there is no longer a code path that can violate it. A scan for something structurally
268 * impossible is not defence, it is a tax: measured at 0.24 ms per outline rebuild on the
269 * reported brush's two 52492-sample buffers, which is a third of what stroking the whole
270 * overlay costs, on the same per-frame path during a mask drag.
271 *
272 * The one place a violation would still be caught is free: dt_masks_point_in_form_exact()
273 * already visits every sample, and reports a non-finite one rather than decoding it. If a
274 * future producer regresses, that is where it will say so. */
275
276 return status;
277}
278
279/* ------------------------------------------------------------------------------------- */
280/* Where does a shape's border cross itself?
281 *
282 * polygon.c has answered this since forever with a pixel grid: walk the contour, stamp each cell
283 * with the index that passed through it, and call a revisited cell a crossing. That works well
284 * enough there and is left alone -- but it is an approximation with shape-specific guards (it
285 * refuses any span containing a shape extremum, and merges a new crossing into the previous one
286 * on an index-ordering test), and measured against ground truth on issue #1313's brush it missed
287 * four of the eight real crossings while finding one the local search could not.
288 *
289 * A brush needs all of them, so this answers the question exactly: bucket the segments into a
290 * coarse spatial hash, and run a real segment-segment intersection on the pairs that share a
291 * bucket. No extrema guard, no merge heuristic, and the intersection POINT falls out of the
292 * test, which is what lets the caller land its cut on the samples that sit at the crossing --
293 * cutting anywhere else leaves the two ends apart and the drawer spans the gap with a chord.
294 *
295 * Complexity is the hash's, not O(n^2): the contour is decimated to about one probe per pixel
296 * first, and only same-bucket pairs are tested. Polygon should migrate here once someone is
297 * willing to re-verify its output against the grid version; until then the two coexist
298 * deliberately, and this comment is the record of why.
299 */
300
301#define MASKS_XSECT_BUCKET 4 /* px */
302
306gboolean dt_masks_skip_contains(const dt_masks_skip_range_t *skips, const int skip_count, const int index)
307{
308 if(IS_NULL_PTR(skips) || skip_count <= 0) return FALSE;
309
310 int lo = 0, hi = skip_count - 1;
311 while(lo <= hi)
312 {
313 const int mid = (lo + hi) / 2;
314 if(index < skips[mid].jump_from) hi = mid - 1;
315 else if(index >= skips[mid].resume_at) lo = mid + 1;
316 else return TRUE;
317 }
318 return FALSE;
319}
320
321
322void dt_masks_points_bounding_box(const float *const points, const int num_points,
323 int *width, int *height, int *posx, int *posy)
324{
325 // NOTE the seeds: -FLT_MAX, not FLT_MIN. FLT_MIN is the smallest POSITIVE normal float, so
326 // seeding a running maximum with it silently clamps the box at 0 for a shape that lies
327 // entirely off the left or top edge -- an over-large box rather than a wrong one, which is
328 // why it went unnoticed, but wrong all the same.
329 float xmin = FLT_MAX, xmax = -FLT_MAX, ymin = FLT_MAX, ymax = -FLT_MAX;
330
331 for(int i = 1; i < num_points; i++) // point 0 is the centre, not part of the outline
332 {
333 xmin = fminf(points[i * 2], xmin);
334 xmax = fmaxf(points[i * 2], xmax);
335 ymin = fminf(points[i * 2 + 1], ymin);
336 ymax = fmaxf(points[i * 2 + 1], ymax);
337 }
338
339 *posx = xmin;
340 *posy = ymin;
341 *width = (xmax - xmin);
342 *height = (ymax - ymin);
343}
344
345float *dt_masks_sample_grid_backtransform(const struct dt_dev_pixelpipe_t *pipe, const double iop_order,
346 const dt_masks_sample_grid_t *const grid,
347 const char *const shape, const char *const form_name)
348{
349 const int gw = grid->width;
350 const int gh = grid->height;
351 const int step = grid->step;
352 const int px = grid->px;
353 const int py = grid->py;
354 const float iscale = grid->iscale;
355 const size_t count = (size_t)gw * gh;
356
357 double start = dt_get_wtime();
358
359 float *const restrict points = dt_pixelpipe_cache_alloc_align_float_cache(2 * count, 0);
360 if(IS_NULL_PTR(points)) return NULL;
361
362 // the grid points, in module coordinates
363 __OMP_PARALLEL_FOR__(collapse(2) if(count > 50000))
364 for(int j = 0; j < gh; j++)
365 for(int i = 0; i < gw; i++)
366 {
367 const size_t index = (size_t)j * gw + i;
368 points[index * 2] = (step * (i + grid->x0) + px) * iscale;
369 points[index * 2 + 1] = (step * (j + grid->y0) + py) * iscale;
370 }
371
373 {
374 dt_print(DT_DEBUG_MASKS, "[masks %s] %s grid took %0.04f sec\n", form_name, shape,
375 dt_get_wtime() - start);
376 start = dt_get_wtime();
377 }
378
379 // and back to input image coordinates
381 {
383 return NULL;
384 }
385
387 dt_print(DT_DEBUG_MASKS, "[masks %s] %s transform took %0.04f sec\n", form_name, shape,
388 dt_get_wtime() - start);
389
390 return points;
391}
392
393void dt_masks_sample_grid_interpolate(const float *const points, const dt_masks_sample_grid_t *const grid,
394 float *const buffer, const int buf_width, const int buf_height,
395 int *const endx, int *const endy)
396{
397 const int step = grid->step;
398 const int gw = grid->width;
399 const int startx = grid->x0 * step;
400 const int starty = grid->y0 * step;
401
402 // the last cell contributes its far corner, so the covered rectangle ends one whole cell short
403 // of the lattice -- and is clipped to the buffer, since a bounding box may overhang the ROI
404 const int ex = MIN(buf_width, (grid->x0 + gw - 1) * step);
405 const int ey = MIN(buf_height, (grid->y0 + grid->height - 1) * step);
406
407 // the two bilinear weight ramps, one entry per position within a cell
409 for(int i = 0; i < step; i++)
410 {
411 w0[i] = (float)(step - i);
412 w1[i] = (float)i;
413 }
414 const float inv_step2 = 1.0f / (step * step);
415
416 __OMP_PARALLEL_FOR__(if((size_t)(ey - starty) * (size_t)(ex - startx) > 50000))
417 for(int j = starty; j < ey; j++)
418 {
419 const int jj = j % step;
420 const int mj = j / step - grid->y0;
421 const float wj0 = w0[jj];
422 const float wj1 = w1[jj];
423 const size_t row_base = (size_t)mj * gw;
424 float *const row = buffer + (size_t)j * buf_width;
425 int ii = 0;
426 int mi = 0;
427
428 for(int i = startx; i < ex; i++)
429 {
430 const size_t mindex = row_base + mi;
431 const float wii0 = w0[ii];
432 const float wii1 = w1[ii];
433 row[i] = (points[mindex * 2] * wii0 * wj0
434 + points[(mindex + 1) * 2] * wii1 * wj0
435 + points[(mindex + gw) * 2] * wii0 * wj1
436 + points[(mindex + gw + 1) * 2] * wii1 * wj1) * inv_step2;
437 ii++;
438 if(ii == step)
439 {
440 ii = 0;
441 mi++;
442 }
443 }
444 }
445
446 if(endx) *endx = ex;
447 if(endy) *endy = ey;
448}
449
450void dt_masks_area_scale(dt_masks_area_t *area, const double scale)
451{
452 area->width *= scale;
453 area->height *= scale;
454 area->x *= scale;
455 area->y *= scale;
456}
457
459{
460 return !(area->y >= roi->y + roi->height || area->y + area->height <= roi->y
461 || area->x >= roi->x + roi->width || area->x + area->width <= roi->x);
462}
463
466 dt_masks_area_t *area)
467{
468 *area = (dt_masks_area_t){ 0 };
469 if(mask_form->functions && mask_form->functions->get_area)
470 return mask_form->functions->get_area(iop_module, pipe, piece, mask_form, &area->width, &area->height,
471 &area->x, &area->y);
473}
474
477 const dt_iop_roi_t *roi_in, const dt_iop_roi_t *roi_out)
478{
479 dt_masks_area_t area;
480 if(dt_masks_get_area(iop_module, pipe, piece, form, &area) != DT_MASKS_RASTER_OK) return FALSE;
481
482 dt_masks_area_scale(&area, roi_in->scale);
483 return dt_masks_area_intersects(&area, roi_out);
484}
485
489 dt_masks_area_t *area)
490{
491 *area = (dt_masks_area_t){ 0 };
492
493 // must be a clone form
494 if((mask_form->type & DT_MASKS_CLONE) && mask_form->functions && mask_form->functions->get_source_area)
495 return mask_form->functions->get_source_area(iop_module, pipe, piece, mask_form, &area->width,
496 &area->height, &area->x, &area->y);
497
498 /* A form that is not a clone has no source area. That is an absence, not a failure. */
500}
501
503{
505}
506
508{
509 /*
510 * difference: before v2 images were originally rotated on load, and then
511 * maybe in flip iop
512 * after v2: images are only rotated in flip iop.
513 */
514
515 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
516
517 const dt_image_orientation_t orientation = dt_image_orientation(&develop->image_storage);
518
519 if(orientation == ORIENTATION_NONE)
520 {
521 // image is not rotated, we're fine!
522 mask_form->version = 2;
523 return 0;
524 }
525 else
526 {
527 if(IS_NULL_PTR(develop->iop)) return 1;
528
529 const char *opname = "flip";
530 dt_iop_module_t *module = NULL;
531
532 for(GList *module_node = develop->iop; module_node; module_node = g_list_next(module_node))
533 {
534 dt_iop_module_t *iop_module = (dt_iop_module_t *)module_node->data;
535 if(!strcmp(iop_module->op, opname))
536 {
537 module = iop_module;
538 break;
539 }
540 }
541
542 if(IS_NULL_PTR(module)) return 1;
543
545
546 module->init_pipe(module, NULL, &piece);
547 module->commit_params(module, module->default_params, NULL, &piece);
548
549 piece.buf_in.width = 1;
550 piece.buf_in.height = 1;
551
552 GList *point_node = mask_form->points;
553
554 if(IS_NULL_PTR(point_node)) return 1;
555
556 if(mask_form->type & DT_MASKS_CIRCLE)
557 {
558 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
559 if(IS_NULL_PTR(circle)) return 1;
560 module->distort_backtransform(module, NULL, &piece, circle->center, 1);
561 }
562 else if(mask_form->type & DT_MASKS_POLYGON)
563 {
564 for(; point_node; point_node = g_list_next(point_node))
565 {
566 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
567 if(IS_NULL_PTR(polygon_node)) return 1;
568 module->distort_backtransform(module, NULL, &piece, polygon_node->node, 1);
569 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl1, 1);
570 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl2, 1);
571 }
572 }
573 else if(mask_form->type & DT_MASKS_GRADIENT)
574 { // TODO: new ones have wrong rotation.
575 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
576 if(IS_NULL_PTR(gradient)) return 1;
577 module->distort_backtransform(module, NULL, &piece, gradient->center, 1);
578
579 if(orientation == ORIENTATION_ROTATE_180_DEG)
580 gradient->rotation -= 180.0f;
581 else if(orientation == ORIENTATION_ROTATE_CCW_90_DEG)
582 gradient->rotation -= 90.0f;
583 else if(orientation == ORIENTATION_ROTATE_CW_90_DEG)
584 gradient->rotation -= -90.0f;
585 }
586 else if(mask_form->type & DT_MASKS_ELLIPSE)
587 {
588 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
589 module->distort_backtransform(module, NULL, &piece, ellipse->center, 1);
590
591 if(orientation & ORIENTATION_SWAP_XY)
592 {
593 const float y = ellipse->radius[0];
594 ellipse->radius[0] = ellipse->radius[1];
595 ellipse->radius[1] = y;
596 }
597 }
598 else if(mask_form->type & DT_MASKS_BRUSH)
599 {
600 for(; point_node; point_node = g_list_next(point_node))
601 {
602 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
603 if(IS_NULL_PTR(brush_node)) return 1;
604 module->distort_backtransform(module, NULL, &piece, brush_node->node, 1);
605 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl1, 1);
606 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl2, 1);
607 }
608 }
609
610 if(mask_form->type & DT_MASKS_CLONE)
611 {
612 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
613 module->distort_backtransform(module, NULL, &piece, mask_form->source, 1);
614 }
615
616 mask_form->version = 2;
617
618 return 0;
619 }
620}
621
622static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
623{
624 const float image_width = (float)image->width;
625 const float image_height = (float)image->height;
626
627 const float crop_x = (float)image->crop_x;
628 const float crop_y = (float)image->crop_y;
629
630 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
631 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
632
633 /*
634 * masks coordinates are normalized, so we need to:
635 * 1. de-normalize them by image original cropped dimensions
636 * 2. un-crop them by adding top-left crop coordinates
637 * 3. normalize them by the image fully uncropped dimensions
638 */
639 coords[0] = ((coords[0] * crop_width) + crop_x) / image_width;
640 coords[1] = ((coords[1] * crop_height) + crop_y) / image_height;
641}
642
644 size_t coords_count)
645{
646 const float image_width = (float)image->width;
647 const float image_height = (float)image->height;
648
649 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
650 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
651
652 /*
653 * masks coordinates are normalized, so we need to:
654 * 1. de-normalize them by minimal of image original cropped dimensions
655 * 2. normalize them by the minimal of image fully uncropped dimensions
656 */
657 const float crop_min = MIN(crop_width, crop_height);
658 const float image_min = MIN(image_width, image_height);
659 for(size_t coord_index = 0; coord_index < coords_count; coord_index++)
660 coords[coord_index] = ((coords[coord_index] * crop_min)) / image_min;
661}
662
664{
665 /*
666 * difference: before v3 images were originally cropped on load
667 * after v3: images are cropped in rawprepare iop.
668 */
669
670 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
671
672 const dt_image_t *image = &(develop->image_storage);
673
674 if(image->crop_x == 0 && image->crop_y == 0 && image->crop_width == 0 && image->crop_height == 0)
675 {
676 // image has no "raw cropping", we're fine!
677 mask_form->version = 3;
678 return 0;
679 }
680 else
681 {
682 GList *point_node = mask_form->points;
683
684 if(IS_NULL_PTR(point_node)) return 1;
685
686 if(mask_form->type & DT_MASKS_CIRCLE)
687 {
688 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
689 if(IS_NULL_PTR(circle)) return 1;
693 }
694 else if(mask_form->type & DT_MASKS_POLYGON)
695 {
696 for(; point_node; point_node = g_list_next(point_node))
697 {
698 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
699 if(IS_NULL_PTR(polygon_node)) return 1;
704 }
705 }
706 else if(mask_form->type & DT_MASKS_GRADIENT)
707 {
708 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
710 }
711 else if(mask_form->type & DT_MASKS_ELLIPSE)
712 {
713 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
717 }
718 else if(mask_form->type & DT_MASKS_BRUSH)
719 {
720 for(; point_node; point_node = g_list_next(point_node))
721 {
722 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
723 if(IS_NULL_PTR(brush_node)) return 1;
728 }
729 }
730
731 if(mask_form->type & DT_MASKS_CLONE)
732 {
733 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
735 }
736
737 mask_form->version = 3;
738
739 return 0;
740 }
741}
742
744{
745 /*
746 * difference affecting ellipse
747 * up to v3: only equidistant feathering
748 * after v4: choice between equidistant and proportional feathering
749 * type of feathering is defined in new flags parameter
750 */
751
752 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
753
754 GList *point_node = mask_form->points;
755
756 if(IS_NULL_PTR(point_node)) return 1;
757
758 if(mask_form->type & DT_MASKS_ELLIPSE)
759 {
760 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
762 }
763
764 mask_form->version = 4;
765
766 return 0;
767}
768
769
771{
772 /*
773 * difference affecting gradient
774 * up to v4: only linear gradient (relative to input image)
775 * after v5: curved gradients
776 */
777
778 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
779
780 GList *point_node = mask_form->points;
781
782 if(IS_NULL_PTR(point_node)) return 1;
783
784 if(mask_form->type & DT_MASKS_GRADIENT)
785 {
786 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
787 gradient->curvature = 0.0f;
788 }
789
790 mask_form->version = 5;
791
792 return 0;
793}
794
796{
797 /*
798 * difference affecting gradient
799 * up to v5: linear transition
800 * after v5: linear or sigmoidal transition
801 */
802
803 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
804
805 GList *point_node = mask_form->points;
806
807 if(IS_NULL_PTR(point_node)) return 1;
808
809 if(mask_form->type & DT_MASKS_GRADIENT)
810 {
811 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
813 }
814
815 mask_form->version = 6;
816
817 return 0;
818}
819
820
821int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
822{
823 int result = 1;
824#if 0 // we should not need this any longer
825 if(old_version == 1 && new_version == 2)
826 {
827 result = dt_masks_legacy_params_v1_to_v2(develop, params);
828 }
829#endif
830
831 if(old_version == 1 && new_version == 6)
832 {
833 result = dt_masks_legacy_params_v1_to_v2(develop, params);
834 if(!result) result = dt_masks_legacy_params_v2_to_v3(develop, params);
835 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
836 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
837 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
838 }
839 else if(old_version == 2 && new_version == 6)
840 {
841 result = dt_masks_legacy_params_v2_to_v3(develop, params);
842 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
843 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
844 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
845 }
846 else if(old_version == 3 && new_version == 6)
847 {
848 result = dt_masks_legacy_params_v3_to_v4(develop, params);
849 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
850 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
851 }
852 else if(old_version == 4 && new_version == 6)
853 {
854 result = dt_masks_legacy_params_v4_to_v5(develop, params);
855 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
856 }
857 else if(old_version == 5 && new_version == 6)
858 {
859 result = dt_masks_legacy_params_v5_to_v6(develop, params);
860 }
861
862 return result;
863}
864
865static int form_id_seed = 0;
866
868{
869 dt_masks_form_t *mask_form = (dt_masks_form_t *)calloc(1, sizeof(dt_masks_form_t));
870 if(IS_NULL_PTR(mask_form)) return NULL;
871
872 // Freshly created: exactly one owner (whoever holds the returned pointer).
873 dt_atomic_set_int(&mask_form->refcount, 1);
874
875 mask_form->type = type;
876 mask_form->version = dt_masks_version();
877 mask_form->formid = time(NULL) + form_id_seed++;
879
880 if (type & DT_MASKS_CIRCLE)
882 else if (type & DT_MASKS_ELLIPSE)
884 else if (type & DT_MASKS_BRUSH)
886 else if (type & DT_MASKS_POLYGON)
888 else if (type & DT_MASKS_GRADIENT)
890 else if (type & DT_MASKS_GROUP)
892
893 if (mask_form->functions && mask_form->functions->sanitize_config)
894 mask_form->functions->sanitize_config(type);
895
897
898 return mask_form;
899}
900
902{
905
906 // all forms created here are registered in dev->allforms for later cleanup
907 if(mask_form)
908 dev->allforms = g_list_append(dev->allforms, mask_form);
909
911
912 return mask_form;
913}
914
915dt_masks_form_t *dt_masks_get_from_id_ext(GList *form_list, int form_id)
916{
917 for(; form_list; form_list = g_list_next(form_list))
918 {
919 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_list->data;
920 if(mask_form->formid == form_id) return mask_form;
921 }
922 return NULL;
923}
924
926{
928 dt_masks_form_t *result = dt_masks_get_from_id_ext(develop->forms, form_id);
930 return result;
931}
932
937
939{
940 for(GList *module_node = g_list_first(develop->iop); module_node; module_node = g_list_next(module_node))
941 {
942 dt_iop_module_t *module = (dt_iop_module_t *)(module_node->data);
943 if(strcmp(module->op, "mask_manager") == 0)
944 return module;
945 }
946 return NULL;
947}
948
949static void _masks_fill_used_forms(GList *forms_list, const int form_id, int *used_form_ids,
950 const int used_count)
951{
952 for(int used_index = 0; used_index < used_count; used_index++)
953 {
954 if(used_form_ids[used_index] == 0)
955 {
956 used_form_ids[used_index] = form_id;
957 break;
958 }
959 if(used_form_ids[used_index] == form_id) break;
960 }
961
962 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(forms_list, form_id);
963 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
964 {
965 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
966 {
967 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
968 _masks_fill_used_forms(forms_list, group_entry->formid, used_form_ids, used_count);
969 }
970 }
971}
972
974 const dt_iop_module_t *source_module)
975{
976 if(!(source_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)) return 0;
977 if(source_module->blend_params->mask_id <= 0) return 0;
978
979 const guint form_count = g_list_length(develop_src->forms);
980 if(form_count == 0) return 0;
981
982 int *used_form_ids = dt_calloc_align(form_count * sizeof(int));
983 if(IS_NULL_PTR(used_form_ids)) return 1;
984
985 _masks_fill_used_forms(develop_src->forms, source_module->blend_params->mask_id,
986 used_form_ids, form_count);
987
988 for(int form_index = 0; form_index < (int)form_count && used_form_ids[form_index] > 0; form_index++)
989 {
990 dt_masks_form_t *mask_form = dt_masks_get_from_id(develop_src, used_form_ids[form_index]);
991 if(!IS_NULL_PTR(mask_form))
992 {
993 dt_masks_form_t *existing_form = dt_masks_get_from_id_ext(develop_dest->forms,
994 used_form_ids[form_index]);
995 if(existing_form)
996 {
997 develop_dest->forms = g_list_remove(develop_dest->forms, existing_form);
998 develop_dest->allforms = g_list_append(develop_dest->allforms, existing_form);
999 }
1000
1001 dt_masks_form_t *new_form = dt_masks_dup_masks_form(mask_form);
1002 if(IS_NULL_PTR(new_form))
1003 {
1004 dt_free_align(used_form_ids);
1005 return 1;
1006 }
1007 develop_dest->forms = g_list_append(develop_dest->forms, new_form);
1008 }
1009 else
1010 {
1011 fprintf(stderr, "[dt_masks_copy_used_forms_for_module] form %i not found in source image\n",
1012 used_form_ids[form_index]);
1013 }
1014 }
1015
1016 dt_free_align(used_form_ids);
1017 return 0;
1018}
1019
1028
1029static void _read_mask_row(void *user_data, const int history_num, const int form_id,
1030 const int form, const char *name, const int version,
1031 const void *points, const int points_len, const int point_count,
1032 const void *source, const int source_len)
1033{
1034 _masks_read_ctx_t *ctx = (_masks_read_ctx_t *)user_data;
1035 dt_develop_t *develop = ctx->develop;
1036
1037 const dt_masks_type_t mask_type = form;
1038 dt_masks_form_t *mask_form = dt_masks_create(mask_type);
1039 mask_form->formid = form_id;
1040 g_strlcpy(mask_form->name, name, sizeof(mask_form->name));
1041 mask_form->version = version;
1042 mask_form->points = NULL;
1043 memcpy(mask_form->source, source, sizeof(float) * 2);
1044
1045 // and now we "read" the blob
1046 if(mask_form->functions)
1047 {
1048 const char *const point_buffer = (const char *)points;
1049 const size_t point_struct_size = mask_form->functions->point_struct_size;
1050 for(int point_index = 0; point_index < point_count; point_index++)
1051 {
1052 char *point_data = (char *)malloc(point_struct_size);
1053 memcpy(point_data, point_buffer + point_index * point_struct_size, point_struct_size);
1054 mask_form->points = g_list_append(mask_form->points, point_data);
1055 }
1056 }
1057
1058 if(mask_form->version != dt_masks_version())
1059 {
1060 if(dt_masks_legacy_params(develop, mask_form, mask_form->version, dt_masks_version()))
1061 {
1062 const char *fname = develop->image_storage.filename + strlen(develop->image_storage.filename);
1063 while(fname > develop->image_storage.filename && *fname != '/') fname--;
1064 if(fname > develop->image_storage.filename) fname++;
1065
1066 fprintf(stderr,
1067 "[_dev_read_masks_history] %s (imgid `%i\'): mask version mismatch: history is %d, dt %d.\n",
1068 fname, ctx->image_id, mask_form->version, dt_masks_version());
1069 dt_history_message(_("%s: mask version mismatch: %d != %d"),
1070 fname, dt_masks_version(), mask_form->version);
1071
1072 // was a `continue` over the rest of the loop body
1073 return;
1074 }
1075 }
1076
1077 // Not computed here: dt_masks_replace_current_forms() below (via
1078 // dt_masks_form_update_gravity_center() in masks_history.c) already computes it for
1079 // every form that actually ends up live in dev->forms. Computing it here too would
1080 // redundantly do it for every history step's row read from masks_history -- including
1081 // every duplicate of a form shared unchanged across many steps (see the un-deduped
1082 // masks_history table, doc/masks_history_dedup.md) -- for forms that are either
1083 // superseded (never read again) or about to be recomputed anyway.
1084
1085 // if this is a new history entry let's find it
1086 if(ctx->previous_num != history_num)
1087 {
1088 ctx->history_item = NULL;
1089 for(GList *history_node = g_list_first(develop->history); history_node; history_node = g_list_next(history_node))
1090 {
1091 dt_dev_history_item_t *history_entry = (dt_dev_history_item_t *)(history_node->data);
1092 if(history_entry->num == history_num)
1093 {
1094 ctx->history_item = history_entry;
1095 break;
1096 }
1097 }
1098 ctx->previous_num = history_num;
1099 }
1100 // add the form to the history entry
1101 // FIXME: there is no reason to hack history_item to add a forms snapshot that doesn't
1102 // belong to it because dt_dev_write_history_item() doesn't save history_item->forms to the DB.
1103 // So this forms snapshot should be attached to its own object, and that object should be
1104 // linked by ID to the history_item object. That would allow to share one forms snapshot
1105 // between several history items without duplication.
1106 if(ctx->history_item)
1107 {
1108 ctx->history_item->forms = g_list_append(ctx->history_item->forms, mask_form);
1109 }
1110 else
1111 fprintf(stderr,
1112 "[_dev_read_masks_history] can't find history entry %i while adding mask %s(%i)\n",
1113 history_num, mask_form->name, form_id);
1114
1115 if(history_num < dt_dev_get_history_end_ext(develop)) ctx->last_history_item = ctx->history_item;
1116}
1117
1118void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
1119{
1120 // The per-row state the loop used to keep in locals lives in the context: which history entry
1121 // the previous row belonged to, and the last one at or below history_end.
1122 _masks_read_ctx_t ctx = { .develop = develop, .image_id = image_id, .history_item = NULL,
1123 .last_history_item = NULL, .previous_num = -1 };
1124
1126
1127 // and we update the current forms snapshot
1129}
1130
1131void dt_masks_write_masks_history_item(const int32_t image_id, const int history_num,
1132 dt_masks_form_t *mask_form)
1133{
1134 dt_print(DT_DEBUG_HISTORY, "[dt_masks_write_masks_history_item] writing mask %s of type %i for image %i\n",
1135 mask_form->name, mask_form->type, image_id);
1136
1137 // The points are a flat array of the shape's own point struct; only this layer knows its size.
1138 const size_t point_struct_size = mask_form->functions ? mask_form->functions->point_struct_size : 0;
1139 const guint point_count = mask_form->functions ? g_list_length(mask_form->points) : 0;
1140
1141 // Preserved: with no functions there is no point struct to serialise, and the original wrote
1142 // nothing at all in that case -- the whole INSERT sat inside this test.
1143 if(!mask_form->functions) return;
1144
1145 char *const restrict point_buffer = (char *)dt_alloc_align(point_count * point_struct_size);
1146 int buffer_offset = 0;
1147 for(GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1148 {
1149 memcpy(point_buffer + buffer_offset, point_node->data, point_struct_size);
1150 buffer_offset += point_struct_size;
1151 }
1152
1153 dt_history_repository_write_mask_item(image_id, history_num, mask_form->formid, mask_form->type,
1154 mask_form->name, mask_form->version, point_buffer,
1155 point_count * point_struct_size, point_count,
1156 mask_form->source, 2 * sizeof(float));
1157
1158 dt_free_align(point_buffer);
1159}
1160
1162{
1163 if(IS_NULL_PTR(mask_form)) return;
1164 g_list_free_full(mask_form->points, dt_free_gpointer);
1165 mask_form->points = NULL;
1166 dt_free(mask_form);
1167}
1168
1170{
1171 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->dev)) return DT_MASKS_EDIT_OFF;
1172 return module->dev->form_gui
1173 ? module->dev->form_gui->edit_mode
1174 : DT_MASKS_EDIT_OFF;
1175}
1176
1177
1179{
1180 if(IS_NULL_PTR(module) || IS_NULL_PTR(source_module)) return;
1181
1182 // we get the source group
1183 int source_id = source_module->blend_params->mask_id;
1184 dt_masks_form_t *source_group = dt_masks_get_from_id(module->dev, source_id);
1185 if(IS_NULL_PTR(source_group) || source_group->type != DT_MASKS_GROUP) return;
1186
1187 // is there already a masks group for this module ?
1188 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
1189 if(IS_NULL_PTR(group_form))
1190 {
1191 group_form = _group_create(module->dev, module, DT_MASKS_GROUP);
1192 }
1193 // Touch once, before the loop: dt_masks_group_add_form mutates group_form->points on every
1194 // iteration, so it must already be private by the first call, or later iterations would
1195 // append to an orphaned clone instead of the object actually in dev->forms.
1196 group_form = dt_masks_cow_touch(module->dev, group_form);
1197 // we copy the src group in this group
1198 for(GList *group_node = source_group->points; group_node; group_node = g_list_next(group_node))
1199 {
1200 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1201 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, group_entry->formid);
1202 if(mask_form)
1203 {
1204 dt_masks_form_group_t *new_entry = dt_masks_group_add_form(module->dev, group_form, mask_form);
1205 if(new_entry)
1206 {
1207 new_entry->state = group_entry->state;
1208 new_entry->opacity = group_entry->opacity;
1209 }
1210 }
1211 }
1212
1213 // we save the group
1214
1215}
1216
1218 dt_masks_form_t *mask_form)
1219{
1220 if(IS_NULL_PTR(mask_form)) return;
1221 int form_id = mask_form->formid;
1222 if(!IS_NULL_PTR(group_form) && !(group_form->type & DT_MASKS_GROUP)) return;
1223
1224 if(!(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) && !IS_NULL_PTR(group_form))
1225 {
1226 group_form = dt_masks_cow_touch(dev, group_form);
1227 // we try to remove the form from the masks group
1228 int removed = 0;
1229 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1230 {
1231 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1232 if(group_entry->formid == form_id)
1233 {
1234 removed = 1;
1235 group_form->points = g_list_remove(group_form->points, group_entry);
1236 dt_free(group_entry);
1237 break;
1238 }
1239 }
1240 if(removed)
1241 if(removed && !IS_NULL_PTR(module))
1242 {
1244
1245 }
1246 if(removed) dt_masks_form_update_gravity_center(dev, group_form);
1247 if(removed && IS_NULL_PTR(group_form->points)) dt_masks_form_delete(dev, module, NULL, group_form);
1248 return;
1249 }
1250
1251 if(mask_form->type & DT_MASKS_GROUP && mask_form->type & DT_MASKS_CLONE)
1252 {
1253 // when removing a cloning group the children have to be removed, too, as they won't be shown in the shape manager
1254 // and are thus not accessible afterwards.
1255 while(mask_form->points)
1256 {
1257 dt_masks_form_group_t *group_child = (dt_masks_form_group_t *)mask_form->points->data;
1258 dt_masks_form_t *child = dt_masks_get_from_id(dev, group_child->formid);
1259 dt_masks_form_delete(dev, module, mask_form, child);
1260 // The recursive call passes mask_form as its own group_form parameter and may have
1261 // COW-cloned it (see the touch above): re-fetch the live object so this loop keeps
1262 // observing the mutation instead of spinning on a stale, now-orphaned copy.
1263 mask_form = dt_masks_get_from_id(dev, form_id);
1264 if(IS_NULL_PTR(mask_form)) break;
1265 }
1266 }
1267
1268 // if we are here that mean we have to permanently delete this form
1269 // we drop the form from all modules
1270 for(GList *iop_node = dev->iop; iop_node; iop_node = g_list_next(iop_node))
1271 {
1272 dt_iop_module_t *iop_module = (dt_iop_module_t *)iop_node->data;
1273 if(iop_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1274 {
1275 // is the form the base group of the iop ?
1276 if(form_id == iop_module->blend_params->mask_id)
1277 {
1278 iop_module->blend_params->mask_id = 0;
1280 }
1281 else
1282 {
1283 dt_masks_form_t *iop_group = _group_from_module(dev, iop_module);
1284 if(iop_group && (iop_group->type & DT_MASKS_GROUP))
1285 {
1286 iop_group = dt_masks_cow_touch(dev, iop_group);
1287 int removed = 0;
1288 GList *shapes = iop_group->points;
1289 while(shapes)
1290 {
1291 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)shapes->data;
1292 if(group_entry->formid == form_id)
1293 {
1294 removed = 1;
1295 // Remove the shape from the list
1296 iop_group->points = g_list_remove(iop_group->points, group_entry);
1297 dt_free(group_entry);
1298 shapes = iop_group->points; // jump back to start of list
1299 continue;
1300 }
1301 shapes = g_list_next(shapes); // advance to next form
1302 }
1303 if(removed)
1304 {
1306
1307 if(IS_NULL_PTR(iop_group->points)) dt_masks_form_delete(dev, iop_module, NULL, iop_group);
1308 }
1309 }
1310 }
1311 }
1312 }
1313 // we drop the form from the general list
1314 for(GList *form_node = dev->forms; form_node; form_node = g_list_next(form_node))
1315 {
1316 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1317 if(existing_form->formid == form_id)
1318 {
1319 dt_masks_remove_form(dev, existing_form);
1320 break;
1321 }
1322 }
1323}
1324
1326{
1327 if(IS_NULL_PTR(mask_form)) return;
1328
1329 float center_point[2];
1330 float area = 0.0f;
1331 const gboolean ok = dt_masks_form_get_gravity_center(dev, mask_form, center_point, &area);
1332 mask_form->gravity_center[0] = center_point[0];
1333 mask_form->gravity_center[1] = center_point[1];
1334 mask_form->area = area;
1335 mask_form->gravity_center_valid = TRUE;
1336
1338 "[masks] gravity center updated: form=%p id=%d type=0x%x ok=%d center=(%f,%f), area=%f\n",
1339 (void *)mask_form, mask_form->formid, mask_form->type, ok,
1340 mask_form->gravity_center[0], mask_form->gravity_center[1], mask_form->area);
1341}
1342
1344{
1345 if(IS_NULL_PTR(mask_form)) return;
1346 mask_form->gravity_center_valid = FALSE;
1347}
1348
1349
1350/* dt_masks_set_edit_mode, _change_opacity and dt_masks_form_change_opacity moved to
1351 * masks_gui.c: edit-mode pokes the blending toggle, and opacity is scroll-interaction
1352 * machinery whose toast belongs beside its events. Declarations unchanged. */
1354 size_t node_size)
1355{
1356 if(IS_NULL_PTR(base_form) || IS_NULL_PTR(dest_form) || IS_NULL_PTR(base_form->points) || node_size == 0) return;
1357
1358 for(const GList *point_node = base_form->points; point_node; point_node = g_list_next(point_node))
1359 {
1360 const void *point_data = point_node->data;
1361 if(IS_NULL_PTR(point_data)) continue;
1362 void *point_copy = malloc(node_size);
1363 if(IS_NULL_PTR(point_copy)) continue;
1364 memcpy(point_copy, point_data, node_size);
1365 dest_form->points = g_list_append(dest_form->points, point_copy);
1366 }
1367}
1368
1369
1370void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
1371{
1372 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return;
1373
1374 // we search the form in the group
1375 dt_masks_form_group_t *group_entry = NULL;
1376 guint group_index = 0;
1377 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1378 {
1379 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)group_node->data;
1380 if(entry->formid == form_id)
1381 {
1382 group_entry = entry;
1383 break;
1384 }
1385 group_index++;
1386 }
1387
1388 // we remove the form and read it
1389 if(!IS_NULL_PTR(group_entry))
1390 {
1391 const guint group_length = g_list_length(group_form->points);
1392 if(!move_up && group_index == 0) return;
1393 if(move_up && group_index == group_length - 1) return;
1394
1395 group_form->points = g_list_remove(group_form->points, group_entry);
1396 if(!move_up)
1397 group_index -= 1;
1398 else
1399 group_index += 1;
1400 group_form->points = g_list_insert(group_form->points, group_entry, group_index);
1401
1402 }
1403}
1404
1405int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
1406{
1407 if(!(group_form->type & DT_MASKS_GROUP)) return 0;
1408 if(group_form->formid == form_id) return 1;
1409 int nested_count = 0;
1410 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1411 {
1412 const dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1413 dt_masks_form_t *mask_form = dt_masks_get_from_id(dev, group_entry->formid);
1414 if(mask_form)
1415 {
1416 if(mask_form->type & DT_MASKS_GROUP) nested_count += _find_in_group(dev, mask_form, form_id);
1417 }
1418 }
1419 return nested_count;
1420}
1421
1423{
1424 if(IS_NULL_PTR(mask_form)) return hash;
1425
1426 // basic infos
1427 hash = dt_hash(hash, (char *)&mask_form->type, sizeof(dt_masks_type_t));
1428 hash = dt_hash(hash, (char *)&mask_form->formid, sizeof(int));
1429 hash = dt_hash(hash, (char *)&mask_form->version, sizeof(int));
1430 hash = dt_hash(hash, (char *)&mask_form->source, sizeof(float) * 2);
1431
1432 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1433 {
1434 if(mask_form->type & DT_MASKS_GROUP)
1435 {
1436 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)point_node->data;
1437 // Children must come from the SAME list the top-level group was resolved from. Resolving
1438 // them from live dev->forms while hashing a history/pipe snapshot mixes two states into
1439 // one hash: two genuinely different states can then hash identically (and vice versa),
1440 // which defeats the cache invalidation this hash exists for.
1441 dt_masks_form_t *child_form = dt_masks_get_from_id_ext(masks, group_entry->formid);
1442 if(child_form)
1443 {
1444 // state & opacity
1445 hash = dt_hash(hash, (char *)&group_entry->state, sizeof(int));
1446 hash = dt_hash(hash, (char *)&group_entry->opacity, sizeof(float));
1447
1448 // the form itself
1449 hash = dt_masks_group_get_hash_ext(hash, masks, child_form);
1450 }
1451 }
1452 else if(mask_form->functions)
1453 {
1454 hash = dt_hash(hash, (char *)point_node->data, mask_form->functions->point_struct_size);
1455 }
1456 }
1457 return hash;
1458}
1459
1461{
1462 if(IS_NULL_PTR(mask_form)) return hash;
1463
1464 // basic infos
1465 hash = dt_hash(hash, (const char *)&mask_form->type, sizeof(dt_masks_type_t));
1466 hash = dt_hash(hash, (const char *)&mask_form->formid, sizeof(int));
1467 hash = dt_hash(hash, (const char *)&mask_form->version, sizeof(int));
1468 hash = dt_hash(hash, (const char *)&mask_form->source, sizeof(float) * 2);
1469
1470 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1471 {
1472 if(mask_form->type & DT_MASKS_GROUP)
1473 {
1474 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)point_node->data;
1475 // Membership only (id + state/opacity): the referenced form's own content is hashed
1476 // once, separately, when dev->forms reaches that form's own top-level entry. Recursing
1477 // into it here would re-hash every grouped shape's full point list a second time.
1478 if(dt_masks_get_from_id_ext(masks, group_entry->formid))
1479 {
1480 hash = dt_hash(hash, (const char *)&group_entry->formid, sizeof(int));
1481 hash = dt_hash(hash, (const char *)&group_entry->state, sizeof(int));
1482 hash = dt_hash(hash, (const char *)&group_entry->opacity, sizeof(float));
1483 }
1484 }
1485 else if(mask_form->functions)
1486 {
1487 hash = dt_hash(hash, (const char *)point_node->data, mask_form->functions->point_struct_size);
1488 }
1489 }
1490 return hash;
1491}
1492
1493/* Marks form_id as used, and every form a group transitively contains.
1494 *
1495 * The set is unbounded on purpose. Its members are not a subset of form_list: every
1496 * blend_params->mask_id ever recorded in history is fed in, groups of modules whose mask was
1497 * since dropped included, and those ids have no form to match in the snapshot being cleaned.
1498 * A fixed table sized on the snapshot's form count therefore fills up on ids that answer
1499 * nothing, and the members discovered last -- the tail of the one group that IS live -- find no
1500 * slot left and are silently taken for unused. Measured on a 20-step history: four departed
1501 * groups ate the eight slots an 8-form snapshot allowed, and the two last shapes of the module's
1502 * own mask group were deleted while the module was still using them.
1503 *
1504 * Re-entering an already-marked id also stops the walk here rather than after it: a group that
1505 * contains itself through some chain of member groups would otherwise not terminate. */
1506static void _cleanup_unused_recurs(GList *form_list, int form_id, GHashTable *used_form_ids)
1507{
1508 if(!g_hash_table_add(used_form_ids, GINT_TO_POINTER(form_id))) return;
1509
1510 // if the form is a group, we iterate through the sub-forms
1511 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(form_list, form_id);
1512 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
1513 {
1514 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
1515 {
1516 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1517 _cleanup_unused_recurs(form_list, group_entry->formid, used_form_ids);
1518 }
1519 }
1520}
1521
1522// removes from _forms all forms that are not used in history_list up to history_end
1523static int _masks_cleanup_unused(dt_develop_t *dev, GList **forms_list, GList *history_list, const int history_end)
1524{
1525 int masks_removed = 0;
1526 GList *forms = *forms_list;
1527
1528 // the set of ids used by the history entries we are about to walk
1529 GHashTable *used_form_ids = g_hash_table_new(g_direct_hash, g_direct_equal);
1530
1531 // check in history if the module has drawn masks and add it to used array
1532 int history_index = 0;
1533 for(GList *history_node = history_list; history_node && history_index < history_end;
1534 history_node = g_list_next(history_node))
1535 {
1536 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1537 dt_develop_blend_params_t *blend_params
1538 = history_item && history_item->blendop_params_size == sizeof(dt_develop_blend_params_t)
1539 ? history_item->blend_params
1540 : NULL;
1541 if(blend_params)
1542 {
1543 if(blend_params->mask_id > 0)
1544 _cleanup_unused_recurs(forms, blend_params->mask_id, used_form_ids);
1545 }
1546 history_index++;
1547 }
1548
1549 // and we delete all unused forms
1550 GList *shape_node = forms;
1551 while(shape_node)
1552 {
1553 dt_masks_form_t *mask_form = (dt_masks_form_t *)shape_node->data;
1554 const gboolean is_used = g_hash_table_contains(used_form_ids, GINT_TO_POINTER(mask_form->formid));
1555
1556 shape_node = g_list_next(shape_node); // need to get 'next' now, because we may be removing the current node
1557
1558 if(!is_used)
1559 {
1560 forms = g_list_remove(forms, mask_form);
1561 // This list's reference is handed over to dev->allforms rather than released: a form read
1562 // from masks_history is created by dt_masks_create() and its snapshot membership is its
1563 // only claim, so unref-ing here would free an object dev->forms may still be holding by
1564 // address. One allforms entry per transferred claim keeps the teardown balanced.
1565 dev->allforms = g_list_append(dev->allforms, mask_form);
1566 masks_removed = 1;
1567 }
1568 }
1569
1570 g_hash_table_destroy(used_form_ids);
1571
1572 *forms_list = forms;
1573
1574 return masks_removed;
1575}
1576
1577// removes all unused form from history
1584static void _masks_cleanup_unused_from_list(dt_develop_t *dev, GList *history_list)
1585{
1586 // a mask is used in a given hist->forms entry if it is used up to the next hist->forms
1587 // so we are going to remove for each hist->forms from the top
1588 int history_count = g_list_length(history_list);
1589 int history_end = history_count;
1590 for(const GList *history_node = g_list_last(history_list); history_node;
1591 history_node = g_list_previous(history_node))
1592 {
1593 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1594 if(!IS_NULL_PTR(history_item->forms)) //&& strcmp(history_item->op_name, "mask_manager") == 0)
1595 {
1596 _masks_cleanup_unused(dev, &history_item->forms, history_list, history_end);
1597 history_end = history_count - 1;
1598 }
1599 history_count--;
1600 }
1601}
1602
1609{
1610 dt_masks_change_form_gui(develop, NULL);
1611
1612 /* The sweep rewrites every hist->forms in place, and the async DB write job walks those same
1613 * lists with history_mutex released. Its snapshot holds a reference on each history ITEM,
1614 * which keeps the item alive but says nothing about the list cells g_list_remove() frees
1615 * under it. Hold the writer across the sweep and the re-point that reads its result, so the
1616 * job sees the image either fully swept or untouched. Order is history_mutex outer,
1617 * masks_mutex (taken by dt_masks_replace_current_forms) inner -- the same way a history
1618 * commit takes them. */
1620
1621 // we remove the forms from history
1622 _masks_cleanup_unused_from_list(develop, develop->history);
1623
1624 // and we save all that
1625 GList *forms = NULL;
1626 int history_index = 0;
1627 for(const GList *history_node = g_list_first(develop->history);
1628 history_node && history_index < dt_dev_get_history_end_ext(develop);
1629 history_node = g_list_next(history_node))
1630 {
1631 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1632
1633 if(history_item->forms) forms = history_item->forms;
1634 history_index++;
1635 }
1636
1637 dt_masks_replace_current_forms(develop, forms);
1638
1640}
1641
1642#include "detail.c"
1643
1644/* The two rasterisation dispatchers. They were inline in masks.h, which forced the
1645 * per-shape function table to be public; a per-buffer call is not a per-pixel cost,
1646 * so the inline bought nothing and the table is private now. */
1648 const dt_dev_pixelpipe_iop_t *const piece,
1649 dt_masks_form_t *const form,
1650 float **buffer, dt_masks_area_t *area)
1651{
1652 *buffer = NULL;
1653 *area = (dt_masks_area_t){ 0 };
1654 /* A shape type with no rasteriser is a programming error, not an empty shape. */
1655 return (form->functions && form->functions->get_mask)
1656 ? form->functions->get_mask(module, pipe, piece, form, buffer, &area->width, &area->height, &area->x,
1657 &area->y)
1659}
1660
1662 const dt_dev_pixelpipe_iop_t *const piece,
1663 dt_masks_form_t *const form, const dt_iop_roi_t *roi,
1664 float *buffer, dt_iop_roi_t *touched)
1665{
1666 dt_masks_touched_none(touched);
1667 /* A shape type with no rasteriser is a programming error, not an empty shape: report ERROR so
1668 * the fold refuses to publish a buffer nobody wrote. */
1669 return (form->functions && form->functions->get_mask_roi)
1670 ? form->functions->get_mask_roi(module, pipe, piece, form, roi, buffer, touched)
1672}
1673
1674
1675/* ==========================================================================================
1676 * The read side of the group API (develop/masks_group.h).
1677 *
1678 * All three are thread-neutral by construction: they read only the object they are handed and
1679 * take no lock. That is what lets the pipeline call them on a snapshot and the GUI call them on
1680 * the live list with the same code.
1681 * ========================================================================================== */
1682
1684{
1685 /* `out' is left untouched on FALSE so a caller may keep a default in it across a failed call --
1686 * the dt_colorspaces_profile_at() convention. */
1687 if(IS_NULL_PTR(form) || IS_NULL_PTR(out)) return FALSE;
1688
1689 out->formid = form->formid;
1690 out->type = form->type;
1691 out->version = form->version;
1692 out->is_group = (form->type & DT_MASKS_GROUP) != 0;
1693 out->is_retouch = (form->type & DT_MASKS_IS_RETOUCHE) != 0;
1694 /* Not recursive: the group's own rows, which is what compositing order is defined over. */
1695 out->member_count = out->is_group ? g_list_length(form->points) : 0;
1696 g_strlcpy(out->name, form->name, sizeof(out->name));
1697 return TRUE;
1698}
1699
1700/* One place builds the value type callers see, so a row is never copied out field by field at
1701 * three separate call sites -- each of which would have to be found again the day
1702 * dt_masks_member_t grows a field. @p entry may be NULL: the member comes back zeroed but KEEPS
1703 * its index, for the reason dt_masks_group_copy_members() spells out below. */
1704static void _member_from_entry(dt_masks_member_t *const out, const dt_masks_form_group_t *const entry,
1705 const guint index)
1706{
1707 if(IS_NULL_PTR(out)) return;
1708
1709 out->index = index;
1710
1711 if(IS_NULL_PTR(entry))
1712 {
1713 out->formid = 0;
1714 out->parentid = 0;
1715 out->state = DT_MASKS_STATE_NONE;
1716 out->opacity = 0.0f;
1717 return;
1718 }
1719
1720 out->formid = entry->formid;
1721 out->parentid = entry->parentid;
1722 out->state = (dt_masks_state_t)entry->state;
1723 out->opacity = entry->opacity;
1724}
1725
1726
1727/* Resolve one membership row from (group, formid), the identity the whole write API is keyed on.
1728 *
1729 * @p touch says whether the caller is about to MUTATE the row, and the asymmetry is the point:
1730 * - a writer must touch FIRST and resolve from what the touch returned, because cloning a group
1731 * clones its membership blocks with it -- a row resolved before the touch belongs to the copy
1732 * that was just abandoned, and the mutation lands in memory nothing reads;
1733 * - a reader must NOT touch. Copy-on-write is for writers; touching on a read would clone a
1734 * shared group every time the GUI merely asks what a shape's opacity is.
1735 */
1736static dt_masks_form_group_t *_resolve_member(dt_develop_t *dev, const int group_id, const int formid,
1737 const gboolean touch, guint *const out_index)
1738{
1739 if(!IS_NULL_PTR(out_index)) *out_index = 0;
1740
1741 dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1742 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return NULL;
1743
1744 if(touch) group = dt_masks_cow_touch(dev, group);
1745 if(IS_NULL_PTR(group)) return NULL;
1746
1747 guint index = 0;
1748 for(GList *node = group->points; node; node = g_list_next(node), index++)
1749 {
1750 dt_masks_form_group_t *const entry = (dt_masks_form_group_t *)node->data;
1751 if(IS_NULL_PTR(entry) || entry->formid != formid) continue;
1752
1753 if(!IS_NULL_PTR(out_index)) *out_index = index;
1754 return entry;
1755 }
1756
1757 return NULL;
1758}
1759
1760
1762 const guint out_max)
1763{
1764 /* Refusing anything that is not a group is what keeps the polymorphic ->points unreachable from
1765 * outside: for a group it holds membership rows, for every other form it holds geometry nodes,
1766 * and the only thing telling them apart is this bit. */
1767 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return 0;
1768
1769 guint total = 0;
1770 for(const GList *node = group->points; node; node = g_list_next(node), total++)
1771 {
1772 if(IS_NULL_PTR(out) || total >= out_max) continue;
1773
1774 /* A row that cannot be read STILL CONSUMES ITS INDEX. Position is the compositing order and
1775 * the index into retouch's rt_forms[] and spots' clone_algo[], both persisted in the user's
1776 * database, so dropping a row here would silently re-pair every later shape with the wrong
1777 * algorithm. _member_from_entry() zeroes it instead, index kept. */
1778 _member_from_entry(&out[total], (const dt_masks_form_group_t *)node->data, total);
1779 }
1780
1781 /* The TOTAL, always -- a caller that passed a short buffer needs to know it was short, and a
1782 * caller that passed NULL is asking for exactly this. */
1783 return total;
1784}
1785
1787{
1788 /* dt_masks_type_t is a bit field, not an enumeration of alternatives, so first match wins and
1789 * this order is load-bearing. It is the order the conf-key builder has always used.
1790 *
1791 * These tokens are PERSISTED: they build plugins/darkroom/<plugin>/<type>/<feature>, declared in
1792 * data/anselconfig.xml.in. "polygon" can never become "path" -- a key outside confgen reads 0,
1793 * which would silently reset the user's fading. */
1794 if(type & DT_MASKS_CIRCLE) return "circle";
1795 else if(type & DT_MASKS_POLYGON) return "polygon";
1796 else if(type & DT_MASKS_ELLIPSE) return "ellipse";
1797 else if(type & DT_MASKS_GRADIENT) return "gradient";
1798 else if(type & DT_MASKS_BRUSH) return "brush";
1799 else if(type & DT_MASKS_GROUP) return "group";
1800 else return "unknown";
1801}
1802
1803
1805 const int formid, const dt_masks_state_t operation,
1807{
1808 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1809 if(operation != DT_MASKS_STATE_INVERSE && !(operation & DT_MASKS_STATE_IS_COMBINE_OP))
1810 return DT_MASKS_INVALID;
1811
1812 guint index = 0;
1813 dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, TRUE, &index);
1814 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
1815
1816 const int before = entry->state;
1817 if(operation == DT_MASKS_STATE_INVERSE)
1818 entry->state ^= DT_MASKS_STATE_INVERSE;
1819 else
1820 entry->state = (entry->state & ~DT_MASKS_STATE_IS_COMBINE_OP) | operation;
1821
1822 _member_from_entry(out, entry, index);
1823 return (entry->state == before) ? DT_MASKS_UNCHANGED : DT_MASKS_OK;
1824}
1825
1826
1828 dt_iop_module_t *module)
1829{
1830 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module)) return DT_MASKS_INVALID;
1831
1832 dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1833 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return DT_MASKS_NOT_FOUND;
1834
1835 gchar *name = dt_masks_group_name_for_module(module);
1836 if(IS_NULL_PTR(name)) return DT_MASKS_INVALID;
1837
1838 // Nothing to write is not a no-op worth cloning a shared group for -- and the caller usually
1839 // turns UNCHANGED into "skip the history commit", the way the other id-keyed setters do.
1840 if(g_strcmp0(group->name, name) == 0)
1841 {
1842 dt_free(name);
1843 return DT_MASKS_UNCHANGED;
1844 }
1845
1846 group = dt_masks_cow_touch(dev, group);
1847 if(IS_NULL_PTR(group))
1848 {
1849 dt_free(name);
1850 return DT_MASKS_NOT_FOUND;
1851 }
1852
1853 g_strlcpy(group->name, name, sizeof(group->name));
1854 dt_free(name);
1855 return DT_MASKS_OK;
1856}
1857
1858
1859/* Depth-first walk for the group that references @p formid. @p grp NULL means "start from every
1860 * top-level group in dev->forms". @p max_depth is a cycle brake, not a modelling limit: a group
1861 * referencing an ancestor of itself would otherwise recurse forever, and nothing in the data model
1862 * forbids one being written to XMP. */
1863static dt_masks_form_t *_find_holder(dt_develop_t *dev, dt_masks_form_t *grp, const int formid,
1864 const int max_depth)
1865{
1866 if(max_depth <= 0) return NULL;
1867
1868 if(IS_NULL_PTR(grp))
1869 {
1870 for(GList *forms = dev->forms; forms; forms = g_list_next(forms))
1871 {
1872 dt_masks_form_t *const form = (dt_masks_form_t *)forms->data;
1873 if(IS_NULL_PTR(form) || !(form->type & DT_MASKS_GROUP)) continue;
1874
1875 dt_masks_form_t *const found = _find_holder(dev, form, formid, max_depth - 1);
1876 if(!IS_NULL_PTR(found)) return found;
1877 }
1878 return NULL;
1879 }
1880
1881 for(GList *points = grp->points; points; points = g_list_next(points))
1882 {
1883 const dt_masks_form_group_t *const point = (const dt_masks_form_group_t *)points->data;
1884 if(IS_NULL_PTR(point)) continue;
1885 if(point->formid == formid) return grp;
1886
1887 dt_masks_form_t *const sub = dt_masks_get_from_id(dev, point->formid);
1888 if(IS_NULL_PTR(sub) || !(sub->type & DT_MASKS_GROUP)) continue;
1889
1890 dt_masks_form_t *const found = _find_holder(dev, sub, formid, max_depth - 1);
1891 if(!IS_NULL_PTR(found)) return found;
1892 }
1893 return NULL;
1894}
1895
1896
1897int dt_masks_group_find_holder(dt_develop_t *dev, const int formid)
1898{
1899 if(IS_NULL_PTR(dev) || formid == 0) return 0;
1900
1901 const dt_masks_form_t *const grp = _find_holder(dev, NULL, formid, 32);
1902 return IS_NULL_PTR(grp) ? 0 : grp->formid;
1903}
1904
1905
1907{
1908 if(IS_NULL_PTR(dev)) return 0;
1909
1910 /* Held across the whole walk, which is safe only because nothing below resolves an id: that
1911 * would take this same lock again, and a nested read queued behind a waiting writer deadlocks. */
1913 guint total = 0;
1914 for(const GList *forms = dev->forms; forms; forms = g_list_next(forms))
1915 {
1916 const dt_masks_form_t *const form = (const dt_masks_form_t *)forms->data;
1917 if(IS_NULL_PTR(form) || !(form->type & DT_MASKS_GROUP)) continue;
1918
1919 if(!IS_NULL_PTR(out) && total < out_max) dt_masks_form_get_info(form, &out[total]);
1920 total++;
1921 }
1923
1924 return total;
1925}
1926
1927
1928dt_masks_result_t dt_masks_group_get_member(dt_develop_t *dev, const int group_id, const int formid,
1930{
1931 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1932
1933 guint index = 0;
1934 const dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, FALSE, &index);
1935 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
1936
1937 _member_from_entry(out, entry, index);
1938 return DT_MASKS_OK;
1939}
1940
1941
1942/* The membership graph queries. All three walk ->points, which is why they live here: outside the
1943 * module a caller would have to reach into dt_masks_form_t and dt_masks_form_group_t to ask. */
1944
1945static gboolean _group_contains_recurs(dt_develop_t *dev, const int container_id, const int needle_id)
1946{
1947 if(container_id == needle_id) return TRUE;
1948
1949 const dt_masks_form_t *container = dt_masks_get_from_id(dev, container_id);
1950 if(IS_NULL_PTR(container) || !(container->type & DT_MASKS_GROUP)) return FALSE;
1951
1952 for(const GList *pts = container->points; pts; pts = g_list_next(pts))
1953 {
1954 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
1955 if(_group_contains_recurs(dev, pt->formid, needle_id)) return TRUE;
1956 }
1957
1958 return FALSE;
1959}
1960
1961dt_masks_result_t dt_masks_group_contains(dt_develop_t *dev, const int container_id, const int needle_id)
1962{
1963 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1964 return _group_contains_recurs(dev, container_id, needle_id) ? DT_MASKS_OK : DT_MASKS_NOT_FOUND;
1965}
1966
1967static gboolean _group_covers_recurs(dt_develop_t *dev, const int group_id, const int target_id,
1968 gboolean *has_shapes)
1969{
1970 const dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1971 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return TRUE;
1972
1973 for(const GList *pts = group->points; pts; pts = g_list_next(pts))
1974 {
1975 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
1976 const dt_masks_form_t *member = dt_masks_get_from_id(dev, pt->formid);
1977 if(IS_NULL_PTR(member)) continue;
1978
1979 if(member->type & DT_MASKS_GROUP)
1980 {
1981 if(!_group_covers_recurs(dev, pt->formid, target_id, has_shapes)) return FALSE;
1982 continue;
1983 }
1984
1985 if(!IS_NULL_PTR(has_shapes)) *has_shapes = TRUE;
1986 if(!_group_contains_recurs(dev, target_id, pt->formid)) return FALSE;
1987 }
1988
1989 return TRUE;
1990}
1991
1992dt_masks_result_t dt_masks_group_covers_shapes(dt_develop_t *dev, const int group_id, const int target_id,
1993 gboolean *has_shapes)
1994{
1995 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1996 if(!IS_NULL_PTR(has_shapes)) *has_shapes = FALSE;
1997 return _group_covers_recurs(dev, group_id, target_id, has_shapes) ? DT_MASKS_OK : DT_MASKS_NOT_FOUND;
1998}
1999
2000static gboolean _first_use_recurs(dt_develop_t *dev, const int group_id, const int formid,
2001 int *holder_id, guint *index, const dt_masks_form_t **holder)
2002{
2003 const dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
2004 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return FALSE;
2005
2006 guint i = 0;
2007 for(const GList *pts = group->points; pts; pts = g_list_next(pts))
2008 {
2009 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
2010
2011 if(pt->formid == formid)
2012 {
2013 if(!IS_NULL_PTR(holder_id)) *holder_id = group_id;
2014 if(!IS_NULL_PTR(index)) *index = i;
2015 *holder = group;
2016 return TRUE;
2017 }
2018
2019 /* Descend where the member sits, not after the whole level: a shape inside a member group is
2020 * composited at that group's position, so pre-order is the compositing order. */
2021 const dt_masks_form_t *member = dt_masks_get_from_id(dev, pt->formid);
2022 if(!IS_NULL_PTR(member) && (member->type & DT_MASKS_GROUP)
2023 && _first_use_recurs(dev, pt->formid, formid, holder_id, index, holder))
2024 return TRUE;
2025
2026 i++;
2027 }
2028
2029 return FALSE;
2030}
2031
2032dt_masks_result_t dt_masks_group_first_use(dt_develop_t *dev, const int root_id, const int formid,
2033 int *holder_id, guint *index,
2034 char *holder_name, const size_t holder_name_size)
2035{
2036 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
2037
2038 const dt_masks_form_t *holder = NULL;
2039 if(!_first_use_recurs(dev, root_id, formid, holder_id, index, &holder)) return DT_MASKS_NOT_FOUND;
2040
2041 // Copied, not borrowed: the next copy-on-write replaces the object the name lives in.
2042 if(!IS_NULL_PTR(holder_name) && holder_name_size > 0)
2043 g_strlcpy(holder_name, IS_NULL_PTR(holder) ? "" : holder->name, holder_name_size);
2044
2045 return DT_MASKS_OK;
2046}
2047
2048dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid,
2049 const float opacity, dt_masks_member_t *out)
2050{
2051 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
2052
2053 /* Reject NaN rather than clamp it. CLAMPF() is written as a pair of ordered comparisons, and
2054 * every comparison against NaN is false, so it would quietly return the LOW bound: a NaN
2055 * arriving from a caller's own arithmetic would blank the shape instead of being reported. */
2056 if(!isfinite(opacity)) return DT_MASKS_INVALID;
2057
2058 guint index = 0;
2059 dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, TRUE, &index);
2060 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
2061
2062 const float before = entry->opacity;
2063 entry->opacity = CLAMPF(opacity, 0.0f, 1.0f);
2064
2065 _member_from_entry(out, entry, index);
2066 return (entry->opacity == before) ? DT_MASKS_UNCHANGED : DT_MASKS_OK;
2067}
2068
2069// clang-format off
2070// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2071// vim: shiftwidth=2 expandtab tabstop=2 cindent
2072// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2073// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_atomic_set_int(dt_atomic_int *var, int value)
uint32_t container(dt_lib_module_t *self)
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
Definition blend_gui.c:3701
The blending panel's GUI state and API, cut out of develop/blend.h.
const dt_masks_functions_t dt_masks_functions_circle
Definition circle.c:1078
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:511
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1966
const dt_masks_functions_t dt_masks_functions_brush
int dt_dev_distort_backtransform_plus(const dt_dev_pixelpipe_t *pipe, const double iop_order, const int transf_direction, float *points, size_t points_count)
Definition develop.c:1893
gchar * dt_dev_get_multi_name(const struct dt_iop_module_t *module)
Definition develop.c:1704
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1712
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
const dt_masks_functions_t dt_masks_functions_ellipse
Definition ellipse.c:1643
const dt_masks_functions_t dt_masks_functions_gradient
Definition gradient.c:1470
const dt_masks_functions_t dt_masks_functions_group
Definition group.c:1045
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
void dt_history_message(const char *format,...)
Everything the history, styles and presets code says to the outside world: messages for the user,...
void dt_history_repository_foreach_mask_item(const int32_t imgid, dt_history_repository_mask_cb cb, void *user_data)
gboolean dt_history_repository_write_mask_item(const int32_t imgid, const int num, const int formid, const int form, const char *name, const int version, const void *points, const int points_len, const int points_count, const void *source, const int source_len)
dt_image_orientation_t
Definition image.h:225
@ ORIENTATION_SWAP_XY
Definition image.h:230
@ ORIENTATION_ROTATE_CCW_90_DEG
Definition image.h:237
@ ORIENTATION_ROTATE_CW_90_DEG
Definition image.h:238
@ ORIENTATION_NONE
Definition image.h:227
@ ORIENTATION_ROTATE_180_DEG
Definition image.h:235
static dt_image_orientation_t dt_image_orientation(const dt_image_t *img)
Definition image.h:701
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
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
static const dt_iop_drawlayer_params_t * params
Definition layers.h:80
#define w1
Definition lmmse.c:59
_lib_location_type_t type
Definition location.c:1
@ DT_DEBUG_HISTORY
Definition logging.h:75
@ 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
static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
Definition masks.c:622
gchar * dt_masks_group_name_for_module(const dt_iop_module_t *module)
Definition masks.c:143
void dt_masks_form_delete(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
Definition masks.c:1217
dt_masks_result_t dt_masks_group_get_member(dt_develop_t *dev, const int group_id, const int formid, dt_masks_member_t *out)
Read one group member by identity.
Definition masks.c:1928
dt_masks_result_t dt_masks_group_set_member_operation(dt_develop_t *dev, const int group_id, const int formid, const dt_masks_state_t operation, dt_masks_member_t *out)
Set a group member's combination operator, or toggle its inversion.
Definition masks.c:1804
dt_masks_form_t * _group_create(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_type_t group_type)
Definition masks.c:170
dt_masks_raster_result_t dt_masks_get_points_border(dt_develop_t *develop, dt_masks_form_t *mask_form, float **point_buffer, int *point_count, float **border_buffer, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int source, dt_iop_module_t *module)
Definition masks.c:238
float * dt_masks_sample_grid_backtransform(const struct dt_dev_pixelpipe_t *pipe, const double iop_order, const dt_masks_sample_grid_t *const grid, const char *const shape, const char *const form_name)
Definition masks.c:345
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
static void _read_mask_row(void *user_data, const int history_num, const int form_id, const int form, const char *name, const int version, const void *points, const int points_len, const int point_count, const void *source, const int source_len)
Definition masks.c:1029
static int _masks_cleanup_unused(dt_develop_t *dev, GList **forms_list, GList *history_list, const int history_end)
Definition masks.c:1523
gboolean dt_masks_form_get_info(const dt_masks_form_t *form, dt_masks_form_info_t *out)
Definition masks.c:1683
static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *develop, void *params)
Definition masks.c:743
static void dt_masks_legacy_params_v2_to_v3_transform_only_rescale(const dt_image_t *image, float *coords, size_t coords_count)
Definition masks.c:643
void dt_masks_free_form(dt_masks_form_t *mask_form)
Definition masks.c:1161
static void _member_from_entry(dt_masks_member_t *const out, const dt_masks_form_group_t *const entry, const guint index)
Definition masks.c:1704
guint dt_masks_group_list(dt_develop_t *dev, dt_masks_form_info_t *out, const guint out_max)
Describe every group of the live list, in dev->forms order, into caller storage.
Definition masks.c:1906
static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *develop, void *params)
Definition masks.c:795
int dt_masks_group_find_holder(dt_develop_t *dev, const int formid)
Which group references formid, searching every group in dev->forms depth-first.
Definition masks.c:1897
static gboolean _first_use_recurs(dt_develop_t *dev, const int group_id, const int formid, int *holder_id, guint *index, const dt_masks_form_t **holder)
Definition masks.c:2000
void _check_id(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition masks.c:125
int dt_masks_form_duplicate_in_group(dt_develop_t *develop, int group_id, int form_id)
Duplicate a shape (dt_masks_form_duplicate) and, if group_id names a valid group, attach the duplicat...
Definition masks.c:210
void dt_masks_duplicate_points(const dt_masks_form_t *base_form, dt_masks_form_t *dest_form, size_t node_size)
Duplicate a points list for a mask using a fixed node size.
Definition masks.c:1353
uint64_t dt_masks_form_get_own_hash(uint64_t hash, GList *masks, const dt_masks_form_t *mask_form)
Definition masks.c:1460
int dt_masks_copy_used_forms_for_module(dt_develop_t *develop_dest, dt_develop_t *develop_src, const dt_iop_module_t *source_module)
Definition masks.c:973
dt_iop_module_t * dt_masks_get_mask_manager(dt_develop_t *develop)
Definition masks.c:938
dt_masks_result_t dt_masks_group_first_use(dt_develop_t *dev, const int root_id, const int formid, int *holder_id, guint *index, char *holder_name, const size_t holder_name_size)
Where the mask rooted at root_id FIRST applies formid.
Definition masks.c:2032
dt_masks_result_t dt_masks_group_set_name_from_module(dt_develop_t *dev, const int group_id, dt_iop_module_t *module)
Name a group after the module that renders it, by id.
Definition masks.c:1827
dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid, const float opacity, dt_masks_member_t *out)
Set a group member's opacity.
Definition masks.c:2048
void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
Definition masks.c:1118
static gboolean _group_contains_recurs(dt_develop_t *dev, const int container_id, const int needle_id)
Definition masks.c:1945
int dt_masks_version(void)
Definition masks.c:502
dt_masks_form_t * dt_masks_get_from_id_ext(GList *form_list, int form_id)
Definition masks.c:915
dt_masks_form_t * dt_masks_dup_masks_form(const dt_masks_form_t *mask_form)
Deep-copy a mask form, including its points list.
Definition masks.c:77
static dt_masks_form_t * _find_holder(dt_develop_t *dev, dt_masks_form_t *grp, const int formid, const int max_depth)
Definition masks.c:1863
static dt_masks_form_group_t * _resolve_member(dt_develop_t *dev, const int group_id, const int formid, const gboolean touch, guint *const out_index)
Definition masks.c:1736
static int form_id_seed
Definition masks.c:865
int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
Definition masks.c:821
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:901
static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *develop, void *params)
Definition masks.c:663
static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *develop, void *params)
Definition masks.c:507
dt_masks_result_t dt_masks_group_contains(dt_develop_t *dev, const int container_id, const int needle_id)
Whether container_id holds needle_id, at any depth (and trivially when they are the same id).
Definition masks.c:1961
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:867
void dt_masks_cleanup_unused(dt_develop_t *develop)
Cleanup unused masks and refresh the current forms snapshot.
Definition masks.c:1608
int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
Definition masks.c:1405
void dt_masks_area_scale(dt_masks_area_t *area, const double scale)
Definition masks.c:450
guint dt_masks_group_copy_members(const dt_masks_form_t *group, dt_masks_member_t *out, const guint out_max)
Definition masks.c:1761
void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
Definition masks.c:1370
void dt_masks_form_invalidate_gravity_center(dt_masks_form_t *mask_form)
Definition masks.c:1343
gboolean dt_masks_area_intersects(const dt_masks_area_t *area, const dt_iop_roi_t *roi)
Definition masks.c:458
void dt_masks_write_masks_history_item(const int32_t image_id, const int history_num, dt_masks_form_t *mask_form)
Definition masks.c:1131
dt_masks_raster_result_t dt_masks_get_source_area(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form, dt_masks_area_t *area)
Definition masks.c:486
dt_masks_result_t dt_masks_group_covers_shapes(dt_develop_t *dev, const int group_id, const int target_id, gboolean *has_shapes)
Whether every shape group_id ultimately holds is also held by target_id.
Definition masks.c:1992
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *develop, int form_id)
Definition masks.c:925
uint64_t dt_masks_group_get_hash_ext(uint64_t hash, GList *masks, dt_masks_form_t *mask_form)
Definition masks.c:1422
static void _cleanup_unused_recurs(GList *form_list, int form_id, GHashTable *used_form_ids)
Definition masks.c:1506
int dt_masks_form_duplicate(dt_develop_t *develop, int form_id)
Definition masks.c:186
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_edit_mode_t dt_masks_get_edit_mode(struct dt_iop_module_t *module)
Definition masks.c:1169
void dt_masks_form_update_gravity_center(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition masks.c:1325
void dt_masks_points_bounding_box(const float *const points, const int num_points, int *width, int *height, int *posx, int *posy)
Definition masks.c:322
void _set_group_name_from_module(dt_iop_module_t *module, dt_masks_form_t *group_form)
Definition masks.c:161
dt_masks_form_t * _group_from_module(dt_develop_t *develop, dt_iop_module_t *module)
Definition masks.c:181
gboolean dt_masks_skip_contains(const dt_masks_skip_range_t *skips, const int skip_count, const int index)
Definition masks.c:306
static void _masks_fill_used_forms(GList *forms_list, const int form_id, int *used_form_ids, const int used_count)
Definition masks.c:949
static void _masks_cleanup_unused_from_list(dt_develop_t *dev, GList *history_list)
Remove unused mask forms from a history list, preserving undo safety.
Definition masks.c:1584
void dt_masks_sample_grid_interpolate(const float *const points, const dt_masks_sample_grid_t *const grid, float *const buffer, const int buf_width, const int buf_height, int *const endx, int *const endy)
Definition masks.c:393
const char * dt_masks_type_name(const dt_masks_type_t type)
The stable, untranslated token for a shape kind: circle, ellipse, polygon, brush, gradient,...
Definition masks.c:1786
void dt_masks_iop_use_same_as(dt_iop_module_t *module, dt_iop_module_t *source_module)
Definition masks.c:1178
static gboolean _group_covers_recurs(dt_develop_t *dev, const int group_id, const int target_id, gboolean *has_shapes)
Definition masks.c:1967
dt_masks_form_t * dt_masks_get_from_id_in_pipe(const dt_dev_pixelpipe_t *pipe, int form_id)
Definition masks.c:933
void dt_masks_form_gui_points_free(gpointer data)
Definition masks.c:112
dt_masks_raster_result_t dt_masks_get_area(const dt_iop_module_t *iop_module, const dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form, dt_masks_area_t *area)
Definition masks.c:464
gboolean dt_masks_form_is_in_roi(const dt_iop_module_t *iop_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_in, const dt_iop_roi_t *roi_out)
Definition masks.c:475
static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *develop, void *params)
Definition masks.c:770
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
@ DT_MASKS_ELLIPSE_EQUIDISTANT
Definition masks.h:182
@ DT_MASKS_GRADIENT_STATE_LINEAR
Definition masks.h:162
#define DEVELOP_MASKS_VERSION
Definition masks.h:136
The per-shape function table, private to the masks implementation.
#define DT_MASKS_GRID_MAX_STEP
const dt_masks_functions_t dt_masks_functions_polygon
Definition polygon.c:3630
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
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
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2505
dt_masks_form_group_t * dt_masks_group_add_form(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
Definition masks_gui.c:5347
void dt_masks_remove_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2515
void dt_masks_replace_current_forms(dt_develop_t *dev, GList *forms)
dt_masks_form_t * dt_masks_cow_touch(dt_develop_t *dev, dt_masks_form_t *form)
static void dt_masks_touched_none(dt_iop_roi_t *touched)
dt_masks_edit_mode_t
@ DT_MASKS_EDIT_OFF
dt_masks_state_t
Definition masks_types.h:87
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks_types.h:98
@ DT_MASKS_STATE_NONE
Definition masks_types.h:88
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_BRUSH
Definition masks_types.h:70
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_GRADIENT
Definition masks_types.h:68
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
@ DT_MASKS_IS_RETOUCHE
Definition masks_types.h:79
dt_masks_result_t
What a request that may change the masks model did.
@ DT_MASKS_OK
@ DT_MASKS_UNCHANGED
@ DT_MASKS_NOT_FOUND
@ DT_MASKS_INVALID
#define CLAMPF(a, mn, mx)
Definition math.h:91
#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 void dt_free_gpointer(gpointer ptr)
g_free() one pointer, with the signature GDestroyNotify wants.
Definition mem_alloc.h:184
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
uint32_t width
Definition mipmap_cache.c:0
dt_imageio_retval_t status
Definition mipmap_cache.c:8
uint32_t height
Definition mipmap_cache.c:1
float iscale
Definition mipmap_cache.c:2
static float gh(const float f)
#define __OMP_PARALLEL_FOR__(...)
Definition openmp.h:95
const char * name
Definition pdf.h:90
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
unsigned __int64 uint64_t
Definition strptime.c:75
int32_t image_id
Definition masks.c:1023
dt_dev_history_item_t * last_history_item
Definition masks.c:1025
dt_develop_t * develop
Definition masks.c:1022
dt_dev_history_item_t * history_item
Definition masks.c:1024
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:57
dt_image_t image_storage
Definition develop.h:225
GList * iop
Definition develop.h:269
dt_pthread_rwlock_t history_mutex
Definition develop.h:247
GList * history
Definition develop.h:259
GList * allforms
Definition develop.h:312
dt_pthread_rwlock_t masks_mutex
Definition develop.h:316
GList * forms
Definition develop.h:304
int32_t height
Definition image.h:406
int32_t crop_height
Definition image.h:407
int32_t width
Definition image.h:406
int32_t crop_y
Definition image.h:407
int32_t crop_x
Definition image.h:407
int32_t crop_width
Definition image.h:407
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:395
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
struct dt_develop_t * dev
Definition imageop.h:311
dt_dev_operation_t op
Definition imageop.h:259
Region of interest passed through the pixelpipe.
Definition format.h:49
double scale
Definition format.h:51
int width
Definition format.h:50
int height
Definition format.h:50
dt_masks_gradient_states_t state
Definition masks.h:241
dt_masks_skip_range_t * border_skips
Definition masks_gui.h:70
What a form IS, BY VALUE: identity, kind, and – for a group – how many members.
const dt_masks_functions_t * functions
Definition masks.h:255
dt_masks_type_t type
Definition masks.h:254
dt_atomic_int refcount
Definition masks.h:285
gboolean gravity_center_valid
Definition masks.h:270
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
float gravity_center[2]
Definition masks.h:265
gboolean uses_bezier_points_layout
Definition masks.h:257
dt_masks_raster_result_t(* get_mask_roi)(const dt_iop_module_t *const fmodule, const struct dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, struct dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer, dt_iop_roi_t *touched)
void(* sanitize_config)(dt_masks_type_t type_flags)
void(* duplicate_points)(struct dt_develop_t *const dev, struct dt_masks_form_t *base, struct dt_masks_form_t *dest)
dt_masks_raster_result_t(* get_mask)(const dt_iop_module_t *const module, const struct dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, struct dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
dt_masks_raster_result_t(* get_source_area)(const dt_iop_module_t *module, const struct dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *piece, struct dt_masks_form_t *form, int *width, int *height, int *posx, int *posy)
dt_masks_raster_result_t(* get_area)(const dt_iop_module_t *const module, const struct dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, struct dt_masks_form_t *const form, int *width, int *height, int *posx, int *posy)
dt_masks_raster_result_t(* get_points_border)(struct dt_develop_t *dev, struct dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int source, const dt_iop_module_t *const module)
One shape's membership of one group, BY VALUE.
dt_masks_ellipse_flags_t flags
Definition masks.h:208
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
void dt_supervisor_form(const dt_sv_op_t op, const dt_masks_form_t *form)
@ DT_SV_CREATE
Definition supervisor.h:78
static gboolean dt_supervisor_active(void)
Definition supervisor.h:95
#define MIN(a, b)
Definition thinplate.c:32
static double dt_get_wtime(void)
Definition times.h:43