Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
develop/masks/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 "common/darktable.h"
44#include "gui/gdkkeys.h"
45#include "develop/masks.h"
46#include "develop/develop.h"
47#include "develop/supervisor.h"
48#include "bauhaus/bauhaus.h"
49#include "common/debug.h"
50#include "common/math.h"
51#include "common/mipmap_cache.h"
52#include "control/conf.h"
53#include "common/undo.h"
54#include "develop/blend.h"
56#include "develop/imageop.h"
57#include "develop/imageop_gui.h"
58#include <stdint.h>
59
67gboolean dt_masks_point_is_within_radius(const float point_x, const float point_y,
68 const float center_x, const float center_y,
69 const float squared_radius)
70{
71 const float delta_x = point_x - center_x;
72 const float delta_y = point_y - center_y;
73 const float squared_distance = delta_x * delta_x + delta_y * delta_y;
74 return squared_distance <= squared_radius;
75}
76
94 dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override,
95 dt_masks_border_handle_fn border_handle_cb,
96 dt_masks_curve_handle_fn curve_handle_cb,
97 dt_masks_node_position_fn node_position_cb,
98 dt_masks_distance_fn distance_cb,
99 dt_masks_post_select_fn post_select_cb,
100 void *user_data)
101{
102 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
103 if(!mask_gui->creation && mask_gui->group_selected != form_index) return 0;
104
106 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
107 if(IS_NULL_PTR(gui_points)) return 0;
108
109 // Handle detection in backbuffer space.
110 const float cursor_radius = DT_GUI_MOUSE_EFFECT_RADIUS;
111 const float cursor_radius2 = cursor_radius * cursor_radius;
112 const float cursor_x = mask_gui->pos[0];
113 const float cursor_y = mask_gui->pos[1];
114 const int selected_node = dt_masks_gui_selected_node_index(mask_gui);
115
116 // Keep track of current state, in case we need to refresh total deselection.
117 const gboolean need_refresh_anyway = dt_masks_gui_was_anything_selected(mask_gui);
118
119 mask_gui->form_selected = FALSE;
120 mask_gui->border_selected = FALSE;
121 mask_gui->source_selected = FALSE;
122
123 mask_gui->node_hovered = -1;
124 mask_gui->handle_hovered = -1;
125 mask_gui->seg_hovered = -1;
126 mask_gui->handle_border_hovered = -1;
127
128 if(mask_gui->node_dragging >= 0)
129 {
130 mask_gui->node_hovered = mask_gui->node_dragging;
131 return 1;
132 }
133 if(mask_gui->handle_dragging >= 0)
134 {
135 mask_gui->handle_hovered = mask_gui->handle_dragging;
136 return 1;
137 }
138 if(mask_gui->handle_border_dragging >= 0)
139 {
140 mask_gui->handle_border_hovered = mask_gui->handle_border_dragging;
141 return 1;
142 }
143 if(mask_gui->seg_dragging >= 0)
144 {
145 mask_gui->seg_hovered = mask_gui->seg_dragging;
146 return 1;
147 }
148
149 const int node_count = (node_count_override >= 0) ? node_count_override
150 : (int)g_list_length(mask_form->points);
151
152 const gboolean has_bezier_layout = mask_form->uses_bezier_points_layout;
153 const gboolean can_test_nodes = (!IS_NULL_PTR(node_position_cb)) || has_bezier_layout;
154 const int first_node_index = has_bezier_layout ? 0 : 1; // skip center node for non-bezier shapes
155 const gboolean has_selected_node = (node_count > 0) && can_test_nodes
156 && (mask_gui->group_selected == form_index)
157 && selected_node >= first_node_index && selected_node < node_count;
158
159 if(has_selected_node)
160 {
161 // Current node's border handle (feather handle).
162 float handle_x = NAN;
163 float handle_y = NAN;
164 if(border_handle_cb
165 && border_handle_cb(gui_points, node_count, selected_node, &handle_x, &handle_y, user_data)
166 && dt_masks_point_is_within_radius(cursor_x, cursor_y, handle_x, handle_y, cursor_radius2))
167 {
168 mask_gui->handle_border_hovered = selected_node;
169 return 1;
170 }
171
172 // Current node's curve handle.
173 if(!dt_masks_node_is_cusp(gui_points, selected_node) && curve_handle_cb)
174 {
175 curve_handle_cb(gui_points, selected_node, &handle_x, &handle_y, user_data);
176 if(dt_masks_point_is_within_radius(cursor_x, cursor_y, handle_x, handle_y, cursor_radius2))
177 {
178 mask_gui->handle_hovered = selected_node;
179 return 1;
180 }
181 }
182
183 // Current node itself.
184 float node_x = NAN;
185 float node_y = NAN;
186 if(node_position_cb)
187 {
188 node_position_cb(gui_points, selected_node, &node_x, &node_y, user_data);
189 }
190 else if(has_bezier_layout)
191 {
192 node_x = gui_points->points[selected_node * 6 + 2];
193 node_y = gui_points->points[selected_node * 6 + 3];
194 }
195 if(!isnan(node_x) && !isnan(node_y)
196 && dt_masks_point_is_within_radius(cursor_x, cursor_y, node_x, node_y, cursor_radius2))
197 {
198 mask_gui->node_hovered = selected_node;
199 return 1;
200 }
201 }
202
203 if(can_test_nodes)
204 {
205 for(int node_index = first_node_index; node_index < node_count; node_index++)
206 {
207 float node_x = NAN;
208 float node_y = NAN;
209 if(node_position_cb)
210 {
211 node_position_cb(gui_points, node_index, &node_x, &node_y, user_data);
212 }
213 else if(has_bezier_layout)
214 {
215 node_x = gui_points->points[node_index * 6 + 2];
216 node_y = gui_points->points[node_index * 6 + 3];
217 }
218 if(!isnan(node_x) && !isnan(node_y)
219 && dt_masks_point_is_within_radius(cursor_x, cursor_y, node_x, node_y, cursor_radius2))
220 {
221 mask_gui->node_hovered = node_index;
222 return 1;
223 }
224 }
225 }
226
227 if(IS_NULL_PTR(distance_cb)) return 0;
228
229 // Segment or shape hit tests.
230 int inside = 0;
231 int inside_border = 0;
232 int near_segment = -1;
233 int inside_source = 0;
234 float nearest_distance = 0.0f;
235 distance_cb(cursor_x, cursor_y, cursor_radius, mask_gui, form_index, node_count, &inside, &inside_border,
236 &near_segment, &inside_source, &nearest_distance, user_data);
237
238
239 if(inside_source)
240 {
241 mask_gui->form_selected = TRUE;
242 mask_gui->source_selected = TRUE;
243 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
244 return 1;
245 }
246 if(inside_border)
247 {
248 mask_gui->form_selected = TRUE;
249 mask_gui->border_selected = TRUE;
250 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
251 return 1;
252 }
253 if(near_segment >= 0)
254 {
255 if(near_segment < node_count)
256 mask_gui->seg_hovered = near_segment;
257 return 1;
258 }
259 if(inside)
260 {
261 mask_gui->form_selected = TRUE;
262 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
263 return 1;
264 }
265
266 // Deselection needs a refresh at least once.
267 return need_refresh_anyway;
268}
269
278{
279 if (IS_NULL_PTR(mask_form)) return NULL;
280
281 dt_masks_form_t *duplicate_form = malloc(sizeof(struct dt_masks_form_t));
282 memcpy(duplicate_form, mask_form, sizeof(struct dt_masks_form_t));
283
284 // The memcpy above copied the source's live refcount: a fresh clone must
285 // start with exactly one owner (whoever holds the returned pointer).
286 dt_atomic_set_int(&duplicate_form->refcount, 1);
287
288 // Duplicate the GList *points payloads into a new list.
289 GList *duplicated_points = NULL;
290
291 if(mask_form->points)
292 {
293 const int point_struct_size = (mask_form->functions) ? mask_form->functions->point_struct_size : 0;
294
295 if(point_struct_size != 0)
296 {
297 for(GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
298 {
299 void *point_copy = malloc(point_struct_size);
300 memcpy(point_copy, point_node->data, point_struct_size);
301 duplicated_points = g_list_prepend(duplicated_points, point_copy);
302 }
303 }
304 }
305
306 // The list was built in reverse order, so un-reverse it.
307 duplicate_form->points = g_list_reverse(duplicated_points);
308
309 return duplicate_form;
310}
311
317static inline dt_masks_form_group_t *_masks_group_find_form(dt_masks_form_t *group_form, const int form_id)
318{
319 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return NULL;
320
321 // Iterate group entries to find the matching form id.
322 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
323 {
324 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
325 if(group_entry && group_entry->formid == form_id) return group_entry;
326 }
327 return NULL;
328}
329
330int dt_masks_group_index_from_formid(const dt_masks_form_t *group_form, int form_id)
331{
332 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return -1;
333
334 int index = 0;
335 for(const GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
336 {
337 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)group_node->data;
338 if(!IS_NULL_PTR(group_entry) && group_entry->formid == form_id) return index;
339 index++;
340 }
341 return -1;
342}
343
351{
352 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return NULL;
353 return dev->form_gui->form_visible;
354}
355
357{
358 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return;
359 dev->form_gui->form_visible = form;
360}
361
363{
364 if(IS_NULL_PTR(dev)) return;
365
366 if(IS_NULL_PTR(dev->form_gui))
367 {
368 dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t));
370 }
371
373 dt_masks_set_visible_form(dev, NULL);
375 dev->form_gui->formid = 0;
376}
377
379{
380 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return;
381
382 // If shutdown happens while a shape is being created, release the unfinished
383 // temporary visible form before clearing the GUI state.
385
387 dt_free(dev->form_gui);
388 dt_masks_set_visible_form(dev, NULL);
389}
390
392{
393 if(IS_NULL_PTR(gui)) return;
394
395 if(gui->handle_selected && gui->handle_hovered >= 0) gui->handle_dragging = gui->handle_hovered;
397 if(gui->node_selected && gui->node_hovered >= 0) gui->node_dragging = gui->node_hovered;
398 if(gui->seg_selected && gui->seg_hovered >= 0) gui->seg_dragging = gui->seg_hovered;
399 if(gui->source_selected)
400 gui->source_dragging = TRUE;
401 else if(gui->form_selected)
402 gui->form_dragging = TRUE;
403}
404
406{
407 if(IS_NULL_PTR(gui)) return;
408
409 gui->handle_dragging = -1;
410 gui->handle_border_dragging = -1;
411 gui->node_dragging = -1;
412 gui->seg_dragging = -1;
413 gui->form_dragging = FALSE;
414 gui->source_dragging = FALSE;
415}
416
418{
419 if(IS_NULL_PTR(gui)) return FALSE;
420 const gboolean dragging = (gui->form_dragging || gui->source_dragging || gui->seg_dragging >= 0 || gui->node_dragging >= 0
421 || gui->handle_dragging >= 0 || gui->handle_border_dragging >= 0);
423 return dragging;
424}
425
432{
433 dt_masks_form_t *group_form = dt_masks_get_from_id(darktable.develop, parent_id);
434 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return NULL;
435 return _masks_group_find_form(group_form, form_id);
436}
437
458 const dt_masks_form_gui_t *mask_gui)
459{
460 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return NULL;
461 if(mask_gui->group_selected < 0) return NULL;
462 return (dt_masks_form_group_t *)g_list_nth_data(mask_form->points, mask_gui->group_selected);
463}
464
475 const dt_masks_form_gui_t *mask_gui)
476{
477 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return NULL;
478
479 dt_masks_form_group_t *selected_group_entry = NULL;
480 if(mask_gui->group_selected >= 0)
481 selected_group_entry = dt_masks_form_get_selected_group(mask_form, mask_gui);
482
483 if(IS_NULL_PTR(selected_group_entry)) return NULL;
484
485 if(selected_group_entry->parentid > 0)
486 {
487 // Re-resolve via parentid to ensure the pointer is still current.
488 dt_masks_form_group_t *resolved_group_entry
489 = dt_masks_form_group_from_parentid(selected_group_entry->parentid, selected_group_entry->formid);
490 if(resolved_group_entry) return resolved_group_entry;
491 }
492
493 return selected_group_entry;
494}
495
503 const dt_masks_form_gui_t *mask_gui,
504 dt_masks_form_group_t **group_entry,
505 int *parent_id, int *form_index)
506{
507 if(group_entry) *group_entry = NULL;
508 if(parent_id) *parent_id = 0;
509 if(form_index) *form_index = 0;
510
511 if(IS_NULL_PTR(visible_form)) return NULL;
512 if(!(visible_form->type & DT_MASKS_GROUP)) return visible_form;
513
514 dt_masks_form_group_t *selected_group_entry
515 = dt_masks_form_get_selected_group_live(visible_form, mask_gui);
516 if(IS_NULL_PTR(selected_group_entry)) return NULL;
517
518 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, selected_group_entry->formid);
519 if(IS_NULL_PTR(selected_form)) return NULL;
520
521 if(group_entry) *group_entry = selected_group_entry;
522 if(parent_id) *parent_id = selected_group_entry->parentid;
523 if(form_index) *form_index = mask_gui->group_selected;
524
525 return selected_form;
526}
527
535 dt_masks_form_gui_t *mask_gui)
536{
537 if(IS_NULL_PTR(group_form) || IS_NULL_PTR(mask_gui)) return FALSE;
538
539 dt_develop_t *const dev = darktable.develop;
540 const float radius = DT_GUI_MOUSE_EFFECT_RADIUS;
541 const float cursor_x = mask_gui->pos[0];
542 const float cursor_y = mask_gui->pos[1];
543 const int prev_group_selected = mask_gui->group_selected;
544 const gboolean prev_border_selected = mask_gui->border_selected;
545 int locked_formid = -1;
546
547 if(dt_masks_is_anything_hovered(mask_gui))
548 return TRUE;
549
550 if(prev_border_selected && prev_group_selected >= 0)
551 {
552 dt_masks_form_group_t *selected_group_entry = dt_masks_form_get_selected_group_live(group_form, mask_gui);
553 dt_masks_form_t *selected_form = selected_group_entry
554 ? dt_masks_get_from_id(dev, selected_group_entry->formid)
555 : NULL;
556 const gboolean has_border_lock_candidate = selected_group_entry
557 && selected_form
558 && (selected_form->type & DT_MASKS_IS_CLOSED_SHAPE)
559 && selected_form->functions
560 && selected_form->functions->get_distance;
561 if(has_border_lock_candidate)
562 {
563 // Lock selection only when the click lands on the selected closed-shape border/segment.
564 int inside = 0;
565 int inside_border = 0;
566 int near = -1;
567 int inside_source = 0;
568 float dist = FLT_MAX;
569 selected_form->functions->get_distance(cursor_x, cursor_y, radius, mask_gui, prev_group_selected,
570 g_list_length(selected_form->points), &inside, &inside_border,
571 &near, &inside_source, &dist);
572 if(inside_border || near >= 0)
573 locked_formid = selected_group_entry->formid;
574 }
575 }
576
577 if(prev_group_selected >= 0)
579
580 dt_masks_form_t *selected_form = NULL;
581 int selected_index = -1;
582 float best_dist = FLT_MAX;
583
584 int index = 0;
585 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node), index++)
586 {
587 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
588 if(IS_NULL_PTR(group_entry)) continue;
589 if(locked_formid >= 0 && group_entry->formid != locked_formid) continue;
590
591 dt_masks_form_t *form = dt_masks_get_from_id(dev, group_entry->formid);
592 if(IS_NULL_PTR(form)) continue;
593
594 int inside = 0;
595 int inside_border = 0;
596 int near = -1;
597 int inside_source = 0;
598 float dist = FLT_MAX;
599 if(form->functions && form->functions->get_distance)
600 form->functions->get_distance(cursor_x, cursor_y, radius, mask_gui, index, g_list_length(form->points),
601 &inside, &inside_border, &near, &inside_source, &dist);
602
603 const gboolean is_selected_form = (prev_group_selected == index);
604 const gboolean hit_border = (inside_border || near >= 0);
605 const gboolean is_open_shape = (form->type & DT_MASKS_IS_OPEN_SHAPE) != 0;
606 // Only open shapes can be selected via their border when unselected.
607 if(!is_selected_form && hit_border && !is_open_shape)
608 continue;
609
610 if(inside || hit_border || inside_source)
611 {
612 // Lazily computed: only shapes actually hit by this click need it, not every member
613 // of the group on every mouse event.
615 const float dx = mask_gui->raw_pos[0] - form->gravity_center[0];
616 const float dy = mask_gui->raw_pos[1] - form->gravity_center[1];
617 const float center_dist2 = dx * dx + dy * dy;
618 const float combined_dist2 = dist * center_dist2;
619 if(combined_dist2 < best_dist)
620 {
621 selected_form = form;
622 selected_index = index;
623 best_dist = combined_dist2;
624 }
625 }
626 }
627
628 if(!IS_NULL_PTR(selected_form))
629 {
630 mask_gui->group_selected = selected_index;
631 return TRUE;
632 }
633
634 return mask_gui->group_selected >= 0;
635}
636
638{
639 if(IS_NULL_PTR(mask_gui) || mask_gui->creation) return FALSE;
640 if(mask_gui->form_rotating || mask_gui->border_toggling || mask_gui->gradient_toggling) return FALSE;
641 if(dt_masks_gui_is_dragging(mask_gui)) return FALSE;
642 return dt_masks_gui_should_hit_test(mask_gui);
643}
644
646 const int form_index)
647{
648 if(IS_NULL_PTR(dispatch_form) || IS_NULL_PTR(mask_gui) || !dispatch_form->functions || !dispatch_form->functions->update_hover)
649 return 0;
650 return dispatch_form->functions->update_hover(dispatch_form, mask_gui, form_index);
651}
652
653static gboolean _dt_masks_events_cursor_over_form(const dt_masks_form_t *dispatch_form,
654 dt_masks_form_gui_t *mask_gui,
655 const int form_index)
656{
657 if(!dispatch_form || IS_NULL_PTR(mask_gui) || !dispatch_form->functions || !dispatch_form->functions->get_distance)
658 return FALSE;
659
660 int inside = 0;
661 int inside_border = 0;
662 int near = -1;
663 int inside_source = 0;
664 float dist = FLT_MAX;
665 dispatch_form->functions->get_distance(mask_gui->pos[0], mask_gui->pos[1], DT_GUI_MOUSE_EFFECT_RADIUS,
666 mask_gui, form_index,
667 g_list_length(dispatch_form->points), &inside, &inside_border, &near,
668 &inside_source, &dist);
669 return inside || inside_border || near >= 0 || inside_source;
670}
671
676{
677 if(IS_NULL_PTR(mask_gui)) return FALSE;
678
679 const float radius = DT_GUI_MOUSE_EFFECT_RADIUS;
680 if(mask_gui->scrollx == 0.0f || mask_gui->scrolly == 0.0f) return FALSE;
681
682 if((mask_gui->scrollx - mask_gui->pos[0] < radius && mask_gui->scrollx - mask_gui->pos[0] > -radius)
683 && (mask_gui->scrolly - mask_gui->pos[1] < radius && mask_gui->scrolly - mask_gui->pos[1] > -radius))
684 return TRUE;
685
686 mask_gui->scrollx = 0.0f;
687 mask_gui->scrolly = 0.0f;
688 return FALSE;
689}
690
695 dt_masks_form_t *dispatch_form,
696 dt_masks_form_gui_t *mask_gui,
697 const int form_index, const int button)
698{
699 if(button != 1) return FALSE;
700 if(!dt_masks_gui_is_dragging(mask_gui)) return FALSE;
701
702 if(mask_gui->rebuild_pending)
703 {
704 if(!IS_NULL_PTR(dispatch_form))
705 {
706 dt_masks_gui_form_create(dispatch_form, mask_gui, form_index, module);
707
708 dt_develop_t *const dev = darktable.develop;
709 if(!IS_NULL_PTR(dev))
710 {
711 mask_gui->last_rebuild_ts = dt_get_wtime();
712 mask_gui->last_rebuild_pos[0] = mask_gui->pos[0];
713 mask_gui->last_rebuild_pos[1] = mask_gui->pos[1];
714 }
715 }
716 mask_gui->rebuild_pending = FALSE;
717 }
718
719 return TRUE;
720}
721
728static gboolean _set_hinter_message(dt_masks_form_gui_t *mask_gui, const dt_masks_form_t *mask_form)
729{
730 char message[256] = "";
731
732 const int form_type = mask_form->type;
733
734 int opacity_percent = 100;
735
736 const dt_masks_form_t *selected_form = mask_form;
737 int selected_form_id = 0;
738 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
739 {
740 const dt_masks_form_group_t *selected_group_entry
741 = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
742 if(!IS_NULL_PTR(selected_group_entry)) selected_form_id = selected_group_entry->formid;
743 }
744
746 "[masks] hint begin: form=%p type=%d gui=%p group_selected=%d form_selected=%d node_hovered=%d seg_hovered=%d selected_formid=%d\n",
747 (void *)mask_form, mask_form ? mask_form->type : -1, (void *)mask_gui,
748 mask_gui->group_selected, mask_gui->form_selected,
749 mask_gui->node_hovered, mask_gui->seg_hovered,
750 selected_form_id);
751 if(form_type & DT_MASKS_GROUP)
752 {
753 // Resolve the selected form inside a group (if any).
754 dt_masks_form_group_t *selected_group_entry = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
755 if(!IS_NULL_PTR(selected_group_entry))
756 {
757 opacity_percent
759 selected_form = dt_masks_get_from_id(darktable.develop, selected_group_entry->formid);
760 if(IS_NULL_PTR(selected_form)) return FALSE;
761 }
762 }
763 else
764 {
765 opacity_percent = (int)(dt_conf_get_float("plugins/darkroom/masks/opacity") * 100);
766 }
767
768 if(selected_form->functions && selected_form->functions->set_hint_message)
769 {
770 selected_form->functions->set_hint_message(mask_gui, selected_form, opacity_percent, message, sizeof(message));
771 }
772
775 "[masks] hint end: sel=%p has_set_hint=%d opacity=%d msg_len=%" G_GSIZE_FORMAT " msg='%s'\n",
776 (void *)selected_form,
777 (selected_form && selected_form->functions && selected_form->functions->set_hint_message) ? 1 : 0,
778 opacity_percent, strlen(message), message);
779 return message[0] != '\0';
780}
781
783{
784 memset(mask_gui, 0, sizeof(dt_masks_form_gui_t));
785
786 mask_gui->pos[0] = mask_gui->pos[1] = -1.0f;
787 mask_gui->rel_pos[0] = mask_gui->rel_pos[1] = -1.0f;
788 mask_gui->raw_pos[0] = mask_gui->raw_pos[1] = -1.0f;
789 mask_gui->pos_source[0] = mask_gui->pos_source[1] = -1.0f;
791 mask_gui->node_hovered = -1;
792 mask_gui->handle_hovered = -1;
793 mask_gui->seg_hovered = -1;
794 mask_gui->handle_border_hovered = -1;
795 mask_gui->node_selected = FALSE;
796 mask_gui->handle_selected = FALSE;
797 mask_gui->seg_selected = FALSE;
798 mask_gui->handle_border_selected = FALSE;
799 mask_gui->node_selected_idx = -1;
800 mask_gui->form_selected = FALSE;
801 mask_gui->border_selected = FALSE;
802 mask_gui->source_selected = FALSE;
803 mask_gui->pivot_selected = FALSE;
804 mask_gui->last_rebuild_ts = 0.0;
805 mask_gui->last_rebuild_pos[0] = mask_gui->last_rebuild_pos[1] = 0.0f;
806 mask_gui->rebuild_pending = FALSE;
807 mask_gui->last_hit_test_pos[0] = mask_gui->last_hit_test_pos[1] = -1.0f;
808}
809
811{
812 // Note: we have an hard reset function below that frees all buffers and such
813 mask_gui->source_selected = FALSE;
814 mask_gui->node_hovered = -1;
815 mask_gui->handle_hovered = -1;
816 mask_gui->seg_hovered = -1;
817 mask_gui->handle_border_hovered = -1;
818 mask_gui->node_selected = FALSE;
819 mask_gui->handle_selected = FALSE;
820 mask_gui->seg_selected = FALSE;
821 mask_gui->handle_border_selected = FALSE;
822 mask_gui->node_selected_idx = -1;
823 mask_gui->group_selected = -1;
824 mask_gui->delta[0] = mask_gui->delta[1] = 0.0f;
825 mask_gui->form_selected = mask_gui->border_selected = mask_gui->form_dragging = mask_gui->form_rotating = FALSE;
826 mask_gui->pivot_selected = FALSE;
827 mask_gui->handle_border_dragging = mask_gui->seg_dragging = mask_gui->handle_dragging = mask_gui->node_dragging = -1;
828 mask_gui->last_rebuild_ts = 0.0;
829 mask_gui->last_rebuild_pos[0] = mask_gui->last_rebuild_pos[1] = 0.0f;
830 mask_gui->rebuild_pending = FALSE;
831 mask_gui->last_hit_test_pos[0] = mask_gui->last_hit_test_pos[1] = -1.0f;
832}
833
835 int form_index, dt_iop_module_t *module)
836{
837 const int gui_points_count = g_list_length(mask_gui->points);
838 if(gui_points_count == form_index)
839 {
840 dt_masks_form_gui_points_t *gui_points_new
842 mask_gui->points = g_list_append(mask_gui->points, gui_points_new);
843 }
844 else if(gui_points_count < form_index)
845 return;
846
847 dt_masks_gui_form_remove(mask_form, mask_gui, form_index);
848
850 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
851 if(dt_masks_get_points_border(darktable.develop, mask_form, &gui_points->points, &gui_points->points_count,
852 &gui_points->border, &gui_points->border_count, 0, NULL) == 0)
853 {
854 if(mask_form->type & DT_MASKS_CLONE)
855 {
856 if(dt_masks_get_points_border(darktable.develop, mask_form, &gui_points->source, &gui_points->source_count,
857 NULL, NULL, TRUE, module)
858 != 0)
859 return;
860 }
862 mask_gui->formid = mask_form->formid;
863 mask_gui->type = mask_form->type;
864
865 }
866
868}
869
871 int form_index, dt_iop_module_t *module,
872 float posx, float posy)
873{
874 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return FALSE;
875
876 dt_develop_t *const develop = darktable.develop;
877 if(IS_NULL_PTR(develop))
878 {
879 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
880 return TRUE;
881 }
882
883 const double now = dt_get_wtime();
884 const double min_delta_time = 1.0 / 60.0;
885 const float min_dist2 = 4.0f;
886 const gboolean force_rebuild
887 = (develop->preview_pipe
888 && mask_gui->pipe_hash != dt_dev_backbuf_get_hash(&develop->preview_pipe->backbuf));
889
890 if(!force_rebuild && mask_gui->last_rebuild_ts > 0.0)
891 {
892 const double elapsed_time = now - mask_gui->last_rebuild_ts;
893 const float delta_x = posx - mask_gui->last_rebuild_pos[0];
894 const float delta_y = posy - mask_gui->last_rebuild_pos[1];
895 if(elapsed_time < min_delta_time && (delta_x * delta_x + delta_y * delta_y) < min_dist2)
896 {
897 mask_gui->rebuild_pending = TRUE;
898 return FALSE;
899 }
900 }
901
902 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
903 mask_gui->last_rebuild_ts = now;
904 mask_gui->last_rebuild_pos[0] = posx;
905 mask_gui->last_rebuild_pos[1] = posy;
906 mask_gui->rebuild_pending = FALSE;
907 return TRUE;
908}
909
911{
912 if(IS_NULL_PTR(data)) return;
913
915
919 dt_free(gui_points);
920}
921
922void dt_masks_remove_node(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id,
923 dt_masks_form_gui_t *mask_gui, int form_index, int node_index)
924{
925 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return;
926 mask_form = dt_masks_cow_touch(darktable.develop, mask_form);
927 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, node_index);
928 if(IS_NULL_PTR(brush_node)) return;
929 mask_form->points = g_list_remove(mask_form->points, brush_node);
930 dt_free(brush_node);
931 mask_gui->node_hovered = -1;
932 mask_gui->node_selected = FALSE;
933 mask_gui->node_selected_idx = -1;
934 if(mask_form->functions && mask_form->functions->init_ctrl_points)
935 mask_form->functions->init_ctrl_points(mask_form);
936
937 // we recreate the form points
938 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
939}
940
952static gboolean _masks_remove_shape(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id,
953 dt_masks_form_gui_t *mask_gui, int form_index)
954{
955 // if the form doesn't below to a group, we don't delete it
956 if(parent_id <= 0) return 1;
957
958 // we hide the form
960 if(IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP))
962 else if(g_list_shorter_than(visible_form->points, 2))
964 else
965 {
966 const int edit_mode = mask_gui->edit_mode;
967
969 visible_form = dt_masks_cow_touch(darktable.develop, visible_form);
970 // Remove the selected shape copy from the visible group before deleting the
971 // real group entry below.
972 for(GList *forms = visible_form->points; forms; forms = g_list_next(forms))
973 {
974 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)forms->data;
975 if(group_entry->formid == mask_form->formid)
976 {
977 visible_form->points = g_list_remove(visible_form->points, group_entry);
978 dt_free(group_entry);
979 break;
980 }
981 }
982 mask_gui->edit_mode = edit_mode;
983 }
984
985 // we delete or remove the shape
986 // Called from node removal, if there was not enough nodes to keep the whole shape,
987 // that's how this was called:
988 // dt_masks_form_remove(module, NULL, form);
989 // Called from shape removal, this is how it was called:
990 dt_masks_form_delete(module, dt_masks_get_from_id(darktable.develop, parent_id), mask_form);
991 // Not sure what difference it makes.
992
993 return 1;
994}
995
996static int _masks_gui_form_group_use_count(const dt_develop_t *dev, const int formid)
997{
998 if(IS_NULL_PTR(dev)) return 0;
999
1000 int count = 0;
1001 for(GList *form_node = dev->forms; form_node; form_node = g_list_next(form_node))
1002 {
1003 dt_masks_form_t *group_form = (dt_masks_form_t *)form_node->data;
1004 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) continue;
1005
1006 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1007 {
1008 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1009 if(group_entry && group_entry->formid == formid)
1010 {
1011 count++;
1012 if(count > 1) goto done;
1013 break;
1014 }
1015 }
1016 }
1017
1018done:
1019 return count;
1020}
1021
1022// Shared tail of dt_masks_remove_or_delete()/dt_masks_remove_shape_from_group()/dt_masks_delete_shape():
1023// once it's decided whether the form is kept unused or fully deleted, the group-selection
1024// bookkeeping, history commit and signal raising are identical.
1025static gboolean _masks_remove_or_delete_finish(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
1026 dt_masks_form_gui_t *mask_gui, int form_id, gboolean keep_unused)
1027{
1029 int next_form_index = -1;
1030 int next_formid = 0;
1031 if(!IS_NULL_PTR(visible_form) && (visible_form->type & DT_MASKS_GROUP)
1032 && mask_gui->group_selected >= 0 && !g_list_shorter_than(visible_form->points, 2))
1033 {
1034 const int group_length = g_list_length(visible_form->points);
1035 next_form_index = mask_gui->group_selected < group_length - 1
1036 ? mask_gui->group_selected
1037 : mask_gui->group_selected - 1;
1038
1039 // Walk from the selected row to remember the shape that should be selected
1040 // after deletion: next row first, previous row if the removed row is last.
1041 GList *selected_node = g_list_nth(visible_form->points, mask_gui->group_selected);
1042 if(!IS_NULL_PTR(selected_node))
1043 {
1044 GList *next_node = g_list_next(selected_node);
1045 if(IS_NULL_PTR(next_node)) next_node = g_list_previous(selected_node);
1046 if(!IS_NULL_PTR(next_node))
1047 {
1048 dt_masks_form_group_t *next_group_entry = (dt_masks_form_group_t *)next_node->data;
1049 if(!IS_NULL_PTR(next_group_entry)) next_formid = next_group_entry->formid;
1050 }
1051 }
1052 }
1053
1054 gboolean res = TRUE;
1055 int signal_parent_id = 0;
1057
1058 if(keep_unused)
1059 {
1060 // Only remove from current group, keep the form itself for potential reuse.
1061 res = _masks_remove_shape(module, sel, parent_id, mask_gui, mask_gui->group_selected);
1062 signal_parent_id = parent_id;
1063 signal_event = DT_MASKS_EVENT_DELETE;
1064 }
1065
1066 else // Permanent delete.
1067 {
1068 if(IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP))
1070 else if(g_list_shorter_than(visible_form->points, 2))
1072 else
1073 {
1074 const int edit_mode = mask_gui->edit_mode;
1076 visible_form = dt_masks_cow_touch(darktable.develop, visible_form);
1077 // Remove the selected shape copy from the visible group before deleting
1078 // the real form from the develop mask list below.
1079 for(GList *forms = visible_form->points; forms; forms = g_list_next(forms))
1080 {
1081 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)forms->data;
1082 if(group_entry->formid == sel->formid)
1083 {
1084 visible_form->points = g_list_remove(visible_form->points, group_entry);
1085 dt_free(group_entry);
1086 break;
1087 }
1088 }
1089 mask_gui->edit_mode = edit_mode;
1090 if(next_formid > 0)
1091 {
1092 mask_gui->group_selected = next_form_index;
1093 mask_gui->form_selected = TRUE;
1094 }
1095 }
1096
1097 dt_masks_form_delete(module, NULL, sel);
1098 }
1099
1102 signal_event);
1103
1104 if(res && next_formid > 0)
1105 {
1106 // The mask manager rebuilds its tree on the delete/remove signal, so apply
1107 // the replacement selection after the signal has finished refreshing lists.
1108 mask_gui->group_selected = next_form_index;
1109 mask_gui->form_selected = TRUE;
1112 }
1113 return res;
1114}
1115
1116gboolean dt_masks_remove_or_delete(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
1117 dt_masks_form_gui_t *mask_gui, int form_id)
1118{
1119 const int use_count = _masks_gui_form_group_use_count(darktable.develop, form_id);
1120
1121 // We don't ask for confirmation if the module uses internal masks,
1122 // just delete the form as it won't be visible in the shape manager.
1123 const gboolean internal_masks
1124 = !IS_NULL_PTR(module)
1125 && ((module->flags() & IOP_FLAGS_INTERNAL_MASKS) == IOP_FLAGS_INTERNAL_MASKS);
1126
1127 int response = GTK_RESPONSE_YES;
1128 if(use_count <= 1 && !internal_masks)
1129 {
1131 if(response == GTK_RESPONSE_CANCEL) return FALSE;
1132 }
1133
1134 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, response == GTK_RESPONSE_NO);
1135}
1136
1137gboolean dt_masks_remove_shape_from_group(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
1138 dt_masks_form_gui_t *mask_gui, int form_id)
1139{
1140 if(IS_NULL_PTR(sel) || IS_NULL_PTR(mask_gui)) return FALSE;
1141 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, TRUE);
1142}
1143
1144gboolean dt_masks_delete_shape(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
1145 dt_masks_form_gui_t *mask_gui, int form_id)
1146{
1147 if(IS_NULL_PTR(sel) || IS_NULL_PTR(mask_gui)) return FALSE;
1149 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, FALSE);
1150}
1151
1153{
1154 if(IS_NULL_PTR(mask_gui)) return FALSE;
1155
1156 if(mask_gui->creation)
1157 {
1158 const int last_formid = mask_gui->creation_last_formid;
1159 dt_iop_module_t *creation_module = mask_gui->creation_module ? mask_gui->creation_module : module;
1161
1162 if(mask_gui->guipoints)
1163 {
1166 mask_gui->guipoints = NULL;
1167 mask_gui->guipoints_payload = NULL;
1168 mask_gui->guipoints_count = 0;
1169 }
1170
1171 // The visible form is the current unfinished shape while creation is active.
1172 // If it was never appended to develop->forms, drop it before selecting the
1173 // last completed shape from this creation session.
1174 if(!IS_NULL_PTR(temporary_form) && IS_NULL_PTR(dt_masks_get_from_id(darktable.develop, temporary_form->formid)))
1175 {
1177 dt_masks_free_form(temporary_form);
1178 }
1179
1181 g_list_free(mask_gui->creation_formids);
1182 mask_gui->creation_formids = NULL;
1183 mask_gui->creation_last_formid = 0;
1184 mask_gui->creation_type = DT_MASKS_NONE;
1185 mask_gui->creation_module = NULL;
1186
1187 if(!IS_NULL_PTR(creation_module))
1188 {
1190 if(last_formid > 0)
1191 {
1192 // Keep the shape manager selection in sync without letting its selection
1193 // handler replace the visible module group with the standalone shape.
1194 dt_dev_masks_selection_change(darktable.develop, creation_module, last_formid, FALSE);
1195 }
1196
1197 dt_masks_iop_update(creation_module);
1198 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)creation_module->blend_data;
1201 if(!IS_NULL_PTR(blend_data) && GTK_IS_TOGGLE_BUTTON(blend_data->masks_edit))
1202 {
1203 // Creation mode keeps the edit button visually inactive while a shape
1204 // type button owns the interaction. Once creation is exited, restore both
1205 // the module edit state and the visible toggle explicitly.
1206 blend_data->masks_shown = DT_MASKS_EDIT_FULL;
1207 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), TRUE);
1208 gtk_widget_queue_draw(blend_data->masks_edit);
1209 }
1210
1211 if(last_formid > 0 && !IS_NULL_PTR(darktable.develop) && !IS_NULL_PTR(darktable.develop->form_gui))
1212 {
1215 const int selected_index = dt_masks_group_index_from_formid(visible_form, last_formid);
1216 if(selected_index >= 0)
1217 {
1218 // The displayed overlay is the module group; select the last completed
1219 // form inside that group once creation is closed.
1220 current_gui->group_selected = selected_index;
1221 current_gui->form_selected = TRUE;
1222 // reset other variables
1223 current_gui->border_selected = FALSE;
1224 current_gui->source_selected = FALSE;
1225 current_gui->node_selected = FALSE;
1226 current_gui->handle_selected = FALSE;
1227 current_gui->seg_selected = FALSE;
1228 current_gui->handle_border_selected = FALSE;
1229 current_gui->node_selected_idx = -1;
1230 current_gui->form_dragging = FALSE;
1231 current_gui->source_dragging = FALSE;
1232 current_gui->form_rotating = FALSE;
1233 current_gui->pivot_selected = FALSE;
1234 }
1235 }
1236 }
1237 else if(last_formid > 0)
1238 {
1242 {
1243 // A standalone visible form is rendered at index 0.
1246 }
1247 }
1248 else
1249 {
1251 }
1252
1253 return TRUE;
1254 }
1255 return FALSE;
1256}
1257
1258gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *mask_form,
1259 dt_masks_form_gui_t *mask_gui, const int parent_id)
1260{
1261 if(mask_gui->edit_mode != DT_MASKS_EDIT_FULL)
1262 return FALSE;
1263
1264 // Just clean temp mask if we are in creation mode
1265 if(dt_masks_form_exit_creation(module, mask_gui))
1266 return TRUE;
1267
1268 // we remove the selected node (and the entire form if there is too few nodes left)
1269 if(((mask_form->type & DT_MASKS_IS_PATH_SHAPE) != 0) && mask_gui->node_selected)
1270 {
1271 if(g_list_shorter_than(mask_form->points, 3))
1272 return _masks_remove_shape(module, mask_form, parent_id, mask_gui, mask_gui->group_selected);
1273
1274 dt_masks_remove_node(module, mask_form, parent_id, mask_gui, mask_gui->group_selected,
1275 mask_gui->node_hovered);
1276
1277 return TRUE;
1278 }
1279 // we remove the entire shape
1280 else if(parent_id > 0)
1281 {
1282 dt_masks_remove_or_delete(module, mask_form, parent_id, mask_gui, mask_form->formid);
1283 return TRUE; // something happened even if the dialog was cancelled.
1284 }
1285 return FALSE;
1286}
1287
1288void dt_masks_gui_form_remove(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
1289{
1290 dt_masks_form_gui_points_t *gui_points
1291 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
1293 mask_gui->formid = 0;
1294
1295 if(!IS_NULL_PTR(gui_points))
1296 {
1297 gui_points->points_count = gui_points->border_count = gui_points->source_count = 0;
1299 gui_points->points = NULL;
1301 gui_points->border = NULL;
1303 gui_points->source = NULL;
1304 }
1305}
1306
1308 dt_iop_module_t *module)
1309{
1310 // we test if the image has changed
1312 {
1314 {
1316 mask_gui->formid = 0;
1317 g_list_free_full(mask_gui->points, dt_masks_form_gui_points_free);
1318 mask_gui->points = NULL;
1319 }
1320 }
1321
1322 // we create the form if needed
1324 {
1325 if(mask_form->type & DT_MASKS_GROUP)
1326 {
1327 int form_index = 0;
1328 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
1329 {
1330 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1331 dt_masks_form_t *child_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
1332 if(IS_NULL_PTR(child_form)) return;
1333 dt_masks_gui_form_create(child_form, mask_gui, form_index, module);
1334 form_index++;
1335 }
1336 }
1337 else
1338 {
1339 dt_masks_gui_form_create(mask_form, mask_gui, 0, module);
1340 }
1341 }
1342}
1343
1344static void _check_id(dt_masks_form_t *mask_form)
1345{
1346 int new_form_id = 100;
1347 for(GList *form_node = darktable.develop->forms; form_node; )
1348 {
1349 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1350 if(existing_form->formid == mask_form->formid)
1351 {
1352 mask_form->formid = new_form_id++;
1353 form_node = darktable.develop->forms; // jump back to start of list
1354 }
1355 else
1356 {
1357 form_node = g_list_next(form_node); // advance to next form
1358 }
1359 }
1360}
1361
1363{
1364 gchar *group_name = dt_dev_get_masks_group_name(module);
1365 g_strlcpy(group_form->name, group_name, sizeof(group_form->name));
1366 dt_free(group_name);
1367}
1368
1370{
1371 dt_masks_form_t *group_form = dt_masks_create(group_type);
1372 _set_group_name_from_module(module, group_form);
1373 _check_id(group_form);
1374 dt_masks_append_form(develop, group_form);
1375 module->blend_params->mask_id = group_form->formid;
1376 return group_form;
1377}
1378
1379// get the group form associated to the module, if any
1381{
1382 return dt_masks_get_from_id(develop, module->blend_params->mask_id);
1383}
1384
1386{
1387 // dev->forms is its own claim on the object, independent of dev->allforms's claim
1388 // (taken at creation, dt_masks_create_ext): take a reference for this list membership.
1389 dt_masks_form_ref(mask_form);
1391 develop->forms = g_list_append(develop->forms, mask_form);
1393}
1394
1396{
1398 develop->forms = g_list_remove(develop->forms, mask_form);
1400 // Release dev->forms's claim (see dt_masks_append_form). Does not necessarily free the
1401 // object: dev->allforms and/or history snapshots may still reference it.
1402 dt_masks_form_unref(mask_form);
1403}
1404
1406 dt_masks_form_gui_t *mask_gui)
1407{
1408 // we check if the id is already registered
1409 _check_id(mask_form);
1410
1411 // mask nb will be at least the length of the list
1412 guint form_count = 0;
1413
1414 // count only the same forms to have a clean numbering
1416 for(GList *form_node = develop->forms; form_node; form_node = g_list_next(form_node))
1417 {
1418 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1419 if(existing_form->type == mask_form->type) form_count++;
1420 }
1422
1423 gboolean name_exists = FALSE;
1424
1425 // check that we do not have duplicate, in case some masks have been
1426 // removed we can have hole and so nb could already exists.
1427 do
1428 {
1429 name_exists = FALSE;
1430 form_count++;
1431
1432 if(mask_form->functions && mask_form->functions->set_form_name)
1433 mask_form->functions->set_form_name(mask_form, form_count);
1434
1436 for(GList *form_node = develop->forms; form_node; form_node = g_list_next(form_node))
1437 {
1438 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
1439 if(!strcmp(existing_form->name, mask_form->name))
1440 {
1441 name_exists = TRUE;
1442 break;
1443 }
1444 }
1446
1447 } while(name_exists);
1448
1450
1451 dt_masks_form_group_t *group_entry = NULL;
1452 if(!IS_NULL_PTR(module))
1453 {
1454 group_entry = malloc(sizeof(dt_masks_form_group_t));
1455 if(IS_NULL_PTR(group_entry)) return;
1456 }
1457
1458 dt_masks_append_form(develop, mask_form);
1459
1460 if(!IS_NULL_PTR(module))
1461 {
1462 // is there already a masks group for this module ?
1463 dt_masks_form_t *group_form = _group_from_module(develop, module);
1464 if(IS_NULL_PTR(group_form))
1465 {
1466 // we create a new group
1467 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
1468 group_form = _group_create(develop, module, DT_MASKS_GROUP | DT_MASKS_CLONE);
1469 else
1470 group_form = _group_create(develop, module, DT_MASKS_GROUP);
1471 }
1472 group_form = dt_masks_cow_touch(develop, group_form);
1473 // we add the form in this group
1474 group_entry->formid = mask_form->formid;
1475 group_entry->parentid = group_form->formid;
1477 group_entry->opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
1478 group_form->points = g_list_append(group_form->points, group_entry);
1479
1480 // we update module gui
1481
1482 if(IS_NULL_PTR(mask_gui)) dt_masks_iop_update(module);
1483 }
1484
1485 if(!IS_NULL_PTR(mask_gui))
1486 {
1487 mask_gui->creation_formids = g_list_append(mask_gui->creation_formids, GINT_TO_POINTER(mask_form->formid));
1488 mask_gui->creation_last_formid = mask_form->formid;
1489
1490 if(!IS_NULL_PTR(module))
1491 {
1493 group_entry->parentid, DT_MASKS_EVENT_ADD);
1494 }
1495 else
1496 {
1499 }
1500
1501 // Keep creation mode active. The saved form remains in develop->forms for
1502 // session rendering, while the visible form becomes the next unfinished
1503 // shape so mouse events still target the creation preview.
1504 dt_masks_form_t *next_form = dt_masks_create(mask_gui->creation_type);
1505 if(IS_NULL_PTR(next_form))
1506 {
1507 dt_masks_form_exit_creation(module, mask_gui);
1508 return;
1509 }
1510
1511 g_list_free_full(mask_gui->points, dt_masks_form_gui_points_free);
1512 mask_gui->points = NULL;
1514 mask_gui->guipoints = NULL;
1516 mask_gui->guipoints_payload = NULL;
1517 mask_gui->guipoints_count = 0;
1519 mask_gui->formid = 0;
1520 mask_gui->creation_closing_form = FALSE;
1522 dt_masks_set_visible_form(develop, next_form);
1523 if(!IS_NULL_PTR(module)) dt_masks_iop_update(module);
1524 }
1525}
1526
1527int dt_masks_form_duplicate(dt_develop_t *develop, int form_id)
1528{
1529 // we create a new empty form
1530 dt_masks_form_t *base_form = dt_masks_get_from_id(develop, form_id);
1531 if(IS_NULL_PTR(base_form)) return -1;
1532 dt_masks_form_t *dest_form = dt_masks_create(base_form->type);
1533 _check_id(dest_form);
1534
1535 // we copy the base values
1536 dest_form->source[0] = base_form->source[0];
1537 dest_form->source[1] = base_form->source[1];
1538 dest_form->version = base_form->version;
1539 snprintf(dest_form->name, sizeof(dest_form->name), _("copy of %s"), base_form->name);
1540
1541 dt_masks_append_form(develop, dest_form);
1542
1543 // we copy all the points
1544 if(base_form->functions)
1545 base_form->functions->duplicate_points(develop, base_form, dest_form);
1546
1547 // and we return its id
1548 return dest_form->formid;
1549}
1550
1552 float **point_buffer, int *point_count,
1553 float **border_buffer, int *border_count,
1554 int source, dt_iop_module_t *module)
1555{
1556 if(mask_form->functions && mask_form->functions->get_points_border)
1557 return mask_form->functions->get_points_border(develop, mask_form, point_buffer, point_count,
1558 border_buffer, border_count, source, module);
1559 return 1;
1560}
1561
1563 dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form,
1564 int *area_width, int *area_height, int *area_pos_x, int *area_pos_y)
1565{
1566 if(mask_form->functions && mask_form->functions->get_area)
1567 return mask_form->functions->get_area(module, pipe, piece, mask_form, area_width, area_height,
1568 area_pos_x, area_pos_y);
1569 return 1;
1570}
1571
1573 dt_dev_pixelpipe_iop_t *piece, dt_masks_form_t *mask_form,
1574 int *area_width, int *area_height,
1575 int *area_pos_x, int *area_pos_y)
1576{
1577 *area_width = *area_height = *area_pos_x = *area_pos_y = 0;
1578
1579 // must be a clone form
1580 if(mask_form->type & DT_MASKS_CLONE)
1581 {
1582 if(mask_form->functions && mask_form->functions->get_source_area)
1583 return mask_form->functions->get_source_area(module, pipe, piece, mask_form, area_width, area_height,
1584 area_pos_x, area_pos_y);
1585 }
1586 return 1;
1587}
1588
1590{
1591 return DEVELOP_MASKS_VERSION;
1592}
1593
1594static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *develop, void *params)
1595{
1596 /*
1597 * difference: before v2 images were originally rotated on load, and then
1598 * maybe in flip iop
1599 * after v2: images are only rotated in flip iop.
1600 */
1601
1602 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
1603
1604 const dt_image_orientation_t orientation = dt_image_orientation(&develop->image_storage);
1605
1606 if(orientation == ORIENTATION_NONE)
1607 {
1608 // image is not rotated, we're fine!
1609 mask_form->version = 2;
1610 return 0;
1611 }
1612 else
1613 {
1614 if(IS_NULL_PTR(develop->iop)) return 1;
1615
1616 const char *opname = "flip";
1617 dt_iop_module_t *module = NULL;
1618
1619 for(GList *module_node = develop->iop; module_node; module_node = g_list_next(module_node))
1620 {
1621 dt_iop_module_t *iop_module = (dt_iop_module_t *)module_node->data;
1622 if(!strcmp(iop_module->op, opname))
1623 {
1624 module = iop_module;
1625 break;
1626 }
1627 }
1628
1629 if(IS_NULL_PTR(module)) return 1;
1630
1631 dt_dev_pixelpipe_iop_t piece = { 0 };
1632
1633 module->init_pipe(module, NULL, &piece);
1634 module->commit_params(module, module->default_params, NULL, &piece);
1635
1636 piece.buf_in.width = 1;
1637 piece.buf_in.height = 1;
1638
1639 GList *point_node = mask_form->points;
1640
1641 if(IS_NULL_PTR(point_node)) return 1;
1642
1643 if(mask_form->type & DT_MASKS_CIRCLE)
1644 {
1645 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
1646 if(IS_NULL_PTR(circle)) return 1;
1647 module->distort_backtransform(module, NULL, &piece, circle->center, 1);
1648 }
1649 else if(mask_form->type & DT_MASKS_POLYGON)
1650 {
1651 for(; point_node; point_node = g_list_next(point_node))
1652 {
1653 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
1654 if(IS_NULL_PTR(polygon_node)) return 1;
1655 module->distort_backtransform(module, NULL, &piece, polygon_node->node, 1);
1656 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl1, 1);
1657 module->distort_backtransform(module, NULL, &piece, polygon_node->ctrl2, 1);
1658 }
1659 }
1660 else if(mask_form->type & DT_MASKS_GRADIENT)
1661 { // TODO: new ones have wrong rotation.
1662 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
1663 if(IS_NULL_PTR(gradient)) return 1;
1664 module->distort_backtransform(module, NULL, &piece, gradient->center, 1);
1665
1666 if(orientation == ORIENTATION_ROTATE_180_DEG)
1667 gradient->rotation -= 180.0f;
1668 else if(orientation == ORIENTATION_ROTATE_CCW_90_DEG)
1669 gradient->rotation -= 90.0f;
1670 else if(orientation == ORIENTATION_ROTATE_CW_90_DEG)
1671 gradient->rotation -= -90.0f;
1672 }
1673 else if(mask_form->type & DT_MASKS_ELLIPSE)
1674 {
1675 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
1676 module->distort_backtransform(module, NULL, &piece, ellipse->center, 1);
1677
1678 if(orientation & ORIENTATION_SWAP_XY)
1679 {
1680 const float y = ellipse->radius[0];
1681 ellipse->radius[0] = ellipse->radius[1];
1682 ellipse->radius[1] = y;
1683 }
1684 }
1685 else if(mask_form->type & DT_MASKS_BRUSH)
1686 {
1687 for(; point_node; point_node = g_list_next(point_node))
1688 {
1689 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
1690 if(IS_NULL_PTR(brush_node)) return 1;
1691 module->distort_backtransform(module, NULL, &piece, brush_node->node, 1);
1692 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl1, 1);
1693 module->distort_backtransform(module, NULL, &piece, brush_node->ctrl2, 1);
1694 }
1695 }
1696
1697 if(mask_form->type & DT_MASKS_CLONE)
1698 {
1699 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
1700 module->distort_backtransform(module, NULL, &piece, mask_form->source, 1);
1701 }
1702
1703 mask_form->version = 2;
1704
1705 return 0;
1706 }
1707}
1708
1709static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
1710{
1711 const float image_width = (float)image->width;
1712 const float image_height = (float)image->height;
1713
1714 const float crop_x = (float)image->crop_x;
1715 const float crop_y = (float)image->crop_y;
1716
1717 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
1718 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
1719
1720 /*
1721 * masks coordinates are normalized, so we need to:
1722 * 1. de-normalize them by image original cropped dimensions
1723 * 2. un-crop them by adding top-left crop coordinates
1724 * 3. normalize them by the image fully uncropped dimensions
1725 */
1726 coords[0] = ((coords[0] * crop_width) + crop_x) / image_width;
1727 coords[1] = ((coords[1] * crop_height) + crop_y) / image_height;
1728}
1729
1731 size_t coords_count)
1732{
1733 const float image_width = (float)image->width;
1734 const float image_height = (float)image->height;
1735
1736 const float crop_width = (float)(image->width - image->crop_x - image->crop_width);
1737 const float crop_height = (float)(image->height - image->crop_y - image->crop_height);
1738
1739 /*
1740 * masks coordinates are normalized, so we need to:
1741 * 1. de-normalize them by minimal of image original cropped dimensions
1742 * 2. normalize them by the minimal of image fully uncropped dimensions
1743 */
1744 const float crop_min = MIN(crop_width, crop_height);
1745 const float image_min = MIN(image_width, image_height);
1746 for(size_t coord_index = 0; coord_index < coords_count; coord_index++)
1747 coords[coord_index] = ((coords[coord_index] * crop_min)) / image_min;
1748}
1749
1750static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *develop, void *params)
1751{
1752 /*
1753 * difference: before v3 images were originally cropped on load
1754 * after v3: images are cropped in rawprepare iop.
1755 */
1756
1757 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
1758
1759 const dt_image_t *image = &(develop->image_storage);
1760
1761 if(image->crop_x == 0 && image->crop_y == 0 && image->crop_width == 0 && image->crop_height == 0)
1762 {
1763 // image has no "raw cropping", we're fine!
1764 mask_form->version = 3;
1765 return 0;
1766 }
1767 else
1768 {
1769 GList *point_node = mask_form->points;
1770
1771 if(IS_NULL_PTR(point_node)) return 1;
1772
1773 if(mask_form->type & DT_MASKS_CIRCLE)
1774 {
1775 dt_masks_node_circle_t *circle = (dt_masks_node_circle_t *)point_node->data;
1776 if(IS_NULL_PTR(circle)) return 1;
1780 }
1781 else if(mask_form->type & DT_MASKS_POLYGON)
1782 {
1783 for(; point_node; point_node = g_list_next(point_node))
1784 {
1785 dt_masks_node_polygon_t *polygon_node = (dt_masks_node_polygon_t *)point_node->data;
1786 if(IS_NULL_PTR(polygon_node)) return 1;
1787 dt_masks_legacy_params_v2_to_v3_transform(image, polygon_node->node);
1791 }
1792 }
1793 else if(mask_form->type & DT_MASKS_GRADIENT)
1794 {
1795 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
1797 }
1798 else if(mask_form->type & DT_MASKS_ELLIPSE)
1799 {
1800 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
1804 }
1805 else if(mask_form->type & DT_MASKS_BRUSH)
1806 {
1807 for(; point_node; point_node = g_list_next(point_node))
1808 {
1809 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)point_node->data;
1810 if(IS_NULL_PTR(brush_node)) return 1;
1815 }
1816 }
1817
1818 if(mask_form->type & DT_MASKS_CLONE)
1819 {
1820 // NOTE: can be: DT_MASKS_CIRCLE, DT_MASKS_ELLIPSE, DT_MASKS_POLYGON
1822 }
1823
1824 mask_form->version = 3;
1825
1826 return 0;
1827 }
1828}
1829
1830static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *develop, void *params)
1831{
1832 /*
1833 * difference affecting ellipse
1834 * up to v3: only equidistant feathering
1835 * after v4: choice between equidistant and proportional feathering
1836 * type of feathering is defined in new flags parameter
1837 */
1838
1839 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
1840
1841 GList *point_node = mask_form->points;
1842
1843 if(IS_NULL_PTR(point_node)) return 1;
1844
1845 if(mask_form->type & DT_MASKS_ELLIPSE)
1846 {
1847 dt_masks_node_ellipse_t *ellipse = (dt_masks_node_ellipse_t *)point_node->data;
1849 }
1850
1851 mask_form->version = 4;
1852
1853 return 0;
1854}
1855
1856
1857static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *develop, void *params)
1858{
1859 /*
1860 * difference affecting gradient
1861 * up to v4: only linear gradient (relative to input image)
1862 * after v5: curved gradients
1863 */
1864
1865 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
1866
1867 GList *point_node = mask_form->points;
1868
1869 if(IS_NULL_PTR(point_node)) return 1;
1870
1871 if(mask_form->type & DT_MASKS_GRADIENT)
1872 {
1873 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
1874 gradient->curvature = 0.0f;
1875 }
1876
1877 mask_form->version = 5;
1878
1879 return 0;
1880}
1881
1882static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *develop, void *params)
1883{
1884 /*
1885 * difference affecting gradient
1886 * up to v5: linear transition
1887 * after v5: linear or sigmoidal transition
1888 */
1889
1890 dt_masks_form_t *mask_form = (dt_masks_form_t *)params;
1891
1892 GList *point_node = mask_form->points;
1893
1894 if(IS_NULL_PTR(point_node)) return 1;
1895
1896 if(mask_form->type & DT_MASKS_GRADIENT)
1897 {
1898 dt_masks_anchor_gradient_t *gradient = (dt_masks_anchor_gradient_t *)point_node->data;
1900 }
1901
1902 mask_form->version = 6;
1903
1904 return 0;
1905}
1906
1907
1908int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
1909{
1910 int result = 1;
1911#if 0 // we should not need this any longer
1912 if(old_version == 1 && new_version == 2)
1913 {
1914 result = dt_masks_legacy_params_v1_to_v2(develop, params);
1915 }
1916#endif
1917
1918 if(old_version == 1 && new_version == 6)
1919 {
1920 result = dt_masks_legacy_params_v1_to_v2(develop, params);
1921 if(!result) result = dt_masks_legacy_params_v2_to_v3(develop, params);
1922 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
1923 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
1924 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
1925 }
1926 else if(old_version == 2 && new_version == 6)
1927 {
1928 result = dt_masks_legacy_params_v2_to_v3(develop, params);
1929 if(!result) result = dt_masks_legacy_params_v3_to_v4(develop, params);
1930 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
1931 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
1932 }
1933 else if(old_version == 3 && new_version == 6)
1934 {
1935 result = dt_masks_legacy_params_v3_to_v4(develop, params);
1936 if(!result) result = dt_masks_legacy_params_v4_to_v5(develop, params);
1937 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
1938 }
1939 else if(old_version == 4 && new_version == 6)
1940 {
1941 result = dt_masks_legacy_params_v4_to_v5(develop, params);
1942 if(!result) result = dt_masks_legacy_params_v5_to_v6(develop, params);
1943 }
1944 else if(old_version == 5 && new_version == 6)
1945 {
1946 result = dt_masks_legacy_params_v5_to_v6(develop, params);
1947 }
1948
1949 return result;
1950}
1951
1952static int form_id_seed = 0;
1953
1955{
1956 dt_masks_form_t *mask_form = (dt_masks_form_t *)calloc(1, sizeof(dt_masks_form_t));
1957 if(IS_NULL_PTR(mask_form)) return NULL;
1958
1959 // Freshly created: exactly one owner (whoever holds the returned pointer).
1960 dt_atomic_set_int(&mask_form->refcount, 1);
1961
1962 mask_form->type = type;
1963 mask_form->version = dt_masks_version();
1964 mask_form->formid = time(NULL) + form_id_seed++;
1966
1967 if (type & DT_MASKS_CIRCLE)
1969 else if (type & DT_MASKS_ELLIPSE)
1971 else if (type & DT_MASKS_BRUSH)
1972 mask_form->functions = &dt_masks_functions_brush;
1973 else if (type & DT_MASKS_POLYGON)
1975 else if (type & DT_MASKS_GRADIENT)
1977 else if (type & DT_MASKS_GROUP)
1978 mask_form->functions = &dt_masks_functions_group;
1979
1980 if (mask_form->functions && mask_form->functions->sanitize_config)
1981 mask_form->functions->sanitize_config(type);
1982
1984
1985 return mask_form;
1986}
1987
1989{
1991 dt_masks_form_t *mask_form = dt_masks_create(type);
1992
1993 // all forms created here are registered in darktable.develop->allforms for later cleanup
1994 if(mask_form)
1995 darktable.develop->allforms = g_list_append(darktable.develop->allforms, mask_form);
1996
1998
1999 return mask_form;
2000}
2001
2002dt_masks_form_t *dt_masks_get_from_id_ext(GList *form_list, int form_id)
2003{
2004 for(; form_list; form_list = g_list_next(form_list))
2005 {
2006 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_list->data;
2007 if(mask_form->formid == form_id) return mask_form;
2008 }
2009 return NULL;
2010}
2011
2013{
2015 dt_masks_form_t *result = dt_masks_get_from_id_ext(develop->forms, form_id);
2017 return result;
2018}
2019
2021{
2022 for(GList *module_node = g_list_first(develop->iop); module_node; module_node = g_list_next(module_node))
2023 {
2024 dt_iop_module_t *module = (dt_iop_module_t *)(module_node->data);
2025 if(strcmp(module->op, "mask_manager") == 0)
2026 return module;
2027 }
2028 return NULL;
2029}
2030
2031static void _masks_fill_used_forms(GList *forms_list, const int form_id, int *used_form_ids,
2032 const int used_count)
2033{
2034 for(int used_index = 0; used_index < used_count; used_index++)
2035 {
2036 if(used_form_ids[used_index] == 0)
2037 {
2038 used_form_ids[used_index] = form_id;
2039 break;
2040 }
2041 if(used_form_ids[used_index] == form_id) break;
2042 }
2043
2044 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(forms_list, form_id);
2045 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
2046 {
2047 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
2048 {
2049 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
2050 _masks_fill_used_forms(forms_list, group_entry->formid, used_form_ids, used_count);
2051 }
2052 }
2053}
2054
2056 const dt_iop_module_t *source_module)
2057{
2058 if(!(source_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)) return 0;
2059 if(source_module->blend_params->mask_id <= 0) return 0;
2060
2061 const guint form_count = g_list_length(develop_src->forms);
2062 if(form_count == 0) return 0;
2063
2064 int *used_form_ids = calloc(form_count, sizeof(int));
2065 if(IS_NULL_PTR(used_form_ids)) return 1;
2066
2067 _masks_fill_used_forms(develop_src->forms, source_module->blend_params->mask_id,
2068 used_form_ids, form_count);
2069
2070 for(int form_index = 0; form_index < (int)form_count && used_form_ids[form_index] > 0; form_index++)
2071 {
2072 dt_masks_form_t *mask_form = dt_masks_get_from_id(develop_src, used_form_ids[form_index]);
2073 if(!IS_NULL_PTR(mask_form))
2074 {
2075 dt_masks_form_t *existing_form = dt_masks_get_from_id_ext(develop_dest->forms,
2076 used_form_ids[form_index]);
2077 if(existing_form)
2078 {
2079 develop_dest->forms = g_list_remove(develop_dest->forms, existing_form);
2080 develop_dest->allforms = g_list_append(develop_dest->allforms, existing_form);
2081 }
2082
2083 dt_masks_form_t *new_form = dt_masks_dup_masks_form(mask_form);
2084 if(IS_NULL_PTR(new_form))
2085 {
2086 dt_free(used_form_ids);
2087 return 1;
2088 }
2089 develop_dest->forms = g_list_append(develop_dest->forms, new_form);
2090 }
2091 else
2092 {
2093 fprintf(stderr, "[dt_masks_copy_used_forms_for_module] form %i not found in source image\n",
2094 used_form_ids[form_index]);
2095 }
2096 }
2097
2098 dt_free(used_form_ids);
2099 return 0;
2100}
2101
2102void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
2103{
2104 dt_dev_history_item_t *history_item = NULL;
2105 dt_dev_history_item_t *last_history_item = NULL;
2106 int previous_num = -1;
2107
2108 sqlite3_stmt *statement = NULL;
2109 // clang-format off
2112 "SELECT imgid, formid, form, name, version, points, points_count, source, num "
2113 "FROM main.masks_history WHERE imgid = ?1 ORDER BY num",
2114 -1, &statement, NULL);
2115 // clang-format on
2116 DT_DEBUG_SQLITE3_BIND_INT(statement, 1, image_id);
2117
2118 while(sqlite3_step(statement) == SQLITE_ROW)
2119 {
2120 // db record:
2121 // 0-img, 1-formid, 2-form_type, 3-name, 4-version, 5-points, 6-points_count, 7-source, 8-num
2122
2123 // we get the values
2124
2125 const int form_id = sqlite3_column_int(statement, 1);
2126 const int history_num = sqlite3_column_int(statement, 8);
2127 const dt_masks_type_t mask_type = sqlite3_column_int(statement, 2);
2128 dt_masks_form_t *mask_form = dt_masks_create(mask_type);
2129 mask_form->formid = form_id;
2130 const char *form_name = (const char *)sqlite3_column_text(statement, 3);
2131 g_strlcpy(mask_form->name, form_name, sizeof(mask_form->name));
2132 mask_form->version = sqlite3_column_int(statement, 4);
2133 mask_form->points = NULL;
2134 const int point_count = sqlite3_column_int(statement, 6);
2135 memcpy(mask_form->source, sqlite3_column_blob(statement, 7), sizeof(float) * 2);
2136
2137 // and now we "read" the blob
2138 if(mask_form->functions)
2139 {
2140 const char *const point_buffer = (char *)sqlite3_column_blob(statement, 5);
2141 const size_t point_struct_size = mask_form->functions->point_struct_size;
2142 for(int point_index = 0; point_index < point_count; point_index++)
2143 {
2144 char *point_data = (char *)malloc(point_struct_size);
2145 memcpy(point_data, point_buffer + point_index * point_struct_size, point_struct_size);
2146 mask_form->points = g_list_append(mask_form->points, point_data);
2147 }
2148 }
2149
2150 if(mask_form->version != dt_masks_version())
2151 {
2152 if(dt_masks_legacy_params(develop, mask_form, mask_form->version, dt_masks_version()))
2153 {
2154 const char *fname = develop->image_storage.filename + strlen(develop->image_storage.filename);
2155 while(fname > develop->image_storage.filename && *fname != '/') fname--;
2156 if(fname > develop->image_storage.filename) fname++;
2157
2158 fprintf(stderr,
2159 "[_dev_read_masks_history] %s (imgid `%i'): mask version mismatch: history is %d, dt %d.\n",
2160 fname, image_id, mask_form->version, dt_masks_version());
2161 dt_control_log(_("%s: mask version mismatch: %d != %d"),
2162 fname, dt_masks_version(), mask_form->version);
2163
2164 continue;
2165 }
2166 }
2167
2168 // Not computed here: dt_masks_replace_current_forms() below (via
2169 // dt_masks_form_update_gravity_center() in masks_history.c) already computes it for
2170 // every form that actually ends up live in dev->forms. Computing it here too would
2171 // redundantly do it for every history step's row read from masks_history -- including
2172 // every duplicate of a form shared unchanged across many steps (see the un-deduped
2173 // masks_history table, doc/masks_history_dedup.md) -- for forms that are either
2174 // superseded (never read again) or about to be recomputed anyway.
2175
2176 // if this is a new history entry let's find it
2177 if(previous_num != history_num)
2178 {
2179 history_item = NULL;
2180 for(GList *history_node = g_list_first(develop->history); history_node; history_node = g_list_next(history_node))
2181 {
2182 dt_dev_history_item_t *history_entry = (dt_dev_history_item_t *)(history_node->data);
2183 if(history_entry->num == history_num)
2184 {
2185 history_item = history_entry;
2186 break;
2187 }
2188 }
2189 previous_num = history_num;
2190 }
2191 // add the form to the history entry
2192 // FIXME: there is no reason to hack history_item to add a forms snapshot that doesn't
2193 // belong to it because dt_dev_write_history_item() doesn't save history_item->forms to the DB.
2194 // So this forms snapshot should be attached to its own object, and that object should be
2195 // linked by ID to the history_item object. That would allow to share one forms snapshot
2196 // between several history items without duplication.
2197 if(history_item)
2198 {
2199 history_item->forms = g_list_append(history_item->forms, mask_form);
2200 }
2201 else
2202 fprintf(stderr,
2203 "[_dev_read_masks_history] can't find history entry %i while adding mask %s(%i)\n",
2204 history_num, mask_form->name, form_id);
2205
2206 if(history_num < dt_dev_get_history_end_ext(develop)) last_history_item = history_item;
2207 }
2208 sqlite3_finalize(statement);
2209
2210 // and we update the current forms snapshot
2211 dt_masks_replace_current_forms(develop, (last_history_item) ? last_history_item->forms : NULL);
2212}
2213
2214void dt_masks_write_masks_history_item(const int32_t image_id, const int history_num,
2215 dt_masks_form_t *mask_form)
2216{
2217 sqlite3_stmt *statement = NULL;
2218
2219 dt_print(DT_DEBUG_HISTORY, "[dt_masks_write_masks_history_item] writing mask %s of type %i for image %i\n",
2220 mask_form->name, mask_form->type, image_id);
2221
2222 // write the form into the database
2223 // clang-format off
2225 "INSERT INTO main.masks_history (imgid, num, formid, form, name, "
2226 "version, points, points_count,source) VALUES "
2227 "(?1, ?9, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
2228 -1, &statement, NULL);
2229 // clang-format on
2230 DT_DEBUG_SQLITE3_BIND_INT(statement, 1, image_id);
2231 DT_DEBUG_SQLITE3_BIND_INT(statement, 9, history_num);
2232 DT_DEBUG_SQLITE3_BIND_INT(statement, 2, mask_form->formid);
2233 DT_DEBUG_SQLITE3_BIND_INT(statement, 3, mask_form->type);
2234 DT_DEBUG_SQLITE3_BIND_TEXT(statement, 4, mask_form->name, -1, SQLITE_TRANSIENT);
2235 DT_DEBUG_SQLITE3_BIND_BLOB(statement, 8, mask_form->source, 2 * sizeof(float), SQLITE_TRANSIENT);
2236 DT_DEBUG_SQLITE3_BIND_INT(statement, 5, mask_form->version);
2237 if(mask_form->functions)
2238 {
2239 const size_t point_struct_size = mask_form->functions->point_struct_size;
2240 const guint point_count = g_list_length(mask_form->points);
2241 char *const restrict point_buffer = (char *)malloc(point_count * point_struct_size);
2242 int buffer_offset = 0;
2243 for(GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
2244 {
2245 memcpy(point_buffer + buffer_offset, point_node->data, point_struct_size);
2246 buffer_offset += point_struct_size;
2247 }
2248 DT_DEBUG_SQLITE3_BIND_BLOB(statement, 6, point_buffer,
2249 point_count * point_struct_size, SQLITE_TRANSIENT);
2250 DT_DEBUG_SQLITE3_BIND_INT(statement, 7, point_count);
2251 sqlite3_step(statement);
2252 sqlite3_finalize(statement);
2253 dt_free(point_buffer);
2254 }
2255}
2256
2258{
2259 if(IS_NULL_PTR(mask_form)) return;
2260 g_list_free_full(mask_form->points, dt_free_gpointer);
2261 mask_form->points = NULL;
2262 dt_free(mask_form);
2263}
2264
2266{
2267 return 0;
2268}
2269
2271{
2272 return 0;
2273}
2274
2276{
2277 return mask_gui->form_selected
2278 || mask_gui->source_selected
2279 || mask_gui->seg_selected
2280 || mask_gui->node_selected
2281 || mask_gui->handle_selected
2282 || mask_gui->handle_border_selected;
2283}
2284
2286{
2287 return mask_gui->node_hovered >= 0
2288 || mask_gui->handle_hovered >= 0
2289 || mask_gui->handle_border_hovered >= 0
2290 || mask_gui->seg_hovered >= 0;
2291}
2292
2294{
2295 if(IS_NULL_PTR(mask_gui)) return;
2296
2297 // circular arrows
2298 if(mask_gui->pivot_selected)
2299 dt_control_queue_cursor(GDK_EXCHANGE);
2300 // pointing hand
2301 else if(mask_gui->creation_closing_form)
2302 dt_control_queue_cursor(GDK_HAND2);
2303
2304 /*else if(gui->handle_dragging >= 0)
2305 dt_control_set_cursor(GDK_HAND1);*/
2306
2307 // crosshair
2308 else if(!mask_gui->creation
2309 && (dt_masks_is_anything_selected(mask_gui)
2310 || dt_masks_is_anything_hovered(mask_gui)))
2311 dt_control_queue_cursor(GDK_FLEUR);
2312}
2313
2314static void _apply_gui_button_pressed_state(dt_masks_form_gui_t *mask_gui, const int button,
2315 const uint32_t state,
2316 const gboolean shape_was_selected)
2317{
2318 if(IS_NULL_PTR(mask_gui) || mask_gui->creation || button != 1) return;
2319 // Drag is only allowed when this click happens on a shape that was already selected.
2320 // We still rebuild the fine-grained selection from the current hover target first, so the
2321 // pressed node/handle/segment becomes the active drag target when dragging is allowed.
2322 const gboolean prev_node_selected = mask_gui->node_selected;
2323 const int prev_node_selected_idx = mask_gui->node_selected_idx;
2324 const gboolean prev_form_selected = mask_gui->form_selected;
2325 const gboolean prev_border_selected = mask_gui->border_selected;
2326 const gboolean prev_source_selected = mask_gui->source_selected;
2327
2328 mask_gui->node_selected = FALSE;
2329 mask_gui->handle_selected = FALSE;
2330 mask_gui->handle_border_selected = FALSE;
2331 mask_gui->seg_selected = FALSE;
2332 mask_gui->node_selected_idx = -1;
2333 mask_gui->form_selected = FALSE;
2334 mask_gui->border_selected = FALSE;
2335 mask_gui->source_selected = FALSE;
2336
2337 if(mask_gui->node_hovered >= 0)
2338 {
2339 mask_gui->node_selected = TRUE;
2340 mask_gui->node_selected_idx = mask_gui->node_hovered;
2341 }
2342 else if(mask_gui->handle_hovered >= 0)
2343 {
2344 if(prev_node_selected)
2345 {
2346 mask_gui->node_selected = TRUE;
2347 mask_gui->node_selected_idx = prev_node_selected_idx;
2348 }
2349 mask_gui->handle_selected = TRUE;
2350 }
2351 else if(mask_gui->handle_border_hovered >= 0)
2352 {
2353 if(prev_node_selected)
2354 {
2355 mask_gui->node_selected = TRUE;
2356 mask_gui->node_selected_idx = prev_node_selected_idx;
2357 }
2358 mask_gui->handle_border_selected = TRUE;
2359 }
2360 else if(mask_gui->seg_hovered >= 0)
2361 {
2362 mask_gui->seg_selected = TRUE;
2363 }
2364 else
2365 {
2366 mask_gui->form_selected = prev_form_selected;
2367 mask_gui->border_selected = prev_border_selected;
2368 mask_gui->source_selected = prev_source_selected;
2369 }
2370
2371 if(mask_gui->form_rotating || mask_gui->border_toggling || mask_gui->gradient_toggling) return;
2372 if(dt_modifier_is(state, GDK_CONTROL_MASK)) return;
2373 if(!shape_was_selected) return;
2374
2375 dt_masks_gui_set_dragging(mask_gui);
2376}
2377
2387static void _dt_masks_events_set_current_pos(const double x, const double y, dt_masks_form_gui_t *mask_gui)
2388{
2389 if(IS_NULL_PTR(mask_gui)) return;
2390
2391 float point[2] = { (float)x, (float)y };
2393 mask_gui->rel_pos[0] = point[0];
2394 mask_gui->rel_pos[1] = point[1];
2395
2397 mask_gui->pos[0] = point[0];
2398 mask_gui->pos[1] = point[1];
2399
2400 mask_gui->raw_pos[0] = point[0];
2401 mask_gui->raw_pos[1] = point[1];
2403}
2404
2405int dt_masks_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which)
2406{
2407 // This assume that if this event is generated, the mouse is over the center window.
2408 // record mouse position even if there are no masks visible
2411 if(IS_NULL_PTR(mask_gui)) return 0;
2412 _dt_masks_events_set_current_pos(x, y, mask_gui);
2413
2414 // do not process if no forms visible
2415 if(IS_NULL_PTR(mask_form)) return 0;
2416
2417 // add an option to allow skip mouse events while editing masks
2419
2420 int result = 0;
2421 if((mask_form->type & DT_MASKS_GROUP) && _dt_masks_events_group_blocks_motion(mask_gui))
2422 {
2423 result = 1;
2424 }
2425 else
2426 {
2427 dt_masks_form_group_t *group_entry = NULL;
2428 int parent_id = 0;
2429 int form_index = 0;
2430 dt_masks_form_t *dispatch_form
2431 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2432 dispatch_form = dt_masks_cow_touch(darktable.develop, dispatch_form);
2433
2435 result = _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
2436
2437 if(!result && dispatch_form && dispatch_form->functions && dispatch_form->functions->mouse_moved)
2438 result = dispatch_form->functions->mouse_moved(module, x, y, pressure, which,
2439 dispatch_form, parent_id, mask_gui, form_index);
2440 }
2441
2442 if(!IS_NULL_PTR(mask_gui))
2443 {
2444 _set_hinter_message(mask_gui, mask_form);
2445 _set_cursor_shape(mask_gui);
2446 }
2447 return result;
2448}
2449
2450int dt_masks_events_button_released(struct dt_iop_module_t *module, double x, double y, int button,
2451 uint32_t state)
2452{
2453 // add an option to allow skip mouse events while editing masks
2455
2458 if(IS_NULL_PTR(mask_gui)) return 0;
2459 _dt_masks_events_set_current_pos(x, y, mask_gui);
2460 if(IS_NULL_PTR(mask_form)) return 0;
2461
2462 dt_masks_form_group_t *group_entry = NULL;
2463 int parent_id = 0;
2464 int form_index = 0;
2465 dt_masks_form_t *dispatch_form
2466 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2467 dispatch_form = dt_masks_cow_touch(darktable.develop, dispatch_form);
2468
2469 int result = 0;
2470 if(!IS_NULL_PTR(dispatch_form) && dispatch_form->functions && dispatch_form->functions->button_released)
2471 result = dispatch_form->functions->button_released(module, x, y, button,
2472 state, dispatch_form, parent_id, mask_gui, form_index);
2473
2474 if(_dt_masks_events_flush_rebuild_if_needed(module, dispatch_form, mask_gui, form_index, button))
2475 result = 1;
2476
2477 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP) && !IS_NULL_PTR(mask_gui))
2478 {
2479 const dt_masks_form_group_t *selected_group_entry
2480 = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
2481 if(selected_group_entry)
2483 selected_group_entry->formid, FALSE);
2484 }
2485
2486 if(mask_gui && !mask_gui->creation && button == 1)
2488
2489 if(!IS_NULL_PTR(mask_gui))
2490 {
2491 _set_hinter_message(mask_gui, mask_form);
2492 _set_cursor_shape(mask_gui);
2493 }
2494
2495 return result;
2496}
2497
2498int dt_masks_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure,
2499 int button, int event_type, uint32_t state)
2500{
2501 // add an option to allow skip mouse events while editing masks
2503
2506 if(IS_NULL_PTR(mask_gui)) return 0;
2507
2508 _dt_masks_events_set_current_pos(x, y, mask_gui);
2509 if(IS_NULL_PTR(mask_form)) return 0;
2510 const gboolean prev_any_selected = dt_masks_is_anything_selected(mask_gui);
2511 const int prev_group_selected = mask_gui->group_selected;
2512
2513 /*DT_DEBUG_CONTROL_SIGNAL_RAISE(darktable.signals, DT_SIGNAL_MASK_SELECTION_CHANGED, NULL, NULL);*/
2514
2515 if(mask_form->type & DT_MASKS_GROUP)
2516 _dt_masks_events_group_update_selection(mask_form, mask_gui);
2517
2518 dt_masks_form_group_t *group_entry = NULL;
2519 int parent_id = 0;
2520 int form_index = 0;
2521 dt_masks_form_t *dispatch_form
2522 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2523 dispatch_form = dt_masks_cow_touch(darktable.develop, dispatch_form);
2524 _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
2525
2526 gboolean return_val = FALSE;
2527 if(!IS_NULL_PTR(dispatch_form) && dispatch_form->functions && dispatch_form->functions->button_pressed)
2528 return_val = dispatch_form->functions->button_pressed(module, x, y, pressure,
2529 button, event_type, state,
2530 dispatch_form, parent_id, mask_gui, form_index);
2531 // Throw a selection change event.
2532 // `dispatch_form` can pass NULL in case of deselection.
2533 dt_masks_select_form(module, dispatch_form);
2534
2535 const gboolean shape_was_selected = (mask_form->type & DT_MASKS_GROUP)
2536 ? (prev_group_selected >= 0 && prev_group_selected == form_index)
2537 : prev_any_selected;
2538 _apply_gui_button_pressed_state(mask_gui, button, state, shape_was_selected);
2539
2540 if(button == 3 && !return_val)
2541 {
2542 // mouse is over a form or one of its handles/nodes
2543 if(!IS_NULL_PTR(dispatch_form) && (mask_gui->creation
2544 || dt_masks_is_anything_hovered(mask_gui)
2545 || dt_masks_is_anything_selected(mask_gui)))
2546 {
2547 GtkWidget *menu = dt_masks_create_menu(mask_gui, dispatch_form, group_entry,
2548 mask_gui->rel_pos[0], mask_gui->rel_pos[1]);
2549 if(!IS_NULL_PTR(menu))
2550 {
2551 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
2552 return_val = TRUE;
2553 }
2554 }
2555 }
2556
2557 return return_val;
2558}
2559
2560int dt_masks_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event)
2561{
2563 if(IS_NULL_PTR(mask_form)) return 0;
2565 if(IS_NULL_PTR(mask_gui)) return 0;
2566
2567 gboolean return_value = FALSE;
2568 if(mask_form->type & DT_MASKS_GROUP)
2569 {
2570 dt_masks_form_group_t *group_entry = NULL;
2571 int parent_id = 0;
2572 int form_index = 0;
2573 dt_masks_form_t *dispatch_form
2574 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2575 dispatch_form = dt_masks_cow_touch(darktable.develop, dispatch_form);
2576 if(dispatch_form && dispatch_form->functions && dispatch_form->functions->key_pressed)
2577 return_value = dispatch_form->functions->key_pressed(module, event, dispatch_form,
2578 parent_id, mask_gui, form_index);
2579
2580 if(!return_value && mask_form->functions->key_pressed)
2581 {
2582 mask_form = dt_masks_cow_touch(darktable.develop, mask_form);
2583 return_value = mask_form->functions->key_pressed(module, event, mask_form, 0, mask_gui, 0);
2584 }
2585 }
2586 else if(mask_form->functions->key_pressed)
2587 {
2588 mask_form = dt_masks_cow_touch(darktable.develop, mask_form);
2589 return_value = mask_form->functions->key_pressed(module, event, mask_form, 0, mask_gui, 0);
2590 }
2591
2592 if(!return_value)
2593 {
2594 guint key = dt_keys_mainpad_alternatives(event->keyval);
2595 switch(key)
2596 {
2597 case GDK_KEY_Escape:
2598 {
2599 return_value = dt_masks_form_exit_creation(module, mask_gui);
2600 break;
2601 }
2602 case GDK_KEY_Delete:
2603 {
2604 if(mask_gui->group_selected >= 0)
2605 {
2606 // Delete shape from current group
2607 dt_masks_form_group_t *selected_group = dt_masks_form_get_selected_group(mask_form, mask_gui);
2608 if(IS_NULL_PTR(selected_group)) return 0;
2609 dt_masks_form_t *selected_form = dt_masks_get_from_id(darktable.develop, selected_group->formid);
2610 if(selected_form)
2611 return_value = dt_masks_gui_remove(module, selected_form, mask_gui, selected_group->parentid);
2612 break;
2613 }
2614 }
2615 }
2616 }
2617
2618 return return_value;
2619}
2620
2621int dt_masks_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y,
2622 int scroll_up, uint32_t key_state, int scrolling_delta)
2623{
2624 // add an option to allow skip mouse events while editing masks
2626
2629 if(IS_NULL_PTR(mask_gui)) return 0;
2630
2631 _dt_masks_events_set_current_pos(x, y, mask_gui);
2632 if(IS_NULL_PTR(mask_form)) return 0;
2633
2634 int result = 0;
2635 const gboolean scroll_increases = dt_mask_scroll_increases(scroll_up);
2636
2637 // we want delta_y to be an absolute scrolling speed
2638 int scroll_flow = (scrolling_delta < 0) ? -scrolling_delta : scrolling_delta;
2639
2640 dt_masks_form_group_t *group_entry = NULL;
2641 int parent_id = 0;
2642 int form_index = 0;
2643 dt_masks_form_t *dispatch_form
2644 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2645 dispatch_form = dt_masks_cow_touch(darktable.develop, dispatch_form);
2646
2647 if(!mask_gui->creation && !dt_masks_is_anything_selected(mask_gui))
2648 return 0;
2649
2650 _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
2651
2652 if(!mask_gui->creation && !_dt_masks_events_cursor_over_form(dispatch_form, mask_gui, form_index))
2653 return 0;
2654
2655 if(dispatch_form && dispatch_form->functions && dispatch_form->functions->mouse_scrolled)
2656 result = dispatch_form->functions->mouse_scrolled(module, x, y,
2657 scroll_increases ? 1 : 0, scroll_flow,
2658 key_state, dispatch_form, parent_id, mask_gui, form_index,
2660
2661 if(!IS_NULL_PTR(mask_gui))
2662 {
2663 const gboolean hinted = _set_hinter_message(mask_gui, mask_form);
2665 "[masks] scroll: ret=%d hinted=%d form=%p type=%d gui=%p group_selected=%d flow=%d state=0x%x\n",
2666 result, hinted, (void *)mask_form, mask_form ? mask_form->type : -1, (void *)mask_gui,
2667 mask_gui->group_selected, scroll_flow, key_state);
2668 if(hinted)
2669 result = 1;
2670 }
2671 return result;
2672}
2673
2674gboolean dt_masks_node_is_cusp(const dt_masks_form_gui_points_t *gui_points, const int node_index)
2675{
2676 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(gui_points->points)) return FALSE;
2677 if(gui_points->points_count <= 0 || node_index < 0 || node_index >= gui_points->points_count) return FALSE;
2678
2679 const float *point_values = &gui_points->points[node_index * 6];
2680 return (point_values[0 + 2] == point_values[2 + 2]
2681 && point_values[1 + 2] == point_values[3 + 2]);
2682}
2683
2699static void _dt_masks_find_best_attachment_point(const float ray_1[2], const float ray_2[2],
2700 const float *points, const int points_count, const float zoom_scale,
2701 const int first_pt,
2702 const gboolean is_closed_shape,
2703 const float offset_factor,
2704 float result[2])
2705{
2706 // Fallback: no intersection found.
2707 result[0] = ray_1[0];
2708 result[1] = ray_1[1];
2709
2710 const int available_points = points_count - first_pt;
2711 if(available_points < 2) return;
2712
2713 const float ray2_x = ray_2[0];
2714 const float ray2_y = ray_2[1];
2715 const float ray_center_x = 0.5f * (ray_1[0] + ray_2[0]);
2716 const float ray_center_y = 0.5f * (ray_1[1] + ray_2[1]);
2717 const float dir_x = ray_1[0] - ray_2[0];
2718 const float dir_y = ray_1[1] - ray_2[1];
2719 float min_s = FLT_MAX;
2720 const float offset = DT_PIXEL_APPLY_DPI(12.0f * offset_factor) / zoom_scale;
2721 const float inv_dir_len = f_inv_sqrtf(dir_x * dir_x + dir_y * dir_y);
2722 const float ux = dir_x * inv_dir_len;
2723 const float uy = dir_y * inv_dir_len;
2724
2725
2726 const int segment_count = (available_points - 1) + ((is_closed_shape) ? 1 : 0);
2727 for(int seg = 0; seg < segment_count; seg++)
2728 {
2729 // Get the current segment [i, j], with wrap-around if the shape is closed.
2730 const int i = first_pt + seg;
2731 const int j = (i + 1 < points_count) ? (i + 1) : first_pt;
2732 const float x3 = points[i * 2];
2733 const float y3 = points[i * 2 + 1];
2734 const float x4 = points[j * 2];
2735 const float y4 = points[j * 2 + 1];
2736
2737 // Compute the intersection of the ray with the segment.
2738 const float dx = x4 - x3;
2739 const float dy = y4 - y3;
2740 const float det = dx * (-dir_y) + dy * dir_x;
2741 if(det > -1e-8f && det < 1e-8f) continue;
2742 const float inv_det = 1.0f / det;
2743
2744 const float segment_param = ((ray2_x - x3) * (-dir_y) + (ray2_y - y3) * dir_x) * inv_det;
2745 const float ray_param = ((x3 - ray2_x) * dy - (y3 - ray2_y) * dx) * inv_det;
2746 if(segment_param < 0.0f || segment_param > 1.0f || ray_param <= 0.0f || ray_param >= min_s) continue;
2747
2748 min_s = ray_param;
2749 const float ix = ray2_x + ray_param * dir_x;
2750 const float iy = ray2_y + ray_param * dir_y;
2751
2752 // Offset along the ray axis toward the ray segment center.
2753 const float to_center = (ray_center_x - ix) * ux + (ray_center_y - iy) * uy;
2754 const float side_sign = (to_center >= 0.0f) ? 1.0f : -1.0f;
2755 result[0] = ix + side_sign * offset * ux;
2756 result[1] = iy + side_sign * offset * uy;
2757 }
2758}
2759
2760void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index,
2761 const int node_count, const float zoom_scale,
2762 struct dt_masks_gui_center_point_t *center_point,
2763 const shape_draw_function_t *draw_shape_func)
2764{
2765 if(IS_NULL_PTR(mask_gui)) return;
2766 dt_masks_form_gui_points_t *gui_points
2767 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
2768 if(IS_NULL_PTR(gui_points)) return;
2769
2770 if(!mask_gui->creation)
2771 {
2772 const float main[2] = { center_point->main.x, center_point->main.y };
2773 const float source[2] = { center_point->source.x, center_point->source.y };
2774 const gboolean is_open_shape = (mask_gui->type & DT_MASKS_IS_OPEN_SHAPE) != 0;
2775 const gboolean is_closed_shape = (mask_gui->type & DT_MASKS_IS_CLOSED_SHAPE) != 0;
2776
2777 int first_point_index = 2;
2778 if((mask_gui->type & DT_MASKS_ELLIPSE) != 0)
2779 first_point_index = 10;
2780 else if((mask_gui->type & DT_MASKS_IS_PATH_SHAPE) != 0)
2781 first_point_index = node_count * 3;
2782
2783 int attach_points_count = gui_points->points_count;
2784 int attach_source_count = gui_points->source_count;
2785 if((mask_gui->type & DT_MASKS_BRUSH) != 0)
2786 {
2787 attach_points_count /= 2;
2788 attach_source_count /= 2;
2789 }
2790
2791 float head[2] = { 0.0f, 0.0f };
2792 float tail[2] = { 0.0f, 0.0f };
2793
2794 // Find attachment point for the arrow's head with the main shape
2795 _dt_masks_find_best_attachment_point(main, source, gui_points->points, attach_points_count,
2796 zoom_scale, first_point_index, is_closed_shape, 1.f, head);
2797
2798 // Find attachment point for the arrow's base with the source shape
2799 _dt_masks_find_best_attachment_point(source, main, gui_points->source, attach_source_count,
2800 zoom_scale, first_point_index, is_closed_shape, 0.5f, tail);
2801
2802 const gboolean selected = (mask_gui->group_selected == form_index)
2803 && (mask_gui->source_selected || mask_gui->source_dragging);
2804 gboolean draw_tail = TRUE;
2805
2806 // Do not draw the arrow tail if the shape overlapes source,
2807 // Just draw the head pointing to the center of the source shape
2808 // and displace it at mid-distance between main and source center.
2809 // Open shapes always draw the tail since they have not really filled area.
2810 if(is_closed_shape)
2811 {
2812 // From more frequent to least frequent, to get out of loop earlier. Tail first, then source.
2813 const float pts[4] = { tail[0], tail[1], source[0], source[1] };
2814 gboolean overlap = (dt_masks_point_in_form_exact(pts, 2, gui_points->points, first_point_index,
2815 gui_points->points_count) >= 0);
2816 // Skip the second containment test when overlap is already detected.
2817 if(!overlap)
2818 {
2819 const float origin_pt[2] = { main[0], main[1] };
2820 overlap = (dt_masks_point_in_form_exact(origin_pt, 1, gui_points->source, first_point_index,
2821 gui_points->source_count) >= 0);
2822 }
2823
2824 // Update head position to be between main and source center point.
2825 if(overlap)
2826 {
2827 head[0] = 0.5f * (main[0] + source[0]);
2828 head[1] = 0.5f * (main[1] + source[1]);
2829 }
2830
2831 const float arrow_len_sq = sqf(tail[0] - head[0]) + sqf(tail[1] - head[1]);
2832 draw_tail = arrow_len_sq > 1e-6f && !overlap;
2833 }
2834
2835 // Calculate the angle, so the arrow head always points in the direction of the main shape's center.
2836 const float angle = is_open_shape ? atan2f(tail[1] - head[1], tail[0] - head[0])
2837 : atan2f(head[1] - main[1], head[0] - main[0]);
2838
2839 dt_draw_arrow(cr, zoom_scale, selected, draw_tail, DT_MASKS_DASH_ROUND, head, tail, angle);
2840
2841
2842
2843
2845 {
2846 // Debug: show the main and source gravity points, show head and tail points
2847 cairo_save(cr);
2848 cairo_arc(cr, main[0], main[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
2849 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1);
2850 cairo_fill(cr);
2851 cairo_arc(cr, source[0], source[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
2852 cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1);
2853 cairo_fill(cr);
2854
2855 cairo_arc(cr, head[0], head[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
2856 cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1);
2857 cairo_fill(cr);
2858 cairo_arc(cr, tail[0], tail[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
2859 cairo_fill(cr);
2860 cairo_restore(cr);
2861 }
2862 }
2863
2864 // draw the source shape
2865 // Trick to draw the current polygon shape lines while editing, but draw the complete shape in all other cases
2866 const int rendered_node_count = node_count + !mask_gui->creation;
2867 const gboolean shape_selected = (mask_gui->group_selected == form_index)
2868 && (mask_gui->form_selected || mask_gui->form_dragging);
2869
2870 dt_draw_source_shape(cr, zoom_scale, shape_selected, gui_points->source, gui_points->source_count,
2871 rendered_node_count, draw_shape_func);
2872
2873}
2874
2875void dt_masks_draw_path_seg_by_seg(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index,
2876 const float *points, const int points_count, const int node_count,
2877 const float zoom_scale)
2878{
2879 if(IS_NULL_PTR(cr) || IS_NULL_PTR(points) || IS_NULL_PTR(mask_gui)) return;
2880 if(node_count <= 0 || points_count <= node_count * 3 + 6) return;
2881
2882 const int total_coords = points_count * 2;
2883 if(total_coords <= (node_count * 6 + 1)) return;
2884
2885 const gboolean group_selected = (mask_gui->group_selected == form_index);
2886
2887 int show_segment_index = 1;
2888 int current_segment_index = 0;
2889 cairo_move_to(cr, points[node_count * 6], points[node_count * 6 + 1]);
2890
2891 for(int point_index = node_count * 3; point_index < points_count; point_index++)
2892 {
2893 const int coord_index = point_index * 2;
2894 if((coord_index + 1) >= total_coords) break;
2895
2896 const double coord_x = points[coord_index];
2897 const double coord_y = points[coord_index + 1];
2898 cairo_line_to(cr, coord_x, coord_y);
2899
2900 const int segment_coord_index = show_segment_index * 6;
2901 if((segment_coord_index + 3) >= total_coords) continue;
2902
2903 const double segment_x = points[segment_coord_index + 2];
2904 const double segment_y = points[segment_coord_index + 3];
2905 if(coord_x == segment_x && coord_y == segment_y)
2906 {
2907 const gboolean seg_is_selected = group_selected
2909 == current_segment_index);
2910 const gboolean all_selected = group_selected
2912 && (mask_gui->form_selected || mask_gui->form_dragging);
2913
2914 if(mask_gui->creation && current_segment_index == node_count - 2)
2915 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
2916 else
2917 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, (seg_is_selected || all_selected), zoom_scale,
2918 CAIRO_LINE_CAP_BUTT);
2919
2920 show_segment_index = (show_segment_index + 1) % node_count;
2921 current_segment_index++;
2922 }
2923
2924 if(mask_gui->creation && current_segment_index >= node_count - 1) break;
2925 }
2926}
2927
2937 cairo_t *cr, const float zoom_scale,
2938 const dt_masks_form_gui_t *creation_gui)
2939{
2940 if(!creation_gui->creation || IS_NULL_PTR(creation_gui->creation_formids)) return;
2941
2942 dt_masks_form_gui_t draw_gui;
2943 dt_masks_init_form_gui(&draw_gui);
2944 draw_gui.edit_mode = creation_gui->edit_mode;
2945 draw_gui.group_selected = -1;
2947
2948 // Iterate over the ids saved in this creation session. Other masks stay
2949 // hidden while creation is active, even if they belong to the same module.
2950 for(GList *formid_node = creation_gui->creation_formids; formid_node; formid_node = g_list_next(formid_node))
2951 {
2952 const int formid = GPOINTER_TO_INT(formid_node->data);
2953 dt_masks_form_t *session_form = dt_masks_get_from_id(develop, formid);
2954 if(IS_NULL_PTR(session_form)) continue;
2955
2956 dt_masks_gui_form_test_create(session_form, &draw_gui, module);
2957 if(session_form->functions && session_form->functions->post_expose)
2958 {
2959 const guint point_count = g_list_length(session_form->points);
2960 draw_gui.type = session_form->type;
2961 session_form->functions->post_expose(cr, zoom_scale, &draw_gui, 0, point_count);
2962 }
2963
2964 // Keep the saved-session shape path from being connected to the active
2965 // creation cursor drawn right after this loop.
2966 cairo_new_path(cr);
2967 g_list_free_full(draw_gui.points, dt_masks_form_gui_points_free);
2968 draw_gui.points = NULL;
2970 draw_gui.formid = 0;
2971 }
2972}
2973
2974void dt_masks_events_post_expose(struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height,
2975 int32_t pointerx, int32_t pointery)
2976{
2977 dt_develop_t *develop = darktable.develop;
2978 if(IS_NULL_PTR(develop)) return;
2979 dt_masks_form_t *mask_form = dt_masks_get_visible_form(develop);
2980 dt_masks_form_gui_t *mask_gui = develop->form_gui;
2981 if(IS_NULL_PTR(mask_gui)) return;
2982 if(IS_NULL_PTR(mask_form)) return;
2983
2984 int buffer_width = 0;
2985 int buffer_height = 0;
2986 dt_dev_get_processed_size(develop, &buffer_width, &buffer_height);
2987
2988 if(buffer_width < 1.0 || buffer_height < 1.0) return;
2989 const float zoom_scale = dt_dev_get_zoom_level(develop);
2990
2991 // Create a surface to draw the mask, so that we can apply
2992 // operation that does not affect the main context
2993 cairo_surface_t *overlay = NULL;
2994 cairo_t *mask_draw = NULL;
2995 cairo_surface_t *target = cairo_get_target(cr);
2996 double sx = 1.0, sy = 1.0;
2997 cairo_surface_get_device_scale(target, &sx, &sy);
2998 overlay = cairo_surface_create_similar(target, CAIRO_CONTENT_COLOR_ALPHA, (int)ceil(width * sx),
2999 (int)ceil(height * sy));
3000 cairo_surface_set_device_scale(overlay, sx, sy);
3001 mask_draw = cairo_create(overlay);
3002
3003 // Apply the same transformation to the mask drawing context
3004 /*cairo_matrix_t m;
3005 cairo_get_matrix(cr, &m);
3006 cairo_set_matrix(mask_draw, &m);*/
3007
3008 cairo_save(mask_draw);
3009
3010 // We rescale to input space
3011 if(dt_dev_rescale_roi_to_input(develop, mask_draw, width, height))
3012 {
3013 cairo_restore(mask_draw);
3014 cairo_destroy(mask_draw);
3015 cairo_surface_destroy(overlay);
3016 return;
3017 }
3018
3019 // We update the form if needed
3020 // Add preview when creating a circle, ellipse and gradient
3021 if(!((mask_form->type & DT_MASKS_IS_PRIMITIVE_SHAPE) && mask_gui->creation))
3022 dt_masks_gui_form_test_create(mask_form, mask_gui, module);
3023
3024 _masks_draw_creation_session_forms(develop, module, mask_draw, zoom_scale, mask_gui);
3025
3026 // Draw form
3027 if(mask_form->type & DT_MASKS_GROUP)
3028 dt_group_events_post_expose(mask_draw, zoom_scale, mask_form, mask_gui);
3029 else if(mask_form->functions && mask_form->functions->post_expose)
3030 {
3031 const guint point_count = g_list_length(mask_form->points);
3032 mask_gui->type = mask_form->type;
3033 mask_form->functions->post_expose(mask_draw, zoom_scale, mask_gui, 0, point_count);
3034 }
3035 cairo_restore(mask_draw);
3036
3037 // Draw the overlay with the same transformation as the main context
3038 cairo_save(cr);
3039 cairo_identity_matrix(cr);
3040 cairo_set_source_surface(cr, overlay, 0.0, 0.0);
3041 cairo_paint(cr);
3042 cairo_restore(cr);
3043
3044 cairo_destroy(mask_draw);
3045 cairo_surface_destroy(overlay);
3046}
3047
3049{
3050 if(IS_NULL_PTR(develop->form_gui)) return;
3051 g_list_free_full(develop->form_gui->points, dt_masks_form_gui_points_free);
3052 develop->form_gui->points = NULL;
3054 develop->form_gui->guipoints = NULL;
3056 develop->form_gui->guipoints_payload = NULL;
3057 develop->form_gui->guipoints_count = 0;
3059 develop->form_gui->formid = 0;
3060 develop->form_gui->delta[0] = develop->form_gui->delta[1] = 0.0f;
3061 develop->form_gui->scrollx = develop->form_gui->scrolly = 0.0f;
3062 develop->form_gui->form_selected = develop->form_gui->border_selected = develop->form_gui->form_dragging
3063 = develop->form_gui->form_rotating = develop->form_gui->border_toggling = develop->form_gui->gradient_toggling = FALSE;
3064 develop->form_gui->source_selected = develop->form_gui->source_dragging = FALSE;
3065 develop->form_gui->pivot_selected = FALSE;
3066 develop->form_gui->node_hovered = -1;
3067 develop->form_gui->handle_hovered = -1;
3068 develop->form_gui->seg_hovered = -1;
3069 develop->form_gui->handle_border_hovered = -1;
3070 develop->form_gui->node_selected = FALSE;
3071 develop->form_gui->handle_selected = FALSE;
3072 develop->form_gui->seg_selected = FALSE;
3074 develop->form_gui->node_selected_idx = -1;
3076 = develop->form_gui->node_dragging = -1;
3080 develop->form_gui->creation_module = NULL;
3082 g_list_free(develop->form_gui->creation_formids);
3083 develop->form_gui->creation_formids = NULL;
3084 develop->form_gui->creation_last_formid = 0;
3085 develop->form_gui->node_selected = FALSE;
3086
3087 develop->form_gui->group_selected = -1;
3088 develop->form_gui->group_selected = -1;
3090 develop->form_gui->last_rebuild_ts = 0.0;
3091 develop->form_gui->last_rebuild_pos[0] = develop->form_gui->last_rebuild_pos[1] = 0.0f;
3092 develop->form_gui->rebuild_pending = FALSE;
3093 develop->form_gui->last_hit_test_pos[0] = develop->form_gui->last_hit_test_pos[1] = -1.0f;
3094 // allow to select a shape inside an iop
3095 dt_masks_select_form(NULL, NULL);
3096}
3097
3099{
3101 if(!IS_NULL_PTR(old_form))
3102 {
3103 gboolean is_registered = FALSE;
3104 gboolean is_registered_for_cleanup = FALSE;
3106 for(const GList *form_node = darktable.develop->forms; form_node; form_node = g_list_next(form_node))
3107 {
3108 if(form_node->data == old_form)
3109 {
3110 is_registered = TRUE;
3111 break;
3112 }
3113 }
3114
3115 for(const GList *form_node = darktable.develop->allforms; form_node; form_node = g_list_next(form_node))
3116 {
3117 if(form_node->data == old_form)
3118 {
3119 is_registered_for_cleanup = TRUE;
3120 break;
3121 }
3122 }
3124
3125 // Free only fully orphan temporary previews. Forms tracked in either list
3126 // are owned by develop and will be released by its teardown path.
3127 if(!is_registered && !is_registered_for_cleanup) dt_masks_free_form(old_form);
3128 }
3129
3132}
3133
3135{
3138 dt_iop_module_t *module = darktable.develop->gui_module;
3139 if(!IS_NULL_PTR(module) && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(module->flags() & IOP_FLAGS_NO_MASKS)
3140 && !IS_NULL_PTR(module->blend_data))
3141 {
3142 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->blend_data;
3143 blend_data->masks_shown = DT_MASKS_EDIT_OFF;
3144 if(!IS_NULL_PTR(blend_data->masks_edit))
3145 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), 0);
3146 }
3147}
3148
3150{
3152 for(GList *module_node = darktable.develop->iop; module_node; module_node = g_list_next(module_node))
3153 {
3154 dt_iop_module_t *module = (dt_iop_module_t *)module_node->data;
3155 if(module && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(module->flags() & IOP_FLAGS_NO_MASKS)
3156 && !IS_NULL_PTR(module->blend_data))
3157 {
3158 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->blend_data;
3159 blend_data->masks_shown = DT_MASKS_EDIT_OFF;
3160 if(!IS_NULL_PTR(blend_data->masks_edit))
3161 {
3162 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), FALSE);
3163 gtk_widget_queue_draw(blend_data->masks_edit);
3164 }
3165 }
3166 }
3167}
3168
3175
3177{
3178 if(IS_NULL_PTR(module)) return;
3180 if(IS_NULL_PTR(blend_data)) return;
3181
3182 dt_masks_form_t *group_form = NULL;
3183 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
3184 if(value && !IS_NULL_PTR(mask_form))
3185 {
3186 group_form = dt_masks_create_ext(DT_MASKS_GROUP);
3187 group_form->formid = 0;
3188 dt_masks_group_ungroup(group_form, mask_form);
3189 }
3190
3191 if(blend_data) blend_data->masks_shown = value;
3192
3193 dt_masks_change_form_gui(group_form);
3195 if(value && mask_form)
3197 else
3199
3200 if(blend_data->masks_support)
3201 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit),
3203
3205}
3206
3207static void _menu_no_masks(struct dt_iop_module_t *module)
3208{
3209 // we drop all the forms in the iop
3210 dt_masks_form_t *group_form = _group_from_module(darktable.develop, module);
3211 if(group_form) dt_masks_form_delete(module, NULL, group_form);
3212 module->blend_params->mask_id = 0;
3213
3214 // and we update the iop
3216 dt_masks_iop_update(module);
3217}
3218
3220{
3222}
3223
3224static void _menu_add_exist(dt_iop_module_t *module, int form_id)
3225{
3226 if(IS_NULL_PTR(module)) return;
3228 if(IS_NULL_PTR(mask_form)) return;
3229
3230 // is there already a masks group for this module ?
3231 dt_masks_form_t *group_form = _group_from_module(darktable.develop, module);
3232 if(IS_NULL_PTR(group_form))
3233 {
3234 group_form = _group_create(darktable.develop, module, DT_MASKS_GROUP);
3235 }
3236 group_form = dt_masks_cow_touch(darktable.develop, group_form);
3237 // we add the form in this group
3238 dt_masks_group_add_form(group_form, mask_form);
3239 // we save the group
3240 // and we ensure that we are in edit mode
3241
3242 dt_masks_iop_update(module);
3244}
3245
3247{
3248 dt_masks_form_t *group_form = _group_from_module(darktable.develop, module);
3249 if (IS_NULL_PTR(group_form))
3250 return;
3251
3252 _set_group_name_from_module(module, group_form);
3253
3254 dt_masks_iop_update(module);
3255}
3256
3258{
3259 if(IS_NULL_PTR(module) || IS_NULL_PTR(source_module)) return;
3260
3261 // we get the source group
3262 int source_id = source_module->blend_params->mask_id;
3263 dt_masks_form_t *source_group = dt_masks_get_from_id(darktable.develop, source_id);
3264 if(IS_NULL_PTR(source_group) || source_group->type != DT_MASKS_GROUP) return;
3265
3266 // is there already a masks group for this module ?
3267 dt_masks_form_t *group_form = _group_from_module(darktable.develop, module);
3268 if(IS_NULL_PTR(group_form))
3269 {
3270 group_form = _group_create(darktable.develop, module, DT_MASKS_GROUP);
3271 }
3272 // Touch once, before the loop: dt_masks_group_add_form mutates group_form->points on every
3273 // iteration, so it must already be private by the first call, or later iterations would
3274 // append to an orphaned clone instead of the object actually in dev->forms.
3275 group_form = dt_masks_cow_touch(darktable.develop, group_form);
3276 // we copy the src group in this group
3277 for(GList *group_node = source_group->points; group_node; group_node = g_list_next(group_node))
3278 {
3279 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
3280 dt_masks_form_t *mask_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3281 if(mask_form)
3282 {
3283 dt_masks_form_group_t *new_entry = dt_masks_group_add_form(group_form, mask_form);
3284 if(new_entry)
3285 {
3286 new_entry->state = group_entry->state;
3287 new_entry->opacity = group_entry->opacity;
3288 }
3289 }
3290 }
3291
3292 // we save the group
3293
3294}
3295
3297{
3298 // we ensure that the module has focus
3299 dt_iop_module_t *module = (dt_iop_module_t *)data;
3300 dt_iop_request_focus(module);
3301 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->blend_data;
3302
3303 // we determine a higher approx of the entry number
3304 const guint forms_count = g_list_length(module->dev->forms);
3305 const guint iop_count = g_list_length(module->dev->iop);
3306 guint combo_capacity = 5 + forms_count + iop_count;
3307 dt_free(blend_data->masks_combo_ids);
3308 blend_data->masks_combo_ids = malloc(sizeof(int) * combo_capacity);
3309
3310 int *combo_ids = blend_data->masks_combo_ids;
3311 GtkWidget *combo = blend_data->masks_combo;
3312
3313 // we remove all the combo entries except the first one
3314 while(dt_bauhaus_combobox_length(combo) > 1)
3315 {
3317 }
3318
3319 int combo_index = 0;
3320 combo_ids[combo_index] = 0; // nothing to do for the first entry (already here)
3321 combo_index++;
3322
3323 // add existing shapes
3324 dt_pthread_rwlock_rdlock(&module->dev->masks_mutex);
3325 for(GList *form_node = module->dev->forms; form_node; form_node = g_list_next(form_node))
3326 {
3327 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_node->data;
3328 if((mask_form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE))
3329 || mask_form->formid == module->blend_params->mask_id)
3330 {
3331 continue;
3332 }
3333
3334 // we search were this form is used in the current module
3335 int is_used = 0;
3336 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
3337 if(group_form && (group_form->type & DT_MASKS_GROUP))
3338 {
3339 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
3340 {
3341 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
3342 if(group_entry->formid == mask_form->formid)
3343 {
3344 is_used = 1;
3345 break;
3346 }
3347 }
3348 }
3349 if(!is_used)
3350 {
3351 dt_bauhaus_combobox_add(combo, mask_form->name);
3352 combo_ids[combo_index] = mask_form->formid;
3353 combo_index++;
3354 }
3355 }
3356 dt_pthread_rwlock_unlock(&module->dev->masks_mutex);
3357
3358 // masks from other iops
3359 int iop_index = 1;
3360 for(GList *module_node = module->dev->iop; module_node; module_node = g_list_next(module_node))
3361 {
3362 dt_iop_module_t *other_module = (dt_iop_module_t *)module_node->data;
3363 if((other_module != module) && (other_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
3364 && !(other_module->flags() & IOP_FLAGS_NO_MASKS))
3365 {
3366 dt_masks_form_t *group_form = _group_from_module(darktable.develop, other_module);
3367 if(group_form)
3368 {
3369 gchar *module_label = dt_history_item_get_name(other_module);
3370 dt_bauhaus_combobox_add(combo, g_strdup_printf(_("reuse shapes from %s"), module_label));
3371 dt_free(module_label);
3372 combo_ids[combo_index] = -1 * iop_index;
3373 combo_index++;
3374 }
3375 }
3376 iop_index++;
3377 }
3378}
3379
3381{
3382 // we get the corresponding value
3384
3385 int selection_index = dt_bauhaus_combobox_get(blend_data->masks_combo);
3386 if(selection_index == 0) return;
3387 if(selection_index > 0)
3388 {
3389 int selection_value = blend_data->masks_combo_ids[selection_index];
3390 const guint iop_count = g_list_length(module->dev->iop);
3391 // FIXME : these values should use binary enums
3392 if(selection_value == -1000000)
3393 {
3394 // delete all masks
3395 _menu_no_masks(module);
3396 }
3397 else if(selection_value == -2000001)
3398 {
3399 // add a circle shape
3401 }
3402 else if(selection_value == -2000002)
3403 {
3404 // add a path shape
3406 }
3407 else if(selection_value == -2000016)
3408 {
3409 // add a gradient shape
3411 }
3412 else if(selection_value == -2000032)
3413 {
3414 // add a gradient shape
3416 }
3417 else if(selection_value == -2000064)
3418 {
3419 // add a brush shape
3421 }
3422 else if(selection_value < 0)
3423 {
3424 // use same shapes as another iop
3425 selection_value = -1 * selection_value - 1;
3426 if(selection_value < (int)iop_count)
3427 {
3428 dt_iop_module_t *source_module
3429 = (dt_iop_module_t *)g_list_nth_data(module->dev->iop, selection_value);
3430 dt_masks_iop_use_same_as(module, source_module);
3431 // and we ensure that we are in edit mode
3432 //
3433
3435 }
3436 }
3437 else if(selection_value > 0)
3438 {
3439 // add an existing shape
3440 _menu_add_exist(module, selection_value);
3441 }
3442 else
3443 return;
3444 }
3445 // we update the combo line
3446 dt_masks_iop_update(module);
3447 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
3448}
3449
3451 dt_masks_form_t *mask_form)
3452{
3453 if(IS_NULL_PTR(mask_form)) return;
3454 int form_id = mask_form->formid;
3455 if(!IS_NULL_PTR(group_form) && !(group_form->type & DT_MASKS_GROUP)) return;
3456
3457 if(!(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE)) && !IS_NULL_PTR(group_form))
3458 {
3459 group_form = dt_masks_cow_touch(darktable.develop, group_form);
3460 // we try to remove the form from the masks group
3461 int removed = 0;
3462 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
3463 {
3464 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
3465 if(group_entry->formid == form_id)
3466 {
3467 removed = 1;
3468 group_form->points = g_list_remove(group_form->points, group_entry);
3469 dt_free(group_entry);
3470 break;
3471 }
3472 }
3473 if(removed)
3474 if(removed && !IS_NULL_PTR(module))
3475 {
3476 dt_masks_iop_update(module);
3477
3478 }
3479 if(removed) dt_masks_form_update_gravity_center(group_form);
3480 if(removed && IS_NULL_PTR(group_form->points)) dt_masks_form_delete(module, NULL, group_form);
3481 return;
3482 }
3483
3484 if(mask_form->type & DT_MASKS_GROUP && mask_form->type & DT_MASKS_CLONE)
3485 {
3486 // when removing a cloning group the children have to be removed, too, as they won't be shown in the mask manager
3487 // and are thus not accessible afterwards.
3488 while(mask_form->points)
3489 {
3490 dt_masks_form_group_t *group_child = (dt_masks_form_group_t *)mask_form->points->data;
3492 dt_masks_form_delete(module, mask_form, child);
3493 // The recursive call passes mask_form as its own group_form parameter and may have
3494 // COW-cloned it (see the touch above): re-fetch the live object so this loop keeps
3495 // observing the mutation instead of spinning on a stale, now-orphaned copy.
3496 mask_form = dt_masks_get_from_id(darktable.develop, form_id);
3497 if(IS_NULL_PTR(mask_form)) break;
3498 }
3499 }
3500
3501 // if we are here that mean we have to permanently delete this form
3502 // we drop the form from all modules
3503 for(GList *iop_node = darktable.develop->iop; iop_node; iop_node = g_list_next(iop_node))
3504 {
3505 dt_iop_module_t *iop_module = (dt_iop_module_t *)iop_node->data;
3506 if(iop_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
3507 {
3508 // is the form the base group of the iop ?
3509 if(form_id == iop_module->blend_params->mask_id)
3510 {
3511 iop_module->blend_params->mask_id = 0;
3512 dt_masks_iop_update(iop_module);
3513 }
3514 else
3515 {
3516 dt_masks_form_t *iop_group = _group_from_module(darktable.develop, iop_module);
3517 if(iop_group && (iop_group->type & DT_MASKS_GROUP))
3518 {
3519 iop_group = dt_masks_cow_touch(darktable.develop, iop_group);
3520 int removed = 0;
3521 GList *shapes = iop_group->points;
3522 while(shapes)
3523 {
3524 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)shapes->data;
3525 if(group_entry->formid == form_id)
3526 {
3527 removed = 1;
3528 // Remove the shape from the list
3529 iop_group->points = g_list_remove(iop_group->points, group_entry);
3530 dt_free(group_entry);
3531 shapes = iop_group->points; // jump back to start of list
3532 continue;
3533 }
3534 shapes = g_list_next(shapes); // advance to next form
3535 }
3536 if(removed)
3537 {
3538 dt_masks_iop_update(iop_module);
3539
3540 if(IS_NULL_PTR(iop_group->points)) dt_masks_form_delete(iop_module, NULL, iop_group);
3541 }
3542 }
3543 }
3544 }
3545 }
3546 // we drop the form from the general list
3547 for(GList *form_node = darktable.develop->forms; form_node; form_node = g_list_next(form_node))
3548 {
3549 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
3550 if(existing_form->formid == form_id)
3551 {
3552 dt_masks_remove_form(darktable.develop, existing_form);
3553 break;
3554 }
3555 }
3556}
3557
3559 dt_masks_interaction_t interaction)
3560{
3561 if(IS_NULL_PTR(form_group)) return NAN;
3562
3563 if(interaction == DT_MASKS_INTERACTION_OPACITY)
3564 {
3565 return form_group->opacity;
3566 }
3567
3568 dt_masks_form_t *target_form = dt_masks_get_from_id(darktable.develop, form_group->formid);
3569 if(IS_NULL_PTR(target_form) || IS_NULL_PTR(target_form->functions) || IS_NULL_PTR(target_form->functions->get_interaction_value)) return NAN;
3570
3571 return target_form->functions->get_interaction_value(target_form, interaction);
3572}
3573
3574gboolean dt_masks_form_get_gravity_center(const dt_masks_form_t *mask_form, float center[2], float *area)
3575{
3576 center[0] = 0.0f;
3577 center[1] = 0.0f;
3578 if(!IS_NULL_PTR(area)) *area = 0.0f;
3579
3580 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->functions) || IS_NULL_PTR(mask_form->functions->get_gravity_center) || IS_NULL_PTR(center)) return FALSE;
3581 return mask_form->functions->get_gravity_center(mask_form, center, area);
3582}
3583
3585{
3586 if(IS_NULL_PTR(mask_form)) return;
3587
3588 float center_point[2];
3589 float area = 0.0f;
3590 const gboolean ok = dt_masks_form_get_gravity_center(mask_form, center_point, &area);
3591 mask_form->gravity_center[0] = center_point[0];
3592 mask_form->gravity_center[1] = center_point[1];
3593 mask_form->area = area;
3594 mask_form->gravity_center_valid = TRUE;
3595
3597 "[masks] gravity center updated: form=%p id=%d type=0x%x ok=%d center=(%f,%f), area=%f\n",
3598 (void *)mask_form, mask_form->formid, mask_form->type, ok,
3599 mask_form->gravity_center[0], mask_form->gravity_center[1], mask_form->area);
3600}
3601
3603{
3604 if(IS_NULL_PTR(mask_form)) return;
3605 mask_form->gravity_center_valid = FALSE;
3606}
3607
3620{
3621 if(IS_NULL_PTR(dev) || IS_NULL_PTR(mask_form)) return 1;
3622
3623 float center[2] = { 0.0f, 0.0f };
3624 float area = 0.0f;
3625 if(!dt_masks_form_get_gravity_center(mask_form, center, &area)) return 1;
3626
3628 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, center, 1)) return 1;
3630
3631 dev->roi.x = center[0];
3632 dev->roi.y = center[1];
3633 dt_dev_check_zoom_pos_bounds(dev, &dev->roi.x, &dev->roi.y, NULL, NULL);
3635
3636 return 0;
3637}
3638
3639static float _change_opacity(dt_masks_form_group_t *form_group, float value,
3640 const dt_masks_increment_t increment, const int flow)
3641{
3642 if(IS_NULL_PTR(form_group)) return 0;
3643
3644 form_group->opacity = CLAMPF(dt_masks_apply_increment(form_group->opacity, value, increment, flow), 0.0f, 1.0f);
3645 dt_toast_log(_("Opacity: %3.2f%%"), form_group->opacity * 100.f);
3646 return form_group->opacity;
3647}
3648
3650 dt_masks_interaction_t interaction,
3651 float value, dt_masks_increment_t increment, int flow,
3652 dt_masks_form_gui_t *mask_gui, dt_iop_module_t *module)
3653{
3654 if(IS_NULL_PTR(form_group)) return NAN;
3655
3656 if(interaction == DT_MASKS_INTERACTION_OPACITY)
3657 {
3658 const float result = _change_opacity(form_group, value, increment, flow);
3659 return result;
3660 }
3661
3662 dt_masks_form_t *target_form = dt_masks_get_from_id(darktable.develop, form_group->formid);
3663 if(IS_NULL_PTR(target_form) || !target_form->functions
3664 || !target_form->functions->set_interaction_value) return NAN;
3665
3666 const float result = target_form->functions->set_interaction_value(target_form, interaction, value, increment,
3667 flow, mask_gui, module);
3668 if(isnan(result)) return NAN;
3670 //dt_dev_add_history_item(darktable.develop, module, TRUE, TRUE);
3671 return result;
3672}
3673
3674const char * _get_mask_plugin(dt_masks_form_t *mask_form)
3675{
3676 // Internal masks are used by spots removal and retouch modules
3677 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
3678 return "spots";
3679 // Regular all-purpose masks
3680 else
3681 return "masks";
3682}
3683
3684const char * _get_mask_type(dt_masks_form_t *mask_form)
3685{
3686 // warning: mask types or not int enum but bit flags ?!?
3687 // that's a shitty design that prevents us from doing a clean switch case over the enum.
3688 // why would we overlap mask types ?!?
3689 if(mask_form->type & DT_MASKS_CIRCLE)
3690 return "circle";
3691 else if(mask_form->type & DT_MASKS_POLYGON)
3692 return "polygon";
3693 else if(mask_form->type & DT_MASKS_ELLIPSE)
3694 return "ellipse";
3695 else if(mask_form->type & DT_MASKS_GRADIENT)
3696 return "gradient";
3697 else if(mask_form->type & DT_MASKS_BRUSH)
3698 return "brush";
3699 else
3700 return "unknown";
3701}
3702
3703float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
3704{
3705 switch(increment)
3706 {
3708 return current * powf(amount, (float)flow);
3710 return current + amount * (float)flow;
3712 default:
3713 return amount;
3714 }
3715}
3716
3717float dt_masks_apply_increment_precomputed(float current, float amount, float scale_amount, float offset_amount,
3718 dt_masks_increment_t increment)
3719{
3720 switch(increment)
3721 {
3723 return current * scale_amount;
3725 return current + offset_amount;
3727 default:
3728 return amount;
3729 }
3730}
3731
3732float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value,
3733 float value_min, float value_max,
3734 dt_masks_increment_t increment, int flow)
3735{
3736 gchar *config_key = NULL;
3737 if(!strcmp(feature, "opacity"))
3738 config_key = g_strdup_printf("plugins/darkroom/%s_opacity", _get_mask_plugin(mask_form));
3739 else
3740 config_key = g_strdup_printf("plugins/darkroom/%s/%s/%s",
3741 _get_mask_plugin(mask_form), _get_mask_type(mask_form), feature);
3742
3743 if(!g_strcmp0(feature, "rotation")) flow = (flow > 1) ? (flow - 1) * 5 : flow;
3744
3745 const float current_value = dt_conf_get_float(config_key);
3746 float updated_value = dt_masks_apply_increment(current_value, new_value, increment, flow);
3747 if(!g_strcmp0(feature, "rotation"))
3748 {
3749 // Ensure the rotation value stays within the interval [min, max)
3750 if(updated_value > value_max) updated_value = fmodf(updated_value, value_max);
3751 else if(updated_value < value_min)
3752 updated_value = value_max - fmodf(value_min - updated_value, value_max);
3753 }
3754 else updated_value = MAX(value_min, MIN(updated_value, value_max));
3755
3756 dt_conf_set_float(config_key, updated_value);
3757
3758 dt_free(config_key);
3759 return updated_value;
3760}
3761
3762float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount,
3763 float value_min, float value_max,
3764 dt_masks_increment_t increment, int flow,
3765 const char *toast_fmt, float toast_scale)
3766{
3767 float value = dt_masks_get_set_conf_value(mask_form, (char *)feature, amount,
3768 value_min, value_max, increment, flow);
3769 if(!IS_NULL_PTR(toast_fmt) && toast_fmt[0] != '\0')
3770 dt_toast_log(toast_fmt, value * toast_scale);
3771 return value;
3772}
3773
3775 size_t node_size)
3776{
3777 if(IS_NULL_PTR(base_form) || IS_NULL_PTR(dest_form) || IS_NULL_PTR(base_form->points) || node_size == 0) return;
3778
3779 for(const GList *point_node = base_form->points; point_node; point_node = g_list_next(point_node))
3780 {
3781 const void *point_data = point_node->data;
3782 if(IS_NULL_PTR(point_data)) continue;
3783 void *point_copy = malloc(node_size);
3784 if(IS_NULL_PTR(point_copy)) continue;
3785 memcpy(point_copy, point_data, node_size);
3786 dest_form->points = g_list_append(dest_form->points, point_copy);
3787 }
3788}
3789
3790int dt_masks_form_change_opacity(dt_masks_form_t *mask_form, int parent_id, int scroll_up,
3791 const int flow)
3792{
3793 if(IS_NULL_PTR(mask_form)) return 0;
3794
3795 // dt_masks_form_set_interaction_value() below mutates the group_entry in place, which is
3796 // memory owned by the parent group, not by mask_form -- touch the parent (not mask_form)
3797 // before resolving the entry, or a shared parent's group_entry gets mutated behind the back
3798 // of a snapshot that still references it.
3799 dt_masks_form_t *parent_form = dt_masks_get_from_id(darktable.develop, parent_id);
3800 if(IS_NULL_PTR(parent_form) || !(parent_form->type & DT_MASKS_GROUP)) return 0;
3801 parent_form = dt_masks_cow_touch(darktable.develop, parent_form);
3802
3803 dt_masks_form_group_t *form_group = _masks_group_find_form(parent_form, mask_form->formid);
3804 if(IS_NULL_PTR(form_group)) return 0;
3805
3806 float amount = scroll_up ? 0.02f : -0.02f;
3807 const float changed = dt_masks_form_set_interaction_value(form_group, DT_MASKS_INTERACTION_OPACITY,
3808 amount, DT_MASKS_INCREMENT_OFFSET, flow, NULL, NULL);
3809 return !isnan(changed);
3810}
3811
3812void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
3813{
3814 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return;
3815
3816 // we search the form in the group
3817 dt_masks_form_group_t *group_entry = NULL;
3818 guint group_index = 0;
3819 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
3820 {
3821 dt_masks_form_group_t *entry = (dt_masks_form_group_t *)group_node->data;
3822 if(entry->formid == form_id)
3823 {
3824 group_entry = entry;
3825 break;
3826 }
3827 group_index++;
3828 }
3829
3830 // we remove the form and read it
3831 if(!IS_NULL_PTR(group_entry))
3832 {
3833 const guint group_length = g_list_length(group_form->points);
3834 if(!move_up && group_index == 0) return;
3835 if(move_up && group_index == group_length - 1) return;
3836
3837 group_form->points = g_list_remove(group_form->points, group_entry);
3838 if(!move_up)
3839 group_index -= 1;
3840 else
3841 group_index += 1;
3842 group_form->points = g_list_insert(group_form->points, group_entry, group_index);
3843
3844 }
3845}
3846
3847static int _find_in_group(dt_masks_form_t *group_form, int form_id)
3848{
3849 if(!(group_form->type & DT_MASKS_GROUP)) return 0;
3850 if(group_form->formid == form_id) return 1;
3851 int nested_count = 0;
3852 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
3853 {
3854 const dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
3855 dt_masks_form_t *mask_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3856 if(mask_form)
3857 {
3858 if(mask_form->type & DT_MASKS_GROUP) nested_count += _find_in_group(mask_form, form_id);
3859 }
3860 }
3861 return nested_count;
3862}
3863
3865{
3866 // add a form to group and check for self inclusion
3867
3868 if(!(group_form->type & DT_MASKS_GROUP)) return NULL;
3869 // either the form to add is not a group, so no risk
3870 // or we go through all points of form to see if we find a ref to grp->formid
3871 if(!(mask_form->type & DT_MASKS_GROUP) || _find_in_group(mask_form, group_form->formid) == 0)
3872 {
3873 dt_masks_form_group_t *group_entry = malloc(sizeof(dt_masks_form_group_t));
3874 group_entry->formid = mask_form->formid;
3875 group_entry->parentid = group_form->formid;
3877 group_entry->opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
3878 group_form->points = g_list_append(group_form->points, group_entry);
3880 return group_entry;
3881 }
3882
3883 dt_control_log(_("Masks can not contain themselves"));
3884 return NULL;
3885}
3886
3888{
3889 if(IS_NULL_PTR(group_form) || IS_NULL_PTR(dest_group)) return;
3890 if(!(group_form->type & DT_MASKS_GROUP) || !(dest_group->type & DT_MASKS_GROUP)) return;
3891
3892 // dest_group->points is mutated below (also across recursive calls, which all receive this
3893 // same touched pointer by value): safe to self-touch here, unlike group_form which is only
3894 // ever read/recursed into, never mutated.
3895 dest_group = dt_masks_cow_touch(darktable.develop, dest_group);
3896
3897 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
3898 {
3899 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
3900 dt_masks_form_t *mask_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3901 if(mask_form)
3902 {
3903 if(mask_form->type & DT_MASKS_GROUP)
3904 {
3905 dt_masks_group_ungroup(dest_group, mask_form);
3906 }
3907 else
3908 {
3910 new_entry->formid = group_entry->formid;
3911 new_entry->parentid = group_entry->parentid;
3912 new_entry->state = group_entry->state;
3913 new_entry->opacity = group_entry->opacity;
3914 dest_group->points = g_list_append(dest_group->points, new_entry);
3915 }
3916 }
3917 }
3918
3920}
3921
3923{
3924 if(IS_NULL_PTR(mask_form)) return hash;
3925
3926 // basic infos
3927 hash = dt_hash(hash, (char *)&mask_form->type, sizeof(dt_masks_type_t));
3928 hash = dt_hash(hash, (char *)&mask_form->formid, sizeof(int));
3929 hash = dt_hash(hash, (char *)&mask_form->version, sizeof(int));
3930 hash = dt_hash(hash, (char *)&mask_form->source, sizeof(float) * 2);
3931
3932 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
3933 {
3934 if(mask_form->type & DT_MASKS_GROUP)
3935 {
3936 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)point_node->data;
3937 dt_masks_form_t *child_form = dt_masks_get_from_id(darktable.develop, group_entry->formid);
3938 if(child_form)
3939 {
3940 // state & opacity
3941 hash = dt_hash(hash, (char *)&group_entry->state, sizeof(int));
3942 hash = dt_hash(hash, (char *)&group_entry->opacity, sizeof(float));
3943
3944 // the form itself
3945 hash = dt_masks_group_get_hash(hash, child_form);
3946 }
3947 }
3948 else if(mask_form->functions)
3949 {
3950 hash = dt_hash(hash, (char *)point_node->data, mask_form->functions->point_struct_size);
3951 }
3952 }
3953 return hash;
3954}
3955
3957{
3958 if(IS_NULL_PTR(mask_form)) return hash;
3959
3960 // basic infos
3961 hash = dt_hash(hash, (const char *)&mask_form->type, sizeof(dt_masks_type_t));
3962 hash = dt_hash(hash, (const char *)&mask_form->formid, sizeof(int));
3963 hash = dt_hash(hash, (const char *)&mask_form->version, sizeof(int));
3964 hash = dt_hash(hash, (const char *)&mask_form->source, sizeof(float) * 2);
3965
3966 for(const GList *point_node = mask_form->points; point_node; point_node = g_list_next(point_node))
3967 {
3968 if(mask_form->type & DT_MASKS_GROUP)
3969 {
3970 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)point_node->data;
3971 // Membership only (id + state/opacity): the referenced form's own content is hashed
3972 // once, separately, when dev->forms reaches that form's own top-level entry. Recursing
3973 // into it here would re-hash every grouped shape's full point list a second time.
3974 if(dt_masks_get_from_id(darktable.develop, group_entry->formid))
3975 {
3976 hash = dt_hash(hash, (const char *)&group_entry->formid, sizeof(int));
3977 hash = dt_hash(hash, (const char *)&group_entry->state, sizeof(int));
3978 hash = dt_hash(hash, (const char *)&group_entry->opacity, sizeof(float));
3979 }
3980 }
3981 else if(mask_form->functions)
3982 {
3983 hash = dt_hash(hash, (const char *)point_node->data, mask_form->functions->point_struct_size);
3984 }
3985 }
3986 return hash;
3987}
3988
3989// adds formid to used array
3990// if formid is a group it adds all the forms that belongs to that group
3991static void _cleanup_unused_recurs(GList *form_list, int form_id, int *used_form_ids, int used_count)
3992{
3993 // first, we search for the formid in used table
3994 for(int used_index = 0; used_index < used_count; used_index++)
3995 {
3996 if(used_form_ids[used_index] == 0)
3997 {
3998 // we store the formid
3999 used_form_ids[used_index] = form_id;
4000 break;
4001 }
4002 if(used_form_ids[used_index] == form_id) break;
4003 }
4004
4005 // if the form is a group, we iterate through the sub-forms
4006 dt_masks_form_t *mask_form = dt_masks_get_from_id_ext(form_list, form_id);
4007 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
4008 {
4009 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
4010 {
4011 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
4012 _cleanup_unused_recurs(form_list, group_entry->formid, used_form_ids, used_count);
4013 }
4014 }
4015}
4016
4017// removes from _forms all forms that are not used in history_list up to history_end
4018static int _masks_cleanup_unused(GList **forms_list, GList *history_list, const int history_end)
4019{
4020 int masks_removed = 0;
4021 GList *forms = *forms_list;
4022
4023 // we create a table to store the ids of used forms
4024 guint form_count = g_list_length(forms);
4025 int *used_form_ids = calloc(form_count, sizeof(int));
4026
4027 // check in history if the module has drawn masks and add it to used array
4028 int history_index = 0;
4029 for(GList *history_node = history_list; history_node && history_index < history_end;
4030 history_node = g_list_next(history_node))
4031 {
4032 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
4033 dt_develop_blend_params_t *blend_params
4034 = history_item && history_item->blendop_params_size == sizeof(dt_develop_blend_params_t)
4035 ? history_item->blend_params
4036 : NULL;
4037 if(blend_params)
4038 {
4039 if(blend_params->mask_id > 0)
4040 _cleanup_unused_recurs(forms, blend_params->mask_id, used_form_ids, form_count);
4041 }
4042 history_index++;
4043 }
4044
4045 // and we delete all unused forms
4046 GList *shape_node = forms;
4047 while(shape_node)
4048 {
4049 dt_masks_form_t *mask_form = (dt_masks_form_t *)shape_node->data;
4050 int is_used = 0;
4051 for(int used_index = 0; used_index < form_count; used_index++)
4052 {
4053 if(used_form_ids[used_index] == mask_form->formid)
4054 {
4055 is_used = 1;
4056 break;
4057 }
4058 if(used_form_ids[used_index] == 0) break;
4059 }
4060
4061 shape_node = g_list_next(shape_node); // need to get 'next' now, because we may be removing the current node
4062
4063 if(is_used == 0)
4064 {
4065 forms = g_list_remove(forms, mask_form);
4066 // and add it to allforms for cleanup
4067 darktable.develop->allforms = g_list_append(darktable.develop->allforms, mask_form);
4068 masks_removed = 1;
4069 }
4070 }
4071
4072 dt_free(used_form_ids);
4073
4074 *forms_list = forms;
4075
4076 return masks_removed;
4077}
4078
4079// removes all unused form from history
4086void dt_masks_cleanup_unused_from_list(GList *history_list)
4087{
4088 // a mask is used in a given hist->forms entry if it is used up to the next hist->forms
4089 // so we are going to remove for each hist->forms from the top
4090 int history_count = g_list_length(history_list);
4091 int history_end = history_count;
4092 for(const GList *history_node = g_list_last(history_list); history_node;
4093 history_node = g_list_previous(history_node))
4094 {
4095 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
4096 if(!IS_NULL_PTR(history_item->forms)) //&& strcmp(history_item->op_name, "mask_manager") == 0)
4097 {
4098 _masks_cleanup_unused(&history_item->forms, history_list, history_end);
4099 history_end = history_count - 1;
4100 }
4101 history_count--;
4102 }
4103}
4104
4111{
4113
4114 // we remove the forms from history
4116
4117 // and we save all that
4118 GList *forms = NULL;
4119 int history_index = 0;
4120 for(const GList *history_node = g_list_first(develop->history);
4121 history_node && history_index < dt_dev_get_history_end_ext(develop);
4122 history_node = g_list_next(history_node))
4123 {
4124 dt_dev_history_item_t *history_item = (dt_dev_history_item_t *)history_node->data;
4125
4126 if(history_item->forms) forms = history_item->forms;
4127 history_index++;
4128 }
4129
4130 dt_masks_replace_current_forms(develop, forms);
4131}
4132
4145int dt_masks_point_in_form_exact(const float *test_points, int test_point_count,
4146 const float *form_points, int form_points_start, int form_points_count)
4147{
4148 if(IS_NULL_PTR(test_points) || test_point_count <= 0 || IS_NULL_PTR(form_points)) return -1;
4149 if(form_points_count <= 2 + form_points_start) return -1;
4150 if(form_points_start < 0 || form_points_start >= form_points_count) return -1;
4151
4152 const int start_index = form_points_start;
4153 for(int test_index = 0; test_index < test_point_count; test_index++)
4154 {
4155 int intersection_count = 0;
4156 const float point_x = test_points[test_index * 2];
4157 const float point_y = test_points[test_index * 2 + 1];
4158 int visited_points = 0;
4159
4160 for(int i = form_points_start, next = start_index + 1; i < form_points_count;)
4161 {
4162 // The form point stream may contain NaN sentinels that jump across
4163 // self-intersection cuts. Broken jump targets must not trap the GUI event
4164 // loop in an endless hit-test.
4165 if(next < start_index || next >= form_points_count) break;
4166 if(++visited_points > form_points_count - start_index + 1) break;
4167
4168 const float y1 = form_points[i * 2 + 1];
4169 const float y2 = form_points[next * 2 + 1];
4170
4171 // if we need to skip points (in case of deleted point, because of self-intersection)
4172 if(isnan(form_points[next * 2]))
4173 {
4174 const int jump_index = isnan(y2) ? start_index : (int)y2;
4175 if(jump_index == next || jump_index < start_index || jump_index >= form_points_count) break;
4176 next = jump_index;
4177 continue;
4178 }
4179
4180 if(((point_y <= y2 && point_y > y1) || (point_y >= y2 && point_y < y1))
4181 && (form_points[i * 2] > point_x))
4182 intersection_count++;
4183
4184 if(next == start_index) break;
4185 i = next++;
4186 // loop
4187 if(next >= form_points_count) next = start_index;
4188 }
4189
4190 if(intersection_count & 1) return test_index;
4191 }
4192
4193 return -1;
4194}
4195
4201void dt_masks_select_form(struct dt_iop_module_t *module, dt_masks_form_t *selected_form)
4202{
4203 const int selected_formid = IS_NULL_PTR(selected_form) ? 0 : selected_form->formid;
4204
4205 if(IS_NULL_PTR(module) && selected_formid == 0)
4206 module = darktable.develop->gui_module;
4207
4208 if(!IS_NULL_PTR(module) && module->masks_selection_changed)
4209 module->masks_selection_changed(module, selected_formid);
4210}
4211
4219void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *mask_gui, const uint32_t key_state)
4220{
4221 if(dt_modifier_is(key_state, GDK_SHIFT_MASK | GDK_CONTROL_MASK))
4223 else if(dt_modifier_is(key_state, GDK_SHIFT_MASK))
4225 else
4226 fprintf(stderr, "[dt_masks_set_source_pos_initial_state] unknown state for setting masks position type\n");
4227
4228 // both source types record an absolute position,
4229 // for the relative type, the first time is used the position is recorded,
4230 // the second time a relative position is calculated based on that one
4231 mask_gui->pos_source[0] = mask_gui->pos[0];
4232 mask_gui->pos_source[1] = mask_gui->pos[1];
4233}
4234
4245{
4246 const float raw_width = darktable.develop->roi.raw_width;
4247 const float raw_height = darktable.develop->roi.raw_height;
4248
4249 const float xx = mask_gui->pos[0];
4250 const float yy = mask_gui->pos[1];
4251
4252 // if this is the first time the relative pos is used
4254 {
4255 // if it has not been defined by the user, set some default
4256 if(mask_gui->pos_source[0] == -1.0f && mask_gui->pos_source[1] == -1.0f)
4257 {
4258 if(mask_form->functions && mask_form->functions->initial_source_pos)
4259 {
4260 mask_form->functions->initial_source_pos(raw_width, raw_height,
4261 &mask_gui->pos_source[0], &mask_gui->pos_source[1]);
4262 }
4263 else
4264 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unsupported masks type when calculating source position initial value\n");
4265
4266 // set offset to form->source
4267 mask_form->source[0] = mask_gui->pos[0] + mask_gui->pos_source[0];
4268 mask_form->source[1] = mask_gui->pos[1] + mask_gui->pos_source[1];
4270 // normalize backbuf points
4272
4273 }
4274 else
4275 {
4276 // if a position was defined by the user, use the absolute value the first time
4277 float source_points[2] = { mask_gui->pos_source[0], mask_gui->pos_source[1] };
4279
4280 mask_form->source[0] = source_points[0];
4281 mask_form->source[1] = source_points[1];
4282
4283 mask_gui->pos_source[0] = mask_gui->pos_source[0] - xx;
4284 mask_gui->pos_source[1] = mask_gui->pos_source[1] - yy;
4285 }
4286
4288 }
4289 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE)
4290 {
4291 // original pos was already defined and relative value calculated, just use it
4292 mask_form->source[0] = mask_gui->pos[0] + mask_gui->pos_source[0];
4293 mask_form->source[1] = mask_gui->pos[1] + mask_gui->pos_source[1];
4295 }
4296 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
4297 {
4298 // an absolute position was defined by the user
4299 float source_points[2] = { mask_gui->pos_source[0], mask_gui->pos_source[1] };
4301
4302 mask_form->source[0] = source_points[0];
4303 mask_form->source[1] = source_points[1];
4304 }
4305 else
4306 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unknown source position type\n");
4307}
4308
4314void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *mask_gui, const float initial_xpos,
4315 const float initial_ypos, const float xpos, const float ypos,
4316 float *pos_x, float *pos_y, const int adding)
4317{
4318 float source_x = 0.0f;
4319 float source_y = 0.0f;
4320 const float raw_width = darktable.develop->roi.raw_width;
4321 const float raw_height = darktable.develop->roi.raw_height;
4323 {
4324 source_x = xpos + mask_gui->pos_source[0];
4325 source_y = ypos + mask_gui->pos_source[1];
4326 }
4328 {
4329 if(mask_gui->pos_source[0] == -1.0f && mask_gui->pos_source[1] == -1.0f)
4330 {
4332 if(!IS_NULL_PTR(visible_form) && visible_form->functions && visible_form->functions->initial_source_pos)
4333 {
4334 visible_form->functions->initial_source_pos(raw_width, raw_height, &source_x, &source_y);
4335 source_x += xpos;
4336 source_y += ypos;
4337 }
4338 else
4339 fprintf(stderr, "[dt_masks_calculate_source_pos_origin] unsupported masks type when calculating source position value\n");
4340 }
4341 else
4342 {
4343 source_x = mask_gui->pos_source[0];
4344 source_y = mask_gui->pos_source[1];
4345 }
4346 }
4347 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
4348 {
4349 // if the user is actually adding, the mask follow the cursor
4350 if(adding)
4351 {
4352 source_x = xpos + mask_gui->pos_source[0] - initial_xpos;
4353 source_y = ypos + mask_gui->pos_source[1] - initial_ypos;
4354 }
4355 else
4356 {
4357 // if not added yet set the start position
4358 source_x = mask_gui->pos_source[0];
4359 source_y = mask_gui->pos_source[1];
4360 }
4361 }
4362 else
4363 fprintf(stderr, "[dt_masks_calculate_source_pos_origin] unknown source position type for setting source position value\n");
4364
4365 *pos_x = source_x;
4366 *pos_y = source_y;
4367}
4368
4377float dt_masks_rotate_with_anchor(dt_develop_t *develop, const float anchor[2], const float center[2],
4378 dt_masks_form_gui_t *mask_gui)
4379{
4380 const float center_x = center[0];
4381 const float center_y = center[1];
4382
4383 // get the current angle
4384 const float anchor_x = anchor[0];
4385 const float anchor_y = anchor[1];
4386 const float angle_current = atan2f(anchor_y - center_y, anchor_x - center_x);
4387
4388 // get the previous angle
4389 const float delta_x = mask_gui->delta[0];
4390 const float delta_y = mask_gui->delta[1];
4391 const float angle_prev = atan2f(delta_y - center_y, delta_x - center_x);
4392
4393 // calculate the angle difference an normalize to -180 to 180 degrees
4394 float delta_angle = angle_current - angle_prev;
4395 float angle = atan2f(sinf(delta_angle), cosf(delta_angle));
4396
4397 // check if distortion inverts the axes
4398 float test_points[8] = { center_x, center_y, anchor_x , anchor_y,
4399 center_x + 10.0f, center_y, center_x, center_y + 10.0f };
4400 dt_dev_coordinates_image_abs_to_raw_abs(develop, test_points, 4);
4401 float check_angle = atan2f(test_points[7] - test_points[1], test_points[6] - test_points[0])
4402 - atan2f(test_points[5] - test_points[1], test_points[4] - test_points[0]);
4403 // Normalize to the range -180 to 180 degrees
4404 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
4405
4406 // Adjust the sign if the axes are inverted by distortion
4407 if(check_angle < 0.0f) angle = -angle;
4408
4409 // Update the delta for the next frame (old position becomes the current one)
4410 mask_gui->delta[0] = anchor_x;
4411 mask_gui->delta[1] = anchor_y;
4412
4413 return angle / M_PI * 180.0f;
4414}
4415
4422{
4423 if(IS_NULL_PTR(mask_gui)) return;
4424
4425 mask_gui->creation = FALSE;
4426}
4427
4434{
4435 if((type & DT_MASKS_ALL) == 0) return FALSE;
4436 // we want to be sure that the iop has focus
4437 if(!IS_NULL_PTR(module)) dt_iop_request_focus(module);
4438
4439 dt_masks_form_t *mask_form = dt_masks_create(type);
4440 if(IS_NULL_PTR(mask_form)) return FALSE;
4441
4442 dt_masks_change_form_gui(mask_form);
4448
4449 // Give focus to central view to allow using shortcuts for mask creation right after selecting a mask type in the manager
4450 gtk_widget_grab_focus(dt_ui_center(darktable.gui->ui));
4451 return TRUE;
4452}
4453
4459void apply_operation(struct dt_masks_form_group_t *group_entry, const dt_masks_state_t apply_state)
4460{
4461 if(IS_NULL_PTR(group_entry)) return;
4462
4463 // Apply Inverse
4464 if(apply_state == DT_MASKS_STATE_INVERSE)
4465 group_entry->state ^= DT_MASKS_STATE_INVERSE;
4466
4467 else if((apply_state & DT_MASKS_STATE_IS_COMBINE_OP) != 0)
4468 {
4469 // Reset all and apply state
4470 group_entry->state = (group_entry->state & ~DT_MASKS_STATE_IS_COMBINE_OP) | apply_state;
4471 }
4472}
4473
4474#include "detail.c"
4475
4476// clang-format off
4477// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
4478// vim: shiftwidth=2 expandtab tabstop=2 cindent
4479// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
4480// clang-format on
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
#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)
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2350
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:2158
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:2104
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:2019
int width
Definition bilateral.h:1
int height
Definition bilateral.h:1
void dt_masks_iop_update(dt_iop_module_t *module)
Definition blend_gui.c:3775
char * key
int type
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:423
void dt_toast_log(const char *msg,...)
Definition control.c:824
void dt_control_log(const char *msg,...)
Definition control.c:777
void dt_control_queue_redraw_center()
request redraw of center window. This redraws the center view within a gdk critical section to preven...
Definition control.c:877
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:934
#define dt_control_queue_cursor(cursor)
Definition control.h:135
darktable_t darktable
Definition darktable.c:183
void dt_print(dt_debug_thread_t thread, const char *msg,...)
Definition darktable.c:1600
@ DT_DEBUG_INPUT
Definition darktable.h:751
@ DT_DEBUG_HISTORY
Definition darktable.h:762
@ DT_DEBUG_MASKS
Definition darktable.h:749
static void dt_free_gpointer(gpointer ptr)
Definition darktable.h:485
#define dt_free(ptr)
Definition darktable.h:478
#define dt_pixelpipe_cache_free_align(mem)
Definition darktable.h:475
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition darktable.h:1123
static const dt_aligned_pixel_simd_t value
Definition darktable.h:599
static double dt_get_wtime(void)
Definition darktable.h:976
static gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
Definition darktable.h:955
#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 darktable.h:293
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition darktable.h:1001
sqlite3 * dt_database_get(const dt_database_t *db)
Definition database.c:3653
#define DT_DEBUG_SQLITE3_BIND_BLOB(a, b, c, d, e)
Definition debug.h:119
#define DT_DEBUG_SQLITE3_PREPARE_V2(a, b, c, d, e)
Definition debug.h:107
#define DT_DEBUG_SQLITE3_BIND_TEXT(a, b, c, d, e)
Definition debug.h:118
#define DT_DEBUG_SQLITE3_BIND_INT(a, b, c)
Definition debug.h:115
#define dt_dev_add_history_item(dev, module, enable, redraw)
int32_t dt_dev_get_history_end_ext(struct dt_develop_t *dev)
Get the current history end index (GUI perspective).
Definition develop.c:1723
void dt_dev_pixelpipe_change_zoom_main(dt_develop_t *dev)
static void dt_masks_legacy_params_v2_to_v3_transform(const dt_image_t *image, float *coords)
gboolean dt_masks_remove_or_delete(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
If the form to remove is used once, ask to the user if he wants to delete it from the list or just re...
void dt_masks_draw_path_seg_by_seg(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index, const float *points, const int points_count, const int node_count, const float zoom_scale)
const char * _get_mask_type(dt_masks_form_t *mask_form)
void dt_masks_events_post_expose(struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index, const int node_count, const float zoom_scale, struct dt_masks_gui_center_point_t *center_point, const shape_draw_function_t *draw_shape_func)
Draw the source for a correction mask.
uint64_t dt_masks_form_get_own_hash(uint64_t hash, const dt_masks_form_t *mask_form)
void dt_masks_gui_set_dragging(dt_masks_form_gui_t *gui)
static int _find_in_group(dt_masks_form_t *group_form, int form_id)
int dt_masks_point_in_form_exact(const float *test_points, int test_point_count, const float *form_points, int form_points_start, int form_points_count)
Check whether any 2D point in pts[] lies inside the form points[].
int dt_masks_events_key_pressed(struct dt_iop_module_t *module, GdkEventKey *event)
void dt_masks_clear_form_gui(dt_develop_t *develop)
static void _menu_add_exist(dt_iop_module_t *module, int form_id)
static gboolean _dt_masks_events_should_update_hover_on_move(dt_masks_form_gui_t *mask_gui)
void dt_masks_remove_node(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id, dt_masks_form_gui_t *mask_gui, int form_index, int node_index)
uint64_t dt_masks_group_get_hash(uint64_t hash, dt_masks_form_t *mask_form)
gboolean dt_masks_point_is_within_radius(const float point_x, const float point_y, const float center_x, const float center_y, const float squared_radius)
Check whether a point lies within a squared radius of a center.
float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value, float value_min, float value_max, dt_masks_increment_t increment, int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
static void _menu_no_masks(struct dt_iop_module_t *module)
void dt_masks_group_ungroup(dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
int dt_masks_form_change_opacity(dt_masks_form_t *mask_form, int parent_id, int scroll_up, const int flow)
int dt_masks_group_index_from_formid(const dt_masks_form_t *group_form, int form_id)
void dt_masks_gui_form_remove(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
static int dt_masks_legacy_params_v3_to_v4(dt_develop_t *develop, void *params)
static void _set_cursor_shape(dt_masks_form_gui_t *mask_gui)
void dt_masks_gui_form_test_create(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, dt_iop_module_t *module)
void dt_masks_gui_reset_dragging(dt_masks_form_gui_t *gui)
static void _apply_gui_button_pressed_state(dt_masks_form_gui_t *mask_gui, const int button, const uint32_t state, const gboolean shape_was_selected)
gboolean dt_masks_gui_form_create_throttled(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module, float posx, float posy)
static void dt_masks_legacy_params_v2_to_v3_transform_only_rescale(const dt_image_t *image, float *coords, size_t coords_count)
void dt_masks_reset_form_gui(void)
void dt_masks_free_form(dt_masks_form_t *mask_form)
int dt_masks_events_mouse_moved(struct dt_iop_module_t *module, double x, double y, double pressure, int which)
static int _masks_gui_form_group_use_count(const dt_develop_t *dev, const int formid)
dt_masks_form_group_t * dt_masks_form_get_selected_group(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Get the selected group entry from the GUI selection index.
int dt_masks_events_button_released(struct dt_iop_module_t *module, double x, double y, int button, uint32_t state)
static gboolean _dt_masks_events_group_update_selection(dt_masks_form_t *group_form, dt_masks_form_gui_t *mask_gui)
Update group selection from the current cached cursor before leaf dispatch.
gboolean dt_masks_is_anything_selected(const dt_masks_form_gui_t *mask_gui)
static int dt_masks_legacy_params_v5_to_v6(dt_develop_t *develop, void *params)
void dt_masks_soft_reset_form_gui(dt_masks_form_gui_t *mask_gui)
static void _dt_masks_events_set_current_pos(const double x, const double y, dt_masks_form_gui_t *mask_gui)
Convert the GTK/Cairo widget cursor once for the full mask event chain.
dt_masks_form_group_t * dt_masks_form_get_selected_group_live(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Resolve a "live" selected group entry, even if GUI selection is stale.
void dt_masks_init_form_gui(dt_masks_form_gui_t *mask_gui)
void dt_masks_select_form(struct dt_iop_module_t *module, dt_masks_form_t *selected_form)
Select or clear the current mask form, notifying the owning module if needed.
static void _menu_add_shape(struct dt_iop_module_t *module, dt_masks_type_t type)
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
static dt_masks_form_t * _dt_masks_events_get_dispatch_form(dt_masks_form_t *visible_form, const dt_masks_form_gui_t *mask_gui, dt_masks_form_group_t **group_entry, int *parent_id, int *form_index)
Resolve the concrete form that should receive an event.
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.
static gboolean _masks_remove_or_delete_finish(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id, gboolean keep_unused)
float dt_masks_rotate_with_anchor(dt_develop_t *develop, const float anchor[2], const float center[2], dt_masks_form_gui_t *mask_gui)
Compute rotation angle (degrees) around a center using an anchor point.
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)
dt_iop_module_t * dt_masks_get_mask_manager(dt_develop_t *develop)
float dt_masks_form_get_interaction_value(dt_masks_form_group_t *form_group, dt_masks_interaction_t interaction)
dt_masks_form_group_t * dt_masks_group_add_form(dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
static gboolean _dt_masks_events_cursor_over_form(const dt_masks_form_t *dispatch_form, dt_masks_form_gui_t *mask_gui, const int form_index)
void dt_masks_read_masks_history(dt_develop_t *develop, const int32_t image_id)
static dt_masks_form_t * _group_create(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_type_t group_type)
gboolean dt_masks_node_is_cusp(const dt_masks_form_gui_points_t *gui_points, const int node_index)
returns wether a node is a corner or not. A node is a corner if its 2 control handles are at the same...
int dt_masks_version(void)
dt_masks_form_t * dt_masks_get_from_id_ext(GList *form_list, int form_id)
static gboolean _masks_remove_shape(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id, dt_masks_form_gui_t *mask_gui, int form_index)
Remove a shape from the GUI and free its resources.
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.
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
int dt_masks_events_mouse_leave(struct dt_iop_module_t *module)
static int form_id_seed
gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, const int parent_id)
remove a mask shape or node form from the GUI. This function is used with a popupmenu "Delete" action...
static gboolean _set_hinter_message(dt_masks_form_gui_t *mask_gui, const dt_masks_form_t *mask_form)
Build and display the on-canvas hint message for masks interactions.
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
gboolean dt_masks_remove_shape_from_group(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
Remove the form from its current group only, keeping it around unused for potential reuse....
int dt_masks_legacy_params(dt_develop_t *develop, void *params, const int old_version, const int new_version)
static void _dt_masks_find_best_attachment_point(const float ray_1[2], const float ray_2[2], const float *points, const int points_count, const float zoom_scale, const int first_pt, const gboolean is_closed_shape, const float offset_factor, float result[2])
Find the best attachment point on the shape contour for a ray crossing the form.
int dt_masks_events_mouse_scrolled(struct dt_iop_module_t *module, double x, double y, int scroll_up, uint32_t key_state, int scrolling_delta)
static int dt_masks_legacy_params_v2_to_v3(dt_develop_t *develop, void *params)
static int dt_masks_legacy_params_v1_to_v2(dt_develop_t *develop, void *params)
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
float dt_masks_form_set_interaction_value(dt_masks_form_group_t *form_group, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, dt_masks_form_gui_t *mask_gui, dt_iop_module_t *module)
int 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, int source, dt_iop_module_t *module)
void dt_masks_iop_combo_populate(GtkWidget *widget, void *data)
void apply_operation(struct dt_masks_form_group_t *group_entry, const dt_masks_state_t apply_state)
Apply a mask state operation on a group entry.
void dt_masks_cleanup_unused(dt_develop_t *develop)
Cleanup unused masks and refresh the current forms snapshot.
void dt_masks_form_move(dt_masks_form_t *group_form, int form_id, int move_up)
void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *mask_gui, const float initial_xpos, const float initial_ypos, const float xpos, const float ypos, float *pos_x, float *pos_y, const int adding)
Compute preview-space source position for drawing the clone indicator.
static dt_masks_form_t * _group_from_module(dt_develop_t *develop, dt_iop_module_t *module)
const char * _get_mask_plugin(dt_masks_form_t *mask_form)
void dt_masks_form_invalidate_gravity_center(dt_masks_form_t *mask_form)
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
int dt_masks_events_mouse_enter(struct dt_iop_module_t *module)
float dt_masks_apply_increment_precomputed(float current, float amount, float scale_amount, float offset_amount, dt_masks_increment_t increment)
Apply a scroll increment using precomputed scale/offset factors.
gboolean dt_masks_creation_mode_enter(dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
static int _dt_masks_events_update_hover(dt_masks_form_t *dispatch_form, dt_masks_form_gui_t *mask_gui, const int form_index)
static gboolean _dt_masks_events_group_blocks_motion(dt_masks_form_gui_t *mask_gui)
Consume the initial drag motion used to disambiguate scrolling vs dragging in groups.
void dt_masks_write_masks_history_item(const int32_t image_id, const int history_num, dt_masks_form_t *mask_form)
int dt_masks_events_button_pressed(struct dt_iop_module_t *module, double x, double y, double pressure, int button, int event_type, uint32_t state)
void dt_masks_gui_cleanup(dt_develop_t *dev)
gboolean dt_masks_delete_shape(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
Permanently delete the form from every group and from dev->forms. Gated by dt_masks_gui_confirm_perma...
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *mask_gui, dt_masks_form_t *mask_form)
Initialize the clone source position based on current GUI state.
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *develop, int form_id)
static int _masks_cleanup_unused(GList **forms_list, GList *history_list, const int history_end)
static float _change_opacity(dt_masks_form_group_t *form_group, float value, const dt_masks_increment_t increment, const int flow)
int dt_masks_form_duplicate(dt_develop_t *develop, int form_id)
static void _check_id(dt_masks_form_t *mask_form)
void dt_masks_set_visible_form(dt_develop_t *dev, dt_masks_form_t *form)
dt_masks_edit_mode_t dt_masks_get_edit_mode(struct dt_iop_module_t *module)
static gboolean _dt_masks_events_flush_rebuild_if_needed(struct dt_iop_module_t *module, dt_masks_form_t *dispatch_form, dt_masks_form_gui_t *mask_gui, const int form_index, const int button)
Flush a deferred throttled rebuild before drag state is reset.
void dt_masks_iop_value_changed_callback(GtkWidget *widget, struct dt_iop_module_t *module)
int dt_masks_center_view_on_form(dt_develop_t *dev, const dt_masks_form_t *mask_form)
Center the darkroom ROI on a mask form gravity center.
void dt_masks_remove_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
void dt_masks_cleanup_unused_from_list(GList *history_list)
Remove unused mask forms from a history list, preserving undo safety.
gboolean dt_masks_form_get_gravity_center(const dt_masks_form_t *mask_form, float center[2], float *area)
static void _masks_fill_used_forms(GList *forms_list, const int form_id, int *used_form_ids, const int used_count)
static dt_masks_form_group_t * _masks_group_find_form(dt_masks_form_t *group_form, const int form_id)
Find a form entry inside a group by form id.
void dt_masks_reset_show_masks_icons(void)
int 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)
static void _masks_draw_creation_session_forms(dt_develop_t *develop, dt_iop_module_t *module, cairo_t *cr, const float zoom_scale, const dt_masks_form_gui_t *creation_gui)
Draw completed shapes from the current creation session.
dt_masks_form_group_t * dt_masks_form_group_from_parentid(int parent_id, int form_id)
Return the group entry for a (parent, form) pair.
void dt_masks_form_update_gravity_center(dt_masks_form_t *mask_form)
static void _set_group_name_from_module(dt_iop_module_t *module, dt_masks_form_t *group_form)
gboolean dt_masks_is_anything_hovered(const dt_masks_form_gui_t *mask_gui)
void dt_masks_iop_use_same_as(dt_iop_module_t *module, dt_iop_module_t *source_module)
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Centralized hit-testing for node/handle/segment selection across shapes.
void dt_masks_gui_init(dt_develop_t *dev)
void dt_masks_creation_mode_quit(dt_masks_form_gui_t *mask_gui)
Exit mask creation mode, restoring cursor visibility and resetting GUI state.
void dt_masks_group_update_name(dt_iop_module_t *module)
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
void dt_masks_form_delete(struct dt_iop_module_t *module, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
dt_masks_form_t * dt_masks_create_ext(dt_masks_type_t type)
void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *mask_gui, const uint32_t key_state)
Decide initial source positioning mode for clone masks.
void dt_masks_form_gui_points_free(gpointer data)
void dt_masks_change_form_gui(dt_masks_form_t *new_form)
void dt_masks_gui_form_create(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module)
int 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)
float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount, float value_min, float value_max, dt_masks_increment_t increment, int flow, const char *toast_fmt, float toast_scale)
Update a mask configuration value and emit a toast message.
static void _cleanup_unused_recurs(GList *form_list, int form_id, int *used_form_ids, int used_count)
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *mask_gui)
static int dt_masks_legacy_params_v4_to_v5(dt_develop_t *develop, void *params)
void dt_masks_gui_form_save_creation(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui)
Save the form creation right after a shape has been finished drawing.
void dt_dev_coordinates_image_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1128
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
Definition develop.c:1032
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1589
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1162
float dt_dev_get_zoom_level(const dt_develop_t *dev)
Definition develop.c:1816
void dt_dev_masks_selection_change(dt_develop_t *dev, struct dt_iop_module_t *module, const int selectid, const int throw_event)
Definition develop.c:1255
void dt_dev_check_zoom_pos_bounds(dt_develop_t *dev, float *dev_x, float *dev_y, float *box_w, float *box_h)
Ensure that the current ROI position is within allowed bounds .
Definition develop.c:997
gboolean dt_dev_rescale_roi_to_input(dt_develop_t *dev, cairo_t *cr, int32_t width, int32_t height)
Scale the ROI to fit the input size within given width/height, centered.
Definition develop.c:1905
int dt_dev_coordinates_image_abs_to_raw_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1594
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1113
gchar * dt_dev_get_masks_group_name(const struct dt_iop_module_t *module)
Definition develop.c:1543
void dt_dev_coordinates_widget_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Coordinate conversion helpers between widget, normalized image, and absolute image spaces.
Definition develop.c:1056
void dt_dev_coordinates_raw_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1145
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1191
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1557
static void dt_draw_stroke_line(const dt_draw_dash_type_t dash_type, const gboolean source, cairo_t *cr, const gboolean selected, const float zoom_scale, const cairo_line_cap_t line_cap)
Stroke a line with style.
Definition draw.h:784
@ DT_MASKS_DASH_ROUND
Definition draw.h:95
@ DT_MASKS_NO_DASH
Definition draw.h:93
static void dt_draw_arrow(cairo_t *cr, const float zoom_scale, const gboolean selected, const gboolean draw_tail, const dt_draw_dash_type_t dash_style, const float arrow[2], const float tail[2], const float angle)
Draw an arrow with head and, if needed, tail. The length of the arrow head is defined by DT_DRAW_SCAL...
Definition draw.h:820
void(* shape_draw_function_t)(cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source)
Definition draw.h:719
static void dt_draw_source_shape(cairo_t *cr, const float zoom_scale, const gboolean selected, const float *source_pts, const int source_pts_count, const int nodes_nb, const shape_draw_function_t *draw_shape_func)
Definition draw.h:917
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:452
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:502
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock)
Definition dtpthread.h:534
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:113
GtkWidget * dt_ui_center(dt_ui_t *ui)
get the center drawable widget
#define DT_GUI_MOUSE_EFFECT_RADIUS
Definition gtk.h:70
#define DT_PIXEL_APPLY_DPI(value)
Definition gtk.h:90
dt_image_orientation_t
Definition image.h:203
@ ORIENTATION_SWAP_XY
Definition image.h:208
@ ORIENTATION_ROTATE_CCW_90_DEG
Definition image.h:215
@ ORIENTATION_ROTATE_CW_90_DEG
Definition image.h:216
@ ORIENTATION_NONE
Definition image.h:205
@ ORIENTATION_ROTATE_180_DEG
Definition image.h:213
static dt_image_orientation_t dt_image_orientation(const dt_image_t *img)
Definition image.h:532
void dt_iop_request_focus(dt_iop_module_t *module)
Definition imageop.c:2221
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:197
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:205
gboolean dt_mask_scroll_increases(int up)
static const float x
static int dt_masks_gui_selected_segment_index(const dt_masks_form_gui_t *gui)
Definition masks.h:563
void(* dt_masks_distance_fn)(float pointer_x, float pointer_y, float cursor_radius, dt_masks_form_gui_t *mask_gui, int form_index, int node_count, int *inside, int *inside_border, int *near, int *inside_source, float *dist, void *user_data)
Shape-specific callback for inside/border/segment hit testing.
Definition masks.h:1523
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks.h:548
dt_masks_edit_mode_t
Definition masks.h:201
@ DT_MASKS_EDIT_OFF
Definition masks.h:202
@ DT_MASKS_EDIT_FULL
Definition masks.h:203
void(* dt_masks_post_select_fn)(dt_masks_form_gui_t *mask_gui, int inside, int inside_border, int inside_source, void *user_data)
Optional hook to customize selection flags after inside/border/source resolution.
Definition masks.h:1530
gboolean dt_masks_gui_confirm_permanent_delete(const char *form_name)
Ask the user to confirm a permanent shape deletion, gated by the "ask_before_delete_mask_shape" pref....
Definition masks_gui.c:464
const dt_masks_functions_t dt_masks_functions_group
Definition group.c:745
dt_masks_state_t
Definition masks.h:167
@ DT_MASKS_STATE_INVERSE
Definition masks.h:171
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks.h:178
@ DT_MASKS_STATE_SHOW
Definition masks.h:170
@ DT_MASKS_STATE_UNION
Definition masks.h:172
@ DT_MASKS_STATE_USE
Definition masks.h:169
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:87
gboolean(* dt_masks_border_handle_fn)(const dt_masks_form_gui_points_t *gui_points, int node_count, int node_index, float *handle_x, float *handle_y, void *user_data)
Shape-specific callback to fetch a node's border handle in GUI space.
Definition masks.h:1501
dt_masks_type_t
Definition masks.h:130
@ DT_MASKS_NON_CLONE
Definition masks.h:139
@ DT_MASKS_ALL
Definition masks.h:141
@ DT_MASKS_POLYGON
Definition masks.h:133
@ DT_MASKS_BRUSH
Definition masks.h:138
@ DT_MASKS_IS_PATH_SHAPE
Definition masks.h:149
@ DT_MASKS_IS_OPEN_SHAPE
Definition masks.h:145
@ DT_MASKS_ELLIPSE
Definition masks.h:137
@ DT_MASKS_CLONE
Definition masks.h:135
@ DT_MASKS_GRADIENT
Definition masks.h:136
@ DT_MASKS_NONE
Definition masks.h:131
@ DT_MASKS_CIRCLE
Definition masks.h:132
@ DT_MASKS_GROUP
Definition masks.h:134
@ DT_MASKS_IS_PRIMITIVE_SHAPE
Definition masks.h:150
@ DT_MASKS_IS_CLOSED_SHAPE
Definition masks.h:144
dt_masks_interaction_t
Definition masks.h:303
@ DT_MASKS_INTERACTION_OPACITY
Definition masks.h:307
@ DT_MASKS_INTERACTION_UNDEF
Definition masks.h:304
@ DT_MASKS_ELLIPSE_EQUIDISTANT
Definition masks.h:219
@ DT_MASKS_SOURCE_POS_RELATIVE_TEMP
Definition masks.h:226
@ DT_MASKS_SOURCE_POS_RELATIVE
Definition masks.h:225
@ DT_MASKS_SOURCE_POS_ABSOLUTE
Definition masks.h:227
static gboolean dt_masks_gui_was_anything_selected(const dt_masks_form_gui_t *gui)
Definition masks.h:543
int dt_masks_gui_confirm_delete_form_dialog(const char *form_name)
Definition masks_gui.c:440
const dt_masks_functions_t dt_masks_functions_polygon
Definition polygon.c:3655
dt_masks_event_t
Definition masks.h:157
@ DT_MASKS_EVENT_ADD
Definition masks.h:159
@ DT_MASKS_EVENT_DELETE
Definition masks.h:162
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:160
static gboolean dt_masks_gui_should_hit_test(dt_masks_form_gui_t *gui)
Definition masks.h:601
dt_masks_increment_t
Definition masks.h:194
@ DT_MASKS_INCREMENT_SCALE
Definition masks.h:196
@ DT_MASKS_INCREMENT_OFFSET
Definition masks.h:197
@ DT_MASKS_INCREMENT_ABSOLUTE
Definition masks.h:195
@ DT_MASKS_PRESSURE_OFF
Definition masks.h:209
const dt_masks_functions_t dt_masks_functions_ellipse
Definition ellipse.c:1702
const dt_masks_functions_t dt_masks_functions_circle
Definition circle.c:1132
GtkWidget * dt_masks_create_menu(dt_masks_form_gui_t *gui, dt_masks_form_t *form, const dt_masks_form_group_t *fpt, const float pzx, const float pzy)
Definition masks_gui.c:657
@ DT_MASKS_GRADIENT_STATE_LINEAR
Definition masks.h:189
const dt_masks_functions_t dt_masks_functions_brush
#define DEVELOP_MASKS_VERSION
Definition masks.h:126
void(* dt_masks_node_position_fn)(const dt_masks_form_gui_points_t *gui_points, int node_index, float *node_x, float *node_y, void *user_data)
Shape-specific callback to fetch a node's position in GUI space.
Definition masks.h:1515
void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Definition group.c:138
void(* dt_masks_curve_handle_fn)(const dt_masks_form_gui_points_t *gui_points, int node_index, float *handle_x, float *handle_y, void *user_data)
Shape-specific callback to fetch a node's curve handle in GUI space.
Definition masks.h:1508
const dt_masks_functions_t dt_masks_functions_gradient
Definition gradient.c:1526
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
Definition masks.h:1466
void dt_masks_replace_current_forms(dt_develop_t *dev, GList *forms)
dt_masks_form_t * dt_masks_form_ref(dt_masks_form_t *form)
void dt_masks_form_unref(dt_masks_form_t *form)
dt_masks_form_t * dt_masks_cow_touch(dt_develop_t *dev, dt_masks_form_t *form)
static float f_inv_sqrtf(const float x)
Definition math.h:469
#define CLAMPF(a, mn, mx)
Definition math.h:89
#define M_PI
Definition math.h:45
#define DT_PIXELPIPE_CACHE_HASH_INVALID
static uint64_t dt_dev_backbuf_get_hash(const dt_backbuf_t *backbuf)
int main()
Definition prova.c:47
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:366
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:312
struct _GtkWidget GtkWidget
Definition splash.h:29
const float uint32_t state[4]
unsigned __int64 uint64_t
Definition strptime.c:75
struct dt_gui_gtk_t * gui
Definition darktable.h:803
const struct dt_database_t * db
Definition darktable.h:807
struct dt_control_signal_t * signals
Definition darktable.h:802
int32_t unmuted
Definition darktable.h:788
struct dt_develop_t * develop
Definition darktable.h:798
struct dt_control_t * control
Definition darktable.h:801
struct dt_develop_blend_params_t * blend_params
Definition dev_history.h:56
dt_backbuf_t backbuf
dt_image_t image_storage
Definition develop.h:259
GList * iop
Definition develop.h:285
int32_t raw_height
Definition develop.h:228
struct dt_dev_pixelpipe_t * preview_pipe
Definition develop.h:247
GList * history
Definition develop.h:275
int32_t raw_width
Definition develop.h:228
gboolean darkroom_skip_mouse_events
Definition develop.h:502
GList * allforms
Definition develop.h:335
struct dt_masks_form_gui_t * form_gui
Definition develop.h:333
struct dt_develop_t::@17 roi
dt_pthread_rwlock_t masks_mutex
Definition develop.h:339
GList * forms
Definition develop.h:327
dt_ui_t * ui
Definition gtk.h:165
int32_t height
Definition image.h:315
int32_t crop_height
Definition image.h:316
int32_t width
Definition image.h:315
int32_t crop_y
Definition image.h:316
int32_t crop_x
Definition image.h:316
int32_t crop_width
Definition image.h:316
char filename[DT_MAX_FILENAME_LEN]
Definition image.h:304
GtkWidget * masks_combo
Definition blend.h:368
GtkWidget * masks_edit
Definition blend.h:371
gpointer blend_data
Definition imageop.h:355
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:353
struct dt_develop_t * dev
Definition imageop.h:333
GModule *dt_dev_operation_t op
Definition imageop.h:286
dt_masks_gradient_states_t state
Definition masks.h:278
gboolean rebuild_pending
Definition masks.h:512
gboolean node_selected
Definition masks.h:484
gboolean handle_border_selected
Definition masks.h:487
double last_rebuild_ts
Definition masks.h:510
gboolean border_toggling
Definition masks.h:502
dt_masks_dynbuf_t * guipoints_payload
Definition masks.h:449
gboolean source_selected
Definition masks.h:492
int handle_border_dragging
Definition masks.h:507
dt_masks_type_t creation_type
Definition masks.h:521
int creation_last_formid
Definition masks.h:525
int handle_border_hovered
Definition masks.h:482
gboolean source_dragging
Definition masks.h:500
dt_masks_edit_mode_t edit_mode
Definition masks.h:477
dt_iop_module_t * creation_module
Definition masks.h:519
gboolean creation_closing_form
Definition masks.h:518
uint64_t pipe_hash
Definition masks.h:531
GList * creation_formids
Definition masks.h:523
dt_masks_pressure_sensitivity_t pressure_sensitivity
Definition masks.h:527
gboolean seg_selected
Definition masks.h:486
gboolean form_dragging
Definition masks.h:499
gboolean gradient_toggling
Definition masks.h:503
gboolean creation
Definition masks.h:517
float pos_source[2]
Definition masks.h:475
dt_masks_type_t type
Definition masks.h:442
dt_masks_form_t * form_visible
Definition masks.h:444
gboolean form_selected
Definition masks.h:490
gboolean handle_selected
Definition masks.h:485
gboolean form_rotating
Definition masks.h:501
dt_masks_dynbuf_t * guipoints
Definition masks.h:449
float rel_pos[2]
Definition masks.h:461
gboolean border_selected
Definition masks.h:491
float last_rebuild_pos[2]
Definition masks.h:511
gboolean pivot_selected
Definition masks.h:493
float raw_pos[2]
Definition masks.h:465
float last_hit_test_pos[2]
Definition masks.h:515
float delta[2]
Definition masks.h:469
const dt_masks_functions_t * functions
Definition masks.h:381
dt_masks_type_t type
Definition masks.h:380
dt_atomic_int refcount
Definition masks.h:411
gboolean gravity_center_valid
Definition masks.h:396
float source[2]
Definition masks.h:387
char name[128]
Definition masks.h:403
GList * points
Definition masks.h:379
float gravity_center[2]
Definition masks.h:391
gboolean uses_bezier_points_layout
Definition masks.h:383
float(* get_interaction_value)(const struct dt_masks_form_t *form, dt_masks_interaction_t interaction)
Definition masks.h:346
gboolean(* get_gravity_center)(const struct dt_masks_form_t *form, float center[2], float *area)
Definition masks.h:345
int(* button_pressed)(struct dt_iop_module_t *module, double x, double y, double pressure, int which, int type, uint32_t state, struct dt_masks_form_t *form, int parentid, struct dt_masks_form_gui_t *gui, int index)
Definition masks.h:360
int(* button_released)(struct dt_iop_module_t *module, double x, double y, int which, uint32_t state, struct dt_masks_form_t *form, int parentid, struct dt_masks_form_gui_t *gui, int index)
Definition masks.h:364
int(* 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)
Definition masks.h:342
int(* key_pressed)(struct dt_iop_module_t *module, GdkEventKey *event, struct dt_masks_form_t *form, int parentid, struct dt_masks_form_gui_t *gui, int index)
Definition masks.h:367
int(* mouse_moved)(struct dt_iop_module_t *module, double x, double y, double pressure, int which, struct dt_masks_form_t *form, int parentid, struct dt_masks_form_gui_t *gui, int index)
Definition masks.h:353
void(* sanitize_config)(dt_masks_type_t type_flags)
Definition masks.h:317
void(* set_form_name)(struct dt_masks_form_t *const form, const size_t nb)
Definition masks.h:318
void(* duplicate_points)(struct dt_develop_t *const dev, struct dt_masks_form_t *base, struct dt_masks_form_t *dest)
Definition masks.h:321
int(* update_hover)(struct dt_masks_form_t *form, struct dt_masks_form_gui_t *gui, int index)
Definition masks.h:351
float(* set_interaction_value)(struct dt_masks_form_t *form, dt_masks_interaction_t interaction, float value, dt_masks_increment_t increment, int flow, struct dt_masks_form_gui_t *gui, struct dt_iop_module_t *module)
Definition masks.h:347
int(* get_points_border)(struct dt_develop_t *dev, struct dt_masks_form_t *form, float **points, int *points_count, float **border, int *border_count, int source, const dt_iop_module_t *const module)
Definition masks.h:328
int(* 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)
Definition masks.h:338
void(* get_distance)(float x, float y, float as, struct dt_masks_form_gui_t *gui, int index, int num_points, int *inside, int *inside_border, int *near, int *inside_source, float *dist)
Definition masks.h:324
int(* mouse_scrolled)(struct dt_iop_module_t *module, double x, double y, int up, const int delta_y, uint32_t state, struct dt_masks_form_t *form, int parentid, struct dt_masks_form_gui_t *gui, int index, dt_masks_interaction_t interaction)
Definition masks.h:356
void(* initial_source_pos)(const float iwd, const float iht, float *x, float *y)
Definition masks.h:322
void(* post_expose)(cairo_t *cr, float zoom_scale, struct dt_masks_form_gui_t *gui, int index, int num_points)
Definition masks.h:368
void(* set_hint_message)(const struct dt_masks_form_gui_t *const gui, const struct dt_masks_form_t *const form, const int opacity, char *const __restrict__ msgbuf, const size_t msgbuf_len)
Definition masks.h:319
void(* init_ctrl_points)(struct dt_masks_form_t *form)
Definition masks.h:372
struct dt_masks_gui_center_point_t::@34 main
struct dt_masks_gui_center_point_t::@35 source
dt_masks_ellipse_flags_t flags
Definition masks.h:245
void dt_supervisor_form(const dt_sv_op_t op, const dt_masks_form_t *form)
@ DT_SV_CREATE
Definition supervisor.h:77
static gboolean dt_supervisor_active(void)
Definition supervisor.h:94
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29