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(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
380 if(!dt_dev_distort_backtransform_plus(pipe, iop_order, DT_DEV_TRANSFORM_DIR_BACK_INCL, points, count))
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
451 dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form,
452 int *area_width, int *area_height, int *area_pos_x, int *area_pos_y)
453{
454 *area_width = 0;
455 *area_height = 0;
456 *area_pos_x = 0;
457 *area_pos_y = 0;
458 if(mask_form->functions && mask_form->functions->get_area)
459 return mask_form->functions->get_area(module, pipe, piece, mask_form, area_width, area_height,
460 area_pos_x, area_pos_y);
462}
463
465 dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form,
466 int *area_width, int *area_height,
467 int *area_pos_x, int *area_pos_y)
468{
469 *area_width = 0;
470 *area_height = 0;
471 *area_pos_x = 0;
472 *area_pos_y = 0;
473
474 // must be a clone form
475 if(mask_form->type & DT_MASKS_CLONE)
476 {
477 if(mask_form->functions && mask_form->functions->get_source_area)
478 return mask_form->functions->get_source_area(module, pipe, piece, mask_form, area_width, area_height,
479 area_pos_x, area_pos_y);
480 }
481
482 /* A form that is not a clone has no source area. That is an absence, not a failure. */
484}
485
487{
489}
490
491static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *develop, void *params)
492{
493 /*
494 * difference: before v2 images were originally rotated on load, and then
495 * maybe in flip iop
496 * after v2: images are only rotated in flip iop.
497 */
498
499 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
500
501 const dt_image_orientation_t orientation = dt_image_orientation(&develop->image_storage);
502
503 if(orientation == ORIENTATION_NONE)
504 {
505 // image is not rotated, we're fine!
506 mask_form->version = 2;
507 return 0;
508 }
509 else
510 {
511 if(IS_NULL_PTR(develop->iop)) return 1;
512
513 const char *opname = "flip";
514 dt_iop_module_t *module = NULL;
515
516 for(GList *module_node = develop->iop; module_node; module_node = g_list_next(module_node))
517 {
518 dt_iop_module_t *iop_module = (dt_iop_module_t *)module_node->data;
519 if(!strcmp(iop_module->op, opname))
520 {
521 module = iop_module;
522 break;
523 }
524 }
525
526 if(IS_NULL_PTR(module)) return 1;
527
528 dt_dev_pixelpipe_iop_t piece = { 0 };
529
530 module->init_pipe(module, NULL, &piece);
531 module->commit_params(module, module->default_params, NULL, &piece);
532
533 piece.buf_in.width = 1;
534 piece.buf_in.height = 1;
535
536 GList *point_node = mask_form->points;
537
538 if(IS_NULL_PTR(point_node)) return 1;
539
540 if(mask_form->type & DT_MASKS_CIRCLE)
541 {
542 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
543 if(IS_NULL_PTR(circle)) return 1;
544 module->distort_backtransform(module, NULL, &piece, circle->center, 1);
545 }
546 else if(mask_form->type & DT_MASKS_POLYGON)
547 {
548 for(; point_node; point_node = g_list_next(point_node))
549 {
550 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
551 if(IS_NULL_PTR(polygon_node)) return 1;
552 module->distort_backtransform(module, NULL, &piece, polygon_node->node, 1);
553 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl1, 1);
554 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl2, 1);
555 }
556 }
557 else if(mask_form->type & DT_MASKS_GRADIENT)
558 { // TODO: new ones have wrong rotation.
559 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
560 if(IS_NULL_PTR(gradient)) return 1;
561 module->distort_backtransform(module, NULL, &piece, gradient->center, 1);
562
563 if(orientation == ORIENTATION_ROTATE_180_DEG)
564 gradient->rotation -= 180.0f;
565 else if(orientation == ORIENTATION_ROTATE_CCW_90_DEG)
566 gradient->rotation -= 90.0f;
567 else if(orientation == ORIENTATION_ROTATE_CW_90_DEG)
568 gradient->rotation -= -90.0f;
569 }
570 else if(mask_form->type & DT_MASKS_ELLIPSE)
571 {
572 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
573 module->distort_backtransform(module, NULL, &piece, ellipse->center, 1);
574
575 if(orientation & ORIENTATION_SWAP_XY)
576 {
577 const float y = ellipse->radius[0];
578 ellipse->radius[0] = ellipse->radius[1];
579 ellipse->radius[1] = y;
580 }
581 }
582 else if(mask_form->type & DT_MASKS_BRUSH)
583 {
584 for(; point_node; point_node = g_list_next(point_node))
585 {
586 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
587 if(IS_NULL_PTR(brush_node)) return 1;
588 module->distort_backtransform(module, NULL, &piece, brush_node->node, 1);
589 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl1, 1);
590 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl2, 1);
591 }
592 }
593
594 if(mask_form->type & DT_MASKS_CLONE)
595 {
596 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
597 module->distort_backtransform(module, NULL, &piece, mask_form->source, 1);
598 }
599
600 mask_form->version = 2;
601
602 return 0;
603 }
604}
605
606static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
607{
608 const float image_width = (float)image->width;
609 const float image_height = (float)image->height;
610
611 const float crop_x = (float)image->crop_x;
612 const float crop_y = (float)image->crop_y;
613
614 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
615 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
616
617 /*
618 * masks coordinates are normalized, so we need to:
619 * 1. de-normalize them by image original cropped dimensions
620 * 2. un-crop them by adding top-left crop coordinates
621 * 3. normalize them by the image fully uncropped dimensions
622 */
623 coords[0] = ((coords[0] * crop_width) + crop_x) / image_width;
624 coords[1] = ((coords[1] * crop_height) + crop_y) / image_height;
625}
626
628 size_t coords_count)
629{
630 const float image_width = (float)image->width;
631 const float image_height = (float)image->height;
632
633 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
634 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
635
636 /*
637 * masks coordinates are normalized, so we need to:
638 * 1. de-normalize them by minimal of image original cropped dimensions
639 * 2. normalize them by the minimal of image fully uncropped dimensions
640 */
641 const float crop_min = MIN(crop_width, crop_height);
642 const float image_min = MIN(image_width, image_height);
643 for(size_t coord_index = 0; coord_index < coords_count; coord_index++)
644 coords[coord_index] = ((coords[coord_index] * crop_min)) / image_min;
645}
646
647static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *develop, void *params)
648{
649 /*
650 * difference: before v3 images were originally cropped on load
651 * after v3: images are cropped in rawprepare iop.
652 */
653
654 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
655
656 const dt_image_t *image = &(develop->image_storage);
657
658 if(image->crop_x == 0 && image->crop_y == 0 && image->crop_width == 0 && image->crop_height == 0)
659 {
660 // image has no "raw cropping", we're fine!
661 mask_form->version = 3;
662 return 0;
663 }
664 else
665 {
666 GList *point_node = mask_form->points;
667
668 if(IS_NULL_PTR(point_node)) return 1;
669
670 if(mask_form->type & DT_MASKS_CIRCLE)
671 {
672 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
673 if(IS_NULL_PTR(circle)) return 1;
677 }
678 else if(mask_form->type & DT_MASKS_POLYGON)
679 {
680 for(; point_node; point_node = g_list_next(point_node))
681 {
682 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
683 if(IS_NULL_PTR(polygon_node)) return 1;
688 }
689 }
690 else if(mask_form->type & DT_MASKS_GRADIENT)
691 {
692 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
694 }
695 else if(mask_form->type & DT_MASKS_ELLIPSE)
696 {
697 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
701 }
702 else if(mask_form->type & DT_MASKS_BRUSH)
703 {
704 for(; point_node; point_node = g_list_next(point_node))
705 {
706 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
707 if(IS_NULL_PTR(brush_node)) return 1;
712 }
713 }
714
715 if(mask_form->type & DT_MASKS_CLONE)
716 {
717 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
719 }
720
721 mask_form->version = 3;
722
723 return 0;
724 }
725}
726
727static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *develop, void *params)
728{
729 /*
730 * difference affecting ellipse
731 * up to v3: only equidistant feathering
732 * after v4: choice between equidistant and proportional feathering
733 * type of feathering is defined in new flags parameter
734 */
735
736 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
737
738 GList *point_node = mask_form->points;
739
740 if(IS_NULL_PTR(point_node)) return 1;
741
742 if(mask_form->type & DT_MASKS_ELLIPSE)
743 {
744 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
746 }
747
748 mask_form->version = 4;
749
750 return 0;
751}
752
753
754static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *develop, void *params)
755{
756 /*
757 * difference affecting gradient
758 * up to v4: only linear gradient (relative to input image)
759 * after v5: curved gradients
760 */
761
762 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
763
764 GList *point_node = mask_form->points;
765
766 if(IS_NULL_PTR(point_node)) return 1;
767
768 if(mask_form->type & DT_MASKS_GRADIENT)
769 {
770 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
771 gradient->curvature = 0.0f;
772 }
773
774 mask_form->version = 5;
775
776 return 0;
777}
778
779static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *develop, void *params)
780{
781 /*
782 * difference affecting gradient
783 * up to v5: linear transition
784 * after v5: linear or sigmoidal transition
785 */
786
787 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
788
789 GList *point_node = mask_form->points;
790
791 if(IS_NULL_PTR(point_node)) return 1;
792
793 if(mask_form->type & DT_MASKS_GRADIENT)
794 {
795 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
797 }
798
799 mask_form->version = 6;
800
801 return 0;
802}
803
804
805int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
806{
807 int result = 1;
808#if 0 // we should not need this any longer
809 if(old_version == 1 && new_version == 2)
810 {
811 result = dt_masks_legacy_params_v1_to_v2(develop, params);
812 }
813#endif
814
815 if(old_version == 1 && new_version == 6)
816 {
817 result = dt_masks_legacy_params_v1_to_v2(develop, params);
818 if(!result) result = dt_masks_legacy_params_v2_to_v3(develop, params);
819 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
820 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
821 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
822 }
823 else if(old_version == 2 && new_version == 6)
824 {
825 result = dt_masks_legacy_params_v2_to_v3(develop, params);
826 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
827 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
828 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
829 }
830 else if(old_version == 3 && new_version == 6)
831 {
832 result = dt_masks_legacy_params_v3_to_v4(develop, params);
833 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
834 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
835 }
836 else if(old_version == 4 && new_version == 6)
837 {
838 result = dt_masks_legacy_params_v4_to_v5(develop, params);
839 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
840 }
841 else if(old_version == 5 && new_version == 6)
842 {
843 result = dt_masks_legacy_params_v5_to_v6(develop, params);
844 }
845
846 return result;
847}
848
849static int form_id_seed = 0;
850
852{
853 dt_masks_form_t *mask_form = (dt_masks_form_t *)calloc(1, sizeof(dt_masks_form_t));
854 if(IS_NULL_PTR(mask_form)) return NULL;
855
856 // Freshly created: exactly one owner (whoever holds the returned pointer).
857 dt_atomic_set_int(&mask_form->refcount, 1);
858
859 mask_form->type = type;
860 mask_form->version = dt_masks_version();
861 mask_form->formid = time(NULL) + form_id_seed++;
863
864 if (type & DT_MASKS_CIRCLE)
866 else if (type & DT_MASKS_ELLIPSE)
868 else if (type & DT_MASKS_BRUSH)
870 else if (type & DT_MASKS_POLYGON)
872 else if (type & DT_MASKS_GRADIENT)
874 else if (type & DT_MASKS_GROUP)
876
877 if (mask_form->functions && mask_form->functions->sanitize_config)
878 mask_form->functions->sanitize_config(type);
879
881
882 return mask_form;
883}
884
886{
889
890 // all forms created here are registered in dev->allforms for later cleanup
891 if(mask_form)
892 dev->allforms = g_list_append(dev->allforms, mask_form);
893
895
896 return mask_form;
897}
898
899dt_masks_form_t *dt_masks_get_from_id_ext(GList *form_list, int form_id)
900{
901 for(; form_list; form_list = g_list_next(form_list))
902 {
903 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_list->data;
904 if(mask_form->formid == form_id) return mask_form;
905 }
906 return NULL;
907}
908
910{
912 dt_masks_form_t *result = dt_masks_get_from_id_ext(develop->forms, form_id);
914 return result;
915}
916
918{
919 for(GList *module_node = g_list_first(develop->iop); module_node; module_node = g_list_next(module_node))
920 {
921 dt_iop_module_t *module = (dt_iop_module_t *)(module_node->data);
922 if(strcmp(module->op, "mask_manager") == 0)
923 return module;
924 }
925 return NULL;
926}
927
928static void _masks_fill_used_forms(GList *forms_list, const int form_id, int *used_form_ids,
929 const int used_count)
930{
931 for(int used_index = 0; used_index < used_count; used_index++)
932 {
933 if(used_form_ids[used_index] == 0)
934 {
935 used_form_ids[used_index] = form_id;
936 break;
937 }
938 if(used_form_ids[used_index] == form_id) break;
939 }
940
941 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(forms_list, form_id);
942 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
943 {
944 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
945 {
946 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
947 _masks_fill_used_forms(forms_list, group_entry->formid, used_form_ids, used_count);
948 }
949 }
950}
951
953 const dt_iop_module_t *source_module)
954{
955 if(!(source_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)) return 0;
956 if(source_module->blend_params->mask_id <= 0) return 0;
957
958 const guint form_count = g_list_length(develop_src->forms);
959 if(form_count == 0) return 0;
960
961 int *used_form_ids = dt_calloc_align(form_count * sizeof(int));
962 if(IS_NULL_PTR(used_form_ids)) return 1;
963
964 _masks_fill_used_forms(develop_src->forms, source_module->blend_params->mask_id,
965 used_form_ids, form_count);
966
967 for(int form_index = 0; form_index < (int)form_count && used_form_ids[form_index] > 0; form_index++)
968 {
969 dt_masks_form_t *mask_form = dt_masks_get_from_id(develop_src, used_form_ids[form_index]);
970 if(!IS_NULL_PTR(mask_form))
971 {
972 dt_masks_form_t *existing_form = dt_masks_get_from_id_ext(develop_dest->forms,
973 used_form_ids[form_index]);
974 if(existing_form)
975 {
976 develop_dest->forms = g_list_remove(develop_dest->forms, existing_form);
977 develop_dest->allforms = g_list_append(develop_dest->allforms, existing_form);
978 }
979
980 dt_masks_form_t *new_form = dt_masks_dup_masks_form(mask_form);
981 if(IS_NULL_PTR(new_form))
982 {
983 dt_free_align(used_form_ids);
984 return 1;
985 }
986 develop_dest->forms = g_list_append(develop_dest->forms, new_form);
987 }
988 else
989 {
990 fprintf(stderr, "[dt_masks_copy_used_forms_for_module] form %i not found in source image\n",
991 used_form_ids[form_index]);
992 }
993 }
994
995 dt_free_align(used_form_ids);
996 return 0;
997}
998
1007
1008static void _read_mask_row(void *user_data, const int history_num, const int form_id,
1009 const int form, const char *name, const int version,
1010 const void *points, const int points_len, const int point_count,
1011 const void *source, const int source_len)
1012{
1013 _masks_read_ctx_t *ctx = (_masks_read_ctx_t *)user_data;
1014 dt_develop_t *develop = ctx->develop;
1015
1016 const dt_masks_type_t mask_type = form;
1017 dt_masks_form_t *mask_form = dt_masks_create(mask_type);
1018 mask_form->formid = form_id;
1019 g_strlcpy(mask_form->name, name, sizeof(mask_form->name));
1020 mask_form->version = version;
1021 mask_form->points = NULL;
1022 memcpy(mask_form->source, source, sizeof(float) * 2);
1023
1024 // and now we "read" the blob
1025 if(mask_form->functions)
1026 {
1027 const char *const point_buffer = (const char *)points;
1028 const size_t point_struct_size = mask_form->functions->point_struct_size;
1029 for(int point_index = 0; point_index < point_count; point_index++)
1030 {
1031 char *point_data = (char *)malloc(point_struct_size);
1032 memcpy(point_data, point_buffer + point_index * point_struct_size, point_struct_size);
1033 mask_form->points = g_list_append(mask_form->points, point_data);
1034 }
1035 }
1036
1037 if(mask_form->version != dt_masks_version())
1038 {
1039 if(dt_masks_legacy_params(develop, mask_form, mask_form->version, dt_masks_version()))
1040 {
1041 const char *fname = develop->image_storage.filename + strlen(develop->image_storage.filename);
1042 while(fname > develop->image_storage.filename && *fname != '/') fname--;
1043 if(fname > develop->image_storage.filename) fname++;
1044
1045 fprintf(stderr,
1046 "[_dev_read_masks_history] %s (imgid `%i\'): mask version mismatch: history is %d, dt %d.\n",
1047 fname, ctx->image_id, mask_form->version, dt_masks_version());
1048 dt_history_message(_("%s: mask version mismatch: %d != %d"),
1049 fname, dt_masks_version(), mask_form->version);
1050
1051 // was a `continue` over the rest of the loop body
1052 return;
1053 }
1054 }
1055
1056 // Not computed here: dt_masks_replace_current_forms() below (via
1057 // dt_masks_form_update_gravity_center() in masks_history.c) already computes it for
1058 // every form that actually ends up live in dev->forms. Computing it here too would
1059 // redundantly do it for every history step's row read from masks_history -- including
1060 // every duplicate of a form shared unchanged across many steps (see the un-deduped
1061 // masks_history table, doc/masks_history_dedup.md) -- for forms that are either
1062 // superseded (never read again) or about to be recomputed anyway.
1063
1064 // if this is a new history entry let's find it
1065 if(ctx->previous_num != history_num)
1066 {
1067 ctx->history_item = NULL;
1068 for(GList *history_node = g_list_first(develop->history); history_node; history_node = g_list_next(history_node))
1069 {
1070 dt_dev_history_item_t *history_entry = (dt_dev_history_item_t *)(history_node->data);
1071 if(history_entry->num == history_num)
1072 {
1073 ctx->history_item = history_entry;
1074 break;
1075 }
1076 }
1077 ctx->previous_num = history_num;
1078 }
1079 // add the form to the history entry
1080 // FIXME: there is no reason to hack history_item to add a forms snapshot that doesn't
1081 // belong to it because dt_dev_write_history_item() doesn't save history_item->forms to the DB.
1082 // So this forms snapshot should be attached to its own object, and that object should be
1083 // linked by ID to the history_item object. That would allow to share one forms snapshot
1084 // between several history items without duplication.
1085 if(ctx->history_item)
1086 {
1087 ctx->history_item->forms = g_list_append(ctx->history_item->forms, mask_form);
1088 }
1089 else
1090 fprintf(stderr,
1091 "[_dev_read_masks_history] can't find history entry %i while adding mask %s(%i)\n",
1092 history_num, mask_form->name, form_id);
1093
1094 if(history_num < dt_dev_get_history_end_ext(develop)) ctx->last_history_item = ctx->history_item;
1095}
1096
1097void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
1098{
1099 // The per-row state the loop used to keep in locals lives in the context: which history entry
1100 // the previous row belonged to, and the last one at or below history_end.
1101 _masks_read_ctx_t ctx = { .develop = develop, .image_id = image_id, .history_item = NULL,
1102 .last_history_item = NULL, .previous_num = -1 };
1103
1105
1106 // and we update the current forms snapshot
1108}
1109
1110void dt_masks_write_masks_history_item(const int32_t image_id, const int history_num,
1111 dt_masks_form_t *mask_form)
1112{
1113 dt_print(DT_DEBUG_HISTORY, "[dt_masks_write_masks_history_item] writing mask %s of type %i for image %i\n",
1114 mask_form->name, mask_form->type, image_id);
1115
1116 // The points are a flat array of the shape's own point struct; only this layer knows its size.
1117 const size_t point_struct_size = mask_form->functions ? mask_form->functions->point_struct_size : 0;
1118 const guint point_count = mask_form->functions ? g_list_length(mask_form->points) : 0;
1119
1120 // Preserved: with no functions there is no point struct to serialise, and the original wrote
1121 // nothing at all in that case -- the whole INSERT sat inside this test.
1122 if(!mask_form->functions) return;
1123
1124 char *const restrict point_buffer = (char *)dt_alloc_align(point_count * point_struct_size);
1125 int buffer_offset = 0;
1126 for(GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1127 {
1128 memcpy(point_buffer + buffer_offset, point_node->data, point_struct_size);
1129 buffer_offset += point_struct_size;
1130 }
1131
1132 dt_history_repository_write_mask_item(image_id, history_num, mask_form->formid, mask_form->type,
1133 mask_form->name, mask_form->version, point_buffer,
1134 point_count * point_struct_size, point_count,
1135 mask_form->source, 2 * sizeof(float));
1136
1137 dt_free_align(point_buffer);
1138}
1139
1141{
1142 if(IS_NULL_PTR(mask_form)) return;
1143 g_list_free_full(mask_form->points, dt_free_gpointer);
1144 mask_form->points = NULL;
1145 dt_free(mask_form);
1146}
1147
1149{
1150 if(IS_NULL_PTR(module) || IS_NULL_PTR(module->dev)) return DT_MASKS_EDIT_OFF;
1151 return module->dev->form_gui
1152 ? module->dev->form_gui->edit_mode
1153 : DT_MASKS_EDIT_OFF;
1154}
1155
1156
1158{
1159 if(IS_NULL_PTR(module) || IS_NULL_PTR(source_module)) return;
1160
1161 // we get the source group
1162 int source_id = source_module->blend_params->mask_id;
1163 dt_masks_form_t *source_group = dt_masks_get_from_id(module->dev, source_id);
1164 if(IS_NULL_PTR(source_group) || source_group->type != DT_MASKS_GROUP) return;
1165
1166 // is there already a masks group for this module ?
1167 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
1168 if(IS_NULL_PTR(group_form))
1169 {
1170 group_form = _group_create(module->dev, module, DT_MASKS_GROUP);
1171 }
1172 // Touch once, before the loop: dt_masks_group_add_form mutates group_form->points on every
1173 // iteration, so it must already be private by the first call, or later iterations would
1174 // append to an orphaned clone instead of the object actually in dev->forms.
1175 group_form = dt_masks_cow_touch(module->dev, group_form);
1176 // we copy the src group in this group
1177 for(GList *group_node = source_group->points; group_node; group_node = g_list_next(group_node))
1178 {
1179 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1180 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, group_entry->formid);
1181 if(mask_form)
1182 {
1183 dt_masks_form_group_t *new_entry = dt_masks_group_add_form(module->dev, group_form, mask_form);
1184 if(new_entry)
1185 {
1186 new_entry->state = group_entry->state;
1187 new_entry->opacity = group_entry->opacity;
1188 }
1189 }
1190 }
1191
1192 // we save the group
1193
1194}
1195
1197 dt_masks_form_t *mask_form)
1198{
1199 if(IS_NULL_PTR(mask_form)) return;
1200 int form_id = mask_form->formid;
1201 if(!IS_NULL_PTR(group_form) && !(group_form->type & DT_MASKS_GROUP)) return;
1202
1203 if(!(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) && !IS_NULL_PTR(group_form))
1204 {
1205 group_form = dt_masks_cow_touch(dev, group_form);
1206 // we try to remove the form from the masks group
1207 int removed = 0;
1208 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1209 {
1210 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1211 if(group_entry->formid == form_id)
1212 {
1213 removed = 1;
1214 group_form->points = g_list_remove(group_form->points, group_entry);
1215 dt_free(group_entry);
1216 break;
1217 }
1218 }
1219 if(removed)
1220 if(removed && !IS_NULL_PTR(module))
1221 {
1223
1224 }
1225 if(removed) dt_masks_form_update_gravity_center(dev, group_form);
1226 if(removed && IS_NULL_PTR(group_form->points)) dt_masks_form_delete(dev, module, NULL, group_form);
1227 return;
1228 }
1229
1230 if(mask_form->type & DT_MASKS_GROUP && mask_form->type & DT_MASKS_CLONE)
1231 {
1232 // when removing a cloning group the children have to be removed, too, as they won't be shown in the shape manager
1233 // and are thus not accessible afterwards.
1234 while(mask_form->points)
1235 {
1236 dt_masks_form_group_t *group_child = (dt_masks_form_group_t *)mask_form->points->data;
1237 dt_masks_form_t *child = dt_masks_get_from_id(dev, group_child->formid);
1238 dt_masks_form_delete(dev, module, mask_form, child);
1239 // The recursive call passes mask_form as its own group_form parameter and may have
1240 // COW-cloned it (see the touch above): re-fetch the live object so this loop keeps
1241 // observing the mutation instead of spinning on a stale, now-orphaned copy.
1242 mask_form = dt_masks_get_from_id(dev, form_id);
1243 if(IS_NULL_PTR(mask_form)) break;
1244 }
1245 }
1246
1247 // if we are here that mean we have to permanently delete this form
1248 // we drop the form from all modules
1249 for(GList *iop_node = dev->iop; iop_node; iop_node = g_list_next(iop_node))
1250 {
1251 dt_iop_module_t *iop_module = (dt_iop_module_t *)iop_node->data;
1252 if(iop_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
1253 {
1254 // is the form the base group of the iop ?
1255 if(form_id == iop_module->blend_params->mask_id)
1256 {
1257 iop_module->blend_params->mask_id = 0;
1259 }
1260 else
1261 {
1262 dt_masks_form_t *iop_group = _group_from_module(dev, iop_module);
1263 if(iop_group && (iop_group->type & DT_MASKS_GROUP))
1264 {
1265 iop_group = dt_masks_cow_touch(dev, iop_group);
1266 int removed = 0;
1267 GList *shapes = iop_group->points;
1268 while(shapes)
1269 {
1270 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)shapes->data;
1271 if(group_entry->formid == form_id)
1272 {
1273 removed = 1;
1274 // Remove the shape from the list
1275 iop_group->points = g_list_remove(iop_group->points, group_entry);
1276 dt_free(group_entry);
1277 shapes = iop_group->points; // jump back to start of list
1278 continue;
1279 }
1280 shapes = g_list_next(shapes); // advance to next form
1281 }
1282 if(removed)
1283 {
1285
1286 if(IS_NULL_PTR(iop_group->points)) dt_masks_form_delete(dev, iop_module, NULL, iop_group);
1287 }
1288 }
1289 }
1290 }
1291 }
1292 // we drop the form from the general list
1293 for(GList *form_node = dev->forms; form_node; form_node = g_list_next(form_node))
1294 {
1295 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1296 if(existing_form->formid == form_id)
1297 {
1298 dt_masks_remove_form(dev, existing_form);
1299 break;
1300 }
1301 }
1302}
1303
1305{
1306 if(IS_NULL_PTR(mask_form)) return;
1307
1308 float center_point[2];
1309 float area = 0.0f;
1310 const gboolean ok = dt_masks_form_get_gravity_center(dev, mask_form, center_point, &area);
1311 mask_form->gravity_center[0] = center_point[0];
1312 mask_form->gravity_center[1] = center_point[1];
1313 mask_form->area = area;
1314 mask_form->gravity_center_valid = TRUE;
1315
1317 "[masks] gravity center updated: form=%p id=%d type=0x%x ok=%d center=(%f,%f), area=%f\n",
1318 (void *)mask_form, mask_form->formid, mask_form->type, ok,
1319 mask_form->gravity_center[0], mask_form->gravity_center[1], mask_form->area);
1320}
1321
1323{
1324 if(IS_NULL_PTR(mask_form)) return;
1325 mask_form->gravity_center_valid = FALSE;
1326}
1327
1328
1329/* dt_masks_set_edit_mode, _change_opacity and dt_masks_form_change_opacity moved to
1330 * masks_gui.c: edit-mode pokes the blending toggle, and opacity is scroll-interaction
1331 * machinery whose toast belongs beside its events. Declarations unchanged. */
1333 size_t node_size)
1334{
1335 if(IS_NULL_PTR(base_form) || IS_NULL_PTR(dest_form) || IS_NULL_PTR(base_form->points) || node_size == 0) return;
1336
1337 for(const GList *point_node = base_form->points; point_node; point_node = g_list_next(point_node))
1338 {
1339 const void *point_data = point_node->data;
1340 if(IS_NULL_PTR(point_data)) continue;
1341 void *point_copy = malloc(node_size);
1342 if(IS_NULL_PTR(point_copy)) continue;
1343 memcpy(point_copy, point_data, node_size);
1344 dest_form->points = g_list_append(dest_form->points, point_copy);
1345 }
1346}
1347
1348
1349void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
1350{
1351 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return;
1352
1353 // we search the form in the group
1354 dt_masks_form_group_t *group_entry = NULL;
1355 guint group_index = 0;
1356 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1357 {
1358 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)group_node->data;
1359 if(entry->formid == form_id)
1360 {
1361 group_entry = entry;
1362 break;
1363 }
1364 group_index++;
1365 }
1366
1367 // we remove the form and read it
1368 if(!IS_NULL_PTR(group_entry))
1369 {
1370 const guint group_length = g_list_length(group_form->points);
1371 if(!move_up && group_index == 0) return;
1372 if(move_up && group_index == group_length - 1) return;
1373
1374 group_form->points = g_list_remove(group_form->points, group_entry);
1375 if(!move_up)
1376 group_index -= 1;
1377 else
1378 group_index += 1;
1379 group_form->points = g_list_insert(group_form->points, group_entry, group_index);
1380
1381 }
1382}
1383
1384int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
1385{
1386 if(!(group_form->type & DT_MASKS_GROUP)) return 0;
1387 if(group_form->formid == form_id) return 1;
1388 int nested_count = 0;
1389 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1390 {
1391 const dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1392 dt_masks_form_t *mask_form = dt_masks_get_from_id(dev, group_entry->formid);
1393 if(mask_form)
1394 {
1395 if(mask_form->type & DT_MASKS_GROUP) nested_count += _find_in_group(dev, mask_form, form_id);
1396 }
1397 }
1398 return nested_count;
1399}
1400
1402{
1403 if(IS_NULL_PTR(mask_form)) return hash;
1404
1405 // basic infos
1406 hash = dt_hash(hash, (char *)&mask_form->type, sizeof(dt_masks_type_t));
1407 hash = dt_hash(hash, (char *)&mask_form->formid, sizeof(int));
1408 hash = dt_hash(hash, (char *)&mask_form->version, sizeof(int));
1409 hash = dt_hash(hash, (char *)&mask_form->source, sizeof(float) * 2);
1410
1411 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1412 {
1413 if(mask_form->type & DT_MASKS_GROUP)
1414 {
1415 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)point_node->data;
1416 // Children must come from the SAME list the top-level group was resolved from. Resolving
1417 // them from live dev->forms while hashing a history/pipe snapshot mixes two states into
1418 // one hash: two genuinely different states can then hash identically (and vice versa),
1419 // which defeats the cache invalidation this hash exists for.
1420 dt_masks_form_t *child_form = dt_masks_get_from_id_ext(masks, group_entry->formid);
1421 if(child_form)
1422 {
1423 // state & opacity
1424 hash = dt_hash(hash, (char *)&group_entry->state, sizeof(int));
1425 hash = dt_hash(hash, (char *)&group_entry->opacity, sizeof(float));
1426
1427 // the form itself
1428 hash = dt_masks_group_get_hash_ext(hash, masks, child_form);
1429 }
1430 }
1431 else if(mask_form->functions)
1432 {
1433 hash = dt_hash(hash, (char *)point_node->data, mask_form->functions->point_struct_size);
1434 }
1435 }
1436 return hash;
1437}
1438
1440{
1441 if(IS_NULL_PTR(mask_form)) return hash;
1442
1443 // basic infos
1444 hash = dt_hash(hash, (const char *)&mask_form->type, sizeof(dt_masks_type_t));
1445 hash = dt_hash(hash, (const char *)&mask_form->formid, sizeof(int));
1446 hash = dt_hash(hash, (const char *)&mask_form->version, sizeof(int));
1447 hash = dt_hash(hash, (const char *)&mask_form->source, sizeof(float) * 2);
1448
1449 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
1450 {
1451 if(mask_form->type & DT_MASKS_GROUP)
1452 {
1453 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)point_node->data;
1454 // Membership only (id + state/opacity): the referenced form's own content is hashed
1455 // once, separately, when dev->forms reaches that form's own top-level entry. Recursing
1456 // into it here would re-hash every grouped shape's full point list a second time.
1457 if(dt_masks_get_from_id_ext(masks, group_entry->formid))
1458 {
1459 hash = dt_hash(hash, (const char *)&group_entry->formid, sizeof(int));
1460 hash = dt_hash(hash, (const char *)&group_entry->state, sizeof(int));
1461 hash = dt_hash(hash, (const char *)&group_entry->opacity, sizeof(float));
1462 }
1463 }
1464 else if(mask_form->functions)
1465 {
1466 hash = dt_hash(hash, (const char *)point_node->data, mask_form->functions->point_struct_size);
1467 }
1468 }
1469 return hash;
1470}
1471
1472/* Marks form_id as used, and every form a group transitively contains.
1473 *
1474 * The set is unbounded on purpose. Its members are not a subset of form_list: every
1475 * blend_params->mask_id ever recorded in history is fed in, groups of modules whose mask was
1476 * since dropped included, and those ids have no form to match in the snapshot being cleaned.
1477 * A fixed table sized on the snapshot's form count therefore fills up on ids that answer
1478 * nothing, and the members discovered last -- the tail of the one group that IS live -- find no
1479 * slot left and are silently taken for unused. Measured on a 20-step history: four departed
1480 * groups ate the eight slots an 8-form snapshot allowed, and the two last shapes of the module's
1481 * own mask group were deleted while the module was still using them.
1482 *
1483 * Re-entering an already-marked id also stops the walk here rather than after it: a group that
1484 * contains itself through some chain of member groups would otherwise not terminate. */
1485static void _cleanup_unused_recurs(GList *form_list, int form_id, GHashTable *used_form_ids)
1486{
1487 if(!g_hash_table_add(used_form_ids, GINT_TO_POINTER(form_id))) return;
1488
1489 // if the form is a group, we iterate through the sub-forms
1490 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(form_list, form_id);
1491 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
1492 {
1493 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
1494 {
1495 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1496 _cleanup_unused_recurs(form_list, group_entry->formid, used_form_ids);
1497 }
1498 }
1499}
1500
1501// removes from _forms all forms that are not used in history_list up to history_end
1502static int _masks_cleanup_unused(dt_develop_t *dev, GList **forms_list, GList *history_list, const int history_end)
1503{
1504 int masks_removed = 0;
1505 GList *forms = *forms_list;
1506
1507 // the set of ids used by the history entries we are about to walk
1508 GHashTable *used_form_ids = g_hash_table_new(g_direct_hash, g_direct_equal);
1509
1510 // check in history if the module has drawn masks and add it to used array
1511 int history_index = 0;
1512 for(GList *history_node = history_list; history_node && history_index < history_end;
1513 history_node = g_list_next(history_node))
1514 {
1515 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1516 dt_develop_blend_params_t *blend_params
1517 = history_item && history_item->blendop_params_size == sizeof(dt_develop_blend_params_t)
1518 ? history_item->blend_params
1519 : NULL;
1520 if(blend_params)
1521 {
1522 if(blend_params->mask_id > 0)
1523 _cleanup_unused_recurs(forms, blend_params->mask_id, used_form_ids);
1524 }
1525 history_index++;
1526 }
1527
1528 // and we delete all unused forms
1529 GList *shape_node = forms;
1530 while(shape_node)
1531 {
1532 dt_masks_form_t *mask_form = (dt_masks_form_t *)shape_node->data;
1533 const gboolean is_used = g_hash_table_contains(used_form_ids, GINT_TO_POINTER(mask_form->formid));
1534
1535 shape_node = g_list_next(shape_node); // need to get 'next' now, because we may be removing the current node
1536
1537 if(!is_used)
1538 {
1539 forms = g_list_remove(forms, mask_form);
1540 // This list's reference is handed over to dev->allforms rather than released: a form read
1541 // from masks_history is created by dt_masks_create() and its snapshot membership is its
1542 // only claim, so unref-ing here would free an object dev->forms may still be holding by
1543 // address. One allforms entry per transferred claim keeps the teardown balanced.
1544 dev->allforms = g_list_append(dev->allforms, mask_form);
1545 masks_removed = 1;
1546 }
1547 }
1548
1549 g_hash_table_destroy(used_form_ids);
1550
1551 *forms_list = forms;
1552
1553 return masks_removed;
1554}
1555
1556// removes all unused form from history
1563static void _masks_cleanup_unused_from_list(dt_develop_t *dev, GList *history_list)
1564{
1565 // a mask is used in a given hist->forms entry if it is used up to the next hist->forms
1566 // so we are going to remove for each hist->forms from the top
1567 int history_count = g_list_length(history_list);
1568 int history_end = history_count;
1569 for(const GList *history_node = g_list_last(history_list); history_node;
1570 history_node = g_list_previous(history_node))
1571 {
1572 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1573 if(!IS_NULL_PTR(history_item->forms)) //&& strcmp(history_item->op_name, "mask_manager") == 0)
1574 {
1575 _masks_cleanup_unused(dev, &history_item->forms, history_list, history_end);
1576 history_end = history_count - 1;
1577 }
1578 history_count--;
1579 }
1580}
1581
1588{
1589 dt_masks_change_form_gui(develop, NULL);
1590
1591 /* The sweep rewrites every hist->forms in place, and the async DB write job walks those same
1592 * lists with history_mutex released. Its snapshot holds a reference on each history ITEM,
1593 * which keeps the item alive but says nothing about the list cells g_list_remove() frees
1594 * under it. Hold the writer across the sweep and the re-point that reads its result, so the
1595 * job sees the image either fully swept or untouched. Order is history_mutex outer,
1596 * masks_mutex (taken by dt_masks_replace_current_forms) inner -- the same way a history
1597 * commit takes them. */
1599
1600 // we remove the forms from history
1601 _masks_cleanup_unused_from_list(develop, develop->history);
1602
1603 // and we save all that
1604 GList *forms = NULL;
1605 int history_index = 0;
1606 for(const GList *history_node = g_list_first(develop->history);
1607 history_node && history_index < dt_dev_get_history_end_ext(develop);
1608 history_node = g_list_next(history_node))
1609 {
1610 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
1611
1612 if(history_item->forms) forms = history_item->forms;
1613 history_index++;
1614 }
1615
1616 dt_masks_replace_current_forms(develop, forms);
1617
1619}
1620
1621#include "detail.c"
1622
1623/* The two rasterisation dispatchers. They were inline in masks.h, which forced the
1624 * per-shape function table to be public; a per-buffer call is not a per-pixel cost,
1625 * so the inline bought nothing and the table is private now. */
1627 const dt_dev_pixelpipe_iop_t *const piece,
1628 dt_masks_form_t *const form,
1629 float **buffer, int *width, int *height, int *posx, int *posy)
1630{
1631 *buffer = NULL;
1632 *width = 0;
1633 *height = 0;
1634 *posx = 0;
1635 *posy = 0;
1636 /* A shape type with no rasteriser is a programming error, not an empty shape. */
1637 return (form->functions && form->functions->get_mask)
1638 ? form->functions->get_mask(module, pipe, piece, form, buffer, width, height, posx, posy)
1640}
1641
1643 const dt_dev_pixelpipe_iop_t *const piece,
1644 dt_masks_form_t *const form, const dt_iop_roi_t *roi,
1645 float *buffer, dt_iop_roi_t *touched)
1646{
1647 dt_masks_touched_none(touched);
1648 /* A shape type with no rasteriser is a programming error, not an empty shape: report ERROR so
1649 * the fold refuses to publish a buffer nobody wrote. */
1650 return (form->functions && form->functions->get_mask_roi)
1651 ? form->functions->get_mask_roi(module, pipe, piece, form, roi, buffer, touched)
1653}
1654
1655
1656/* ==========================================================================================
1657 * The read side of the group API (develop/masks_group.h).
1658 *
1659 * All three are thread-neutral by construction: they read only the object they are handed and
1660 * take no lock. That is what lets the pipeline call them on a snapshot and the GUI call them on
1661 * the live list with the same code.
1662 * ========================================================================================== */
1663
1665{
1666 /* `out' is left untouched on FALSE so a caller may keep a default in it across a failed call --
1667 * the dt_colorspaces_profile_at() convention. */
1668 if(IS_NULL_PTR(form) || IS_NULL_PTR(out)) return FALSE;
1669
1670 out->formid = form->formid;
1671 out->type = form->type;
1672 out->version = form->version;
1673 out->is_group = (form->type & DT_MASKS_GROUP) != 0;
1674 out->is_retouch = (form->type & DT_MASKS_IS_RETOUCHE) != 0;
1675 /* Not recursive: the group's own rows, which is what compositing order is defined over. */
1676 out->member_count = out->is_group ? g_list_length(form->points) : 0;
1677 g_strlcpy(out->name, form->name, sizeof(out->name));
1678 return TRUE;
1679}
1680
1681/* One place builds the value type callers see, so a row is never copied out field by field at
1682 * three separate call sites -- each of which would have to be found again the day
1683 * dt_masks_member_t grows a field. @p entry may be NULL: the member comes back zeroed but KEEPS
1684 * its index, for the reason dt_masks_group_copy_members() spells out below. */
1685static void _member_from_entry(dt_masks_member_t *const out, const dt_masks_form_group_t *const entry,
1686 const guint index)
1687{
1688 if(IS_NULL_PTR(out)) return;
1689
1690 out->index = index;
1691
1692 if(IS_NULL_PTR(entry))
1693 {
1694 out->formid = 0;
1695 out->parentid = 0;
1696 out->state = DT_MASKS_STATE_NONE;
1697 out->opacity = 0.0f;
1698 return;
1699 }
1700
1701 out->formid = entry->formid;
1702 out->parentid = entry->parentid;
1703 out->state = (dt_masks_state_t)entry->state;
1704 out->opacity = entry->opacity;
1705}
1706
1707
1708/* Resolve one membership row from (group, formid), the identity the whole write API is keyed on.
1709 *
1710 * @p touch says whether the caller is about to MUTATE the row, and the asymmetry is the point:
1711 * - a writer must touch FIRST and resolve from what the touch returned, because cloning a group
1712 * clones its membership blocks with it -- a row resolved before the touch belongs to the copy
1713 * that was just abandoned, and the mutation lands in memory nothing reads;
1714 * - a reader must NOT touch. Copy-on-write is for writers; touching on a read would clone a
1715 * shared group every time the GUI merely asks what a shape's opacity is.
1716 */
1717static dt_masks_form_group_t *_resolve_member(dt_develop_t *dev, const int group_id, const int formid,
1718 const gboolean touch, guint *const out_index)
1719{
1720 if(!IS_NULL_PTR(out_index)) *out_index = 0;
1721
1722 dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1723 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return NULL;
1724
1725 if(touch) group = dt_masks_cow_touch(dev, group);
1726 if(IS_NULL_PTR(group)) return NULL;
1727
1728 guint index = 0;
1729 for(GList *node = group->points; node; node = g_list_next(node), index++)
1730 {
1731 dt_masks_form_group_t *const entry = (dt_masks_form_group_t *)node->data;
1732 if(IS_NULL_PTR(entry) || entry->formid != formid) continue;
1733
1734 if(!IS_NULL_PTR(out_index)) *out_index = index;
1735 return entry;
1736 }
1737
1738 return NULL;
1739}
1740
1741
1743 const guint out_max)
1744{
1745 /* Refusing anything that is not a group is what keeps the polymorphic ->points unreachable from
1746 * outside: for a group it holds membership rows, for every other form it holds geometry nodes,
1747 * and the only thing telling them apart is this bit. */
1748 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return 0;
1749
1750 guint total = 0;
1751 for(const GList *node = group->points; node; node = g_list_next(node), total++)
1752 {
1753 if(IS_NULL_PTR(out) || total >= out_max) continue;
1754
1755 /* A row that cannot be read STILL CONSUMES ITS INDEX. Position is the compositing order and
1756 * the index into retouch's rt_forms[] and spots' clone_algo[], both persisted in the user's
1757 * database, so dropping a row here would silently re-pair every later shape with the wrong
1758 * algorithm. _member_from_entry() zeroes it instead, index kept. */
1759 _member_from_entry(&out[total], (const dt_masks_form_group_t *)node->data, total);
1760 }
1761
1762 /* The TOTAL, always -- a caller that passed a short buffer needs to know it was short, and a
1763 * caller that passed NULL is asking for exactly this. */
1764 return total;
1765}
1766
1768{
1769 /* dt_masks_type_t is a bit field, not an enumeration of alternatives, so first match wins and
1770 * this order is load-bearing. It is the order the conf-key builder has always used.
1771 *
1772 * These tokens are PERSISTED: they build plugins/darkroom/<plugin>/<type>/<feature>, declared in
1773 * data/anselconfig.xml.in. "polygon" can never become "path" -- a key outside confgen reads 0,
1774 * which would silently reset the user's fading. */
1775 if(type & DT_MASKS_CIRCLE) return "circle";
1776 else if(type & DT_MASKS_POLYGON) return "polygon";
1777 else if(type & DT_MASKS_ELLIPSE) return "ellipse";
1778 else if(type & DT_MASKS_GRADIENT) return "gradient";
1779 else if(type & DT_MASKS_BRUSH) return "brush";
1780 else if(type & DT_MASKS_GROUP) return "group";
1781 else return "unknown";
1782}
1783
1784
1786 const int formid, const dt_masks_state_t operation,
1788{
1789 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1790 if(operation != DT_MASKS_STATE_INVERSE && !(operation & DT_MASKS_STATE_IS_COMBINE_OP))
1791 return DT_MASKS_INVALID;
1792
1793 guint index = 0;
1794 dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, TRUE, &index);
1795 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
1796
1797 const int before = entry->state;
1798 if(operation == DT_MASKS_STATE_INVERSE)
1799 entry->state ^= DT_MASKS_STATE_INVERSE;
1800 else
1801 entry->state = (entry->state & ~DT_MASKS_STATE_IS_COMBINE_OP) | operation;
1802
1803 _member_from_entry(out, entry, index);
1804 return (entry->state == before) ? DT_MASKS_UNCHANGED : DT_MASKS_OK;
1805}
1806
1807
1809 dt_iop_module_t *module)
1810{
1811 if(IS_NULL_PTR(dev) || IS_NULL_PTR(module)) return DT_MASKS_INVALID;
1812
1813 dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1814 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return DT_MASKS_NOT_FOUND;
1815
1816 gchar *name = dt_masks_group_name_for_module(module);
1817 if(IS_NULL_PTR(name)) return DT_MASKS_INVALID;
1818
1819 // Nothing to write is not a no-op worth cloning a shared group for -- and the caller usually
1820 // turns UNCHANGED into "skip the history commit", the way the other id-keyed setters do.
1821 if(g_strcmp0(group->name, name) == 0)
1822 {
1823 dt_free(name);
1824 return DT_MASKS_UNCHANGED;
1825 }
1826
1827 group = dt_masks_cow_touch(dev, group);
1828 if(IS_NULL_PTR(group))
1829 {
1830 dt_free(name);
1831 return DT_MASKS_NOT_FOUND;
1832 }
1833
1834 g_strlcpy(group->name, name, sizeof(group->name));
1835 dt_free(name);
1836 return DT_MASKS_OK;
1837}
1838
1839
1840/* Depth-first walk for the group that references @p formid. @p grp NULL means "start from every
1841 * top-level group in dev->forms". @p max_depth is a cycle brake, not a modelling limit: a group
1842 * referencing an ancestor of itself would otherwise recurse forever, and nothing in the data model
1843 * forbids one being written to XMP. */
1844static dt_masks_form_t *_find_holder(dt_develop_t *dev, dt_masks_form_t *grp, const int formid,
1845 const int max_depth)
1846{
1847 if(max_depth <= 0) return NULL;
1848
1849 if(IS_NULL_PTR(grp))
1850 {
1851 for(GList *forms = dev->forms; forms; forms = g_list_next(forms))
1852 {
1853 dt_masks_form_t *const form = (dt_masks_form_t *)forms->data;
1854 if(IS_NULL_PTR(form) || !(form->type & DT_MASKS_GROUP)) continue;
1855
1856 dt_masks_form_t *const found = _find_holder(dev, form, formid, max_depth - 1);
1857 if(!IS_NULL_PTR(found)) return found;
1858 }
1859 return NULL;
1860 }
1861
1862 for(GList *points = grp->points; points; points = g_list_next(points))
1863 {
1864 const dt_masks_form_group_t *const point = (const dt_masks_form_group_t *)points->data;
1865 if(IS_NULL_PTR(point)) continue;
1866 if(point->formid == formid) return grp;
1867
1868 dt_masks_form_t *const sub = dt_masks_get_from_id(dev, point->formid);
1869 if(IS_NULL_PTR(sub) || !(sub->type & DT_MASKS_GROUP)) continue;
1870
1871 dt_masks_form_t *const found = _find_holder(dev, sub, formid, max_depth - 1);
1872 if(!IS_NULL_PTR(found)) return found;
1873 }
1874 return NULL;
1875}
1876
1877
1878int dt_masks_group_find_holder(dt_develop_t *dev, const int formid)
1879{
1880 if(IS_NULL_PTR(dev) || formid == 0) return 0;
1881
1882 const dt_masks_form_t *const grp = _find_holder(dev, NULL, formid, 32);
1883 return IS_NULL_PTR(grp) ? 0 : grp->formid;
1884}
1885
1886
1888{
1889 if(IS_NULL_PTR(dev)) return 0;
1890
1891 /* Held across the whole walk, which is safe only because nothing below resolves an id: that
1892 * would take this same lock again, and a nested read queued behind a waiting writer deadlocks. */
1894 guint total = 0;
1895 for(const GList *forms = dev->forms; forms; forms = g_list_next(forms))
1896 {
1897 const dt_masks_form_t *const form = (const dt_masks_form_t *)forms->data;
1898 if(IS_NULL_PTR(form) || !(form->type & DT_MASKS_GROUP)) continue;
1899
1900 if(!IS_NULL_PTR(out) && total < out_max) dt_masks_form_get_info(form, &out[total]);
1901 total++;
1902 }
1904
1905 return total;
1906}
1907
1908
1909dt_masks_result_t dt_masks_group_get_member(dt_develop_t *dev, const int group_id, const int formid,
1911{
1912 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1913
1914 guint index = 0;
1915 const dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, FALSE, &index);
1916 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
1917
1918 _member_from_entry(out, entry, index);
1919 return DT_MASKS_OK;
1920}
1921
1922
1923/* The membership graph queries. All three walk ->points, which is why they live here: outside the
1924 * module a caller would have to reach into dt_masks_form_t and dt_masks_form_group_t to ask. */
1925
1926static gboolean _group_contains_recurs(dt_develop_t *dev, const int container_id, const int needle_id)
1927{
1928 if(container_id == needle_id) return TRUE;
1929
1930 const dt_masks_form_t *container = dt_masks_get_from_id(dev, container_id);
1931 if(IS_NULL_PTR(container) || !(container->type & DT_MASKS_GROUP)) return FALSE;
1932
1933 for(const GList *pts = container->points; pts; pts = g_list_next(pts))
1934 {
1935 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
1936 if(_group_contains_recurs(dev, pt->formid, needle_id)) return TRUE;
1937 }
1938
1939 return FALSE;
1940}
1941
1942dt_masks_result_t dt_masks_group_contains(dt_develop_t *dev, const int container_id, const int needle_id)
1943{
1944 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1945 return _group_contains_recurs(dev, container_id, needle_id) ? DT_MASKS_OK : DT_MASKS_NOT_FOUND;
1946}
1947
1948static gboolean _group_covers_recurs(dt_develop_t *dev, const int group_id, const int target_id,
1949 gboolean *has_shapes)
1950{
1951 const dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1952 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return TRUE;
1953
1954 for(const GList *pts = group->points; pts; pts = g_list_next(pts))
1955 {
1956 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
1957 const dt_masks_form_t *member = dt_masks_get_from_id(dev, pt->formid);
1958 if(IS_NULL_PTR(member)) continue;
1959
1960 if(member->type & DT_MASKS_GROUP)
1961 {
1962 if(!_group_covers_recurs(dev, pt->formid, target_id, has_shapes)) return FALSE;
1963 continue;
1964 }
1965
1966 if(!IS_NULL_PTR(has_shapes)) *has_shapes = TRUE;
1967 if(!_group_contains_recurs(dev, target_id, pt->formid)) return FALSE;
1968 }
1969
1970 return TRUE;
1971}
1972
1973dt_masks_result_t dt_masks_group_covers_shapes(dt_develop_t *dev, const int group_id, const int target_id,
1974 gboolean *has_shapes)
1975{
1976 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
1977 if(!IS_NULL_PTR(has_shapes)) *has_shapes = FALSE;
1978 return _group_covers_recurs(dev, group_id, target_id, has_shapes) ? DT_MASKS_OK : DT_MASKS_NOT_FOUND;
1979}
1980
1981static gboolean _first_use_recurs(dt_develop_t *dev, const int group_id, const int formid,
1982 int *holder_id, guint *index, const dt_masks_form_t **holder)
1983{
1984 const dt_masks_form_t *group = dt_masks_get_from_id(dev, group_id);
1985 if(IS_NULL_PTR(group) || !(group->type & DT_MASKS_GROUP)) return FALSE;
1986
1987 guint i = 0;
1988 for(const GList *pts = group->points; pts; pts = g_list_next(pts))
1989 {
1990 const dt_masks_form_group_t *pt = (const dt_masks_form_group_t *)pts->data;
1991
1992 if(pt->formid == formid)
1993 {
1994 if(!IS_NULL_PTR(holder_id)) *holder_id = group_id;
1995 if(!IS_NULL_PTR(index)) *index = i;
1996 *holder = group;
1997 return TRUE;
1998 }
1999
2000 /* Descend where the member sits, not after the whole level: a shape inside a member group is
2001 * composited at that group's position, so pre-order is the compositing order. */
2002 const dt_masks_form_t *member = dt_masks_get_from_id(dev, pt->formid);
2003 if(!IS_NULL_PTR(member) && (member->type & DT_MASKS_GROUP)
2004 && _first_use_recurs(dev, pt->formid, formid, holder_id, index, holder))
2005 return TRUE;
2006
2007 i++;
2008 }
2009
2010 return FALSE;
2011}
2012
2013dt_masks_result_t dt_masks_group_first_use(dt_develop_t *dev, const int root_id, const int formid,
2014 int *holder_id, guint *index,
2015 char *holder_name, const size_t holder_name_size)
2016{
2017 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
2018
2019 const dt_masks_form_t *holder = NULL;
2020 if(!_first_use_recurs(dev, root_id, formid, holder_id, index, &holder)) return DT_MASKS_NOT_FOUND;
2021
2022 // Copied, not borrowed: the next copy-on-write replaces the object the name lives in.
2023 if(!IS_NULL_PTR(holder_name) && holder_name_size > 0)
2024 g_strlcpy(holder_name, IS_NULL_PTR(holder) ? "" : holder->name, holder_name_size);
2025
2026 return DT_MASKS_OK;
2027}
2028
2029dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid,
2030 const float opacity, dt_masks_member_t *out)
2031{
2032 if(IS_NULL_PTR(dev)) return DT_MASKS_INVALID;
2033
2034 /* Reject NaN rather than clamp it. CLAMPF() is written as a pair of ordered comparisons, and
2035 * every comparison against NaN is false, so it would quietly return the LOW bound: a NaN
2036 * arriving from a caller's own arithmetic would blank the shape instead of being reported. */
2037 if(!isfinite(opacity)) return DT_MASKS_INVALID;
2038
2039 guint index = 0;
2040 dt_masks_form_group_t *const entry = _resolve_member(dev, group_id, formid, TRUE, &index);
2041 if(IS_NULL_PTR(entry)) return DT_MASKS_NOT_FOUND;
2042
2043 const float before = entry->opacity;
2044 entry->opacity = CLAMPF(opacity, 0.0f, 1.0f);
2045
2046 _member_from_entry(out, entry, index);
2047 return (entry->opacity == before) ? DT_MASKS_UNCHANGED : DT_MASKS_OK;
2048}
2049
2050// clang-format off
2051// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
2052// vim: shiftwidth=2 expandtab tabstop=2 cindent
2053// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
2054// 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:3700
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:508
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1899
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:1826
gchar * dt_dev_get_multi_name(const struct dt_iop_module_t *module)
Definition develop.c:1637
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
@ DT_DEV_TRANSFORM_DIR_BACK_INCL
Definition develop.h:110
GtkWidget * status
result of the last capture
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:1020
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:216
@ ORIENTATION_SWAP_XY
Definition image.h:221
@ ORIENTATION_ROTATE_CCW_90_DEG
Definition image.h:228
@ ORIENTATION_ROTATE_CW_90_DEG
Definition image.h:229
@ ORIENTATION_NONE
Definition image.h:218
@ ORIENTATION_ROTATE_180_DEG
Definition image.h:226
static dt_image_orientation_t dt_image_orientation(const dt_image_t *img)
Definition image.h:692
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
#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:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
Definition masks.c:606
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:1196
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:1909
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:1785
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
dt_masks_raster_result_t dt_masks_get_mask(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, float **buffer, int *width, int *height, int *posx, int *posy)
Definition masks.c:1626
dt_masks_raster_result_t dt_masks_get_mask_roi(const dt_iop_module_t *const module, dt_dev_pixelpipe_t *pipe, const dt_dev_pixelpipe_iop_t *const piece, dt_masks_form_t *const form, const dt_iop_roi_t *roi, float *buffer, dt_iop_roi_t *touched)
Definition masks.c:1642
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:1008
static int _masks_cleanup_unused(dt_develop_t *dev, GList **forms_list, GList *history_list, const int history_end)
Definition masks.c:1502
gboolean dt_masks_form_get_info(const dt_masks_form_t *form, dt_masks_form_info_t *out)
Definition masks.c:1664
static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *develop, void *params)
Definition masks.c:727
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:627
void dt_masks_free_form(dt_masks_form_t *mask_form)
Definition masks.c:1140
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:1685
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:1887
static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *develop, void *params)
Definition masks.c:779
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:1878
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:1981
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:1332
uint64_t dt_masks_form_get_own_hash(uint64_t hash, GList *masks, const dt_masks_form_t *mask_form)
Definition masks.c:1439
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:952
dt_iop_module_t * dt_masks_get_mask_manager(dt_develop_t *develop)
Definition masks.c:917
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:2013
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:1808
dt_masks_raster_result_t dt_masks_get_source_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form, int *area_width, int *area_height, int *area_pos_x, int *area_pos_y)
Definition masks.c:464
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:2029
void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
Definition masks.c:1097
static gboolean _group_contains_recurs(dt_develop_t *dev, const int container_id, const int needle_id)
Definition masks.c:1926
int dt_masks_version(void)
Definition masks.c:486
dt_masks_form_t * dt_masks_get_from_id_ext(GList *form_list, int form_id)
Definition masks.c:899
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:1844
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:1717
static int form_id_seed
Definition masks.c:849
int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
Definition masks.c:805
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:885
static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *develop, void *params)
Definition masks.c:647
static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *develop, void *params)
Definition masks.c:491
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:1942
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:851
void dt_masks_cleanup_unused(dt_develop_t *develop)
Cleanup unused masks and refresh the current forms snapshot.
Definition masks.c:1587
int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
Definition masks.c:1384
guint dt_masks_group_copy_members(const dt_masks_form_t *group, dt_masks_member_t *out, const guint out_max)
Definition masks.c:1742
void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
Definition masks.c:1349
void dt_masks_form_invalidate_gravity_center(dt_masks_form_t *mask_form)
Definition masks.c:1322
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:1110
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:1973
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *develop, int form_id)
Definition masks.c:909
uint64_t dt_masks_group_get_hash_ext(uint64_t hash, GList *masks, dt_masks_form_t *mask_form)
Definition masks.c:1401
static void _cleanup_unused_recurs(GList *form_list, int form_id, GHashTable *used_form_ids)
Definition masks.c:1485
int dt_masks_form_duplicate(dt_develop_t *develop, int form_id)
Definition masks.c:186
dt_masks_edit_mode_t dt_masks_get_edit_mode(struct dt_iop_module_t *module)
Definition masks.c:1148
void dt_masks_form_update_gravity_center(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition masks.c:1304
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:928
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:1563
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:1767
void dt_masks_iop_use_same_as(dt_iop_module_t *module, dt_iop_module_t *source_module)
Definition masks.c:1157
dt_masks_raster_result_t dt_masks_get_area(dt_iop_module_t *module, dt_dev_pixelpipe_t *pipe, dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form, int *area_width, int *area_height, int *area_pos_x, int *area_pos_y)
Definition masks.c:450
static gboolean _group_covers_recurs(dt_develop_t *dev, const int group_id, const int target_id, gboolean *has_shapes)
Definition masks.c:1948
void dt_masks_form_gui_points_free(gpointer data)
Definition masks.c:112
float * dt_masks_sample_grid_backtransform(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
static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *develop, void *params)
Definition masks.c:754
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_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
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:1002
dt_dev_history_item_t * last_history_item
Definition masks.c:1004
dt_develop_t * develop
Definition masks.c:1001
dt_dev_history_item_t * history_item
Definition masks.c:1003
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:397
int32_t crop_height
Definition image.h:398
int32_t width
Definition image.h:397
int32_t crop_y
Definition image.h:398
int32_t crop_x
Definition image.h:398
int32_t crop_width
Definition image.h:398
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:386
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
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
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_roi)(const dt_iop_module_t *const fmodule, 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)
dt_masks_raster_result_t(* get_area)(const dt_iop_module_t *const module, 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_mask)(const dt_iop_module_t *const module, 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)(dt_iop_module_t *module, struct dt_dev_pixelpipe_t *pipe, 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_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