Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
test_masks_raster_contract.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
39#include "develop/masks.h"
42#include "develop/masks_group.h"
43#include "develop/develop.h" // dt_develop_t, for the write-API fixture
44
45#include <stdarg.h>
46#include <stddef.h>
47// cmocka.h declares `extern jmp_buf global_expect_assert_env' at file scope without including
48// <setjmp.h> itself. Same suppression as the other tests here, and for the same reason.
49#include <setjmp.h> // NOLINT(misc-include-cleaner)
50#include <stdint.h>
51#include <cmocka.h>
52
58typedef struct
59{
60 const char *name;
63 /* Whether this shape's bounding box is a function of its geometry at all. A gradient's is
64 * not: it covers the whole frame by construction, so it reads the pipe's dimensions without
65 * ever looking at its points, and "no points" is not a degenerate area for it. Every other
66 * shape derives its box from its geometry and must report the absence of one. */
68} _shape_t;
69
76static const size_t _shape_count = sizeof(_shapes) / sizeof(_shapes[0]);
77
81{
82 dt_masks_form_t form = { 0 };
83 form.type = shape->type;
84 form.functions = shape->functions;
85 form.points = NULL;
86 return form;
87}
88
90{
91 (void)state;
92 // The adapter for helpers still on the old convention: they cannot report EMPTY, so
93 // anything non-zero has to read as the conservative outcome.
97}
98
100{
101 (void)state;
102 const dt_iop_roi_t roi = { 0, 0, 4, 4, 1.0f };
103 float buffer[16] = { 0.0f };
104
105 for(size_t i = 0; i < _shape_count; i++)
106 {
108 dt_iop_roi_t touched;
109 const dt_masks_raster_result_t result
110 = dt_masks_get_mask_roi(NULL, NULL, NULL, &form, &roi, buffer, &touched);
111
112 // Not ERROR (which aborts the group fold and blanks the whole mask) and not OK (which
113 // would claim the buffer was written). This is the disagreement that used to exist
114 // between the circle and the ellipse, on identical input.
115 assert_int_equal(result, DT_MASKS_RASTER_EMPTY);
116
117 // EMPTY promises the caller an empty touched box, which is what lets the group fold clear
118 // and combine only what a child actually wrote.
119 assert_true(dt_masks_touched_is_empty(&touched));
120 }
121}
122
124{
125 (void)state;
126 const dt_iop_roi_t roi = { 0, 0, 4, 4, 1.0f };
127 float buffer[16] = { 0.0f };
128 dt_iop_roi_t touched;
129
130 // No function table at all: a shape type nobody implemented is a programming error, never an
131 // empty shape -- the fold must refuse to publish a buffer nobody wrote.
132 dt_masks_form_t form = { 0 };
133 form.type = DT_MASKS_NONE;
134 form.functions = NULL;
135
136 assert_int_equal(dt_masks_get_mask_roi(NULL, NULL, NULL, &form, &roi, buffer, &touched),
138}
139
141{
142 (void)state;
143
144 for(size_t i = 0; i < _shape_count; i++)
145 {
146 if(IS_NULL_PTR(_shapes[i].functions->get_points_border)) continue; // group has none
147
149 float *points = NULL;
150 float *border = NULL;
151 int points_count = -1;
152 int border_count = -1;
153
154 // The caller stamps the group-wide outline cache key on success. Reporting success here
155 // with *points still NULL is what marked the cache current over an outline that had never
156 // been built, hiding the shape until the geometry generation next changed.
157 assert_int_not_equal(dt_masks_get_points_border(NULL, &form, &points, &points_count, &border,
158 &border_count, NULL, NULL, 0, NULL),
160 assert_null(points);
161 }
162}
163
165{
166 (void)state;
167
168 for(size_t i = 0; i < _shape_count; i++)
169 {
171
172 /* Poisoned on purpose: these are the callers' uninitialised stack. iop/spots.c reads
173 * width/height straight after the call and iop/retouch.c sizes an allocation from them,
174 * so "reported not-OK" is not enough -- the values have to be defined too. */
175 int width = -12345;
176 int height = -12345;
177 int posx = -12345;
178 int posy = -12345;
179 float *buffer = (float *)(intptr_t)-1;
180
181 if(_shapes[i].area_depends_on_geometry && !IS_NULL_PTR(form.functions->get_area))
182 {
183 const dt_masks_raster_result_t area = dt_masks_get_area(NULL, NULL, NULL, &form, &width, &height,
184 &posx, &posy);
185 assert_int_not_equal(area, DT_MASKS_RASTER_OK);
186 assert_int_equal(width, 0);
187 assert_int_equal(height, 0);
188 assert_int_equal(posx, 0);
189 assert_int_equal(posy, 0);
190 }
191
192 width = -12345;
193 height = -12345;
194 posx = -12345;
195 posy = -12345;
196 if(!IS_NULL_PTR(form.functions->get_mask))
197 {
198 const dt_masks_raster_result_t mask
199 = dt_masks_get_mask(NULL, NULL, NULL, &form, &buffer, &width, &height, &posx, &posy);
200 assert_int_not_equal(mask, DT_MASKS_RASTER_OK);
201 assert_null(buffer);
202 assert_int_equal(width, 0);
203 assert_int_equal(height, 0);
204 assert_int_equal(posx, 0);
205 assert_int_equal(posy, 0);
206 }
207 }
208}
209
210
211/* ---------------------------------------------------------------------------------------
212 * The read side of the group API (develop/masks_group.h).
213 *
214 * These pin the contract rather than any implementation: what a caller is promised when it asks
215 * the module a question instead of reading its structs. The group fixture is built on the stack
216 * with its membership rows in a plain array, so the ORDER under test is unambiguous.
217 * ------------------------------------------------------------------------------------- */
218
220{
221 (void)state;
222 dt_masks_form_t form = { 0 };
223 form.type = DT_MASKS_CIRCLE;
224 form.formid = 4242;
225 form.version = 6;
226 g_strlcpy(form.name, "circle #2", sizeof(form.name));
227
229 assert_true(dt_masks_form_get_info(&form, &info));
230 assert_int_equal(info.formid, 4242);
231 assert_int_equal(info.version, 6);
232 assert_false(info.is_group);
233 assert_false(info.is_retouch);
234 assert_int_equal(info.member_count, 0); // not a group
235 assert_string_equal(info.name, "circle #2");
236
237 // A clone shape belongs to retouch, and the predicate for that was hand-written in eight files.
239 assert_true(dt_masks_form_get_info(&form, &info));
240 assert_true(info.is_retouch);
241}
242
244{
245 (void)state;
247 info.formid = -999; // a default the caller wants to survive a failed call
248 assert_false(dt_masks_form_get_info(NULL, &info));
249 assert_int_equal(info.formid, -999);
250}
251
253{
254 (void)state;
255 dt_masks_form_group_t rows[3] = {
256 { .formid = 11, .parentid = 7, .state = DT_MASKS_STATE_USE | DT_MASKS_STATE_UNION, .opacity = 0.25f },
257 { .formid = 22, .parentid = 7, .state = DT_MASKS_STATE_USE | DT_MASKS_STATE_INTERSECTION, .opacity = 0.50f },
258 { .formid = 33, .parentid = 7, .state = DT_MASKS_STATE_USE | DT_MASKS_STATE_DIFFERENCE, .opacity = 1.00f },
259 };
260 dt_masks_form_t group = { 0 };
261 group.type = DT_MASKS_GROUP;
262 for(int i = 0; i < 3; i++) group.points = g_list_append(group.points, &rows[i]);
263
265 assert_true(dt_masks_form_get_info(&group, &info));
266 assert_true(info.is_group);
267 assert_int_equal(info.member_count, 3);
268
269 // NULL storage asks for the count only.
270 assert_int_equal(dt_masks_group_copy_members(&group, NULL, 0), 3);
271
272 dt_masks_member_t members[3];
273 assert_int_equal(dt_masks_group_copy_members(&group, members, 3), 3);
274 for(guint i = 0; i < 3; i++)
275 {
276 // Order is the contract: it is the compositing order, the GTK row order, and the index into
277 // retouch's rt_forms[] and spots' clone_algo[] -- the last two persisted in the user's
278 // database. A filtering or reordering implementation passes every other test in this file.
279 assert_int_equal(members[i].index, i);
280 assert_int_equal(members[i].formid, rows[i].formid);
281 assert_int_equal(members[i].parentid, rows[i].parentid);
282 assert_int_equal((int)members[i].state, rows[i].state);
283 }
284 assert_true(members[0].opacity == rows[0].opacity);
285
286 // A short buffer still reports the total, and writes exactly what fits.
287 dt_masks_member_t two[2];
288 two[0].formid = two[1].formid = -1;
289 assert_int_equal(dt_masks_group_copy_members(&group, two, 2), 3);
290 assert_int_equal(two[0].formid, 11);
291 assert_int_equal(two[1].formid, 22);
292
293 g_list_free(group.points);
294}
295
297{
298 (void)state;
299 // A shape's ->points holds geometry nodes, not membership rows -- the same field, a different
300 // element type, told apart only by this bit. Refusing here is what keeps that polymorphism
301 // unreachable from outside the module.
302 dt_masks_node_circle_t node = { 0 };
303 dt_masks_form_t shape = { 0 };
304 shape.type = DT_MASKS_CIRCLE;
305 shape.points = g_list_append(NULL, &node);
306
307 dt_masks_member_t members[4];
308 assert_int_equal(dt_masks_group_copy_members(&shape, members, 4), 0);
309 assert_int_equal(dt_masks_group_copy_members(NULL, members, 4), 0);
310
311 g_list_free(shape.points);
312}
313
315{
316 (void)state;
317 /* These strings build plugins/darkroom/<plugin>/<type>/<feature>, declared in
318 * data/anselconfig.xml.in. A shape reading a key that is not in confgen gets 0, so renaming a
319 * token here silently resets that setting for every existing user. In particular the polygon
320 * token is "polygon" and must never become "path" -- another part of the tree spells it that
321 * way in machine-readable JSON, and unifying on that spelling is the trap. */
322 assert_string_equal(dt_masks_type_name(DT_MASKS_CIRCLE), "circle");
323 assert_string_equal(dt_masks_type_name(DT_MASKS_ELLIPSE), "ellipse");
324 assert_string_equal(dt_masks_type_name(DT_MASKS_POLYGON), "polygon");
325 assert_string_equal(dt_masks_type_name(DT_MASKS_BRUSH), "brush");
326 assert_string_equal(dt_masks_type_name(DT_MASKS_GRADIENT), "gradient");
327 assert_string_equal(dt_masks_type_name(DT_MASKS_GROUP), "group");
328 assert_string_equal(dt_masks_type_name(DT_MASKS_NONE), "unknown");
329
330 // The type is a bit field, so a clone circle is still a circle for the conf key.
331 assert_string_equal(dt_masks_type_name(DT_MASKS_CIRCLE | DT_MASKS_CLONE), "circle");
332}
333
334
335/* ---------------------------------------------------------------------------------------
336 * The write side: dt_masks_group_set_member_operation().
337 *
338 * The fixture is a one-group dev on the stack. dt_masks_cow_touch() returns immediately when a
339 * form's refcount is 1 -- no lock, no list walk -- so a freshly built form exercises the setter
340 * end to end without needing a live darkroom. That is also the case every caller hits when
341 * nothing else references the group.
342 * ------------------------------------------------------------------------------------- */
343
350
352{
353 memset(f, 0, sizeof(*f));
354 dt_pthread_rwlock_init(&f->dev.masks_mutex, NULL);
355
356 f->rows[0] = (dt_masks_form_group_t){ .formid = 11, .parentid = 7,
358 .opacity = 0.5f };
359 f->rows[1] = (dt_masks_form_group_t){ .formid = 22, .parentid = 7,
361 .opacity = 1.0f };
362 f->group.type = DT_MASKS_GROUP;
363 f->group.formid = 7;
364 /* The vtable is what makes a group's membership rows copyable: dt_masks_dup_masks_form() sizes
365 * each ->points payload from functions->point_struct_size, and a form whose functions are NULL
366 * clones with an EMPTY membership list. Needed by the copy-on-write case below. */
367 f->group.functions = &dt_masks_functions_group;
368 /* Sole owner: dt_masks_cow_touch() returns the form unchanged at refcount 1, without taking a
369 * lock or walking dev->forms, so the setter runs end to end on a stack fixture. */
370 dt_atomic_set_int(&f->group.refcount, 1);
371 for(int i = 0; i < 2; i++) f->group.points = g_list_append(f->group.points, &f->rows[i]);
372 f->dev.forms = g_list_append(NULL, &f->group);
373}
374
376{
377 g_list_free(f->group.points);
378 g_list_free(f->dev.forms);
379 dt_pthread_rwlock_destroy(&f->dev.masks_mutex);
380}
381
383{
384 (void)state;
387
389 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 22, DT_MASKS_STATE_UNION, &m),
391 // the OLD operator is gone, not merely joined by the new one
392 assert_int_equal(f.rows[1].state & DT_MASKS_STATE_INTERSECTION, 0);
393 assert_true((f.rows[1].state & DT_MASKS_STATE_UNION) != 0);
394 // and everything that is not a combine operator survives
395 assert_true((f.rows[1].state & DT_MASKS_STATE_USE) != 0);
396 // the caller gets the row back, index included, without ever holding a pointer into the group
397 assert_int_equal(m.formid, 22);
398 assert_int_equal(m.index, 1);
399 assert_true(m.opacity == 1.0f);
400
402}
403
405{
406 (void)state;
409
410 // row 0 is already UNION. A caller must be able to tell "nothing happened" from "done", or a
411 // no-op click writes an undo step and a database row.
412 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 11, DT_MASKS_STATE_UNION, NULL),
414
416}
417
419{
420 (void)state;
423
424 const int before = f.rows[0].state;
425 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 11, DT_MASKS_STATE_INVERSE, NULL),
427 assert_true((f.rows[0].state & DT_MASKS_STATE_INVERSE) != 0);
428 assert_true((f.rows[0].state & DT_MASKS_STATE_UNION) != 0); // operator untouched
429
430 // toggling back restores exactly the previous state -- it is a toggle, not a set
431 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 11, DT_MASKS_STATE_INVERSE, NULL),
433 assert_int_equal(f.rows[0].state, before);
434
436}
437
439{
440 (void)state;
443
444 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 999, 11, DT_MASKS_STATE_UNION, NULL),
445 DT_MASKS_NOT_FOUND); // no such group
446 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 999, DT_MASKS_STATE_UNION, NULL),
447 DT_MASKS_NOT_FOUND); // no such member
448 assert_int_equal(dt_masks_group_set_member_operation(&f.dev, 7, 11, DT_MASKS_STATE_USE, NULL),
449 DT_MASKS_INVALID); // not an operator at all
450 assert_int_equal(dt_masks_group_set_member_operation(NULL, 7, 11, DT_MASKS_STATE_UNION, NULL),
452
453 // a refused call changes nothing
454 assert_int_equal(f.rows[0].state, DT_MASKS_STATE_USE | DT_MASKS_STATE_UNION);
455
457}
458
459
460/* ---------------------------------------------------------------------------------------
461 * dt_masks_group_get_member() / dt_masks_group_set_member_opacity().
462 * ------------------------------------------------------------------------------------- */
463
465{
466 (void)state;
469
471 assert_int_equal(dt_masks_group_get_member(&f.dev, 7, 22, &m), DT_MASKS_OK);
472 assert_int_equal(m.formid, 22);
473 assert_int_equal(m.parentid, 7);
474 assert_int_equal(m.index, 1);
475 assert_true(m.opacity == 1.0f);
476
477 // NULL out is a legitimate existence probe -- the menu builders use it exactly that way
478 assert_int_equal(dt_masks_group_get_member(&f.dev, 7, 11, NULL), DT_MASKS_OK);
479
480 assert_int_equal(dt_masks_group_get_member(&f.dev, 999, 11, &m), DT_MASKS_NOT_FOUND);
481 assert_int_equal(dt_masks_group_get_member(&f.dev, 7, 999, &m), DT_MASKS_NOT_FOUND);
482 assert_int_equal(dt_masks_group_get_member(NULL, 7, 11, &m), DT_MASKS_INVALID);
483
485}
486
488{
489 (void)state;
492
494 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, 0.25f, &m), DT_MASKS_OK);
495 assert_true(f.rows[0].opacity == 0.25f);
496 assert_true(m.opacity == 0.25f);
497
498 // out-of-range is clamped, and the caller is told the CLAMPED value -- a slider showing what it
499 // asked for rather than what was stored is how a control drifts away from its own model
500 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, 4.0f, &m), DT_MASKS_OK);
501 assert_true(f.rows[0].opacity == 1.0f);
502 assert_true(m.opacity == 1.0f);
503
504 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, -2.0f, &m), DT_MASKS_OK);
505 assert_true(f.rows[0].opacity == 0.0f);
506
507 // and re-setting the value it already holds is not a change: every shape's scrolled handler
508 // treats this as "handled" but must not push an undo step for it
509 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, 0.0f, &m), DT_MASKS_UNCHANGED);
510
511 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 999, 11, 0.5f, NULL), DT_MASKS_NOT_FOUND);
512 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 999, 0.5f, NULL), DT_MASKS_NOT_FOUND);
513 assert_int_equal(dt_masks_group_set_member_opacity(NULL, 7, 11, 0.5f, NULL), DT_MASKS_INVALID);
514
516}
517
519{
520 (void)state;
523
524 /* CLAMPF() is a pair of ordered comparisons and every comparison against NaN is false, so
525 * clamping a NaN yields the LOW bound: a NaN reaching this from a caller's own arithmetic would
526 * silently blank the shape instead of being reported. */
527 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, NAN, NULL), DT_MASKS_INVALID);
528 assert_true(f.rows[0].opacity == 0.5f);
529
531}
532
534{
535 (void)state;
538
539 /* Refcount 2 is what a history commit leaves behind: the dev's live form list holds one
540 * reference and the frozen snapshot holds the other. It is the state an opacity slider is in from its SECOND step
541 * onward, because each step commits history -- which is exactly why writing through a row
542 * pointer resolved once, when the menu was built, could not stay correct. */
543 dt_atomic_set_int(&f.group.refcount, 2);
544
546 assert_int_equal(dt_masks_group_set_member_opacity(&f.dev, 7, 11, 0.25f, &m), DT_MASKS_OK);
547
548 // The snapshot still reads what it froze. This assertion IS the bug that motivated the API.
549 assert_true(f.rows[0].opacity == 0.5f);
550
551 // dev->forms carries the clone, and the clone carries the edit
552 dt_masks_form_t *const live = (dt_masks_form_t *)f.dev.forms->data;
553 assert_ptr_not_equal(live, &f.group);
554 assert_true(m.opacity == 0.25f);
555
556 dt_masks_member_t read_back;
557 assert_int_equal(dt_masks_group_get_member(&f.dev, 7, 11, &read_back), DT_MASKS_OK);
558 assert_true(read_back.opacity == 0.25f);
559
562}
563
565{
566 (void)state;
569
570 assert_int_equal(dt_masks_group_find_holder(&f.dev, 22), 7);
571 assert_int_equal(dt_masks_group_find_holder(&f.dev, 999), 0);
572 assert_int_equal(dt_masks_group_find_holder(&f.dev, 0), 0);
573 assert_int_equal(dt_masks_group_find_holder(NULL, 22), 0);
574
576}
577
578int main(void)
579{
580 const struct CMUnitTest tests[] = {
586 cmocka_unit_test(_form_info_describes_a_shape),
595 cmocka_unit_test(_get_member_reads_a_row_by_identity),
600 };
601
602 return cmocka_run_group_tests(tests, NULL, NULL);
603}
604
605// clang-format off
606// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
607// vim: shiftwidth=2 expandtab tabstop=2 cindent
608// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
609// 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)
#define m
Definition basecurve.c:283
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
const dt_masks_functions_t dt_masks_functions_circle
Definition circle.c:1078
const float f
static int dt_pthread_rwlock_destroy(dt_pthread_rwlock_t *lock)
Definition dtpthread.h:212
static int dt_pthread_rwlock_init(dt_pthread_rwlock_t *lock, const pthread_rwlockattr_t *attr)
Definition dtpthread.h:192
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
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_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_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_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
gboolean dt_masks_form_get_info(const dt_masks_form_t *form, dt_masks_form_info_t *out)
Definition masks.c:1664
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
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
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
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
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
static dt_masks_raster_result_t dt_masks_raster_from_status(const int status)
Definition masks.h:322
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_EMPTY
Definition masks.h:315
@ DT_MASKS_RASTER_OK
Definition masks.h:314
dt_masks_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 *form, int *width, int *height, int *posx, int *posy)
Definition masks.c:450
The per-shape function table, private to the masks implementation.
Ask the masks module about a shape or a group, instead of reading its structs.
void dt_masks_form_unref(dt_masks_form_t *form)
static int dt_masks_touched_is_empty(const dt_iop_roi_t *touched)
@ DT_MASKS_STATE_DIFFERENCE
Definition masks_types.h:94
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_INTERSECTION
Definition masks_types.h:93
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
@ DT_MASKS_STATE_USE
Definition masks_types.h:89
dt_masks_type_t
Definition masks_types.h:62
@ 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_NONE
Definition masks_types.h:63
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
@ DT_MASKS_OK
@ DT_MASKS_UNCHANGED
@ DT_MASKS_NOT_FOUND
@ DT_MASKS_INVALID
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
const float uint32_t state[4]
gboolean area_depends_on_geometry
const dt_masks_functions_t * functions
dt_masks_type_t type
Region of interest passed through the pixelpipe.
Definition format.h:49
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
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
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)
One shape's membership of one group, BY VALUE.
static dt_masks_form_t _empty_form(const _shape_t *shape)
static void _from_status_maps_zero_to_ok_and_everything_else_to_error(void **state)
static void _inverse_toggles_and_leaves_the_operator_alone(void **state)
static void _a_shape_with_no_rasteriser_is_an_error(void **state)
static void _find_holder_names_the_group_that_references_a_shape(void **state)
static void _a_shape_with_no_geometry_rasterises_empty(void **state)
static const size_t _shape_count
static void _write_fixture_cleanup(_write_fixture_t *f)
static void _get_member_reads_a_row_by_identity(void **state)
static void _write_fixture_init(_write_fixture_t *f)
static void _form_info_leaves_out_untouched_on_failure(void **state)
static void _copy_members_refuses_anything_that_is_not_a_group(void **state)
static void _set_opacity_refuses_a_nan_rather_than_clamping_it(void **state)
int main(void)
static void _set_operation_rejects_what_it_cannot_do(void **state)
static void _a_shared_group_is_cloned_before_its_opacity_changes(void **state)
static const _shape_t _shapes[]
static void _an_unbuildable_outline_is_never_reported_as_built(void **state)
static void _type_tokens_are_the_persisted_conf_key_spellings(void **state)
static void _set_operation_replaces_the_combine_op(void **state)
static void _area_and_mask_always_write_their_out_parameters(void **state)
static void _set_opacity_clamps_and_reports_what_it_stored(void **state)
static void _setting_the_state_it_already_has_is_unchanged(void **state)
static void _form_info_describes_a_shape(void **state)
static void _copy_members_preserves_order_and_reports_the_total(void **state)