Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
masks_gui.c
Go to the documentation of this file.
1/*
2 This file is part of Ansel
3 Copyright (C) 2022-2023, 2025-2026 Aurélien PIERRE.
4 Copyright (C) 2025-2026 Guillaume Stutin.
5
6 Ansel is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 Ansel is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with darktable. If not, see <http://www.gnu.org/licenses/>.
18*/
19#include "common/glib_utils.h"
20#include "common/hash.h"
21#include "common/logging.h"
22#include "common/times.h"
23#include "develop/blend.h"
24#include "develop/blend_gui.h"
25#include "develop/develop.h"
27#include "develop/imageop.h"
28#include "develop/supervisor.h"
29#include "math/math.h"
30#include "system/dtpthread.h"
31#include "widgets/gdkkeys.h"
33#include "control/control.h"
34#include "system/macros.h"
35#include "system/mem_alloc.h"
36#include "develop/geometry/geometry.h" // dt_geometry_chain_generation()
37#include "develop/masks.h"
38#include "develop/masks_debug.h"
39#include "develop/masks_gui.h"
40#include "develop/masks_group.h"
44#include "widgets/bauhaus.h"
45#include "common/conf.h"
46#include "control/signal.h"
47#include "develop/imageop_gui.h"
48#include "widgets/paint.h"
49#include "gui/actions/menu.h"
50#include "widgets/draw.h"
51#include "gui/application.h"
52
53#include <math.h>
54#include <stdlib.h>
56#include <glib/gstdio.h>
57
58#define DT_MASKS_SHAPE_BUTTON_COUNT 5
59
61{
62 int index;
63 guint flag;
65 // label/ctrl_label are the button's two tooltips and read as actions ("add circle"); name is the
66 // shape itself, for a place that already says what it does -- an "Add new shape ..." submenu.
67 const gchar *label;
68 const gchar *ctrl_label;
69 const gchar *name;
72
80
83 N_("add gradient"), N_("add multiple gradients"), N_("Gradient"), dtgtk_cairo_paint_masks_gradient },
85 N_("add brush"), N_("add multiple brush strokes"), N_("Brush"), dtgtk_cairo_paint_masks_brush },
87 N_("add polygon"), N_("add multiple polygons"), N_("Polygon"), dtgtk_cairo_paint_masks_polygon },
89 N_("add ellipse"), N_("add multiple ellipses"), N_("Ellipse"), dtgtk_cairo_paint_masks_ellipse },
91 N_("add circle"), N_("add multiple circles"), N_("Circle"), dtgtk_cairo_paint_masks_circle },
92};
93
95{
96 if(IS_NULL_PTR(data)) return;
97
98 // Walk all buttons in this group so any caller can reset every masks shape toolbar through the shared signal.
99 for(int i = 0; i < DT_MASKS_SHAPE_BUTTON_COUNT; i++)
100 {
101 GtkWidget *button = data->buttons[i];
102 if(GTK_IS_TOGGLE_BUTTON(button) && button != active_button)
103 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
104 }
105}
106
107static void _masks_shape_buttons_deactivate_signal(gpointer instance, GtkWidget *active_button,
109{
110 _masks_shape_buttons_deactivate(active_button, data);
111}
112
117
119{
120 if(IS_NULL_PTR(data)) return -1;
121
122 // Search the stored button pointers because callers may keep their own storage arrays.
123 for(int i = 0; i < DT_MASKS_SHAPE_BUTTON_COUNT; i++)
124 if(data->buttons[i] == button) return i;
125
126 return -1;
127}
128
131 const int button_index)
132{
133 dt_masks_form_gui_t *mask_gui = dev->form_gui;
134 dt_masks_form_t *visible_form = dt_masks_get_visible_form(dev);
135
136 if(IS_NULL_PTR(mask_gui) || !mask_gui->creation || IS_NULL_PTR(visible_form)) return FALSE;
137 if(!(visible_form->type & data->types[button_index])) return FALSE;
138
139 /* A toolbar with no creation_module belongs to no module: it is the shape manager's, which lists
140 * every plain drawn shape whoever created it, so it reports any creation but a retouch/spot one,
141 * whose shapes it never shows. Every other toolbar is a module's own and speaks only for it. */
143 return (visible_form->type & DT_MASKS_IS_RETOUCHE) == 0;
144
145 return mask_gui->creation_module == data->config.creation_module;
146}
147
148/* The pressed button is a view on dev->form_gui, not a state of its own: recompute it rather than
149 * remembering what was clicked, since creation is armed from places that have no button at all --
150 * the shape manager's "Add new shape ..." context menu, the keyboard shortcuts. */
152{
153 if(IS_NULL_PTR(data)) return;
154
156 : data->config.dev;
157 if(IS_NULL_PTR(dev)) return;
158
159 for(int i = 0; i < DT_MASKS_SHAPE_BUTTON_COUNT; i++)
160 {
161 GtkWidget *button = data->buttons[i];
162 if(!GTK_IS_TOGGLE_BUTTON(button)) continue;
163
164 const gboolean active = _masks_shape_button_is_current_creation(dev, data, i);
165 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) != active)
166 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
167 }
168}
169
170static void _masks_shape_buttons_sync_signal(gpointer instance __attribute__((unused)),
172{
174}
175
180
182{
183 const size_t count = sizeof(_masks_shape_button_defs) / sizeof(_masks_shape_button_defs[0]);
184 for(size_t i = 0; i < count; i++)
186
187 return NULL;
188}
189
190static gboolean _masks_shape_icon_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
191{
193
194 // Follow the menu item's own foreground, so the icon dims and highlights with the row it is in.
195 GdkRGBA color;
196 gtk_style_context_get_color(gtk_widget_get_style_context(widget),
197 gtk_widget_get_state_flags(widget), &color);
198 gdk_cairo_set_source_rgba(cr, &color);
199
200 GtkAllocation alloc;
201 gtk_widget_get_allocation(widget, &alloc);
202 // The shapes are drawn out to the edges of the square they are handed, so inset it: at this
203 // size the outermost stroke would otherwise be clipped by the allocation.
204 const gint pad = 1;
205 paint(cr, pad, pad, alloc.width - 2 * pad, alloc.height - 2 * pad, 0, NULL);
206
207 return FALSE;
208}
209
211 GCallback activate_callback, gpointer user_data)
212{
214 if(IS_NULL_PTR(def) || !GTK_IS_MENU_SHELL(menu)) return NULL;
215
216 GtkWidget *menu_item = gtk_menu_item_new();
217 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
218 GtkWidget *icon = gtk_drawing_area_new();
219
220 // Square, and the height of a line of text, so the row keeps the height it would have had.
221 const gint size = DT_PIXEL_APPLY_DPI(12);
222 gtk_widget_set_size_request(icon, size, size);
223 gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
224 g_signal_connect(G_OBJECT(icon), "draw", G_CALLBACK(_masks_shape_icon_draw), def->paint);
225
226 GtkWidget *label = gtk_label_new(_(def->name));
227 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
228
229 gtk_box_pack_start(GTK_BOX(box), icon, FALSE, FALSE, 0);
230 gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0);
231 gtk_container_add(GTK_CONTAINER(menu_item), box);
232 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
233
234 if(activate_callback) g_signal_connect(G_OBJECT(menu_item), "activate", activate_callback, user_data);
235
236 return menu_item;
237}
238
239static gboolean _masks_shape_button_pressed(GtkWidget *button, GdkEventButton *event, gpointer user_data)
240{
241 if(dt_gui_widgets_suppressed() || event->button != GDK_BUTTON_PRIMARY) return TRUE;
242
244 (dt_masks_shape_buttons_data_t *)g_object_get_data(G_OBJECT(button), "dt-masks-shape-buttons-data");
245 const int button_index = _masks_shape_button_index(data, button);
246 if(button_index < 0) return FALSE;
247
248 dt_masks_type_t type = data->types[button_index];
249 dt_iop_module_t *module = data->config.creation_module;
250 dt_develop_t *dev = !IS_NULL_PTR(module) ? module->dev : data->config.dev;
251 if(IS_NULL_PTR(dev)) return FALSE;
252 dt_masks_form_gui_t *mask_gui = dev->form_gui;
253
254 if(_masks_shape_button_is_current_creation(dev, data, button_index))
255 {
257 dt_masks_form_exit_creation(module, mask_gui);
258 if(data->config.exited) data->config.exited(button, module, type, data->config.user_data);
260 return TRUE;
261 }
262
263 if(data->config.can_start && !data->config.can_start(button, module, type, data->config.user_data))
264 {
267 return TRUE;
268 }
269
270 if(data->config.form_type) type = data->config.form_type(module, type, data->config.user_data);
271
273 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), TRUE);
274
275 if(dt_masks_creation_mode_enter(dev, module, type))
276 {
277 if(data->config.started)
278 {
279 data->config.started(button, module, type, data->config.user_data);
280 // Force focus back to the drawing area after creation mode enabling
281 gtk_widget_grab_focus(dt_gui_center_widget());
282 }
283 }
284 else
285 {
287 }
288
290 return TRUE;
291}
292
299
309{
310 if(IS_NULL_PTR(config)) return NULL;
311
313 if(IS_NULL_PTR(data)) return NULL;
314
315 data->config = *config;
316 data->box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
317 gtk_widget_set_halign(data->box, GTK_ALIGN_END);
318 gtk_widget_set_valign(data->box, GTK_ALIGN_CENTER);
319
320 const char *action_section = config->action_section ? config->action_section : N_("shapes");
321 const size_t button_defs_count = sizeof(_masks_shape_button_defs) / sizeof(_masks_shape_button_defs[0]);
322
323 // Create buttons in the same visible order used by the module-local toolbars.
324 for(size_t i = 0; i < button_defs_count; i++)
325 {
327 if(!(config->flags & def->flag)) continue;
328
329 GtkWidget *button = NULL;
330 if(config->owner_module)
331 {
332 const gboolean register_button = (config->register_flags & def->flag);
333 if(register_button)
334 {
335 button = dt_iop_togglebutton_new(config->owner_module, action_section, def->label, def->ctrl_label,
336 G_CALLBACK(_masks_shape_button_pressed), config->local,
337 0, 0, def->paint, data->box);
338 }
339 else
340 {
341 button = dt_iop_togglebutton_new_no_register(config->owner_module, action_section, def->label, def->ctrl_label,
342 G_CALLBACK(_masks_shape_button_pressed), config->local,
343 0, 0, def->paint, data->box);
344 }
345 }
346 else
347 {
348 button = dtgtk_togglebutton_new(def->paint, 0, NULL);
349 gtk_widget_set_tooltip_text(button, _(def->label));
350 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
351 gtk_box_pack_end(GTK_BOX(data->box), button, FALSE, FALSE, 0);
352 g_signal_connect(G_OBJECT(button), "button-press-event", G_CALLBACK(_masks_shape_button_pressed), NULL);
353 }
354
355 gtk_widget_set_can_focus(button, FALSE);
356 g_object_set_data(G_OBJECT(button), "dt-masks-shape-buttons-data", data);
357
358 data->buttons[def->index] = button;
359 data->types[def->index] = def->type;
360 if(config->buttons) config->buttons[def->index] = button;
361 if(config->types) config->types[def->index] = def->type;
362 }
363
367 G_CALLBACK(_masks_shape_buttons_sync_signal), data);
368 g_signal_connect(G_OBJECT(data->box), "destroy", G_CALLBACK(_masks_shape_buttons_destroy), data);
369
370 return data->box;
371}
372
374{
375 /* The row this slider drives, named by IDENTITY and never held as a pointer. A slider outlives
376 * many mutations of the group it points into: it commits history on every step, each commit
377 * re-snapshots dev->forms, and the next step's copy-on-write then clones the group -- so a
378 * cached dt_masks_form_group_t* would, from the second step on, address the abandoned copy
379 * while the snapshots it was supposed to leave frozen kept the edits. */
384 dt_iop_module_t *module;
390
391// Push the new value to history (so the pipeline re-renders) and refresh the mask
392// treeviews (opacity text, etc.).
393//
394// This is called from the slider "value-changed" handler, which fires on every step of a
395// drag. That is fine: the history commit batches the pipeline resync it triggers, so
396// transient values do not flood the pipeline with renders, yet the image updates without
397// waiting for the context menu to be closed.
406
408{
409 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->dev)) return;
410
411 if(data->increment == DT_MASKS_INCREMENT_ABSOLUTE) // aka opacity
412 {
414 data->increment, 1, data->gui, data->module);
415 data->last_value = value;
417 return;
418 }
419
420 const float delta = value - data->last_value;
421 if(fabsf(delta) < 1e-6f) return;
422
423 // Slider value is a log2 scale factor in [-3;3], so apply the delta in log space.
424 const float scale = exp2f(delta);
425 dt_masks_form_set_interaction_value(data->dev, data->group_id, data->formid, data->interaction, scale,
426 DT_MASKS_INCREMENT_SCALE, 1.f, data->gui, data->module);
427 data->last_value = value;
429}
430
431static void _masks_gui_menu_item_block_activate(GtkWidget *widget, gpointer user_data)
432{
433 g_signal_stop_emission_by_name(widget, "activate");
434}
435
436static gboolean _masks_gui_menu_item_forward_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
437{
439 if(IS_NULL_PTR(data) || !data->slider) return FALSE;
440
441 GdkEvent *copy = gdk_event_copy(event);
442 if(IS_NULL_PTR(copy)) return FALSE;
443
444 double x = 0.0, y = 0.0;
445 gboolean has_coords = FALSE;
446 switch(copy->type)
447 {
448 case GDK_BUTTON_PRESS:
449 case GDK_2BUTTON_PRESS:
450 case GDK_3BUTTON_PRESS:
451 case GDK_BUTTON_RELEASE:
452 x = copy->button.x;
453 y = copy->button.y;
454 has_coords = TRUE;
455 break;
456 case GDK_MOTION_NOTIFY:
457 x = copy->motion.x;
458 y = copy->motion.y;
459 has_coords = TRUE;
460 break;
461 case GDK_SCROLL:
462 x = copy->scroll.x;
463 y = copy->scroll.y;
464 has_coords = TRUE;
465 break;
466 default:
467 break;
468 }
469
470 if(has_coords)
471 {
472 int sx = 0, sy = 0;
473 if(gtk_widget_translate_coordinates(widget, data->slider, (int)x, (int)y, &sx, &sy))
474 {
475 switch(copy->type)
476 {
477 case GDK_BUTTON_PRESS:
478 case GDK_2BUTTON_PRESS:
479 case GDK_3BUTTON_PRESS:
480 case GDK_BUTTON_RELEASE:
481 copy->button.x = sx;
482 copy->button.y = sy;
483 break;
484 case GDK_MOTION_NOTIFY:
485 copy->motion.x = sx;
486 copy->motion.y = sy;
487 break;
488 case GDK_SCROLL:
489 copy->scroll.x = sx;
490 copy->scroll.y = sy;
491 break;
492 default:
493 break;
494 }
495 }
496 }
497
498 GdkWindow *slider_window = gtk_widget_get_window(data->slider);
499 if(slider_window)
500 {
501 if(copy->any.window) g_object_unref(copy->any.window);
502 copy->any.window = g_object_ref(slider_window);
503 copy->any.send_event = TRUE;
504 }
505
506 gtk_widget_event(data->slider, copy);
507 gdk_event_free(copy);
508 return TRUE;
509}
510
511static void _masks_gui_interaction_slider_changed(GtkWidget *widget, gpointer user_data)
512{
514 if(IS_NULL_PTR(data) || IS_NULL_PTR(data->dev)) return;
515
517}
518
520 const int group_id, const int formid,
521 dt_masks_interaction_t interaction, dt_masks_increment_t increment,
522 float min, float max, float step, float value, int digits,
523 const char *format, float factor,
525{
526 GtkWidget *menu_item = gtk_menu_item_new();
527 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, DT_GUI_BOX_SPACING);
528
529 gtk_widget_set_can_focus(menu_item, FALSE);
530 g_signal_connect(G_OBJECT(menu_item), "activate",
531 G_CALLBACK(_masks_gui_menu_item_block_activate), NULL);
532 gtk_widget_add_events(menu_item, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
533 | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
534
536 min, max, step, value, digits);
537 dt_bauhaus_widget_set_label(slider, label);
538 dt_bauhaus_slider_set_digits(slider, digits);
539 if(format && format[0] != '\0') dt_bauhaus_slider_set_format(slider, format);
540 if(factor != 1.0f) dt_bauhaus_slider_set_factor(slider, factor);
542 DT_BAUHAUS_WIDGET(slider)->expand = TRUE;
543 gtk_widget_set_hexpand(slider, TRUE);
544 gtk_widget_set_halign(slider, GTK_ALIGN_FILL);
545 gtk_widget_set_valign(slider, GTK_ALIGN_CENTER);
546 gtk_widget_set_size_request(slider, DT_PIXEL_APPLY_DPI(220), DT_PIXEL_APPLY_DPI(28));
547 gtk_widget_set_can_focus(slider, TRUE);
548
550 data->dev = dev;
551 data->group_id = group_id;
552 data->formid = formid;
553 data->gui = gui;
554 data->module = module;
555 data->interaction = interaction;
556 data->increment = increment;
557 data->last_value = value;
558 data->slider = slider;
559 g_signal_connect_data(G_OBJECT(slider), "value-changed",
561 data, (GClosureNotify)g_free, 0);
562 g_signal_connect(G_OBJECT(menu_item), "button-press-event",
563 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
564 g_signal_connect(G_OBJECT(menu_item), "button-release-event",
565 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
566 g_signal_connect(G_OBJECT(menu_item), "motion-notify-event",
567 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
568 g_signal_connect(G_OBJECT(menu_item), "scroll-event",
569 G_CALLBACK(_masks_gui_menu_item_forward_event), data);
570
571 gtk_box_pack_start(GTK_BOX(box), slider, TRUE, TRUE, 0);
572 gtk_container_add(GTK_CONTAINER(menu_item), box);
573 gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
574
575 return menu_item;
576}
577
579{
580 if(IS_NULL_PTR(dt_gui_get_global()) || IS_NULL_PTR(dt_gui_get_ui())) return GTK_RESPONSE_NO;
581
582 GtkWidget *dialog = gtk_message_dialog_new(
583 GTK_WINDOW(dt_gui_main_window()),
584 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
585 GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Delete the shape '%s' ?"), form_name);
586 gtk_message_dialog_format_secondary_text(
587 GTK_MESSAGE_DIALOG(dialog), "'%s' %s\n\n%s", form_name,
588 _("will no longer be used."),
589 _("Do you want to permanently delete it, or keep it unused for potential reuse?"));
590
591 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Delete shape"), GTK_RESPONSE_YES);
592 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Keep unused shape"), GTK_RESPONSE_NO);
593 gtk_dialog_add_button(GTK_DIALOG(dialog), _("Cancel"), GTK_RESPONSE_CANCEL);
594 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
595
596 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
597 gtk_widget_destroy(dialog);
598
599 return response;
600}
601
602gboolean dt_masks_gui_confirm_permanent_delete(const char *form_name)
603{
604 if(!dt_conf_get_bool("ask_before_delete_mask_shape")) return TRUE;
606
607 GtkWidget *dialog = gtk_message_dialog_new(
608 GTK_WINDOW(dt_gui_main_window()),
609 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
610 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
611 _("Permanently delete the shape '%s' ?"), form_name);
612 gtk_message_dialog_format_secondary_text(
613 GTK_MESSAGE_DIALOG(dialog), "%s",
614 _("It will be removed from every mask using it and from the list of available shapes."));
615
616 GtkWidget *message_area = gtk_message_dialog_get_message_area(GTK_MESSAGE_DIALOG(dialog));
617 GtkWidget *ask_check = gtk_check_button_new_with_label(_("Always ask"));
618 gtk_widget_set_tooltip_text(ask_check,
619 _("when unchecked, mask shapes will be deleted silently from now on without this confirmation.\n"
620 "you can turn it back on from preferences."));
621 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ask_check), TRUE);
622 gtk_box_pack_start(GTK_BOX(message_area), ask_check, FALSE, FALSE, 6);
623 gtk_widget_show(ask_check);
624
625 const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
626 dt_conf_set_bool("ask_before_delete_mask_shape", gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ask_check)));
627 gtk_widget_destroy(dialog);
628
629 return response == GTK_RESPONSE_YES;
630}
631
632// Resolves the group entry selected in gui to (module, sel, parentid, formid). Returns FALSE
633// if nothing usable is selected, in which case *module/*sel are left untouched.
635 dt_masks_form_t **sel, int *parentid, int *formid)
636{
637 if(IS_NULL_PTR(gui) || gui->group_selected < 0) return FALSE;
639 if(IS_NULL_PTR(forms)) return FALSE;
640
642 if(IS_NULL_PTR(fpt)) return FALSE;
643 dt_iop_module_t *mod = gui->dev->gui_module;
644 if(IS_NULL_PTR(mod)) return FALSE;
646 if(IS_NULL_PTR(form)) return FALSE;
647
648 *module = mod;
649 *sel = form;
650 *parentid = fpt->parentid;
651 *formid = fpt->formid;
652 return TRUE;
653}
654
655// "Remove shape from mask": detach from the current group only, keep the form for reuse.
656static void _masks_gui_remove_from_group_callback(GtkWidget *menu, gpointer user_data)
657{
658 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
659 dt_iop_module_t *module = NULL;
660 dt_masks_form_t *sel = NULL;
661 int parentid = 0, formid = 0;
662 if(!_masks_gui_resolve_selected_shape(gui, &module, &sel, &parentid, &formid)) return;
663
664 dt_masks_remove_shape_from_group(module, sel, parentid, gui, formid);
665}
666
667// "Delete shape": permanently delete, gated by dt_masks_gui_confirm_permanent_delete().
668static void _masks_gui_full_delete_callback(GtkWidget *menu, gpointer user_data)
669{
670 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
671 dt_iop_module_t *module = NULL;
672 dt_masks_form_t *sel = NULL;
673 int parentid = 0, formid = 0;
674 if(!_masks_gui_resolve_selected_shape(gui, &module, &sel, &parentid, &formid)) return;
675
676 dt_masks_delete_shape(module, sel, parentid, gui, formid);
677}
678
679void _masks_gui_delete_node_callback(GtkWidget *menu, gpointer user_data)
680{
681 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
682 if(IS_NULL_PTR(gui)) return;
684 if(IS_NULL_PTR(forms)) return;
685
686 dt_iop_module_t *module = gui->dev->gui_module;
687 if(IS_NULL_PTR(module)) return;
688
689 if(gui->creation)
690 {
691 // Minimum points to create a polygon
692 if(gui->node_dragging < 1)
693 {
694 dt_masks_form_exit_creation(module, gui);
695 return;
696 }
698 if(sel)
699 dt_masks_remove_node(module, sel, 0, gui, 0, gui->node_dragging);
700 gui->node_dragging -= 1;
701 }
702 else if(gui->group_selected >= 0)
703 {
704 // Delete shape from current group
705
707 if(IS_NULL_PTR(fpt)) return;
709 if(sel)
710 dt_masks_remove_node(module, sel, fpt->parentid, gui, gui->group_selected, gui->node_hovered);
711
712 dt_dev_add_history_item(gui->dev, module, TRUE, TRUE);
713 }
714}
715
716static void _masks_gui_exit_creation_callback(GtkWidget *menu, gpointer user_data)
717{
718 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
719 if(IS_NULL_PTR(gui)) return;
720 dt_iop_module_t *module = gui->dev->gui_module;
721 dt_masks_form_exit_creation(module, gui);
722}
723
724static void _masks_move_up_down_callback(gpointer user_data, const int up)
725{
726 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
727 if(IS_NULL_PTR(gui)) return;
728 if(gui->group_selected < 0) return;
729
730 dt_iop_module_t *module = gui->dev->gui_module;
731 if(IS_NULL_PTR(module)) return;
732
734 if(IS_NULL_PTR(forms)) return;
736 if(IS_NULL_PTR(fpt)) return;
738 if(IS_NULL_PTR(grp) || !(grp->type & DT_MASKS_GROUP)) return;
739 grp = dt_masks_cow_touch(gui->dev, grp);
740
741 dt_masks_form_move(grp, fpt->formid, up);
742
744}
745
746static void _masks_moveup_callback(GtkWidget *menu, gpointer user_data)
747{
748 _masks_move_up_down_callback(user_data, 0);
749}
750
751static void _masks_movedown_callback(GtkWidget *menu, gpointer user_data)
752{
753 _masks_move_up_down_callback(user_data, 1);
754}
755
758static void _masks_operation_callback(GtkWidget *menu, gpointer user_data)
759{
760 dt_masks_form_gui_t *gui = (dt_masks_form_gui_t *)user_data;
761 if(IS_NULL_PTR(gui) || IS_NULL_PTR(menu)) return;
762
763 const guint form_pos = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(menu), "form_pos"));
764 const dt_masks_state_t state_op = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu), "state_op"));
765 // Advert the user if it will have no effect
766 if(form_pos == 0 && (state_op & DT_MASKS_STATE_IS_COMBINE_OP) != 0)
767 {
768 dt_control_log(_("Applying a boolean operation has no effect on the first shape of a group.\n"
769 "Move it to at least the 2nd position if you need to use boolean operations"));
770 }
771
772 dt_masks_form_group_t *form_op = (dt_masks_form_group_t *)g_object_get_data(G_OBJECT(menu), "op_form");
773 if(IS_NULL_PTR(form_op)) return;
774
775 dt_masks_group_entry_apply_operation(form_op, state_op);
776
778}
779
780#define masks_gtk_menu_item_new_bold(label, selected, state, icon) \
781{ \
782 gchar *op_label = g_strdup(label); \
783 menu_item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(op_label, icon, \
784 sub_menu, \
785 _masks_operation_callback, gui, \
786 (selected != 0), \
787 ((state) == DT_MASKS_STATE_INVERSE)); \
788 dt_free(op_label); \
789 op_label = NULL; \
790 g_object_set_data(G_OBJECT(menu_item), "state_op", GINT_TO_POINTER(state)); \
791 g_object_set_data(G_OBJECT(menu_item), "op_form", op_form); \
792 g_object_set_data(G_OBJECT(menu_item), "form_pos", GINT_TO_POINTER(form_pos)); \
793}
794
795
796// Shared by the darkroom canvas context menu (dt_masks_create_menu) and the blend module's
797// own shape-list context menus (develop/blend_gui.c), so both offer the same shape parameters.
799 const int group_id,
801{
802 if(IS_NULL_PTR(menu) || IS_NULL_PTR(form) || IS_NULL_PTR(dev)) return;
803
804 /* The row has to exist -- opacity is read from it, and every slider writes back to it -- but
805 * asking by identity means no caller has to resolve one and hand it over, and none of them has
806 * to copy-on-write the group merely to open a menu. */
807 if(dt_masks_group_get_member(dev, group_id, form->formid, NULL) != DT_MASKS_OK) return;
808
809 const float opacity = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_OPACITY);
810
811 if(form->type & DT_MASKS_GRADIENT)
812 {
813 // For gradients, DT_MASKS_INTERACTION_FADING is the shape curvature -- expose it
814 // under its actual name.
815 const float curvature = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_FADING);
816 const float fade = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_SIZE);
817 float rotation = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_ROTATION);
818 if(!isfinite(rotation)) rotation = 0.0f;
819 if(rotation > 180.0f) rotation -= 360.0f;
820
821 dt_masks_gui_add_interaction_slider(menu, _("Curvature"), dev, group_id, form->formid, DT_MASKS_INTERACTION_FADING,
822 DT_MASKS_INCREMENT_ABSOLUTE, -2.0f, 2.0f, 0.01f,
823 isfinite(curvature) ? curvature : 0.0f, 3, "%", 50.0f,
824 gui, module);
825 dt_masks_gui_add_interaction_slider(menu, _("Size"), dev, group_id, form->formid, DT_MASKS_INTERACTION_SIZE,
826 DT_MASKS_INCREMENT_ABSOLUTE, 0.0f, 1.0f, 0.001f,
827 isfinite(fade) ? fade : 1.0f, 3, "%", 100.0f,
828 gui, module);
829 dt_masks_gui_add_interaction_slider(menu, _("Rotation"), dev, group_id, form->formid, DT_MASKS_INTERACTION_ROTATION,
830 DT_MASKS_INCREMENT_ABSOLUTE, -180.0f, 180.0f, 1.0f,
831 rotation, 1, "\302\260", 1.0f,
832 gui, module);
833 }
834 else
835 {
836 const float fading = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_FADING);
837
838 dt_masks_gui_add_interaction_slider(menu, _("Size"), dev, group_id, form->formid, DT_MASKS_INTERACTION_SIZE,
839 DT_MASKS_INCREMENT_SCALE, -4.f, 4.0f, 0.01f, 0.0f, 2, "x", 1.0f,
840 gui, module);
841 dt_masks_gui_add_interaction_slider(menu, _("Fading"), dev, group_id, form->formid, DT_MASKS_INTERACTION_FADING,
842 DT_MASKS_INCREMENT_ABSOLUTE, 0.f, 1.0f, 0.01f,
843 isfinite(fading) ? fading : 1.0f, 3, "%", 100.0f,
844 gui, module);
845
846 if(form->type & DT_MASKS_ELLIPSE)
847 {
848 float rotation = dt_masks_form_get_interaction_value(dev, group_id, form->formid, DT_MASKS_INTERACTION_ROTATION);
849 if(!isfinite(rotation)) rotation = 0.0f;
850 if(rotation > 180.0f) rotation -= 360.0f;
851
852 dt_masks_gui_add_interaction_slider(menu, _("Rotation"), dev, group_id, form->formid, DT_MASKS_INTERACTION_ROTATION,
853 DT_MASKS_INCREMENT_ABSOLUTE, -180.0f, 180.0f, 1.0f,
854 rotation, 1, "\302\260", 1.0f,
855 gui, module);
856 }
857 }
858
859 dt_masks_gui_add_interaction_slider(menu, _("Opacity"), dev, group_id, form->formid, DT_MASKS_INTERACTION_OPACITY,
860 DT_MASKS_INCREMENT_ABSOLUTE, 0.0f, 1.0f, 0.01f,
861 isfinite(opacity) ? opacity : 1.0f, 3, "%", 100.0f,
862 gui, module);
863}
864
866 const float pzx, const float pzy)
867{
868 assert(gui);
869 assert(form);
870 // Always re-create the menu when we show it because we don't bother updating info during the lifetime of the mask
871 GtkWidget *menu = gtk_menu_new();
872 gtk_style_context_add_class(gtk_widget_get_style_context(menu), "dt-masks-context-menu");
873
874 // Create an array of icons for the operations
875 const int bs2 = DT_PIXEL_APPLY_DPI(13);
876 GdkPixbuf *op_icon[DT_MASKS_STATE_EXCLUSION + 1] = { 0 };
877 int width = bs2 * 2;
883
884 // Get the current group to apply operations on it if needed
885 dt_masks_form_group_t *op_form = NULL;
886 dt_masks_form_t *grp = formgroup ? dt_masks_get_from_id(gui->dev, formgroup->parentid) : NULL;
887 if(grp && (grp->type & DT_MASKS_GROUP))
888 op_form = dt_masks_form_group_from_parentid(gui->dev, grp->formid, form->formid);
889 if(IS_NULL_PTR(op_form) && !gui->creation)
890 {
891 for(size_t k = 0; k < G_N_ELEMENTS(op_icon); k++)
892 g_clear_object(&op_icon[k]);
893 gtk_widget_destroy(menu);
894 return NULL;
895 }
896
897 // Find the position of the current form in the group
898 guint form_pos = 0;
899 gboolean form_found = FALSE;
900 if(grp && (grp->type & DT_MASKS_GROUP))
901 {
902 for(GList *fpts = grp->points; fpts; fpts = g_list_next(fpts))
903 {
904 dt_masks_form_group_t *fpt = (dt_masks_form_group_t *)fpts->data;
905 if(fpt->formid == form->formid)
906 {
907 form_found = TRUE;
908 break;
909 }
910 form_pos++;
911 }
912 }
913
914 // Get the number of shapes in the group
915 guint list_length = (form_found && grp) ? g_list_length(grp->points) : 0;
916
917
918 // Title
919 gchar *form_name = NULL;
920 if(form->name[0])
921 form_name = g_strdup(form->name);
922 else if(gui->creation)
923 {
924 // if no name, we are probably creating a new form, we create one based on the type
925 form_name = g_strdup(_("New "));
926 switch (form->type)
927 {
928 case DT_MASKS_CIRCLE:
929 form_name = g_strconcat(form_name, _("circle"), NULL);
930 break;
931 case DT_MASKS_ELLIPSE:
932 form_name = g_strconcat(form_name, _("ellipse"), NULL);
933 break;
934 case DT_MASKS_POLYGON:
935 form_name = g_strconcat(form_name, _("polygon"), NULL);
936 break;
937 case DT_MASKS_BRUSH:
938 form_name = g_strconcat(form_name, _("brush"), NULL);
939 break;
941 form_name = g_strconcat(form_name, _("gradient"), NULL);
942 break;
943 case DT_MASKS_GROUP:
944 form_name = g_strconcat(form_name, _("mask"), NULL);
945 break;
946 default:
947 dt_free(form_name); // Erase the "New " prefix
948 form_name = g_strdup(_("Unknown shape"));
949 break;
950 }
951 }
952
953 // Create the main label string
954 gchar *item_str = NULL;
955 if(gui->node_hovered >= 0 || gui->seg_hovered >= 0)
956 {
957 const int item_index = (gui->node_hovered >= 0) ? gui->node_hovered : gui->seg_hovered;
958 item_str = g_strdup_printf("%s %d - ", gui->node_hovered >= 0 ? _("Node") : _("Segment"), item_index);
959 }
960 else
961 item_str = g_strdup("");
962
963 // Create an assembled image if we have an inverse state to show
964 const dt_masks_state_t state = IS_NULL_PTR(op_form) ? 0 : op_form->state & DT_MASKS_STATE_IS_COMBINE_OP;
965 const gboolean has_inverse = !IS_NULL_PTR(op_form) && (op_form->state & DT_MASKS_STATE_INVERSE) != 0;
966 GdkPixbuf *icon = (state <= DT_MASKS_STATE_EXCLUSION) ? op_icon[state] : NULL;
967 GdkPixbuf *composed_icon = NULL;
968 if(has_inverse && op_icon[DT_MASKS_STATE_INVERSE])
969 {
970 if(icon)
971 {
972 const int base_w = gdk_pixbuf_get_width(icon);
973 const int base_h = gdk_pixbuf_get_height(icon);
974 const int inv_w = gdk_pixbuf_get_width(op_icon[DT_MASKS_STATE_INVERSE]);
975 const int inv_h = gdk_pixbuf_get_height(op_icon[DT_MASKS_STATE_INVERSE]);
976 const int out_w = base_w + inv_w;
977 const int out_h = MAX(base_h, inv_h);
978
979 composed_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, out_w, out_h);
980 if(composed_icon)
981 {
982 gdk_pixbuf_fill(composed_icon, 0x00000000);
983 gdk_pixbuf_copy_area(icon, 0, 0, base_w, base_h, composed_icon, 0, 0);
984 gdk_pixbuf_copy_area(op_icon[DT_MASKS_STATE_INVERSE], 0, 0, inv_w, inv_h, composed_icon, base_w, 0);
985 icon = composed_icon;
986 }
987 }
988 else
989 icon = op_icon[DT_MASKS_STATE_INVERSE];
990 }
991
992 const gboolean draw_icon = !IS_NULL_PTR(op_form) && form_pos > 0;
993 gchar *title = g_strdup_printf("<b><big>%s%s</big></b>", item_str, form_name);
994 GtkWidget *menu_item = ctx_gtk_menu_item_new_with_markup_and_pixbuf(title, (draw_icon) ? icon : NULL, menu, NULL, gui);
995 gtk_widget_set_sensitive(menu_item, FALSE);
996 dt_free(item_str);
997 dt_free(title);
998 dt_free(form_name);
999
1000 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1001
1002 // Common menu items
1003 if(!gui->creation && (gui->form_selected || gui->node_selected) && op_form)
1004 {
1005 dt_masks_gui_populate_interaction_sliders(menu, gui->dev, form, grp->formid, gui, gui->dev->gui_module);
1006 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1007 }
1008
1009 // Shape specific menu items
1010 if(!IS_NULL_PTR(form) && form->functions && form->functions->populate_context_menu)
1011 if(form->functions->populate_context_menu(menu, form, gui, pzx, pzy))
1012 {
1013 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1014 }
1015
1016
1017 /* Module specific */
1018 {
1019 dt_iop_module_t *module = gui->dev->gui_module;
1020 if(!IS_NULL_PTR(module) && module->populate_masks_context_menu)
1021 if(module->populate_masks_context_menu(module, menu, form->formid, pzx, pzy))
1022 {
1023 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1024 }
1025 }
1026
1027 /* Operation */
1028
1029 if(!gui->creation && !(form->type & DT_MASKS_IS_RETOUCHE) && (op_form) && !gui->node_selected)
1030 {
1031 menu_item = ctx_gtk_menu_item_new_with_markup(_("Operation"), menu, NULL, gui);
1032 GtkWidget *sub_menu = gtk_menu_new();
1033 gtk_style_context_add_class(gtk_widget_get_style_context(sub_menu), "dt-masks-context-menu");
1034 gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), sub_menu);
1035
1037 op_icon[DT_MASKS_STATE_INVERSE]);
1038 gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), gtk_separator_menu_item_new());
1040 op_icon[DT_MASKS_STATE_UNION]);
1044 op_icon[DT_MASKS_STATE_DIFFERENCE]);
1046 op_icon[DT_MASKS_STATE_EXCLUSION]);
1047
1048 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1049 }
1050
1051 if(!gui->creation && gui->form_selected)
1052 {
1053 menu_item = ctx_gtk_menu_item_new_with_markup(_("Move up"), menu, _masks_moveup_callback, gui);
1054 gtk_widget_set_sensitive(menu_item, (form_pos > 0));
1055 menu_item = ctx_gtk_menu_item_new_with_markup(_("Move down"), menu, _masks_movedown_callback, gui);
1056 gtk_widget_set_sensitive(menu_item, (form_pos < list_length - 1));
1057
1058 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1059 }
1060
1061 // Risky stuff at the end
1062 if(gui->creation)
1063 {
1064 menu_item = ctx_gtk_menu_item_new_with_markup(_("Done shape creation"), menu,
1066 menu_item_set_fake_accel(menu_item, GDK_KEY_Escape, 0);
1067 }
1068 else
1069 {
1070 if(gui->node_hovered >= 0)
1071 {
1072 menu_item = ctx_gtk_menu_item_new_with_markup(_("Delete node"), menu, _masks_gui_delete_node_callback, gui);
1073 menu_item_set_fake_accel(menu_item, GDK_KEY_Delete, 0);
1074 }
1075 else
1076 {
1077 menu_item = ctx_gtk_menu_item_new_with_markup(_("Remove shape from mask"), menu, _masks_gui_remove_from_group_callback, gui);
1078 menu_item_set_fake_accel(menu_item, GDK_KEY_Delete, 0);
1079 gtk_widget_set_sensitive(menu_item, gui->form_selected >= 0);
1080
1081 menu_item = ctx_gtk_menu_item_new_with_markup(_("Delete shape"), menu, _masks_gui_full_delete_callback, gui);
1082 gtk_widget_set_sensitive(menu_item, gui->form_selected >= 0);
1083 }
1084 }
1085
1086 for(size_t k = 0; k < G_N_ELEMENTS(op_icon); k++)
1087 g_clear_object(&op_icon[k]);
1088 g_clear_object(&composed_icon);
1089
1090 gtk_widget_show_all(menu);
1091 return menu;
1092}
1093
1094/* De-inlined from masks_gui.h: they dispatch through the private per-shape table. */
1096 struct dt_masks_form_t *mask_form,
1097 struct dt_masks_form_gui_t *mask_gui,
1098 const int form_index,
1099 const struct dt_masks_form_gui_points_t *gui_points,
1100 const int node_index,
1101 float node[2], float ctrl1[2], float ctrl2[2],
1103{
1104 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui) || IS_NULL_PTR(gui_points) || IS_NULL_PTR(state) || node_index < 0) return FALSE;
1105
1106 if(dt_masks_node_is_cusp(gui_points, node_index))
1107 {
1109 if(mask_form->functions && mask_form->functions->init_ctrl_points)
1110 mask_form->functions->init_ctrl_points(mask_form);
1111 }
1112 else
1113 {
1114 ctrl1[0] = ctrl2[0] = node[0];
1115 ctrl1[1] = ctrl2[1] = node[1];
1117 }
1118
1119 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
1120 return TRUE;
1121}
1122
1124 struct dt_masks_form_t *mask_form,
1125 struct dt_masks_form_gui_t *mask_gui,
1126 const int form_index,
1127 const struct dt_masks_form_gui_points_t *gui_points,
1128 const int node_index,
1130{
1131 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui) || IS_NULL_PTR(gui_points) || IS_NULL_PTR(state) || node_index < 0) return FALSE;
1132
1133 if(*state != DT_MASKS_POINT_STATE_NORMAL && !dt_masks_node_is_cusp(gui_points, node_index))
1134 {
1136 if(mask_form->functions && mask_form->functions->init_ctrl_points)
1137 mask_form->functions->init_ctrl_points(mask_form);
1138 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
1139 }
1140
1141 return TRUE;
1142}
1143
1144/* ------------------------------------------------------------------------------------
1145 * Everything below was moved verbatim from masks.c: the interactive half -- hit testing,
1146 * event dispatch, hover/cursor, form_gui lifecycle, creation flows, drawing, menus and
1147 * the interaction engine. The data model and rasterisation stayed behind.
1148 * ------------------------------------------------------------------------------------ */
1149
1157gboolean dt_masks_point_is_within_radius(const float point_x, const float point_y,
1158 const float center_x, const float center_y,
1159 const float squared_radius)
1160{
1161 const float delta_x = point_x - center_x;
1162 const float delta_y = point_y - center_y;
1163 const float squared_distance = delta_x * delta_x + delta_y * delta_y;
1164 return squared_distance <= squared_radius;
1165}
1166
1184 dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override,
1185 dt_masks_border_handle_fn border_handle_cb,
1186 dt_masks_curve_handle_fn curve_handle_cb,
1187 dt_masks_node_position_fn node_position_cb,
1188 dt_masks_distance_fn distance_cb,
1189 dt_masks_post_select_fn post_select_cb,
1190 void *user_data)
1191{
1192 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return 0;
1193 if(!mask_gui->creation && mask_gui->group_selected != form_index) return 0;
1194
1195 dt_masks_form_gui_points_t *gui_points
1196 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
1197 if(IS_NULL_PTR(gui_points)) return 0;
1198
1199 // Handle detection in backbuffer space.
1200 const float cursor_radius = DT_GUI_MOUSE_EFFECT_RADIUS;
1201 const float cursor_radius2 = cursor_radius * cursor_radius;
1202 const float cursor_x = mask_gui->pos[0];
1203 const float cursor_y = mask_gui->pos[1];
1204 const int selected_node = dt_masks_gui_selected_node_index(mask_gui);
1205
1206 // Keep track of current state, in case we need to refresh total deselection.
1207 const gboolean need_refresh_anyway = dt_masks_gui_was_anything_selected(mask_gui);
1208
1209 mask_gui->form_selected = FALSE;
1210 mask_gui->border_selected = FALSE;
1211 mask_gui->source_selected = FALSE;
1212
1213 mask_gui->node_hovered = -1;
1214 mask_gui->handle_hovered = -1;
1215 mask_gui->seg_hovered = -1;
1216 mask_gui->handle_border_hovered = -1;
1217
1218 if(mask_gui->node_dragging >= 0)
1219 {
1220 mask_gui->node_hovered = mask_gui->node_dragging;
1221 return 1;
1222 }
1223 if(mask_gui->handle_dragging >= 0)
1224 {
1225 mask_gui->handle_hovered = mask_gui->handle_dragging;
1226 return 1;
1227 }
1228 if(mask_gui->handle_border_dragging >= 0)
1229 {
1230 mask_gui->handle_border_hovered = mask_gui->handle_border_dragging;
1231 return 1;
1232 }
1233 if(mask_gui->seg_dragging >= 0)
1234 {
1235 mask_gui->seg_hovered = mask_gui->seg_dragging;
1236 return 1;
1237 }
1238
1239 const int node_count = (node_count_override >= 0) ? node_count_override
1240 : (int)g_list_length(mask_form->points);
1241
1242 const gboolean has_bezier_layout = mask_form->uses_bezier_points_layout;
1243 const gboolean can_test_nodes = (!IS_NULL_PTR(node_position_cb)) || has_bezier_layout;
1244 const int first_node_index = has_bezier_layout ? 0 : 1; // skip center node for non-bezier shapes
1245 const gboolean has_selected_node = (node_count > 0) && can_test_nodes
1246 && (mask_gui->group_selected == form_index)
1247 && selected_node >= first_node_index && selected_node < node_count;
1248
1249 if(has_selected_node)
1250 {
1251 // Current node's border handle (feather handle).
1252 float handle_x = NAN;
1253 float handle_y = NAN;
1254 if(border_handle_cb
1255 && border_handle_cb(gui_points, node_count, selected_node, &handle_x, &handle_y, user_data)
1256 && dt_masks_point_is_within_radius(cursor_x, cursor_y, handle_x, handle_y, cursor_radius2))
1257 {
1258 mask_gui->handle_border_hovered = selected_node;
1259 return 1;
1260 }
1261
1262 // Current node's curve handle.
1263 if(!dt_masks_node_is_cusp(gui_points, selected_node) && curve_handle_cb)
1264 {
1265 curve_handle_cb(gui_points, selected_node, &handle_x, &handle_y, user_data);
1266 if(dt_masks_point_is_within_radius(cursor_x, cursor_y, handle_x, handle_y, cursor_radius2))
1267 {
1268 mask_gui->handle_hovered = selected_node;
1269 return 1;
1270 }
1271 }
1272
1273 // Current node itself.
1274 float node_x = NAN;
1275 float node_y = NAN;
1276 if(node_position_cb)
1277 {
1278 node_position_cb(gui_points, selected_node, &node_x, &node_y, user_data);
1279 }
1280 else if(has_bezier_layout)
1281 {
1282 node_x = gui_points->points[selected_node * 6 + 2];
1283 node_y = gui_points->points[selected_node * 6 + 3];
1284 }
1285 if(!isnan(node_x) && !isnan(node_y)
1286 && dt_masks_point_is_within_radius(cursor_x, cursor_y, node_x, node_y, cursor_radius2))
1287 {
1288 mask_gui->node_hovered = selected_node;
1289 return 1;
1290 }
1291 }
1292
1293 if(can_test_nodes)
1294 {
1295 for(int node_index = first_node_index; node_index < node_count; node_index++)
1296 {
1297 float node_x = NAN;
1298 float node_y = NAN;
1299 if(node_position_cb)
1300 {
1301 node_position_cb(gui_points, node_index, &node_x, &node_y, user_data);
1302 }
1303 else if(has_bezier_layout)
1304 {
1305 node_x = gui_points->points[node_index * 6 + 2];
1306 node_y = gui_points->points[node_index * 6 + 3];
1307 }
1308 if(!isnan(node_x) && !isnan(node_y)
1309 && dt_masks_point_is_within_radius(cursor_x, cursor_y, node_x, node_y, cursor_radius2))
1310 {
1311 mask_gui->node_hovered = node_index;
1312 return 1;
1313 }
1314 }
1315 }
1316
1317 if(IS_NULL_PTR(distance_cb)) return 0;
1318
1319 // Segment or shape hit tests.
1320 int inside = 0;
1321 int inside_border = 0;
1322 int near_segment = -1;
1323 int inside_source = 0;
1324 float nearest_distance = 0.0f;
1325 distance_cb(cursor_x, cursor_y, cursor_radius, mask_gui, form_index, node_count, &inside, &inside_border,
1326 &near_segment, &inside_source, &nearest_distance, user_data);
1327
1328
1329 if(inside_source)
1330 {
1331 mask_gui->form_selected = TRUE;
1332 mask_gui->source_selected = TRUE;
1333 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1334 return 1;
1335 }
1336 if(inside_border)
1337 {
1338 mask_gui->form_selected = TRUE;
1339 mask_gui->border_selected = TRUE;
1340 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1341 return 1;
1342 }
1343 if(near_segment >= 0)
1344 {
1345 if(near_segment < node_count)
1346 mask_gui->seg_hovered = near_segment;
1347 return 1;
1348 }
1349 if(inside)
1350 {
1351 mask_gui->form_selected = TRUE;
1352 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1353 return 1;
1354 }
1355
1356 // Deselection needs a refresh at least once.
1357 return need_refresh_anyway;
1358}
1359
1368dt_masks_form_group_t *dt_masks_form_group_find_entry(dt_masks_form_t *group_form, const int form_id, int *out_index)
1369{
1370 if(out_index) *out_index = -1;
1371 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return NULL;
1372
1373 // Iterate group entries to find the matching form id.
1374 int index = 0;
1375 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1376 {
1377 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1378 if(group_entry && group_entry->formid == form_id)
1379 {
1380 if(out_index) *out_index = index;
1381 return group_entry;
1382 }
1383 index++;
1384 }
1385 return NULL;
1386}
1387
1388int dt_masks_group_index_from_formid(const dt_masks_form_t *group_form, int form_id)
1389{
1390 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return -1;
1391
1392 int index = 0;
1393 for(const GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
1394 {
1395 const dt_masks_form_group_t *group_entry = (const dt_masks_form_group_t *)group_node->data;
1396 if(!IS_NULL_PTR(group_entry) && group_entry->formid == form_id) return index;
1397 index++;
1398 }
1399 return -1;
1400}
1401
1409{
1410 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return NULL;
1411 return dev->form_gui->form_visible;
1412}
1413
1415{
1416 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return;
1417 dev->form_gui->form_visible = form;
1418}
1419
1421{
1422 if(IS_NULL_PTR(dev)) return;
1423
1424 if(IS_NULL_PTR(dev->form_gui))
1425 {
1426 dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t));
1428 }
1429
1431 dt_masks_set_visible_form(dev, NULL);
1432 dev->form_gui->geometry_generation = 0;
1433 dev->form_gui->formid = 0;
1434}
1435
1437{
1438 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return;
1439
1440 // If shutdown happens while a shape is being created, release the unfinished
1441 // temporary visible form before clearing the GUI state.
1443
1445 dt_free(dev->form_gui);
1446 dt_masks_set_visible_form(dev, NULL);
1447}
1448
1450{
1451 if(IS_NULL_PTR(gui)) return;
1452
1453 if(gui->handle_selected && gui->handle_hovered >= 0) gui->handle_dragging = gui->handle_hovered;
1455 if(gui->node_selected && gui->node_hovered >= 0) gui->node_dragging = gui->node_hovered;
1456 if(gui->seg_selected && gui->seg_hovered >= 0) gui->seg_dragging = gui->seg_hovered;
1457 if(gui->source_selected)
1458 gui->source_dragging = TRUE;
1459 else if(gui->form_selected)
1460 gui->form_dragging = TRUE;
1461}
1462
1464{
1465 if(IS_NULL_PTR(gui)) return;
1466
1467 gui->handle_dragging = -1;
1468 gui->handle_border_dragging = -1;
1469 gui->node_dragging = -1;
1470 gui->seg_dragging = -1;
1471 gui->form_dragging = FALSE;
1472 gui->source_dragging = FALSE;
1473}
1474
1476{
1477 if(IS_NULL_PTR(gui)) return FALSE;
1478 const gboolean dragging = (gui->form_dragging || gui->source_dragging || gui->seg_dragging >= 0 || gui->node_dragging >= 0
1479 || gui->handle_dragging >= 0 || gui->handle_border_dragging >= 0);
1481 return dragging;
1482}
1483
1490{
1491 dt_masks_form_t *group_form = dt_masks_get_from_id(dev, parent_id);
1492 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) return NULL;
1493 return dt_masks_form_group_find_entry(group_form, form_id, NULL);
1494}
1495
1496// Read-only recursive search: dev->forms's top-level groups and their nested subgroups.
1497// grp == NULL starts the search at dev->forms itself. max_depth bounds the recursion so a
1498// corrupted or maliciously crafted masks_history (a group referencing an ancestor of itself --
1499// dt_masks_group_add_form guards against this interactively via _find_in_group, but a raw
1500// DB/XMP load does not validate it) cannot stack-overflow the caller; the UI never nests
1501// groups anywhere near this deep (see the `depth < 3` guards in libs/shape_manager.c).
1502
1523 const dt_masks_form_gui_t *mask_gui)
1524{
1525 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return NULL;
1526 if(mask_gui->group_selected < 0) return NULL;
1527 return (dt_masks_form_group_t *)g_list_nth_data(mask_form->points, mask_gui->group_selected);
1528}
1529
1540 const dt_masks_form_gui_t *mask_gui)
1541{
1542 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return NULL;
1543
1544 dt_masks_form_group_t *selected_group_entry = NULL;
1545 if(mask_gui->group_selected >= 0)
1546 selected_group_entry = dt_masks_form_get_selected_group(mask_form, mask_gui);
1547
1548 if(IS_NULL_PTR(selected_group_entry)) return NULL;
1549
1550 if(selected_group_entry->parentid > 0)
1551 {
1552 // Re-resolve via parentid to ensure the pointer is still current.
1553 dt_masks_form_group_t *resolved_group_entry
1554 = dt_masks_form_group_from_parentid(mask_gui->dev, selected_group_entry->parentid,
1555 selected_group_entry->formid);
1556 if(resolved_group_entry) return resolved_group_entry;
1557 }
1558
1559 return selected_group_entry;
1560}
1561
1569 const dt_masks_form_gui_t *mask_gui,
1570 dt_masks_form_group_t **group_entry,
1571 int *parent_id, int *form_index)
1572{
1573 if(group_entry) *group_entry = NULL;
1574 if(parent_id) *parent_id = 0;
1575 if(form_index) *form_index = 0;
1576
1577 if(IS_NULL_PTR(visible_form)) return NULL;
1578 if(!(visible_form->type & DT_MASKS_GROUP)) return visible_form;
1579
1580 dt_masks_form_group_t *selected_group_entry
1581 = dt_masks_form_get_selected_group_live(visible_form, mask_gui);
1582 if(IS_NULL_PTR(selected_group_entry)) return NULL;
1583
1584 dt_masks_form_t *selected_form = dt_masks_get_from_id(mask_gui->dev, selected_group_entry->formid);
1585 if(IS_NULL_PTR(selected_form)) return NULL;
1586
1587 if(group_entry) *group_entry = selected_group_entry;
1588 if(parent_id) *parent_id = selected_group_entry->parentid;
1589 if(form_index) *form_index = mask_gui->group_selected;
1590
1591 return selected_form;
1592}
1593
1601 dt_masks_form_gui_t *mask_gui)
1602{
1603 if(IS_NULL_PTR(group_form) || IS_NULL_PTR(mask_gui)) return FALSE;
1604
1605 dt_develop_t *const dev = mask_gui->dev;
1606 const float radius = DT_GUI_MOUSE_EFFECT_RADIUS;
1607 const float cursor_x = mask_gui->pos[0];
1608 const float cursor_y = mask_gui->pos[1];
1609 const int prev_group_selected = mask_gui->group_selected;
1610 const gboolean prev_border_selected = mask_gui->border_selected;
1611 int locked_formid = -1;
1612
1613 if(dt_masks_is_anything_hovered(mask_gui))
1614 return TRUE;
1615
1616 if(prev_border_selected && prev_group_selected >= 0)
1617 {
1618 dt_masks_form_group_t *selected_group_entry = dt_masks_form_get_selected_group_live(group_form, mask_gui);
1619 dt_masks_form_t *selected_form = selected_group_entry
1620 ? dt_masks_get_from_id(dev, selected_group_entry->formid)
1621 : NULL;
1622 const gboolean has_border_lock_candidate = selected_group_entry
1623 && selected_form
1624 && (selected_form->type & DT_MASKS_IS_CLOSED_SHAPE)
1625 && selected_form->functions
1626 && selected_form->functions->get_distance;
1627 if(has_border_lock_candidate)
1628 {
1629 // Lock selection only when the click lands on the selected closed-shape border/segment.
1630 int inside = 0;
1631 int inside_border = 0;
1632 int near_handle = -1;
1633 int inside_source = 0;
1634 float dist = FLT_MAX;
1635 selected_form->functions->get_distance(cursor_x, cursor_y, radius, mask_gui, prev_group_selected,
1636 g_list_length(selected_form->points), &inside, &inside_border,
1637 &near_handle, &inside_source, &dist);
1638 if(inside_border || near_handle >= 0)
1639 locked_formid = selected_group_entry->formid;
1640 }
1641 }
1642
1643 if(prev_group_selected >= 0)
1645
1646 dt_masks_form_t *selected_form = NULL;
1647 int selected_index = -1;
1648 float best_dist = FLT_MAX;
1649
1650 int index = 0;
1651 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node), index++)
1652 {
1653 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
1654 if(IS_NULL_PTR(group_entry)) continue;
1655 if(locked_formid >= 0 && group_entry->formid != locked_formid) continue;
1656
1657 dt_masks_form_t *form = dt_masks_get_from_id(dev, group_entry->formid);
1658 if(IS_NULL_PTR(form)) continue;
1659
1660 int inside = 0;
1661 int inside_border = 0;
1662 int near_handle = -1;
1663 int inside_source = 0;
1664 float dist = FLT_MAX;
1665 if(form->functions && form->functions->get_distance)
1666 form->functions->get_distance(cursor_x, cursor_y, radius, mask_gui, index, g_list_length(form->points),
1667 &inside, &inside_border, &near_handle, &inside_source, &dist);
1668
1669 const gboolean is_selected_form = (prev_group_selected == index);
1670 const gboolean hit_border = (inside_border || near_handle >= 0);
1671 const gboolean is_open_shape = (form->type & DT_MASKS_IS_OPEN_SHAPE) != 0;
1672 // Only open shapes can be selected via their border when unselected.
1673 if(!is_selected_form && hit_border && !is_open_shape)
1674 continue;
1675
1676 if(inside || hit_border || inside_source)
1677 {
1678 // Lazily computed: only shapes actually hit by this click need it, not every member
1679 // of the group on every mouse event.
1681 const float dx = mask_gui->raw_pos[0] - form->gravity_center[0];
1682 const float dy = mask_gui->raw_pos[1] - form->gravity_center[1];
1683 const float center_dist2 = dx * dx + dy * dy;
1684 const float combined_dist2 = dist * center_dist2;
1685 if(combined_dist2 < best_dist)
1686 {
1687 selected_form = form;
1688 selected_index = index;
1689 best_dist = combined_dist2;
1690 }
1691 }
1692 }
1693
1694 if(!IS_NULL_PTR(selected_form))
1695 {
1696 mask_gui->group_selected = selected_index;
1697 return TRUE;
1698 }
1699
1700 return mask_gui->group_selected >= 0;
1701}
1702
1704{
1705 if(IS_NULL_PTR(mask_gui) || mask_gui->creation) return FALSE;
1706 if(mask_gui->form_rotating || mask_gui->border_toggling || mask_gui->gradient_toggling) return FALSE;
1707 if(dt_masks_gui_is_dragging(mask_gui)) return FALSE;
1708 return dt_masks_gui_should_hit_test(mask_gui);
1709}
1710
1712 const int form_index)
1713{
1714 if(IS_NULL_PTR(dispatch_form) || IS_NULL_PTR(mask_gui) || !dispatch_form->functions || !dispatch_form->functions->update_hover)
1715 return 0;
1716 return dispatch_form->functions->update_hover(dispatch_form, mask_gui, form_index);
1717}
1718
1719static gboolean _dt_masks_events_cursor_over_form(const dt_masks_form_t *dispatch_form,
1720 dt_masks_form_gui_t *mask_gui,
1721 const int form_index)
1722{
1723 if(!dispatch_form || IS_NULL_PTR(mask_gui) || !dispatch_form->functions || !dispatch_form->functions->get_distance)
1724 return FALSE;
1725
1726 int inside = 0;
1727 int inside_border = 0;
1728 int near_handle = -1;
1729 int inside_source = 0;
1730 float dist = FLT_MAX;
1731 dispatch_form->functions->get_distance(mask_gui->pos[0], mask_gui->pos[1], DT_GUI_MOUSE_EFFECT_RADIUS,
1732 mask_gui, form_index,
1733 g_list_length(dispatch_form->points), &inside, &inside_border, &near_handle,
1734 &inside_source, &dist);
1735 return inside || inside_border || near_handle >= 0 || inside_source;
1736}
1737
1742{
1743 if(IS_NULL_PTR(mask_gui)) return FALSE;
1744
1745 const float radius = DT_GUI_MOUSE_EFFECT_RADIUS;
1746 if(mask_gui->scrollx == 0.0f || mask_gui->scrolly == 0.0f) return FALSE;
1747
1748 if((mask_gui->scrollx - mask_gui->pos[0] < radius && mask_gui->scrollx - mask_gui->pos[0] > -radius)
1749 && (mask_gui->scrolly - mask_gui->pos[1] < radius && mask_gui->scrolly - mask_gui->pos[1] > -radius))
1750 return TRUE;
1751
1752 mask_gui->scrollx = 0.0f;
1753 mask_gui->scrolly = 0.0f;
1754 return FALSE;
1755}
1756
1761 dt_masks_form_t *dispatch_form,
1762 dt_masks_form_gui_t *mask_gui,
1763 const int form_index, const int button)
1764{
1765 if(button != 1) return FALSE;
1766 if(!dt_masks_gui_is_dragging(mask_gui)) return FALSE;
1767
1768 if(mask_gui->rebuild_pending)
1769 {
1770 if(!IS_NULL_PTR(dispatch_form))
1771 {
1772 dt_masks_gui_form_create(dispatch_form, mask_gui, form_index, module);
1773
1774 dt_develop_t *const dev = mask_gui->dev;
1775 if(!IS_NULL_PTR(dev))
1776 {
1777 mask_gui->last_rebuild_ts = dt_get_wtime();
1778 mask_gui->last_rebuild_pos[0] = mask_gui->pos[0];
1779 mask_gui->last_rebuild_pos[1] = mask_gui->pos[1];
1780 }
1781 }
1782 mask_gui->rebuild_pending = FALSE;
1783 }
1784
1785 return TRUE;
1786}
1787
1794static gboolean _set_hinter_message(dt_masks_form_gui_t *mask_gui, const dt_masks_form_t *mask_form)
1795{
1796 // Sized for the longest hint once translated: a truncated message would also cut a Pango
1797 // markup tag in half, and dt_hinter_set_message() feeds the result to gtk_label_set_markup().
1798 char message[512] = "";
1799
1800 // Checked before use, not after: this function tests IS_NULL_PTR(mask_form) further down
1801 // and its own dt_print writes `mask_form ? mask_form->type : -1`, so a NULL was always
1802 // considered possible here -- but form_type read it unconditionally first.
1803 if(IS_NULL_PTR(mask_form)) return FALSE;
1804
1805 const int form_type = mask_form->type;
1806
1807 const dt_masks_form_t *selected_form = mask_form;
1808 int selected_form_id = 0;
1809 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP))
1810 {
1811 const dt_masks_form_group_t *selected_group_entry
1812 = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
1813 if(!IS_NULL_PTR(selected_group_entry)) selected_form_id = selected_group_entry->formid;
1814 }
1815
1817 "[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",
1818 (void *)mask_form, mask_form ? mask_form->type : -1, (void *)mask_gui,
1819 mask_gui->group_selected, mask_gui->form_selected,
1820 mask_gui->node_hovered, mask_gui->seg_hovered,
1821 selected_form_id);
1822 if(form_type & DT_MASKS_GROUP)
1823 {
1824 // Resolve the selected form inside a group (if any).
1825 dt_masks_form_group_t *selected_group_entry = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
1826 if(!IS_NULL_PTR(selected_group_entry))
1827 {
1828 selected_form = dt_masks_get_from_id(mask_gui->dev, selected_group_entry->formid);
1829 if(IS_NULL_PTR(selected_form)) return FALSE;
1830 }
1831 }
1832
1833 if(selected_form->functions && selected_form->functions->set_hint_message)
1834 {
1835 selected_form->functions->set_hint_message(mask_gui, selected_form, message, sizeof(message));
1836 }
1837
1840 "[masks] hint end: sel=%p has_set_hint=%d msg_len=%" G_GSIZE_FORMAT " msg='%s'\n",
1841 (void *)selected_form,
1842 (selected_form && selected_form->functions && selected_form->functions->set_hint_message) ? 1 : 0,
1843 strlen(message), message);
1844 return message[0] != '\0';
1845}
1846
1848{
1849 memset(mask_gui, 0, sizeof(dt_masks_form_gui_t));
1850
1851 mask_gui->dev = dev;
1852 mask_gui->outline_step = 1;
1853 mask_gui->pos[0] = mask_gui->pos[1] = -1.0f;
1854 mask_gui->rel_pos[0] = mask_gui->rel_pos[1] = -1.0f;
1855 mask_gui->raw_pos[0] = mask_gui->raw_pos[1] = -1.0f;
1856 mask_gui->pos_source[0] = mask_gui->pos_source[1] = -1.0f;
1858 mask_gui->node_hovered = -1;
1859 mask_gui->handle_hovered = -1;
1860 mask_gui->seg_hovered = -1;
1861 mask_gui->handle_border_hovered = -1;
1862 mask_gui->node_selected = FALSE;
1863 mask_gui->handle_selected = FALSE;
1864 mask_gui->seg_selected = FALSE;
1865 mask_gui->handle_border_selected = FALSE;
1866 mask_gui->node_selected_idx = -1;
1867 mask_gui->form_selected = FALSE;
1868 mask_gui->border_selected = FALSE;
1869 mask_gui->source_selected = FALSE;
1870 mask_gui->pivot_selected = FALSE;
1871 mask_gui->last_rebuild_ts = 0.0;
1872 mask_gui->last_rebuild_pos[0] = mask_gui->last_rebuild_pos[1] = 0.0f;
1873 mask_gui->rebuild_pending = FALSE;
1874 mask_gui->last_hit_test_pos[0] = mask_gui->last_hit_test_pos[1] = -1.0f;
1875}
1876
1878{
1879 // Note: we have an hard reset function below that frees all buffers and such
1880 mask_gui->source_selected = FALSE;
1881 mask_gui->node_hovered = -1;
1882 mask_gui->handle_hovered = -1;
1883 mask_gui->seg_hovered = -1;
1884 mask_gui->handle_border_hovered = -1;
1885 mask_gui->node_selected = FALSE;
1886 mask_gui->handle_selected = FALSE;
1887 mask_gui->seg_selected = FALSE;
1888 mask_gui->handle_border_selected = FALSE;
1889 mask_gui->node_selected_idx = -1;
1890 mask_gui->group_selected = -1;
1891 mask_gui->delta[0] = mask_gui->delta[1] = 0.0f;
1892 mask_gui->form_selected = mask_gui->border_selected = mask_gui->form_dragging = mask_gui->form_rotating = FALSE;
1893 mask_gui->pivot_selected = FALSE;
1894 mask_gui->handle_border_dragging = mask_gui->seg_dragging = mask_gui->handle_dragging = mask_gui->node_dragging = -1;
1895 mask_gui->last_rebuild_ts = 0.0;
1896 mask_gui->last_rebuild_pos[0] = mask_gui->last_rebuild_pos[1] = 0.0f;
1897 mask_gui->rebuild_pending = FALSE;
1898 mask_gui->last_hit_test_pos[0] = mask_gui->last_hit_test_pos[1] = -1.0f;
1899}
1900
1901void dt_masks_gui_set_outline_density(dt_masks_form_gui_t *mask_gui, const double image_px_per_device_px)
1902{
1903 if(IS_NULL_PTR(mask_gui)) return;
1904 const double span = isfinite(image_px_per_device_px) ? floor(image_px_per_device_px) : 1.0;
1905 mask_gui->outline_step = (span > 1.0) ? (int)span : 1;
1906}
1907
1909{
1910 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return 1;
1911 return MAX(1, dev->form_gui->outline_step);
1912}
1913
1915 int form_index, dt_iop_module_t *module)
1916{
1917 // Never guarded before the move; every branch below reads mask_gui->dev, and the
1918 // analyzer is right that nothing upstream promises it is set.
1919 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui) || IS_NULL_PTR(mask_gui->dev)) return;
1920
1921 const int gui_points_count = g_list_length(mask_gui->points);
1922 if(gui_points_count == form_index)
1923 {
1924 dt_masks_form_gui_points_t *gui_points_new
1926 mask_gui->points = g_list_append(mask_gui->points, gui_points_new);
1927 }
1928 else if(gui_points_count < form_index)
1929 return;
1930
1931 dt_masks_gui_form_remove(mask_form, mask_gui, form_index);
1932
1933 dt_masks_form_gui_points_t *gui_points
1934 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
1935 const dt_masks_raster_result_t border_status
1936 = dt_masks_get_points_border(mask_gui->dev, mask_form, &gui_points->points, &gui_points->points_count,
1937 &gui_points->border, &gui_points->border_count,
1938 &gui_points->border_skips, &gui_points->border_skip_count, 0, NULL);
1939
1940 /* Only a genuine FAILURE may leave the cache key unset, and it must: the key covers the whole
1941 * group, so one unstamped shape rebuilds every shape of the group on every later expose --
1942 * forever, until the geometry moves.
1943 *
1944 * That is precisely why "this shape has no outline" and "building the outline broke" had to
1945 * stop sharing one return value. A shape with no geometry HAS an outline -- the empty one --
1946 * and caching it is correct; the drawing code already skips a NULL outline. A build that
1947 * failed produced nothing to cache and must be retried. Three of the five shapes used to
1948 * report the first case as success, so the cache was stamped over a NULL outline and the
1949 * shape stayed invisible until the geometry next moved. */
1950 if(border_status == DT_MASKS_RASTER_ERROR && (dt_get_debug_flags() & DT_DEBUG_MASKS))
1952 "[masks] outline build FAILED for %s (index %d, %d nodes): the cache key stays unset,"
1953 " so every later expose rebuilds this whole group\n",
1954 mask_form->name, form_index, g_list_length(mask_form->points));
1955
1956 if(border_status != DT_MASKS_RASTER_ERROR)
1957 {
1958 if(border_status == DT_MASKS_RASTER_OK && (mask_form->type & DT_MASKS_CLONE))
1959 {
1960 if(dt_masks_get_points_border(mask_gui->dev, mask_form, &gui_points->source, &gui_points->source_count,
1961 NULL, NULL, NULL, NULL, TRUE, module)
1963 return;
1964 }
1967 mask_gui->outline_step_built = dt_masks_gui_outline_step(mask_gui->dev);
1968 mask_gui->formid = mask_form->formid;
1969 mask_gui->type = mask_form->type;
1970
1971 }
1972
1973 dt_masks_form_update_gravity_center(mask_gui->dev, mask_form);
1974}
1975
1977 int form_index, dt_iop_module_t *module,
1978 float posx, float posy)
1979{
1980 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_gui)) return FALSE;
1981
1982 dt_develop_t *const develop = mask_gui->dev;
1983 if(IS_NULL_PTR(develop))
1984 {
1985 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
1986 return TRUE;
1987 }
1988
1989 const double now = dt_get_wtime();
1990 const double min_delta_time = 1.0 / 60.0;
1991 const float min_dist2 = 4.0f;
1992 /* Bypass the throttle only when the GEOMETRY moved -- the outlines would then be drawn in the
1993 * wrong place, which no amount of throttling makes acceptable. It used to bypass on the preview
1994 * pipe's backbuffer hash instead, so a republished frame counted as a reason: dragging a brush
1995 * republishes continuously, the clause was therefore true on essentially every mouse move, and
1996 * the throttle below never ran. #1158. */
1997 const gboolean force_rebuild
1999
2000 if(!force_rebuild && mask_gui->last_rebuild_ts > 0.0)
2001 {
2002 const double elapsed_time = now - mask_gui->last_rebuild_ts;
2003 const float delta_x = posx - mask_gui->last_rebuild_pos[0];
2004 const float delta_y = posy - mask_gui->last_rebuild_pos[1];
2005 if(elapsed_time < min_delta_time && (delta_x * delta_x + delta_y * delta_y) < min_dist2)
2006 {
2007 mask_gui->rebuild_pending = TRUE;
2008 return FALSE;
2009 }
2010 }
2011
2012 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
2013 mask_gui->last_rebuild_ts = now;
2014 mask_gui->last_rebuild_pos[0] = posx;
2015 mask_gui->last_rebuild_pos[1] = posy;
2016 mask_gui->rebuild_pending = FALSE;
2017 return TRUE;
2018}
2019
2020void dt_masks_remove_node(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id,
2021 dt_masks_form_gui_t *mask_gui, int form_index, int node_index)
2022{
2023 if(IS_NULL_PTR(mask_form) || IS_NULL_PTR(mask_form->points)) return;
2024 mask_form = dt_masks_cow_touch(mask_gui->dev, mask_form);
2025 dt_masks_node_brush_t *brush_node = (dt_masks_node_brush_t *)g_list_nth_data(mask_form->points, node_index);
2026 if(IS_NULL_PTR(brush_node)) return;
2027 mask_form->points = g_list_remove(mask_form->points, brush_node);
2028 dt_free(brush_node);
2029 mask_gui->node_hovered = -1;
2030 mask_gui->node_selected = FALSE;
2031 mask_gui->node_selected_idx = -1;
2032 if(mask_form->functions && mask_form->functions->init_ctrl_points)
2033 mask_form->functions->init_ctrl_points(mask_form);
2034
2035 // we recreate the form points
2036 dt_masks_gui_form_create(mask_form, mask_gui, form_index, module);
2037}
2038
2050static gboolean _masks_remove_shape(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, int parent_id,
2051 dt_masks_form_gui_t *mask_gui, int form_index)
2052{
2053 // if the form doesn't below to a group, we don't delete it
2054 if(parent_id <= 0) return 1;
2055
2056 // we hide the form
2057 dt_masks_form_t *visible_form = dt_masks_get_visible_form(mask_gui->dev);
2058 if(IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP))
2059 dt_masks_change_form_gui(mask_gui->dev, NULL);
2060 else if(g_list_shorter_than(visible_form->points, 2))
2061 dt_masks_change_form_gui(mask_gui->dev, NULL);
2062 else
2063 {
2064 const int edit_mode = mask_gui->edit_mode;
2065
2066 dt_masks_clear_form_gui(mask_gui->dev);
2067 visible_form = dt_masks_cow_touch(mask_gui->dev, visible_form);
2068 // Remove the selected shape copy from the visible group before deleting the
2069 // real group entry below.
2070 for(GList *forms = visible_form->points; forms; forms = g_list_next(forms))
2071 {
2072 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)forms->data;
2073 if(group_entry->formid == mask_form->formid)
2074 {
2075 visible_form->points = g_list_remove(visible_form->points, group_entry);
2076 dt_free(group_entry);
2077 break;
2078 }
2079 }
2080 mask_gui->edit_mode = edit_mode;
2081 }
2082
2083 // we delete or remove the shape
2084 // Called from node removal, if there was not enough nodes to keep the whole shape,
2085 // that's how this was called:
2086 // dt_masks_form_remove(module, NULL, form);
2087 // Called from shape removal, this is how it was called:
2088 dt_masks_form_delete(mask_gui->dev, module, dt_masks_get_from_id(mask_gui->dev, parent_id), mask_form);
2089 // Not sure what difference it makes.
2090
2091 return 1;
2092}
2093
2094static int _masks_gui_form_group_use_count(const dt_develop_t *dev, const int formid)
2095{
2096 if(IS_NULL_PTR(dev)) return 0;
2097
2098 int count = 0;
2099 for(GList *form_node = dev->forms; form_node; form_node = g_list_next(form_node))
2100 {
2101 dt_masks_form_t *group_form = (dt_masks_form_t *)form_node->data;
2102 if(IS_NULL_PTR(group_form) || !(group_form->type & DT_MASKS_GROUP)) continue;
2103
2104 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
2105 {
2106 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
2107 if(group_entry && group_entry->formid == formid)
2108 {
2109 count++;
2110 if(count > 1) goto done;
2111 break;
2112 }
2113 }
2114 }
2115
2116done:
2117 return count;
2118}
2119
2120// Shared tail of dt_masks_remove_or_delete()/dt_masks_remove_shape_from_group()/dt_masks_delete_shape():
2121// once it's decided whether the form is kept unused or fully deleted, the group-selection
2122// bookkeeping, history commit and signal raising are identical.
2123static gboolean _masks_remove_or_delete_finish(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
2124 dt_masks_form_gui_t *mask_gui, int form_id, gboolean keep_unused)
2125{
2126 dt_masks_form_t *visible_form = dt_masks_get_visible_form(mask_gui->dev);
2127 int next_form_index = -1;
2128 int next_formid = 0;
2129 if(!IS_NULL_PTR(visible_form) && (visible_form->type & DT_MASKS_GROUP)
2130 && mask_gui->group_selected >= 0 && !g_list_shorter_than(visible_form->points, 2))
2131 {
2132 const int group_length = g_list_length(visible_form->points);
2133 next_form_index = mask_gui->group_selected < group_length - 1
2134 ? mask_gui->group_selected
2135 : mask_gui->group_selected - 1;
2136
2137 // Walk from the selected row to remember the shape that should be selected
2138 // after deletion: next row first, previous row if the removed row is last.
2139 GList *selected_node = g_list_nth(visible_form->points, mask_gui->group_selected);
2140 if(!IS_NULL_PTR(selected_node))
2141 {
2142 GList *next_node = g_list_next(selected_node);
2143 if(IS_NULL_PTR(next_node)) next_node = g_list_previous(selected_node);
2144 if(!IS_NULL_PTR(next_node))
2145 {
2146 dt_masks_form_group_t *next_group_entry = (dt_masks_form_group_t *)next_node->data;
2147 if(!IS_NULL_PTR(next_group_entry)) next_formid = next_group_entry->formid;
2148 }
2149 }
2150 }
2151
2152 gboolean res = TRUE;
2153 int signal_parent_id = 0;
2155
2156 if(keep_unused)
2157 {
2158 // Only remove from current group, keep the form itself for potential reuse.
2159 res = _masks_remove_shape(module, sel, parent_id, mask_gui, mask_gui->group_selected);
2160 signal_parent_id = parent_id;
2161 signal_event = DT_MASKS_EVENT_DELETE;
2162 }
2163
2164 else // Permanent delete.
2165 {
2166 if(IS_NULL_PTR(visible_form) || !(visible_form->type & DT_MASKS_GROUP))
2167 dt_masks_change_form_gui(mask_gui->dev, NULL);
2168 else if(g_list_shorter_than(visible_form->points, 2))
2169 dt_masks_change_form_gui(mask_gui->dev, NULL);
2170 else
2171 {
2172 const int edit_mode = mask_gui->edit_mode;
2173 dt_masks_clear_form_gui(mask_gui->dev);
2174 visible_form = dt_masks_cow_touch(mask_gui->dev, visible_form);
2175 // Remove the selected shape copy from the visible group before deleting
2176 // the real form from the develop mask list below.
2177 for(GList *forms = visible_form->points; forms; forms = g_list_next(forms))
2178 {
2179 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)forms->data;
2180 if(group_entry->formid == sel->formid)
2181 {
2182 visible_form->points = g_list_remove(visible_form->points, group_entry);
2183 dt_free(group_entry);
2184 break;
2185 }
2186 }
2187 mask_gui->edit_mode = edit_mode;
2188 if(next_formid > 0)
2189 {
2190 mask_gui->group_selected = next_form_index;
2191 mask_gui->form_selected = TRUE;
2192 }
2193 }
2194
2195 dt_masks_form_delete(mask_gui->dev, module, NULL, sel);
2196 }
2197
2198 dt_dev_add_history_item(mask_gui->dev, module, TRUE, TRUE);
2200 signal_event);
2201
2202 if(res && next_formid > 0)
2203 {
2204 // The shape manager rebuilds its tree on the delete/remove signal, so apply
2205 // the replacement selection after the signal has finished refreshing lists.
2206 mask_gui->group_selected = next_form_index;
2207 mask_gui->form_selected = TRUE;
2208 dt_dev_masks_selection_change(mask_gui->dev, module, next_formid, FALSE);
2209 dt_masks_select_form(mask_gui->dev, module, dt_masks_get_from_id(mask_gui->dev, next_formid));
2210 }
2211 return res;
2212}
2213
2214gboolean dt_masks_remove_or_delete(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
2215 dt_masks_form_gui_t *mask_gui, int form_id)
2216{
2217 const int use_count = _masks_gui_form_group_use_count(mask_gui->dev, form_id);
2218
2219 // We don't ask for confirmation if the module uses internal masks,
2220 // just delete the form as it won't be visible in the shape manager.
2221 const gboolean internal_masks
2222 = !IS_NULL_PTR(module)
2223 && ((module->flags() & IOP_FLAGS_INTERNAL_MASKS) == IOP_FLAGS_INTERNAL_MASKS);
2224
2225 int response = GTK_RESPONSE_YES;
2226 if(use_count <= 1 && !internal_masks)
2227 {
2229 if(response == GTK_RESPONSE_CANCEL) return FALSE;
2230 }
2231
2232 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, response == GTK_RESPONSE_NO);
2233}
2234
2235gboolean dt_masks_remove_shape_from_group(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
2236 dt_masks_form_gui_t *mask_gui, int form_id)
2237{
2238 if(IS_NULL_PTR(sel) || IS_NULL_PTR(mask_gui)) return FALSE;
2239 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, TRUE);
2240}
2241
2242gboolean dt_masks_delete_shape(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id,
2243 dt_masks_form_gui_t *mask_gui, int form_id)
2244{
2245 if(IS_NULL_PTR(sel) || IS_NULL_PTR(mask_gui)) return FALSE;
2247 return _masks_remove_or_delete_finish(module, sel, parent_id, mask_gui, form_id, FALSE);
2248}
2249
2251{
2252 if(IS_NULL_PTR(mask_gui)) return FALSE;
2253
2254 if(mask_gui->creation)
2255 {
2256 const int last_formid = mask_gui->creation_last_formid;
2257 dt_iop_module_t *creation_module = mask_gui->creation_module ? mask_gui->creation_module : module;
2258 dt_develop_t *dev = mask_gui->dev;
2259 dt_masks_form_t *temporary_form = dt_masks_get_visible_form(dev);
2260
2261 if(mask_gui->guipoints)
2262 {
2265 mask_gui->guipoints = NULL;
2266 mask_gui->guipoints_payload = NULL;
2267 mask_gui->guipoints_count = 0;
2268 }
2269
2270 // The visible form is the current unfinished shape while creation is active.
2271 // If it was never appended to develop->forms, drop it before selecting the
2272 // last completed shape from this creation session.
2273 if(!IS_NULL_PTR(temporary_form) && IS_NULL_PTR(dt_masks_get_from_id(dev, temporary_form->formid)))
2274 {
2275 dt_masks_set_visible_form(dev, NULL);
2276 dt_masks_free_form(temporary_form);
2277 }
2278
2280
2281 /* The user-facing ways to abandon a creation session all land here -- Esc, the right-click
2282 * menu entry, backspace on a polygon with no node placed, the darkroom tearing down
2283 * mid-creation -- and none of them told the shape toolbars, so a shape abandoned with Esc
2284 * left its button pressed. Only the click-the-armed-button-again path deactivated, because
2285 * it does so itself before calling us. Finishing a shape is NOT one of these: continuous
2286 * creation deliberately keeps the tool armed for the next one.
2287 *
2288 * This is not the only place mask_gui->creation is cleared -- dt_masks_clear_form_gui()
2289 * does it too, so entering edit mode leaves creation through dt_masks_change_form_gui()
2290 * instead, and deactivates the toolbars on its own (develop/blend_gui.c). Neither that
2291 * function nor dt_masks_creation_mode_quit() can carry this call, tempting as the latter
2292 * looks: dt_masks_creation_mode_enter() reaches both while ARMING a tool, so deactivating
2293 * there would switch off the button the same gesture just pressed.
2294 *
2295 * Raised after creation is cleared, so a handler that asks is told the truth; the buttons
2296 * are on "button-press-event", not "toggled", so setting them inactive cannot re-enter
2297 * this function. */
2299
2300 g_list_free(mask_gui->creation_formids);
2301 mask_gui->creation_formids = NULL;
2302 mask_gui->creation_last_formid = 0;
2303 mask_gui->creation_type = DT_MASKS_NONE;
2304 mask_gui->creation_module = NULL;
2305
2306 if(!IS_NULL_PTR(creation_module))
2307 {
2309 if(last_formid > 0)
2310 {
2311 // Keep the shape manager selection in sync without letting its selection
2312 // handler replace the visible module group with the standalone shape.
2313 dt_dev_masks_selection_change(dev, creation_module, last_formid, FALSE);
2314 }
2315
2316 dt_iop_gui_blend_masks_update(creation_module);
2317 dt_iop_gui_blend_data_t *blend_data
2318 = creation_module->gui ? (dt_iop_gui_blend_data_t *)creation_module->gui->blend_data : NULL;
2319 if(!IS_NULL_PTR(dev) && !IS_NULL_PTR(dev->form_gui))
2321 if(!IS_NULL_PTR(blend_data) && GTK_IS_TOGGLE_BUTTON(blend_data->masks_edit))
2322 {
2323 // Creation mode keeps the edit button visually inactive while a shape
2324 // type button owns the interaction. Once creation is exited, restore both
2325 // the module edit state and the visible toggle explicitly.
2326 blend_data->masks_shown = DT_MASKS_EDIT_FULL;
2327 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), TRUE);
2328 gtk_widget_queue_draw(blend_data->masks_edit);
2329 }
2330
2331 if(last_formid > 0 && !IS_NULL_PTR(dev) && !IS_NULL_PTR(dev->form_gui))
2332 {
2333 dt_masks_form_gui_t *current_gui = dev->form_gui;
2334 dt_masks_form_t *visible_form = dt_masks_get_visible_form(dev);
2335 const int selected_index = dt_masks_group_index_from_formid(visible_form, last_formid);
2336 if(selected_index >= 0)
2337 {
2338 // The displayed overlay is the module group; select the last completed
2339 // form inside that group once creation is closed.
2340 current_gui->group_selected = selected_index;
2341 current_gui->form_selected = TRUE;
2342 // reset other variables
2343 current_gui->border_selected = FALSE;
2344 current_gui->source_selected = FALSE;
2345 current_gui->node_selected = FALSE;
2346 current_gui->handle_selected = FALSE;
2347 current_gui->seg_selected = FALSE;
2348 current_gui->handle_border_selected = FALSE;
2349 current_gui->node_selected_idx = -1;
2350 current_gui->form_dragging = FALSE;
2351 current_gui->source_dragging = FALSE;
2352 current_gui->form_rotating = FALSE;
2353 current_gui->pivot_selected = FALSE;
2354 }
2355 }
2356 }
2357 else if(last_formid > 0)
2358 {
2359 dt_masks_change_form_gui(dev, dt_masks_get_from_id(dev, last_formid));
2360 dt_dev_masks_selection_change(dev, NULL, last_formid, TRUE);
2361 if(!IS_NULL_PTR(dev) && !IS_NULL_PTR(dev->form_gui))
2362 {
2363 // A standalone visible form is rendered at index 0.
2364 dev->form_gui->group_selected = 0;
2365 dev->form_gui->form_selected = TRUE;
2366 }
2367 }
2368 else
2369 {
2370 dt_masks_change_form_gui(dev, NULL);
2371 }
2372
2373 return TRUE;
2374 }
2375 return FALSE;
2376}
2377
2378gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *mask_form,
2379 dt_masks_form_gui_t *mask_gui, const int parent_id)
2380{
2381 if(mask_gui->edit_mode != DT_MASKS_EDIT_FULL)
2382 return FALSE;
2383
2384 // Just clean temp mask if we are in creation mode
2385 if(dt_masks_form_exit_creation(module, mask_gui))
2386 return TRUE;
2387
2388 // we remove the selected node (and the entire form if there is too few nodes left)
2389 if(((mask_form->type & DT_MASKS_IS_PATH_SHAPE) != 0) && mask_gui->node_selected)
2390 {
2391 if(g_list_shorter_than(mask_form->points, 3))
2392 return _masks_remove_shape(module, mask_form, parent_id, mask_gui, mask_gui->group_selected);
2393
2394 dt_masks_remove_node(module, mask_form, parent_id, mask_gui, mask_gui->group_selected,
2395 mask_gui->node_hovered);
2396
2397 return TRUE;
2398 }
2399 // we remove the entire shape
2400 else if(parent_id > 0)
2401 {
2402 dt_masks_remove_or_delete(module, mask_form, parent_id, mask_gui, mask_form->formid);
2403 return TRUE; // something happened even if the dialog was cancelled.
2404 }
2405 return FALSE;
2406}
2407
2408static void _gui_points_bbox_extend(float *const bbox, const float *const samples, const int count)
2409{
2410 for(int i = 0; i < count; i++)
2411 {
2412 const float x = samples[2 * i];
2413 const float y = samples[2 * i + 1];
2414 bbox[0] = fminf(bbox[0], x);
2415 bbox[1] = fmaxf(bbox[1], x);
2416 bbox[2] = fminf(bbox[2], y);
2417 bbox[3] = fmaxf(bbox[3], y);
2418 }
2419}
2420
2422{
2423 if(IS_NULL_PTR(gp)) return;
2424 gp->bbox[0] = FLT_MAX;
2425 gp->bbox[1] = -FLT_MAX;
2426 gp->bbox[2] = FLT_MAX;
2427 gp->bbox[3] = -FLT_MAX;
2431}
2432
2433void dt_masks_gui_form_remove(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
2434{
2435 dt_masks_form_gui_points_t *gui_points
2436 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
2437 mask_gui->geometry_generation = 0;
2438 mask_gui->formid = 0;
2439
2440 if(!IS_NULL_PTR(gui_points))
2441 {
2442 gui_points->points_count = gui_points->border_count = gui_points->source_count = 0;
2443 gui_points->border_skip_count = 0;
2445 gui_points->points = NULL;
2447 gui_points->border = NULL;
2449 gui_points->border_skips = NULL;
2451 gui_points->source = NULL;
2452 dt_masks_gui_points_update_bbox(gui_points); /* empty */
2453 }
2454}
2455
2457 dt_iop_module_t *module)
2458{
2459 // we test if the geometry the cached outlines were built against has moved, or the density
2460 // the view shows them at
2461 const uint64_t live_generation = dt_geometry_chain_generation(mask_gui->dev->geometry_chain);
2462 const int live_step = dt_masks_gui_outline_step(mask_gui->dev);
2463 const gboolean stale = (mask_gui->geometry_generation != live_generation)
2464 || (mask_gui->outline_step_built != live_step);
2466 {
2467 const char *verdict = "reuse";
2468 if(mask_gui->geometry_generation == 0) verdict = "REBUILD (nothing cached)";
2469 else if(stale) verdict = "REBUILD (geometry or density moved)";
2470 dt_print(DT_DEBUG_MASKS, "[masks] outline cache: held for geometry %lu at step %d, live %lu at step %d -> %s\n",
2471 (unsigned long)mask_gui->geometry_generation, mask_gui->outline_step_built,
2472 (unsigned long)live_generation, live_step, verdict);
2473 }
2474
2475 if(mask_gui->geometry_generation != 0 && stale)
2476 {
2477 mask_gui->geometry_generation = 0;
2478 mask_gui->formid = 0;
2479 g_list_free_full(mask_gui->points, dt_masks_form_gui_points_free);
2480 mask_gui->points = NULL;
2481 }
2482
2483 // we create the form if needed
2484 if(mask_gui->geometry_generation == 0)
2485 {
2486 if(mask_form->type & DT_MASKS_GROUP)
2487 {
2488 int form_index = 0;
2489 for(GList *group_node = mask_form->points; group_node; group_node = g_list_next(group_node))
2490 {
2491 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
2492 dt_masks_form_t *child_form = dt_masks_get_from_id(mask_gui->dev, group_entry->formid);
2493 if(IS_NULL_PTR(child_form)) return;
2494 dt_masks_gui_form_create(child_form, mask_gui, form_index, module);
2495 form_index++;
2496 }
2497 }
2498 else
2499 {
2500 dt_masks_gui_form_create(mask_form, mask_gui, 0, module);
2501 }
2502 }
2503}
2504
2506{
2507 // dev->forms is its own claim on the object, independent of dev->allforms's claim
2508 // (taken at creation, dt_masks_create_ext): take a reference for this list membership.
2509 dt_masks_form_ref(mask_form);
2511 develop->forms = g_list_append(develop->forms, mask_form);
2513}
2514
2516{
2518 develop->forms = g_list_remove(develop->forms, mask_form);
2520 // Release dev->forms's claim (see dt_masks_append_form). Does not necessarily free the
2521 // object: dev->allforms and/or history snapshots may still reference it.
2522 dt_masks_form_unref(mask_form);
2523}
2524
2526 dt_masks_form_gui_t *mask_gui)
2527{
2528 // we check if the id is already registered
2529 _check_id(develop, mask_form);
2530
2531 // mask nb will be at least the length of the list
2532 guint form_count = 0;
2533
2534 // count only the same forms to have a clean numbering
2536 for(GList *form_node = develop->forms; form_node; form_node = g_list_next(form_node))
2537 {
2538 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
2539 if(existing_form->type == mask_form->type) form_count++;
2540 }
2542
2543 gboolean name_exists = FALSE;
2544
2545 // check that we do not have duplicate, in case some masks have been
2546 // removed we can have hole and so nb could already exists.
2547 do
2548 {
2549 name_exists = FALSE;
2550 form_count++;
2551
2552 if(mask_form->functions && mask_form->functions->set_form_name)
2553 mask_form->functions->set_form_name(mask_form, form_count);
2554
2556 for(GList *form_node = develop->forms; form_node; form_node = g_list_next(form_node))
2557 {
2558 dt_masks_form_t *existing_form = (dt_masks_form_t *)form_node->data;
2559 if(!strcmp(existing_form->name, mask_form->name))
2560 {
2561 name_exists = TRUE;
2562 break;
2563 }
2564 }
2566
2567 } while(name_exists);
2568
2569 dt_masks_form_update_gravity_center(develop, mask_form);
2570
2571 dt_masks_form_group_t *group_entry = NULL;
2572 if(!IS_NULL_PTR(module))
2573 {
2574 group_entry = malloc(sizeof(dt_masks_form_group_t));
2575 if(IS_NULL_PTR(group_entry)) return;
2576 }
2577
2578 dt_masks_append_form(develop, mask_form);
2579
2580 if(!IS_NULL_PTR(module))
2581 {
2582 // is there already a masks group for this module ?
2583 dt_masks_form_t *group_form = _group_from_module(develop, module);
2584 if(IS_NULL_PTR(group_form))
2585 {
2586 // we create a new group
2587 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
2588 group_form = _group_create(develop, module, DT_MASKS_GROUP | DT_MASKS_CLONE);
2589 else
2590 group_form = _group_create(develop, module, DT_MASKS_GROUP);
2591 }
2592 group_form = dt_masks_cow_touch(develop, group_form);
2593 // we add the form in this group
2594 group_entry->formid = mask_form->formid;
2595 group_entry->parentid = group_form->formid;
2597 group_entry->opacity = dt_conf_get_float("plugins/darkroom/masks/opacity");
2598 group_form->points = g_list_append(group_form->points, group_entry);
2599
2600 // we update module gui
2601
2602 if(IS_NULL_PTR(mask_gui)) dt_iop_gui_blend_masks_update(module);
2603 dt_dev_add_history_item(develop, module, TRUE, TRUE);
2604 }
2605
2606 if(!IS_NULL_PTR(mask_gui))
2607 {
2608 mask_gui->creation_formids = g_list_append(mask_gui->creation_formids, GINT_TO_POINTER(mask_form->formid));
2609 mask_gui->creation_last_formid = mask_form->formid;
2610
2611 if(!IS_NULL_PTR(module))
2612 {
2614 group_entry->parentid, DT_MASKS_EVENT_ADD);
2615 }
2616 else
2617 {
2620 }
2621
2622 // Keep creation mode active. The saved form remains in develop->forms for
2623 // session rendering, while the visible form becomes the next unfinished
2624 // shape so mouse events still target the creation preview.
2625 dt_masks_form_t *next_form = dt_masks_create(mask_gui->creation_type);
2626 if(IS_NULL_PTR(next_form))
2627 {
2628 dt_masks_form_exit_creation(module, mask_gui);
2629 return;
2630 }
2631
2632 g_list_free_full(mask_gui->points, dt_masks_form_gui_points_free);
2633 mask_gui->points = NULL;
2635 mask_gui->guipoints = NULL;
2637 mask_gui->guipoints_payload = NULL;
2638 mask_gui->guipoints_count = 0;
2639 mask_gui->geometry_generation = 0;
2640 mask_gui->formid = 0;
2641 mask_gui->creation_closing_form = FALSE;
2643 dt_masks_set_visible_form(develop, next_form);
2644 if(!IS_NULL_PTR(module)) dt_iop_gui_blend_masks_update(module);
2645 }
2646}
2647
2649{
2650 return 0;
2651}
2652
2654{
2655 return 0;
2656}
2657
2659{
2660 return mask_gui->form_selected
2661 || mask_gui->source_selected
2662 || mask_gui->seg_selected
2663 || mask_gui->node_selected
2664 || mask_gui->handle_selected
2665 || mask_gui->handle_border_selected;
2666}
2667
2669{
2670 return mask_gui->node_hovered >= 0
2671 || mask_gui->handle_hovered >= 0
2672 || mask_gui->handle_border_hovered >= 0
2673 || mask_gui->seg_hovered >= 0;
2674}
2675
2677{
2678 if(IS_NULL_PTR(mask_gui)) return;
2679
2680 if(mask_gui->creation)
2681 {
2682 dt_masks_form_t *creation_form = dt_masks_get_visible_form(mask_gui->dev);
2683 if(!IS_NULL_PTR(creation_form) && (creation_form->type & DT_MASKS_BRUSH))
2684 {
2685 // The brush tool draws its own filled size/fading preview circle at the cursor
2686 // position (_brush_events_post_expose) -- a system cursor on top of it is redundant.
2688 return;
2689 }
2690 }
2691
2692 // Any other case un-hides the cursor: it may have been hidden by a brush creation that
2693 // just ended or was switched away from.
2695
2696 // circular arrows
2697 if(mask_gui->pivot_selected)
2698 dt_control_queue_cursor(GDK_EXCHANGE);
2699 // pointing hand
2700 else if(mask_gui->creation_closing_form)
2701 dt_control_queue_cursor(GDK_HAND2);
2702 // precise-placement cursor while drawing a new shape -- distinct from darkroom's own
2703 // default "dot"/"crosshair" cursors (views/darkroom.c's _darkroom_set_default_cursor)
2704 else if(mask_gui->creation)
2706
2707 /*else if(gui->handle_dragging >= 0)
2708 dt_control_set_cursor(GDK_HAND1);*/
2709
2710 // crosshair
2711 else if(dt_masks_is_anything_selected(mask_gui) || dt_masks_is_anything_hovered(mask_gui))
2712 dt_control_queue_cursor(GDK_FLEUR);
2713}
2714
2715static void _apply_gui_button_pressed_state(dt_masks_form_gui_t *mask_gui, const int button,
2716 const uint32_t state,
2717 const gboolean shape_was_selected)
2718{
2719 if(IS_NULL_PTR(mask_gui) || mask_gui->creation) return;
2720 if(button != 1 && button != 3) return;
2721 /* The fine-grained selection is rebuilt for the right button too, so that every `_selected'
2722 * flag names what the cursor is on. Three of them -- form, border and source -- are written by
2723 * update_hover() and follow the cursor whatever the button; leaving the node/segment/handle
2724 * ones on the left button alone splits one state in two, and the context menu, which is the
2725 * one consumer reading both families at once, is where the split shows. */
2726 // Drag is only allowed when this click happens on a shape that was already selected.
2727 // We still rebuild the fine-grained selection from the current hover target first, so the
2728 // pressed node/handle/segment becomes the active drag target when dragging is allowed.
2729 const gboolean prev_node_selected = mask_gui->node_selected;
2730 const int prev_node_selected_idx = mask_gui->node_selected_idx;
2731 const gboolean prev_form_selected = mask_gui->form_selected;
2732 const gboolean prev_border_selected = mask_gui->border_selected;
2733 const gboolean prev_source_selected = mask_gui->source_selected;
2734
2735 mask_gui->node_selected = FALSE;
2736 mask_gui->handle_selected = FALSE;
2737 mask_gui->handle_border_selected = FALSE;
2738 mask_gui->seg_selected = FALSE;
2739 mask_gui->node_selected_idx = -1;
2740 mask_gui->form_selected = FALSE;
2741 mask_gui->border_selected = FALSE;
2742 mask_gui->source_selected = FALSE;
2743
2744 if(mask_gui->node_hovered >= 0)
2745 {
2746 mask_gui->node_selected = TRUE;
2747 mask_gui->node_selected_idx = mask_gui->node_hovered;
2748 }
2749 else if(mask_gui->handle_hovered >= 0)
2750 {
2751 if(prev_node_selected)
2752 {
2753 mask_gui->node_selected = TRUE;
2754 mask_gui->node_selected_idx = prev_node_selected_idx;
2755 }
2756 mask_gui->handle_selected = TRUE;
2757 }
2758 else if(mask_gui->handle_border_hovered >= 0)
2759 {
2760 if(prev_node_selected)
2761 {
2762 mask_gui->node_selected = TRUE;
2763 mask_gui->node_selected_idx = prev_node_selected_idx;
2764 }
2765 mask_gui->handle_border_selected = TRUE;
2766 }
2767 else if(mask_gui->seg_hovered >= 0)
2768 {
2769 mask_gui->seg_selected = TRUE;
2770 }
2771 else
2772 {
2773 mask_gui->form_selected = prev_form_selected;
2774 mask_gui->border_selected = prev_border_selected;
2775 mask_gui->source_selected = prev_source_selected;
2776 }
2777
2778 // Arming a drag is the left button's business alone -- a right click targets, it never drags.
2779 if(button != 1) return;
2780 if(mask_gui->form_rotating || mask_gui->border_toggling || mask_gui->gradient_toggling) return;
2782 if(!shape_was_selected) return;
2783
2784 dt_masks_gui_set_dragging(mask_gui);
2785}
2786
2796static void _dt_masks_events_set_current_pos(const double x, const double y, dt_masks_form_gui_t *mask_gui)
2797{
2798 if(IS_NULL_PTR(mask_gui)) return;
2799
2800 float point[2] = { (float)x, (float)y };
2802 mask_gui->rel_pos[0] = point[0];
2803 mask_gui->rel_pos[1] = point[1];
2804
2806 mask_gui->pos[0] = point[0];
2807 mask_gui->pos[1] = point[1];
2808
2809 mask_gui->raw_pos[0] = point[0];
2810 mask_gui->raw_pos[1] = point[1];
2811 dt_dev_coordinates_image_abs_to_raw_abs(mask_gui->dev, mask_gui->raw_pos, 1);
2812}
2813
2814static int _dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x,
2815 double y, double pressure, int which);
2816
2817/* How far the pointer moved since the motion before, in the view's coordinates: with the
2818 * rectangle the last overlay frame touched, it says which rectangle of the view a motion the
2819 * masks handled can have changed. */
2820static double _overlay_pointer[2] = { 0.0, 0.0 };
2821static double _overlay_pointer_delta[2] = { 0.0, 0.0 };
2823
2824int dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
2825{
2827 {
2830 }
2831 else
2832 {
2833 _overlay_pointer_delta[0] = 0.0;
2834 _overlay_pointer_delta[1] = 0.0;
2835 }
2836 _overlay_pointer[0] = x;
2837 _overlay_pointer[1] = y;
2839
2840 /* Timed because it is the other thing that scales with the number of shapes and is not part of
2841 * the expose: hit-testing walks every shape in the group and, for a brush, every point of it.
2842 * The #1158 logs show the darkroom redraw growing to 400 ms with only 1.5 ms of mask overlay in
2843 * it, so whatever grows is either here or in the expose's earlier stages -- both are now timed. */
2844 dt_times_t moved_start = { 0 };
2845 dt_get_times(&moved_start);
2846 const int moved_result = _dt_masks_events_mouse_moved(dev, module, x, y, pressure, which);
2847 if(dt_get_debug_flags() & DT_DEBUG_PERF) dt_show_times(&moved_start, "[masks] mouse moved");
2848 return moved_result;
2849}
2850
2851static int _dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
2852{
2853 // This assume that if this event is generated, the mouse is over the center window.
2854 // record mouse position even if there are no masks visible
2855 dt_masks_form_gui_t *mask_gui = dev->form_gui;
2857 if(IS_NULL_PTR(mask_gui)) return 0;
2858 _dt_masks_events_set_current_pos(x, y, mask_gui);
2859
2860 // do not process if no forms visible
2861 if(IS_NULL_PTR(mask_form)) return 0;
2862
2863 // add an option to allow skip mouse events while editing masks
2864 if(dev->darkroom_skip_mouse_events) return 0;
2865
2866 int result = 0;
2867 if((mask_form->type & DT_MASKS_GROUP) && _dt_masks_events_group_blocks_motion(mask_gui))
2868 {
2869 result = 1;
2870 }
2871 else
2872 {
2873 dt_masks_form_group_t *group_entry = NULL;
2874 int parent_id = 0;
2875 int form_index = 0;
2876 dt_masks_form_t *dispatch_form
2877 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2878 dispatch_form = dt_masks_cow_touch(dev, dispatch_form);
2879
2881 result = _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
2882
2883 if(!result && dispatch_form && dispatch_form->functions && dispatch_form->functions->mouse_moved)
2884 result = dispatch_form->functions->mouse_moved(module, x, y, pressure, which,
2885 dispatch_form, parent_id, mask_gui, form_index);
2886 }
2887
2888 if(!IS_NULL_PTR(mask_gui))
2889 {
2890 // Re-read the visible form. dt_masks_cow_touch() above may have cloned it, spliced the
2891 // clone into dev->forms in place of the original, re-pointed form_gui->form_visible at
2892 // the clone and dropped the original's last reference -- freeing it. mask_form was
2893 // captured BEFORE that call, so it can be dangling here; form_visible is the pointer the
2894 // COW maintains.
2895 //
2896 // Sentry 143237622: SIGSEGV inside g_list_length() walking the freed points list, via
2897 // _set_hinter_message() -> _polygon_set_hint_message(), on an ordinary darkroom
2898 // mouse-move over a polygon.
2899 mask_form = dt_masks_get_visible_form(dev);
2900 if(!IS_NULL_PTR(mask_form)) _set_hinter_message(mask_gui, mask_form);
2901 _set_cursor_shape(mask_gui);
2902 }
2903 return result;
2904}
2905
2906int dt_masks_events_button_released(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, int button,
2907 uint32_t state)
2908{
2909 // add an option to allow skip mouse events while editing masks
2910 if(dev->darkroom_skip_mouse_events) return 0;
2911
2913 dt_masks_form_gui_t *mask_gui = dev->form_gui;
2914 if(IS_NULL_PTR(mask_gui)) return 0;
2915 _dt_masks_events_set_current_pos(x, y, mask_gui);
2916 if(IS_NULL_PTR(mask_form)) return 0;
2917
2918 dt_masks_form_group_t *group_entry = NULL;
2919 int parent_id = 0;
2920 int form_index = 0;
2921 dt_masks_form_t *dispatch_form
2922 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2923 dispatch_form = dt_masks_cow_touch(dev, dispatch_form);
2924
2925 // dt_masks_cow_touch() above may have cloned the visible form, spliced the clone into
2926 // dev->forms in place of the original, re-pointed form_gui->form_visible at it and
2927 // dropped the original's last reference -- freeing it. mask_form was captured before
2928 // that call, so re-read what form_visible points at now rather than using a pointer
2929 // that may already be dangling. Same defect as Sentry 143237622 in mouse_moved.
2930 mask_form = dt_masks_get_visible_form(dev);
2931
2932 int result = 0;
2933 if(!IS_NULL_PTR(dispatch_form) && dispatch_form->functions && dispatch_form->functions->button_released)
2934 result = dispatch_form->functions->button_released(module, x, y, button,
2935 state, dispatch_form, parent_id, mask_gui, form_index);
2936
2937 if(_dt_masks_events_flush_rebuild_if_needed(module, dispatch_form, mask_gui, form_index, button))
2938 result = 1;
2939
2940 if(!IS_NULL_PTR(mask_form) && (mask_form->type & DT_MASKS_GROUP) && !IS_NULL_PTR(mask_gui))
2941 {
2942 const dt_masks_form_group_t *selected_group_entry
2943 = dt_masks_form_get_selected_group_live(mask_form, mask_gui);
2944 if(selected_group_entry)
2946 selected_group_entry->formid, FALSE);
2947 }
2948
2949 if(mask_gui && !mask_gui->creation && button == 1)
2951
2952 if(!IS_NULL_PTR(mask_gui))
2953 {
2954 _set_hinter_message(mask_gui, mask_form);
2955 _set_cursor_shape(mask_gui);
2956 }
2957
2958 return result;
2959}
2960
2961int dt_masks_events_button_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure,
2962 int button, int event_type, uint32_t state)
2963{
2964 // add an option to allow skip mouse events while editing masks
2965 if(dev->darkroom_skip_mouse_events) return 0;
2966
2968 dt_masks_form_gui_t *mask_gui = dev->form_gui;
2969 if(IS_NULL_PTR(mask_gui)) return 0;
2970
2971 _dt_masks_events_set_current_pos(x, y, mask_gui);
2972 if(IS_NULL_PTR(mask_form)) return 0;
2973 const gboolean prev_any_selected = dt_masks_is_anything_selected(mask_gui);
2974 const int prev_group_selected = mask_gui->group_selected;
2975
2976 /*DT_DEBUG_CONTROL_SIGNAL_RAISE(dt_control_signal_get_global(), DT_SIGNAL_MASK_SELECTION_CHANGED, NULL, NULL);*/
2977
2978 if(mask_form->type & DT_MASKS_GROUP)
2979 _dt_masks_events_group_update_selection(mask_form, mask_gui);
2980
2981 dt_masks_form_group_t *group_entry = NULL;
2982 int parent_id = 0;
2983 int form_index = 0;
2984 dt_masks_form_t *dispatch_form
2985 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
2986 dispatch_form = dt_masks_cow_touch(dev, dispatch_form);
2987
2988 // dt_masks_cow_touch() above may have cloned the visible form, spliced the clone into
2989 // dev->forms in place of the original, re-pointed form_gui->form_visible at it and
2990 // dropped the original's last reference -- freeing it. mask_form was captured before
2991 // that call, so re-read what form_visible points at now rather than using a pointer
2992 // that may already be dangling. Same defect as Sentry 143237622 in mouse_moved.
2993 mask_form = dt_masks_get_visible_form(dev);
2994 _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
2995
2996 gboolean return_val = FALSE;
2997 if(!IS_NULL_PTR(dispatch_form) && dispatch_form->functions && dispatch_form->functions->button_pressed)
2998 return_val = dispatch_form->functions->button_pressed(module, x, y, pressure,
2999 button, event_type, state,
3000 dispatch_form, parent_id, mask_gui, form_index);
3001 // Throw a selection change event.
3002 // `dispatch_form` can pass NULL in case of deselection.
3003 dt_masks_select_form(dev, module, dispatch_form);
3004
3005 const gboolean shape_was_selected = (mask_form->type & DT_MASKS_GROUP)
3006 ? (prev_group_selected >= 0 && prev_group_selected == form_index)
3007 : prev_any_selected;
3008 _apply_gui_button_pressed_state(mask_gui, button, state, shape_was_selected);
3009
3010 // Refresh hover/highlight state, the hint message and the cursor shape now that the click
3011 // has been dispatched: button_pressed above may have changed geometry (e.g. a new node in
3012 // creation mode) or the active selection, neither of which the pre-dispatch hover computed
3013 // at the top of this function accounts for. Without this, the display only catches up with
3014 // what the click just did on the next physical mouse move -- same class of staleness as
3015 // mouse_moved()/button_released(), which already refresh all three at their own tail.
3016 _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
3017 _set_hinter_message(mask_gui, mask_form);
3018 _set_cursor_shape(mask_gui);
3019
3020 if(button == 3 && !return_val)
3021 {
3022 // mouse is over a form or one of its handles/nodes
3023 if(!IS_NULL_PTR(dispatch_form) && (mask_gui->creation
3024 || dt_masks_is_anything_hovered(mask_gui)
3025 || dt_masks_is_anything_selected(mask_gui)))
3026 {
3027 GtkWidget *menu = dt_masks_create_menu(mask_gui, dispatch_form, group_entry,
3028 mask_gui->rel_pos[0], mask_gui->rel_pos[1]);
3029 if(!IS_NULL_PTR(menu))
3030 {
3031 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
3032 return_val = TRUE;
3033 }
3034 }
3035 }
3036
3037 return return_val;
3038}
3039
3040int dt_masks_events_key_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, GdkEventKey *event)
3041{
3043 if(IS_NULL_PTR(mask_form)) return 0;
3044 dt_masks_form_gui_t *mask_gui = dev->form_gui;
3045 if(IS_NULL_PTR(mask_gui)) return 0;
3046
3047 gboolean return_value = FALSE;
3048 if(mask_form->type & DT_MASKS_GROUP)
3049 {
3050 dt_masks_form_group_t *group_entry = NULL;
3051 int parent_id = 0;
3052 int form_index = 0;
3053 dt_masks_form_t *dispatch_form
3054 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
3055 dispatch_form = dt_masks_cow_touch(dev, dispatch_form);
3056
3057 // dt_masks_cow_touch() above may have cloned the visible form, spliced the clone into
3058 // dev->forms in place of the original, re-pointed form_gui->form_visible at it and
3059 // dropped the original's last reference -- freeing it. mask_form was captured before
3060 // that call, so re-read what form_visible points at now rather than using a pointer
3061 // that may already be dangling. Same defect as Sentry 143237622 in mouse_moved.
3062 mask_form = dt_masks_get_visible_form(dev);
3063 if(dispatch_form && dispatch_form->functions && dispatch_form->functions->key_pressed)
3064 return_value = dispatch_form->functions->key_pressed(module, event, dispatch_form,
3065 parent_id, mask_gui, form_index);
3066
3067 if(!return_value && mask_form->functions->key_pressed)
3068 {
3069 mask_form = dt_masks_cow_touch(dev, mask_form);
3070 return_value = mask_form->functions->key_pressed(module, event, mask_form, 0, mask_gui, 0);
3071 }
3072 }
3073 else if(mask_form->functions->key_pressed)
3074 {
3075 mask_form = dt_masks_cow_touch(dev, mask_form);
3076 return_value = mask_form->functions->key_pressed(module, event, mask_form, 0, mask_gui, 0);
3077 }
3078
3079 if(!return_value)
3080 {
3081 guint key = dt_keys_mainpad_alternatives(event->keyval);
3082 switch(key)
3083 {
3084 case GDK_KEY_Escape:
3085 {
3086 return_value = dt_masks_form_exit_creation(module, mask_gui);
3087 break;
3088 }
3089 case GDK_KEY_Delete:
3090 {
3091 if(mask_gui->group_selected >= 0)
3092 {
3093 // Delete shape from current group
3094 dt_masks_form_group_t *selected_group = dt_masks_form_get_selected_group(mask_form, mask_gui);
3095 if(IS_NULL_PTR(selected_group)) return 0;
3096 dt_masks_form_t *selected_form = dt_masks_get_from_id(dev, selected_group->formid);
3097 if(selected_form)
3098 return_value = dt_masks_gui_remove(module, selected_form, mask_gui, selected_group->parentid);
3099 break;
3100 }
3101 }
3102 }
3103 }
3104
3105 return return_value;
3106}
3107
3108/* One conf key per wheel row. The mapping is application-wide on purpose: which property the
3109 * wheel edits is a user habit, not a property of a shape or of the module the mask belongs to. */
3111 = { "plugins/darkroom/masks/scroll/plain",
3112 "plugins/darkroom/masks/scroll/shift",
3113 "plugins/darkroom/masks/scroll/primary",
3114 "plugins/darkroom/masks/scroll/primary_shift" };
3115
3116/* Written verbatim into the user's conf file, so these are a storage format: never translate
3117 * them, never reorder them against dt_masks_interaction_t, and keep them in sync with the enum
3118 * declared for these four keys in data/anselconfig.xml.in. */
3120 = { "none", "size", "fading", "opacity", "rotation" };
3121
3122/* Both enums declare only non-negative enumerators, so a compiler is free to give them an
3123 * unsigned underlying type -- clang does, and then `x < 0' is a tautology it rejects under
3124 * -Weverything -Werror. Casting to unsigned instead of dropping the lower bound keeps the
3125 * check honest either way: a negative value converts to a huge unsigned one and is caught by
3126 * the upper bound, whatever signedness the compiler picked. */
3139
3141{
3142 if((unsigned int)modifier >= DT_MASKS_SCROLL_MODIFIER_LAST) return;
3143 if((unsigned int)interaction >= DT_MASKS_INTERACTION_LAST)
3144 interaction = DT_MASKS_INTERACTION_UNDEF;
3145
3147}
3148
3150{
3151 switch(modifier)
3152 {
3154 return N_("Shift+Scroll");
3156 return N_("Ctrl+Scroll");
3158 return N_("Ctrl+Shift+Scroll");
3160 default:
3161 return N_("Scroll");
3162 }
3163}
3164
3166{
3167 switch(interaction)
3168 {
3170 return N_("Size");
3172 return N_("Fading");
3174 return N_("Opacity");
3176 return N_("Rotation");
3178 default:
3179 return N_("Nothing");
3180 }
3181}
3182
3184{
3185 switch(interaction)
3186 {
3188 return N_("Curvature");
3189 default:
3190 // Size, opacity and rotation mean the same thing to every shape, and "nothing" is not
3191 // a property.
3192 return NULL;
3193 }
3194}
3195
3197{
3198 const GdkModifierType state = (GdkModifierType)key_state;
3200
3201 // Most specific first: ctrl+shift also satisfies neither of the single-modifier tests, but
3202 // reading it last would leave the combination unreachable if that ever stopped being true.
3203 if(dt_modifier_is(state, GDK_SHIFT_MASK | DT_PRIMARY_MASK))
3206 modifier = DT_MASKS_SCROLL_PRIMARY;
3207 else if(dt_modifier_is(state, GDK_SHIFT_MASK))
3208 modifier = DT_MASKS_SCROLL_SHIFT;
3209 else if(dt_modifier_is(state, 0))
3210 modifier = DT_MASKS_SCROLL_PLAIN;
3211 else
3212 // A combination the mapping does not cover (alt+scroll, ...): not ours to interpret.
3214
3215 return dt_masks_scroll_mapping_get(modifier);
3216}
3217
3218int dt_masks_events_mouse_scrolled(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y,
3219 int scroll_up, uint32_t key_state, int scrolling_delta)
3220{
3221 // add an option to allow skip mouse events while editing masks
3222 if(dev->darkroom_skip_mouse_events) return 0;
3223
3225 dt_masks_form_gui_t *mask_gui = dev->form_gui;
3226 if(IS_NULL_PTR(mask_gui)) return 0;
3227
3228 _dt_masks_events_set_current_pos(x, y, mask_gui);
3229 if(IS_NULL_PTR(mask_form)) return 0;
3230
3231 int result = 0;
3232 const gboolean scroll_increases = dt_mask_scroll_increases(scroll_up);
3233
3234 // we want delta_y to be an absolute scrolling speed
3235 int scroll_flow = (scrolling_delta < 0) ? -scrolling_delta : scrolling_delta;
3236
3237 dt_masks_form_group_t *group_entry = NULL;
3238 int parent_id = 0;
3239 int form_index = 0;
3240 dt_masks_form_t *dispatch_form
3241 = _dt_masks_events_get_dispatch_form(mask_form, mask_gui, &group_entry, &parent_id, &form_index);
3242 dispatch_form = dt_masks_cow_touch(dev, dispatch_form);
3243
3244 // dt_masks_cow_touch() above may have cloned the visible form, spliced the clone into
3245 // dev->forms in place of the original, re-pointed form_gui->form_visible at it and
3246 // dropped the original's last reference -- freeing it. mask_form was captured before
3247 // that call, so re-read what form_visible points at now rather than using a pointer
3248 // that may already be dangling. Same defect as Sentry 143237622 in mouse_moved.
3249 mask_form = dt_masks_get_visible_form(dev);
3250
3251 if(!mask_gui->creation && !dt_masks_is_anything_selected(mask_gui))
3252 return 0;
3253
3254 _dt_masks_events_update_hover(dispatch_form, mask_gui, form_index);
3255
3256 if(!mask_gui->creation && !_dt_masks_events_cursor_over_form(dispatch_form, mask_gui, form_index))
3257 return 0;
3258
3259 // Resolved once, here: shapes act on a named property and never read key modifiers.
3260 const dt_masks_interaction_t interaction = dt_masks_scroll_get_interaction(key_state);
3261
3262 if(dispatch_form && dispatch_form->functions && dispatch_form->functions->mouse_scrolled
3263 && interaction != DT_MASKS_INTERACTION_UNDEF)
3264 result = dispatch_form->functions->mouse_scrolled(module, x, y,
3265 scroll_increases ? 1 : 0, scroll_flow,
3266 key_state, dispatch_form, parent_id, mask_gui, form_index,
3267 interaction);
3268
3269 if(!IS_NULL_PTR(mask_gui))
3270 {
3271 const gboolean hinted = _set_hinter_message(mask_gui, mask_form);
3273 "[masks] scroll: ret=%d hinted=%d form=%p type=%d gui=%p group_selected=%d flow=%d state=0x%x\n",
3274 result, hinted, (void *)mask_form, mask_form ? mask_form->type : -1, (void *)mask_gui,
3275 mask_gui->group_selected, scroll_flow, key_state);
3276 if(hinted)
3277 result = 1;
3278 }
3279 return result;
3280}
3281
3282gboolean dt_masks_node_is_cusp(const dt_masks_form_gui_points_t *gui_points, const int node_index)
3283{
3284 if(IS_NULL_PTR(gui_points) || IS_NULL_PTR(gui_points->points)) return FALSE;
3285 if(gui_points->points_count <= 0 || node_index < 0 || node_index >= gui_points->points_count) return FALSE;
3286
3287 const float *point_values = &gui_points->points[node_index * 6];
3288 return (point_values[0 + 2] == point_values[2 + 2]
3289 && point_values[1 + 2] == point_values[3 + 2]);
3290}
3291
3307static void _dt_masks_find_best_attachment_point(const float ray_1[2], const float ray_2[2],
3308 const float *points, const int points_count, const float zoom_scale,
3309 const int first_pt,
3310 const gboolean is_closed_shape,
3311 const float offset_factor,
3312 float result[2])
3313{
3314 // Fallback: no intersection found.
3315 result[0] = ray_1[0];
3316 result[1] = ray_1[1];
3317
3318 const int available_points = points_count - first_pt;
3319 if(available_points < 2) return;
3320
3321 const float ray2_x = ray_2[0];
3322 const float ray2_y = ray_2[1];
3323 const float ray_center_x = 0.5f * (ray_1[0] + ray_2[0]);
3324 const float ray_center_y = 0.5f * (ray_1[1] + ray_2[1]);
3325 const float dir_x = ray_1[0] - ray_2[0];
3326 const float dir_y = ray_1[1] - ray_2[1];
3327 float min_s = FLT_MAX;
3328 const float offset = DT_PIXEL_APPLY_DPI(12.0f * offset_factor) / zoom_scale;
3329 const float inv_dir_len = f_inv_sqrtf(dir_x * dir_x + dir_y * dir_y);
3330 const float ux = dir_x * inv_dir_len;
3331 const float uy = dir_y * inv_dir_len;
3332
3333
3334 const int segment_count = (available_points - 1) + ((is_closed_shape) ? 1 : 0);
3335 for(int seg = 0; seg < segment_count; seg++)
3336 {
3337 // Get the current segment [i, j], with wrap-around if the shape is closed.
3338 const int i = first_pt + seg;
3339 const int j = (i + 1 < points_count) ? (i + 1) : first_pt;
3340 const float x3 = points[i * 2];
3341 const float y3 = points[i * 2 + 1];
3342 const float x4 = points[j * 2];
3343 const float y4 = points[j * 2 + 1];
3344
3345 // Compute the intersection of the ray with the segment.
3346 const float dx = x4 - x3;
3347 const float dy = y4 - y3;
3348 const float det = dx * (-dir_y) + dy * dir_x;
3349 if(det > -1e-8f && det < 1e-8f) continue;
3350 const float inv_det = 1.0f / det;
3351
3352 const float segment_param = ((ray2_x - x3) * (-dir_y) + (ray2_y - y3) * dir_x) * inv_det;
3353 const float ray_param = ((x3 - ray2_x) * dy - (y3 - ray2_y) * dx) * inv_det;
3354 if(segment_param < 0.0f || segment_param > 1.0f || ray_param <= 0.0f || ray_param >= min_s) continue;
3355
3356 min_s = ray_param;
3357 const float ix = ray2_x + ray_param * dir_x;
3358 const float iy = ray2_y + ray_param * dir_y;
3359
3360 // Offset along the ray axis toward the ray segment center.
3361 const float to_center = (ray_center_x - ix) * ux + (ray_center_y - iy) * uy;
3362 const float side_sign = (to_center >= 0.0f) ? 1.0f : -1.0f;
3363 result[0] = ix + side_sign * offset * ux;
3364 result[1] = iy + side_sign * offset * uy;
3365 }
3366}
3367
3368void dt_masks_draw_source(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index,
3369 const int node_count, const float zoom_scale,
3370 struct dt_masks_gui_center_point_t *center_point,
3371 const shape_draw_function_t *draw_shape_func)
3372{
3373 if(IS_NULL_PTR(mask_gui)) return;
3374 dt_masks_form_gui_points_t *gui_points
3375 = (dt_masks_form_gui_points_t *)g_list_nth_data(mask_gui->points, form_index);
3376 if(IS_NULL_PTR(gui_points)) return;
3377
3378 if(!mask_gui->creation)
3379 {
3380 const float main[2] = { center_point->main.x, center_point->main.y };
3381 const float source[2] = { center_point->source.x, center_point->source.y };
3382 const gboolean is_open_shape = (mask_gui->type & DT_MASKS_IS_OPEN_SHAPE) != 0;
3383 const gboolean is_closed_shape = (mask_gui->type & DT_MASKS_IS_CLOSED_SHAPE) != 0;
3384
3385 int first_point_index = 2;
3386 if((mask_gui->type & DT_MASKS_ELLIPSE) != 0)
3387 first_point_index = 10;
3388 else if((mask_gui->type & DT_MASKS_IS_PATH_SHAPE) != 0)
3389 first_point_index = node_count * 3;
3390
3391 int attach_points_count = gui_points->points_count;
3392 int attach_source_count = gui_points->source_count;
3393 if((mask_gui->type & DT_MASKS_BRUSH) != 0)
3394 {
3395 attach_points_count /= 2;
3396 attach_source_count /= 2;
3397 }
3398
3399 float head[2] = { 0.0f, 0.0f };
3400 float tail[2] = { 0.0f, 0.0f };
3401
3402 // Find attachment point for the arrow's head with the main shape
3403 _dt_masks_find_best_attachment_point(main, source, gui_points->points, attach_points_count,
3404 zoom_scale, first_point_index, is_closed_shape, 1.f, head);
3405
3406 // Find attachment point for the arrow's base with the source shape
3407 _dt_masks_find_best_attachment_point(source, main, gui_points->source, attach_source_count,
3408 zoom_scale, first_point_index, is_closed_shape, 0.5f, tail);
3409
3410 const gboolean selected = (mask_gui->group_selected == form_index)
3411 && (mask_gui->source_selected || mask_gui->source_dragging);
3412 gboolean draw_tail = TRUE;
3413
3414 // Do not draw the arrow tail if the shape overlapes source,
3415 // Just draw the head pointing to the center of the source shape
3416 // and displace it at mid-distance between main and source center.
3417 // Open shapes always draw the tail since they have not really filled area.
3418 if(is_closed_shape)
3419 {
3420 // From more frequent to least frequent, to get out of loop earlier. Tail first, then source.
3421 const float pts[4] = { tail[0], tail[1], source[0], source[1] };
3422 gboolean overlap = (dt_masks_point_in_form_exact(pts, 2, gui_points->points, first_point_index,
3423 gui_points->points_count, NULL, 0) >= 0);
3424 // Skip the second containment test when overlap is already detected.
3425 if(!overlap)
3426 {
3427 const float origin_pt[2] = { main[0], main[1] };
3428 overlap = (dt_masks_point_in_form_exact(origin_pt, 1, gui_points->source, first_point_index,
3429 gui_points->source_count, NULL, 0) >= 0);
3430 }
3431
3432 // Update head position to be between main and source center point.
3433 if(overlap)
3434 {
3435 head[0] = 0.5f * (main[0] + source[0]);
3436 head[1] = 0.5f * (main[1] + source[1]);
3437 }
3438
3439 const float arrow_len_sq = sqf(tail[0] - head[0]) + sqf(tail[1] - head[1]);
3440 draw_tail = arrow_len_sq > 1e-6f && !overlap;
3441 }
3442
3443 // Calculate the angle, so the arrow head always points in the direction of the main shape's center.
3444 const float angle = is_open_shape ? atan2f(tail[1] - head[1], tail[0] - head[0])
3445 : atan2f(head[1] - main[1], head[0] - main[0]);
3446
3447 dt_draw_arrow(cr, zoom_scale, selected, draw_tail, DT_MASKS_DASH_ROUND, head, tail, angle);
3448
3449
3450
3451
3453 {
3454 // Debug: show the main and source gravity points, show head and tail points
3455 cairo_save(cr);
3456 cairo_arc(cr, main[0], main[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
3457 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1);
3458 cairo_fill(cr);
3459 cairo_arc(cr, source[0], source[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
3460 cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1);
3461 cairo_fill(cr);
3462
3463 cairo_arc(cr, head[0], head[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
3464 cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1);
3465 cairo_fill(cr);
3466 cairo_arc(cr, tail[0], tail[1], DT_PIXEL_APPLY_DPI(4.0f) / zoom_scale, 0, 2 * M_PI);
3467 cairo_fill(cr);
3468 cairo_restore(cr);
3469 }
3470 }
3471
3472 // draw the source shape
3473 // Trick to draw the current polygon shape lines while editing, but draw the complete shape in all other cases
3474 const int rendered_node_count = node_count + !mask_gui->creation;
3475 const gboolean shape_selected = (mask_gui->group_selected == form_index)
3476 && (mask_gui->form_selected || mask_gui->form_dragging);
3477
3478 dt_draw_source_shape(mask_gui->dev, cr, zoom_scale, shape_selected, gui_points->source, gui_points->source_count,
3479 rendered_node_count, draw_shape_func);
3480
3481}
3482
3483void dt_masks_draw_path_seg_by_seg(cairo_t *cr, dt_masks_form_gui_t *mask_gui, const int form_index,
3484 const float *points, const int points_count, const int node_count,
3485 const float zoom_scale, const gboolean round_ends)
3486{
3487 if(IS_NULL_PTR(cr) || IS_NULL_PTR(points) || IS_NULL_PTR(mask_gui)) return;
3488 if(node_count <= 0 || points_count <= node_count * 3 + 6) return;
3489
3490 const int total_coords = points_count * 2;
3491 if(total_coords <= (node_count * 6 + 1)) return;
3492
3493 const gboolean group_selected = (mask_gui->group_selected == form_index);
3494
3495 /* The last segment there is to draw. An OPEN path -- a brush, the only caller asking for round
3496 * ends, since only an open path has two true ends -- has one segment fewer than it has nodes; a
3497 * closed one also has the segment returning to node 0. A shape still being created has no
3498 * closing segment either: the last one is the one the cursor is dragging. */
3499 const int last_segment_index = (round_ends || mask_gui->creation) ? node_count - 2 : node_count - 1;
3500
3501 /* Round only the OUTWARD side of the two true ends of the WHOLE path (e.g. a brush stroke), not
3502 * every segment's own two ends -- that would also round every interior node joint, which is a
3503 * line JOIN, not a cap, and looks like a bump at each node instead of the intended smooth stroke
3504 * tip. cairo_line_cap is a property of the whole stroke() call, and each segment below is
3505 * stroked independently with CAIRO_LINE_CAP_BUTT (so that per-segment selection/dash state
3506 * works AND consecutive segments join flush, without a cap bump at every node), so there is no
3507 * single stroke covering just the two true ends to set a cap on.
3508 *
3509 * Instead: paint a full disc (radius = half the line width) at each true end FIRST, before any
3510 * segment is stroked. The first and last segments below then get their normal, unchanged
3511 * BUTT-capped stroke painted OVER these discs -- flush at the true end, extending only inward --
3512 * which exactly covers the inward half of each disc (same centerline, same width, same colour
3513 * passes) and leaves only the outward half showing: precisely what CAIRO_LINE_CAP_ROUND draws
3514 * for a real one-sided cap. Node positions come straight from the node header (points[i*6+2/+3]
3515 * is node i's own coordinate), not the tessellated tail, so this needs no walk of the outline
3516 * and no assumption about whether that tail wraps back to node 0. */
3517 if(round_ends)
3518 {
3519 const gboolean all_selected = group_selected
3521 && (mask_gui->form_selected || mask_gui->form_dragging);
3522
3523 const double start_x = points[2];
3524 const double start_y = points[3];
3525 const double end_x = points[(node_count - 1) * 6 + 2];
3526 const double end_y = points[(node_count - 1) * 6 + 3];
3527
3528 cairo_move_to(cr, start_x, start_y);
3529 cairo_line_to(cr, start_x, start_y);
3530 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
3531
3532 cairo_move_to(cr, end_x, end_y);
3533 cairo_line_to(cr, end_x, end_y);
3534 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
3535 }
3536
3537 int show_segment_index = 1;
3538 int current_segment_index = 0;
3539 cairo_move_to(cr, points[node_count * 6], points[node_count * 6 + 1]);
3540
3541 /* Emit at screen resolution, not at the resolution the outline was built with.
3542 *
3543 * The outline is sampled one point per RAW pixel along the path -- measured on #1158, a mean of
3544 * 89 150 points per shape and a maximum of 208 095, for a mean of 11.9 nodes -- while the view
3545 * it is drawn into is a fraction of that: at the zoom in those logs, roughly four outline points
3546 * land inside every device pixel. Issuing a cairo_line_to() for each of them costs 93 ms per
3547 * shape and draws a curve indistinguishable from the one below, which is 14.87 s of a 30.45 s
3548 * session spent on segments shorter than a pixel.
3549 *
3550 * Points nearer than half a device pixel to the last EMITTED one are dropped -- not skipped in
3551 * the walk: every point is still tested for a segment boundary below, and a boundary always
3552 * emits, so the strokes still start and end exactly where the nodes are. */
3553 const double min_step = dt_draw_min_emit_step(cr);
3554 const double min_step2 = min_step * min_step;
3555 double last_x = points[node_count * 6];
3556 double last_y = points[node_count * 6 + 1];
3557
3558 for(int point_index = node_count * 3; point_index < points_count; point_index++)
3559 {
3560 const int coord_index = point_index * 2;
3561 if((coord_index + 1) >= total_coords) break;
3562
3563 const double coord_x = points[coord_index];
3564 const double coord_y = points[coord_index + 1];
3565
3566 const double step_x = coord_x - last_x;
3567 const double step_y = coord_y - last_y;
3568 const gboolean far_enough = (step_x * step_x + step_y * step_y) >= min_step2;
3569 if(far_enough)
3570 {
3571 cairo_line_to(cr, coord_x, coord_y);
3572 last_x = coord_x;
3573 last_y = coord_y;
3574 }
3575
3576 const int segment_coord_index = show_segment_index * 6;
3577 if((segment_coord_index + 3) >= total_coords) continue;
3578
3579 const double segment_x = points[segment_coord_index + 2];
3580 const double segment_y = points[segment_coord_index + 3];
3581 if(coord_x == segment_x && coord_y == segment_y)
3582 {
3583 // a segment ends exactly on a node: emit it even when it was too near to the last one,
3584 // or the stroke would stop short of the handle it belongs to
3585 if(!far_enough)
3586 {
3587 cairo_line_to(cr, coord_x, coord_y);
3588 last_x = coord_x;
3589 last_y = coord_y;
3590 }
3591
3592 const gboolean seg_is_selected = group_selected
3594 == current_segment_index);
3595 const gboolean all_selected = group_selected
3597 && (mask_gui->form_selected || mask_gui->form_dragging);
3598
3599 if(mask_gui->creation && current_segment_index == node_count - 2)
3600 dt_draw_stroke_line(DT_MASKS_DASH_ROUND, FALSE, cr, all_selected, zoom_scale, CAIRO_LINE_CAP_ROUND);
3601 else
3602 dt_draw_stroke_line(DT_MASKS_NO_DASH, FALSE, cr, (seg_is_selected || all_selected), zoom_scale,
3603 CAIRO_LINE_CAP_BUTT);
3604
3605 show_segment_index = (show_segment_index + 1) % node_count;
3606 current_segment_index++;
3607
3608 /* Every segment has been stroked. What the array still holds past this node is not part of
3609 * the outline: for a brush it is the same centerline recorded a second time in the opposite
3610 * direction (the border wraps around the stroke, so the line under it is walked there and
3611 * back -- see _brush_get_pts_border()), and for a shape being created it is the segment that
3612 * will close it once it exists. Walking further only re-detects nodes in reverse order and
3613 * strokes them again. */
3614 if(current_segment_index > last_segment_index) break;
3615
3616 // dt_draw_stroke_line() consumed the path; the next one starts here
3617 cairo_move_to(cr, coord_x, coord_y);
3618 }
3619 }
3620
3621 /* Leave nothing behind. The walk above stops on a node boundary, so the points that follow it
3622 * are still an un-stroked path in `cr', and cairo keeps a path across calls: the next stroke
3623 * ANYWHERE picks it up and paints it in ITS own style -- this shape's own dashed border draws
3624 * the leftover dashed, another shape's hover highlight draws it highlighted, and with nothing
3625 * drawn after it at all it never appears. */
3626 cairo_new_path(cr);
3627}
3628
3637/* The creation session's own outline cache.
3638 *
3639 * GUI-thread state, like every other dt_masks_form_gui_t. It is static because the shapes it
3640 * describes outlive a single expose and nothing else owns them: `creation_formids' is a list of
3641 * ids, and the outlines built from them used to live in a STACK LOCAL that was initialised fresh
3642 * every expose, with its cache key forced to zero after each shape. That defeated the cache by
3643 * construction -- every shape drawn since creation mode was entered was re-derived and thrown away
3644 * on every frame.
3645 *
3646 * Measured on #1158, with the list length as the variable: 0 shapes 0.0000 s, 1 shape 0.1141 s,
3647 * 2 shapes 0.2431 s, 3 shapes 0.3189 s, 4 shapes 0.4444 s -- about 110 ms per shape per expose,
3648 * while the mask GROUP, whose outlines ARE cached, drew all of its shapes in 0.0020 s. Everything
3649 * else in that stage measured zero: the predicates, the preamble, the group push, the outline
3650 * refresh, the composite, the hit test.
3651 */
3653static gboolean _session_gui_inited = FALSE;
3655static int _session_gui_count = 0;
3656
3657/* The shapes already saved in this session, rendered.
3658 *
3659 * They do not change while a new stroke is being drawn -- that is what makes them "already saved"
3660 * -- so re-stroking them on every frame is redrawing a still image. Measured: 8.8 ms per shape per
3661 * frame with the outline cached and decimated, 15.9 ms of a 25.7 ms redraw, and it is rasterisation
3662 * rather than geometry (only the path is stroked for these; borders, nodes and handles all sit
3663 * behind `group_selected == index', which is never true here).
3664 *
3665 * A cairo pattern from cairo_pop_group() is that rendering, kept. Painting it costs one composite
3666 * whatever the shape count, and cairo owns the surface behind it so there is nothing to size or
3667 * free by hand.
3668 *
3669 * The key has to include the MATRIX: a group's pattern carries the transform in effect when it was
3670 * pushed, so painting it under a different one would put the shapes somewhere else. Panning or
3671 * zooming therefore re-renders, which is correct and is not a per-frame cost while drawing. */
3672static cairo_pattern_t *_session_pattern = NULL;
3674
3675/* The area those shapes actually occupy, in the same coordinates they are drawn in.
3676 *
3677 * Both halves of this cost the whole window otherwise: the group is sized to the CLIP at push
3678 * time, and painting its pattern fills the clip. Measured with no clip: a cache HIT still costs
3679 * 7.5 ms -- compositing a device-scaled full-frame ARGB surface -- and a miss 19.3 ms, together
3680 * 3.13 s of a 5.77 s redraw total. Shapes that cover a fifth of the frame should cost a fifth of
3681 * that, and clipping to their union is what says so to cairo. */
3682static gboolean _session_bbox_valid = FALSE;
3683static double _session_bbox[4] = { 0.0, 0.0, 0.0, 0.0 };
3685/* Did the outlines actually move?
3686 *
3687 * The rendering is keyed on the geometry GENERATION, which is deliberately a version and not a
3688 * content hash -- over-invalidating is the safe direction for the geometry service, whose consumers
3689 * mostly recompute something cheap. Here it is not cheap: committing a stroke advances the
3690 * generation, and re-rendering costs ~18 ms per shape in the session, 218 ms at twelve of them.
3691 * That is the whole of the tail.
3692 *
3693 * But a generation bump does not mean the outlines moved. This asks whether they did, from the
3694 * outlines themselves: the point count and the two endpoints of every shape. Anything that moves an
3695 * outline -- a crop, a rotation, a scale, a keystone -- shifts every coordinate in it, endpoints
3696 * included, and a shape edited into a different path changes its point count or its ends. What it
3697 * cannot see is a change that preserves both endpoints AND the exact point count while moving
3698 * points in between; no geometry module does that, and an edit that did would have committed a
3699 * different point count on the way.
3700 *
3701 * Cheap on purpose: three numbers per shape, against hashing 89 000 points each. */
3708
3710static int _session_sigs_count = 0;
3711
3712static void _session_sigs_reset(void)
3713{
3716}
3717
3720{
3721 int taken = 0;
3722 for(const GList *node = gui->points; node && taken < max; node = g_list_next(node))
3723 {
3724 const dt_masks_form_gui_points_t *const pts = (const dt_masks_form_gui_points_t *)node->data;
3725 if(IS_NULL_PTR(pts)) continue;
3726
3727 out[taken].points_count = pts->points_count;
3728 if(!IS_NULL_PTR(pts->points) && pts->points_count > 0)
3729 {
3730 out[taken].first[0] = pts->points[0];
3731 out[taken].first[1] = pts->points[1];
3732 out[taken].last[0] = pts->points[2 * (pts->points_count - 1)];
3733 out[taken].last[1] = pts->points[2 * (pts->points_count - 1) + 1];
3734 }
3735 else
3736 out[taken].first[0] = out[taken].first[1] = out[taken].last[0] = out[taken].last[1] = 0.0f;
3737 taken++;
3738 }
3739 return taken;
3740}
3741
3743static void _bbox_include_array(const float *const array, const int count, double bbox[4], gboolean *any)
3744{
3745 if(IS_NULL_PTR(array) || count <= 0) return;
3746 for(int i = 0; i < count; i++)
3747 {
3748 const double x = array[2 * i];
3749 const double y = array[2 * i + 1];
3750 if(!*any)
3751 {
3752 bbox[0] = x;
3753 bbox[2] = x;
3754 bbox[1] = y;
3755 bbox[3] = y;
3756 *any = TRUE;
3757 continue;
3758 }
3759 bbox[0] = fmin(bbox[0], x);
3760 bbox[1] = fmin(bbox[1], y);
3761 bbox[2] = fmax(bbox[2], x);
3762 bbox[3] = fmax(bbox[3], y);
3763 }
3764}
3765
3766/* The box of everything the session draws: the spines AND the borders. It held the spines
3767 * alone, and a brush's dashed border lies a radius outside its spine, further than the clip's
3768 * margin; the pattern was clipped to it on a cache hit after a pan or zoom. */
3769static gboolean _session_bbox_from_points(const dt_masks_form_gui_t *gui, double bbox[4])
3770{
3771 gboolean any = FALSE;
3772 for(const GList *node = gui->points; node; node = g_list_next(node))
3773 {
3774 const dt_masks_form_gui_points_t *const pts = (const dt_masks_form_gui_points_t *)node->data;
3775 if(IS_NULL_PTR(pts)) continue;
3776 _bbox_include_array(pts->points, pts->points_count, bbox, &any);
3777 _bbox_include_array(pts->border, pts->border_count, bbox, &any);
3778 }
3779 return any;
3780}
3781
3783static gboolean _session_clip(cairo_t *cr, const float zoom_scale)
3784{
3785 if(!_session_bbox_valid) return FALSE;
3786
3787 const double margin = (zoom_scale > 1e-6f) ? (16.0 / zoom_scale) : 0.0;
3788 cairo_save(cr);
3789 cairo_rectangle(cr, _session_bbox[0] - margin, _session_bbox[1] - margin,
3790 (_session_bbox[2] - _session_bbox[0]) + 2.0 * margin,
3791 (_session_bbox[3] - _session_bbox[1]) + 2.0 * margin);
3792 cairo_clip(cr);
3793 cairo_new_path(cr);
3794 return TRUE;
3795}
3796
3798static void _session_pattern_reset(void)
3799{
3800 if(!IS_NULL_PTR(_session_pattern)) cairo_pattern_destroy(_session_pattern);
3801 _session_pattern = NULL;
3803}
3804
3806{
3808}
3809
3811static uint64_t _session_pattern_hash(cairo_t *cr, const dt_masks_form_gui_t *creation_gui,
3812 const uint64_t generation, const int count)
3813{
3814 cairo_matrix_t matrix;
3815 cairo_get_matrix(cr, &matrix);
3816
3817 /* No generation here: whether the geometry MOVED is asked of the outlines themselves
3818 * (_session_sigs_take), because the generation advances on every history commit -- including the
3819 * one that saves the stroke being drawn -- without shifting any of these shapes. What remains in
3820 * the key is what genuinely describes this rendering: where it was drawn, and which shapes. */
3821 (void)generation;
3822 uint64_t hash = 5381;
3823 hash = dt_hash(hash, (const char *)&matrix, sizeof(matrix));
3824 hash = dt_hash(hash, (const char *)&count, sizeof(count));
3825 for(const GList *node = creation_gui->creation_formids; node; node = g_list_next(node))
3826 {
3827 const int formid = GPOINTER_TO_INT(node->data);
3828 hash = dt_hash(hash, (const char *)&formid, sizeof(formid));
3829 }
3830 return hash ? hash : 1;
3831}
3832
3833static void _session_gui_reset(void)
3834{
3838 if(!_session_gui_inited) return;
3840 _session_gui.points = NULL;
3843}
3844
3846 cairo_t *cr, const float zoom_scale,
3847 const dt_masks_form_gui_t *creation_gui)
3848{
3849 /* Nothing to draw and nothing to forget.
3850 *
3851 * `creation' goes false between strokes -- a shape is committed, then the next one begins -- and
3852 * resetting on that threw the session's rendering away every time: 46 of 78 cache misses in one
3853 * log had no reason to report beyond the pattern having been destroyed, at ~21 ms each. The
3854 * shapes it describes are still there, and will be drawn again the moment creation resumes.
3855 *
3856 * What ends the session is the LIST emptying, which is the only thing reset on here now. */
3857 if(IS_NULL_PTR(creation_gui->creation_formids))
3858 {
3860 return;
3861 }
3862
3863 const int count = g_list_length(creation_gui->creation_formids);
3864
3865 if(!creation_gui->creation)
3866 {
3867 // Nothing to draw between strokes. Said out loud so a log reader does not count it as a cache
3868 // miss: the outer timer around this call fires either way.
3870 dt_print(DT_DEBUG_MASKS, "[masks] creation session idle (%d shapes kept)\n", count);
3871 return;
3872 }
3873
3874 const uint64_t live_generation = dt_geometry_chain_generation(develop->geometry_chain);
3875
3877 {
3882 }
3883 _session_gui.dev = develop;
3884 _session_gui.edit_mode = creation_gui->edit_mode;
3886
3887 /* Rebuild only when the composed geometry moved, the view's density changed or the session
3888 * gained a shape -- the same rule the mask group's outlines follow. A shape's own content
3889 * changing commits history, which rebuilds the chain, which advances the generation. */
3890 const gboolean rebuild = (_session_gui_generation != live_generation) || (_session_gui_count != count)
3892 if(rebuild)
3893 {
3895 _session_gui.points = NULL;
3896 }
3897
3898 /* The outlines are current from here on. Ask whether they actually MOVED before deciding the
3899 * rendering is stale: the generation advances when a stroke is committed, which does not shift a
3900 * single point of the shapes already saved. */
3901 gboolean outlines_moved = TRUE;
3902 if(rebuild)
3903 {
3904 _session_outline_sig_t *const sigs
3906 const int taken = IS_NULL_PTR(sigs) ? 0 : _session_sigs_take(&_session_gui, sigs, count);
3907
3908 outlines_moved = IS_NULL_PTR(_session_sigs) || taken != _session_sigs_count
3909 || IS_NULL_PTR(sigs)
3910 || memcmp(sigs, _session_sigs, sizeof(_session_outline_sig_t) * taken) != 0;
3911
3912 if(!IS_NULL_PTR(sigs))
3913 {
3915 _session_sigs = sigs;
3916 _session_sigs_count = taken;
3917 }
3918 }
3919 else
3920 outlines_moved = FALSE;
3921
3922 /* Nothing about these shapes changed: paint the rendering we already have. */
3923 const uint64_t pattern_key = _session_pattern_hash(cr, creation_gui, live_generation, count);
3924
3925 if((dt_get_debug_flags() & DT_DEBUG_PERF) && (outlines_moved || pattern_key != _session_pattern_key))
3926 {
3927 cairo_matrix_t matrix;
3928 cairo_get_matrix(cr, &matrix);
3930 "[masks] session re-render: rebuild=%d moved=%d generation %lu->%lu count %d->%d"
3931 " matrix (%.4f %.4f %.4f %.4f %.2f %.2f)\n",
3932 rebuild, outlines_moved, (unsigned long)_session_gui_generation,
3933 (unsigned long)live_generation, _session_gui_count, count, matrix.xx, matrix.yx,
3934 matrix.xy, matrix.yy, matrix.x0, matrix.y0);
3935 }
3936 if(!outlines_moved && !IS_NULL_PTR(_session_pattern) && pattern_key == _session_pattern_key)
3937 {
3938 const gboolean clipped = _session_clip(cr, zoom_scale);
3939 cairo_set_source(cr, _session_pattern);
3940 cairo_paint(cr);
3941 if(clipped) cairo_restore(cr);
3943 dt_print(DT_DEBUG_MASKS, "[masks] creation session (%d shapes) painted from cache\n", count);
3944 return;
3945 }
3946
3948
3949 /* Bound the group to what the previous render occupied -- but ONLY when the shapes being drawn
3950 * into it are the same ones that bbox was measured from (rebuild == FALSE: nothing but the
3951 * matrix moved). On a rebuild the bbox is stale by construction -- it describes the PREVIOUS
3952 * shape set, recomputed from the fresh one only AFTER this group is painted -- so clipping to it
3953 * here would cut off exactly the part of a newly added or changed shape that falls outside what
3954 * the old shapes occupied. That clipped result is what gets cached as `_session_pattern`, so the
3955 * shape stays silently truncated on every following "painted from cache" replay, not just this
3956 * one frame. A brush/polygon stroke reaching outside the old bbox is the visible case: part or
3957 * all of its outline never gets recorded into the group in the first place. */
3958 const gboolean clipped = !rebuild && _session_clip(cr, zoom_scale);
3959 cairo_push_group(cr);
3960
3961 // Iterate over the ids saved in this creation session. Other masks stay
3962 // hidden while creation is active, even if they belong to the same module.
3963 int index = 0;
3964 for(GList *formid_node = creation_gui->creation_formids; formid_node; formid_node = g_list_next(formid_node))
3965 {
3966 const int formid = GPOINTER_TO_INT(formid_node->data);
3967 dt_masks_form_t *session_form = dt_masks_get_from_id(develop, formid);
3968 if(IS_NULL_PTR(session_form)) continue;
3969
3970 /* Built at `index', not at 0: one slot per shape, so they can all be kept. The old loop wrote
3971 * every shape into slot 0 and freed it again, which is why none of them could be cached. */
3972 const double shape_start = dt_get_wtime();
3973 if(rebuild) dt_masks_gui_form_create(session_form, &_session_gui, index, module);
3974 const double built = dt_get_wtime();
3975
3976 if(session_form->functions && session_form->functions->post_expose)
3977 {
3978 const guint point_count = g_list_length(session_form->points);
3979 _session_gui.type = session_form->type;
3980 session_form->functions->post_expose(cr, zoom_scale, &_session_gui, index, point_count);
3981 }
3982
3983 /* Which half costs: rebuilding an outline, or stroking it. The session is 15.9 ms of a 25.7 ms
3984 * redraw, about 5 ms per shape, while the shape being drawn -- same stroker, cached outline --
3985 * costs 1.4 ms. Either the cache is not holding here or the draw is genuinely dearer, and
3986 * those want opposite fixes. */
3988 dt_print(DT_DEBUG_MASKS, "[masks] session shape %d: %s %0.04f sec, drawn %0.04f sec\n", index,
3989 rebuild ? "rebuilt in" : "cached,", built - shape_start, dt_get_wtime() - built);
3990
3991 // Keep the saved-session shape path from being connected to the active
3992 // creation cursor drawn right after this loop.
3993 cairo_new_path(cr);
3994 index++;
3995 }
3996
3997 /* Keep the rendering, and paint it -- this frame included, so the first frame after a change
3998 * costs the same as it did before and every later one costs a composite. */
3999 _session_pattern = cairo_pop_group(cr);
4000 _session_pattern_key = pattern_key;
4001 cairo_set_source(cr, _session_pattern);
4002 cairo_paint(cr);
4003 if(clipped) cairo_restore(cr);
4004
4005 /* The bbox is a function of the OUTLINES, so it is recomputed when they are and not when the
4006 * matrix moved under them -- walking every point of every shape is not free (measured: doing it
4007 * on every render took the miss path from 19.3 ms to 25.1 ms, more than the clip saved). */
4009
4010 if(rebuild)
4011 {
4012 _session_gui_generation = live_generation;
4013 _session_gui_count = count;
4014 }
4015}
4016
4017void dt_masks_events_post_expose(dt_develop_t *dev, struct dt_iop_module_t *module, cairo_t *cr,
4018 int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
4019{
4020 dt_masks_events_post_expose_with(dev, module, cr, width, height, pointerx, pointery, NULL);
4021}
4022/* ---------------------------------------------------------------------------------------------
4023 * THE CANVAS.
4024 *
4025 * The overlay is painted into an image surface of its own, in device pixels, and composited
4026 * onto the view once. That is what cairo_push_group() did too, with two costs this avoids:
4027 * the group is sized to the clip -- the whole view -- and both its pixels and its composite
4028 * are paid over that whole area every frame whether one small shape is being dragged or not;
4029 * and a group is cairo's, so nothing but cairo can paint into it. The canvas is ours: the
4030 * stroke rasteriser writes its pixels directly (see widgets/stroke_raster.c), cairo still
4031 * draws the nodes, handles and arrows into the same surface, and the composite covers only
4032 * the rectangle that was painted, which the rasteriser reports and the node header bounds.
4033 *
4034 * It persists across frames -- an ARGB32 surface of the view's size is not something to
4035 * allocate per frame; that is what the group had replaced an explicit surface for -- and is
4036 * cleared after each composite over the same rectangle, so a frame starts from transparency
4037 * without touching the rest. GUI thread only, like everything else in this file. */
4038static cairo_surface_t *_canvas = NULL;
4039static int _canvas_width = 0;
4040static int _canvas_height = 0;
4041
4042static cairo_surface_t *_canvas_acquire(const int width, const int height, const double device_scale)
4043{
4045 {
4046 cairo_surface_destroy(_canvas);
4047 _canvas = NULL;
4048 }
4049 if(IS_NULL_PTR(_canvas))
4050 {
4051 _canvas = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
4052 if(cairo_surface_status(_canvas) != CAIRO_STATUS_SUCCESS)
4053 {
4054 cairo_surface_destroy(_canvas);
4055 _canvas = NULL;
4056 return NULL;
4057 }
4060 }
4061 cairo_surface_set_device_scale(_canvas, device_scale, device_scale);
4062 return _canvas;
4063}
4064
4065/* Clear @p rect of the canvas back to transparent, by hand: a handful of rows of a surface we
4066 * own, where cairo's CLEAR would walk the same pixels with more ceremony. */
4067static void _canvas_clear(cairo_surface_t *canvas, const cairo_rectangle_int_t *rect)
4068{
4069 cairo_surface_flush(canvas);
4070 uint8_t *const data = cairo_image_surface_get_data(canvas);
4071 const int stride = cairo_image_surface_get_stride(canvas);
4072 const int x0 = MAX(rect->x, 0);
4073 const int y0 = MAX(rect->y, 0);
4074 const int x1 = MIN(rect->x + rect->width, cairo_image_surface_get_width(canvas));
4075 const int y1 = MIN(rect->y + rect->height, cairo_image_surface_get_height(canvas));
4076 if(x1 <= x0 || y1 <= y0) return;
4077 for(int y = y0; y < y1; y++) memset(data + (size_t)y * stride + (size_t)x0 * 4, 0, (size_t)(x1 - x0) * 4);
4078 cairo_surface_mark_dirty_rectangle(canvas, x0, y0, x1 - x0, y1 - y0);
4079}
4080
4081/* Grow @p rect to hold the canvas pixel (x, y), with @p margin around it. */
4082static void _rect_include(cairo_rectangle_int_t *rect, gboolean *any, const double x, const double y, const int margin)
4083{
4084 const int x0 = (int)floor(x) - margin;
4085 const int y0 = (int)floor(y) - margin;
4086 const int x1 = (int)ceil(x) + margin + 1;
4087 const int y1 = (int)ceil(y) + margin + 1;
4088 if(!*any)
4089 {
4090 rect->x = x0;
4091 rect->y = y0;
4092 rect->width = x1 - x0;
4093 rect->height = y1 - y0;
4094 *any = TRUE;
4095 return;
4096 }
4097 const int rx1 = MAX(rect->x + rect->width, x1);
4098 const int ry1 = MAX(rect->y + rect->height, y1);
4099 rect->x = MIN(rect->x, x0);
4100 rect->y = MIN(rect->y, y0);
4101 rect->width = rx1 - rect->x;
4102 rect->height = ry1 - rect->y;
4103}
4104
4105/* Where cairo may paint this frame -- the nodes, their handles, the clone arrow -- as a
4106 * rectangle the canvas context is CLIPPED to before anything is drawn, so that what cairo
4107 * paints is bounded by construction rather than by an estimate made afterwards. What escapes
4108 * a too-small rectangle is clipped, which is seen at once; what escaped an estimate was
4109 * neither composited nor cleared, and stayed in the canvas to reappear in a later frame's
4110 * rectangle, possibly after a pan -- a ghost, which is seen much later and understood never.
4111 *
4112 * The bound: every header point of every outline -- node and both control points, three per
4113 * node, plus the border's own header, where the border handle sits, and the source's --
4114 * grown by the longest control vector, because a curvature handle is that vector rotated a
4115 * quarter turn about its node (same length, any direction), by a node's disc and by an arrow
4116 * head. A buffer too short to have a header (a circle's, a source's) contributes its first
4117 * points. The outlines themselves the rasteriser reports exactly, on top of this. */
4118/* A frame of the canvas: where it sits in cr's device space, and the context that draws
4119 * into it in cr's coordinates. */
4120typedef struct _canvas_frame_t
4121{
4122 cairo_surface_t *canvas;
4123 cairo_t *cr; /* draws into the canvas, in the caller's coordinates */
4124 int x; /* the canvas's origin in the target's device pixels */
4125 int y;
4128 double device_scale; /* the target's, copied to the canvas */
4130
4131/* Does this buffer carry a node header at all: three points per node, ahead of the samples. */
4132static inline gboolean header_reach_wanted(const int nodes, const int points_count)
4133{
4134 return nodes > 0 && points_count >= nodes * 3;
4135}
4136
4137/* The device scale of the surface @p cr draws on: pixel = device unit * scale. */
4138static double _canvas_device_scale(cairo_t *cr)
4139{
4140 double scale_x = 1.0;
4141 double scale_y = 1.0;
4142 cairo_surface_get_device_scale(cairo_get_target(cr), &scale_x, &scale_y);
4143 return (scale_x > 0.0) ? scale_x : 1.0;
4144}
4145
4146/* Include the first @p limit points of @p array, taken through @p canvas_cr's matrix to canvas
4147 * pixels, in @p rect. */
4148static void _bound_include_points(cairo_t *canvas_cr, const float *const array, const int limit,
4149 const double scale, cairo_rectangle_int_t *rect, gboolean *any)
4150{
4151 for(int i = 0; i < limit; i++)
4152 {
4153 double x = array[2 * i];
4154 double y = array[2 * i + 1];
4155 cairo_user_to_device(canvas_cr, &x, &y);
4156 _rect_include(rect, any, x * scale, y * scale, 0); /* device units to canvas pixels */
4157 }
4158}
4159
4160/* Include the node header of each of an outline's three buffers -- or, for a buffer with no
4161 * header, its first samples -- in @p rect. */
4162static void _bound_include_headers(cairo_t *canvas_cr, const dt_masks_form_gui_points_t *const pts, const int nodes,
4163 const double scale, cairo_rectangle_int_t *rect, gboolean *any)
4164{
4165 const float *const arrays[3] = { pts->points, pts->border, pts->source };
4166 const int counts[3] = { pts->points_count, pts->border_count, pts->source_count };
4167 for(int a = 0; a < 3; a++)
4168 {
4169 if(IS_NULL_PTR(arrays[a]) || counts[a] <= 0) continue;
4170 const int header = MIN(nodes * 3, counts[a]);
4171 const int limit = (header > 0) ? header : MIN(counts[a], 64);
4172 _bound_include_points(canvas_cr, arrays[a], limit, scale, rect, any);
4173 }
4174}
4175
4176/* The longest control vector of an outline's node header, in canvas pixels: how far from a
4177 * node its curvature handle can be drawn. */
4178static double _bound_header_reach(cairo_t *canvas_cr, const dt_masks_form_gui_points_t *const pts, const int nodes,
4179 const double scale)
4180{
4181 if(IS_NULL_PTR(pts->points) || !header_reach_wanted(nodes, pts->points_count)) return 0.0;
4182 double reach = 0.0;
4183 for(int k = 0; k < nodes; k++)
4184 {
4185 double nx = pts->points[k * 6 + 2];
4186 double ny = pts->points[k * 6 + 3];
4187 cairo_user_to_device(canvas_cr, &nx, &ny);
4188 for(int c = 0; c < 2; c++)
4189 {
4190 double cx = pts->points[k * 6 + (c ? 4 : 0)];
4191 double cy = pts->points[k * 6 + (c ? 5 : 1)];
4192 cairo_user_to_device(canvas_cr, &cx, &cy);
4193 reach = MAX(reach, hypot(cx - nx, cy - ny) * scale);
4194 }
4195 }
4196 return reach;
4197}
4198
4199/* The node count of the outline at @p index of the visible form: a group's member's own, a
4200 * single form's. The header of an outline is three points per node. */
4201static int _outline_nodes(dt_develop_t *dev, const dt_masks_form_t *form, const int index)
4202{
4203 if(IS_NULL_PTR(form)) return 0;
4204 if(!(form->type & DT_MASKS_GROUP)) return (int)g_list_length(form->points);
4205 const dt_masks_form_group_t *entry = (const dt_masks_form_group_t *)g_list_nth_data(form->points, index);
4206 if(IS_NULL_PTR(entry)) return 0;
4207 const dt_masks_form_t *member = dt_masks_get_from_id(dev, entry->formid);
4208 return IS_NULL_PTR(member) ? 0 : (int)g_list_length(member->points);
4209}
4210
4211/* Cairo draws nodes, handles and arrows for the selected member of a group and for a single
4212 * form, and for no other member: the other members are their rasterised paths and nothing
4213 * else. Bounding every member's header made a spread-out group's dirty rectangle the whole
4214 * window on every frame, and with it the composite, the clear and the rectangle a motion asks
4215 * a redraw of. */
4216static void _canvas_cairo_bound(cairo_t *canvas_cr, const dt_masks_form_gui_t *gui, cairo_rectangle_int_t *rect,
4217 gboolean *any)
4218{
4219 const dt_masks_form_t *form = dt_masks_get_visible_form(gui->dev);
4220 const gboolean group = !IS_NULL_PTR(form) && (form->type & DT_MASKS_GROUP);
4221 const double scale = _canvas_device_scale(canvas_cr);
4222 double reach = 0.0;
4223 int index = 0;
4224 for(const GList *node = gui->points; node; node = g_list_next(node), index++)
4225 {
4226 if(group && index != gui->group_selected) continue;
4227 const dt_masks_form_gui_points_t *const pts = (const dt_masks_form_gui_points_t *)node->data;
4228 if(IS_NULL_PTR(pts)) continue;
4229 const int nodes = _outline_nodes(gui->dev, form, index);
4230 _bound_include_headers(canvas_cr, pts, nodes, scale, rect, any);
4231 reach = MAX(reach, _bound_header_reach(canvas_cr, pts, nodes, scale));
4232 }
4233 if(!*any) return;
4234 const int margin = (int)ceil(reach + 2.0 * DT_DRAW_RADIUS_NODE_SELECTED + DT_DRAW_SCALE_ARROW) + 2;
4235 rect->x -= margin;
4236 rect->y -= margin;
4237 rect->width += 2 * margin;
4238 rect->height += 2 * margin;
4239}
4240
4241/* Grow @p rect to the session's box, taken through the canvas context to pixels, with the
4242 * margin the session clip uses. */
4243static void _session_bound_include(cairo_t *canvas_cr, const float zoom_scale, cairo_rectangle_int_t *rect,
4244 gboolean *any)
4245{
4246 if(!_session_bbox_valid) return;
4247 const double scale = _canvas_device_scale(canvas_cr);
4248 const double margin = (zoom_scale > 1e-6f) ? (16.0 / zoom_scale) : 0.0;
4249 const double xs[2] = { _session_bbox[0] - margin, _session_bbox[2] + margin };
4250 const double ys[2] = { _session_bbox[1] - margin, _session_bbox[3] + margin };
4251 for(int c = 0; c < 4; c++)
4252 {
4253 double x = xs[c & 1];
4254 double y = ys[c >> 1];
4255 cairo_user_to_device(canvas_cr, &x, &y);
4256 _rect_include(rect, any, x * scale, y * scale, 2);
4257 }
4258}
4259
4260/* Bound what cairo may paint on top of the outlines and clip the canvas context to it.
4261 *
4262 * A creation session is bounded but not clipped: the pattern of the saved strokes is rebuilt
4263 * inside a group cairo sizes to the clip, and a group cut to a stale box truncated a new stroke
4264 * for the rest of the session. Its bound is the session's box and the live shape's, which is
4265 * what the composite and the clear then span. Before, a session took @p bound as the caller
4266 * set it, the whole canvas: a full-window composite and a full-window memset on every frame
4267 * drawn, 11.6 ms of a frame at a 2560x1440 window on a 2x screen. Returns whether @p bound
4268 * holds a rectangle at all. */
4269static gboolean _overlay_clip_to_bound(cairo_t *mask_draw, const dt_masks_form_gui_t *gui,
4270 const _canvas_frame_t *const frame, cairo_rectangle_int_t *bound,
4271 const float zoom_scale)
4272{
4273 if(gui->creation || !IS_NULL_PTR(gui->creation_formids))
4274 {
4275 cairo_rectangle_int_t session = { 0 };
4276 gboolean session_bounded = FALSE;
4277 _canvas_cairo_bound(mask_draw, gui, &session, &session_bounded);
4278 _session_bound_include(mask_draw, zoom_scale, &session, &session_bounded);
4279 if(session_bounded) *bound = session;
4280 return TRUE;
4281 }
4282 gboolean bounded = FALSE;
4283 _canvas_cairo_bound(mask_draw, gui, bound, &bounded);
4284 if(!bounded) return FALSE;
4285 const double s = frame->device_scale;
4286 cairo_save(mask_draw);
4287 cairo_identity_matrix(mask_draw);
4288 cairo_rectangle(mask_draw, bound->x / s, bound->y / s, bound->width / s, bound->height / s);
4289 cairo_restore(mask_draw);
4290 cairo_clip(mask_draw);
4291 return TRUE;
4292}
4293
4294/* Open a frame covering cr's clip. Returns FALSE with nothing to draw into when the clip is
4295 * empty or the canvas cannot be had. */
4296static gboolean _canvas_begin(cairo_t *cr, const int width, const int height, _canvas_frame_t *frame)
4297{
4298 /* The frame is the view, not cr's clip. A redraw asked for a rectangle narrows the clip to
4299 * what moved, and a canvas sized to the clip would be reallocated on every such frame and
4300 * hold nothing beyond it. The canvas covers the view and stays; what reaches the target is
4301 * the composite, and cairo clips that. */
4302 double device_x0 = 0.0;
4303 double device_y0 = 0.0;
4304 double device_x1 = width;
4305 double device_y1 = height;
4306 cairo_user_to_device(cr, &device_x0, &device_y0);
4307 cairo_user_to_device(cr, &device_x1, &device_y1);
4308
4309 /* cairo's device space stops short of the pixel grid: a pixel is device * device_scale +
4310 * device_offset, the scale being what a HiDPI widget carries. The frame is in pixels. */
4311 double device_scale_x = 1.0;
4312 double device_scale_y = 1.0;
4313 double device_offset_x = 0.0;
4314 double device_offset_y = 0.0;
4315 cairo_surface_get_device_scale(cairo_get_target(cr), &device_scale_x, &device_scale_y);
4316 cairo_surface_get_device_offset(cairo_get_target(cr), &device_offset_x, &device_offset_y);
4317 frame->device_scale = (device_scale_x > 0.0) ? device_scale_x : 1.0;
4318 const double px0 = MIN(device_x0, device_x1) * frame->device_scale + device_offset_x;
4319 const double px1 = MAX(device_x0, device_x1) * frame->device_scale + device_offset_x;
4320 const double py0 = MIN(device_y0, device_y1) * frame->device_scale + device_offset_y;
4321 const double py1 = MAX(device_y0, device_y1) * frame->device_scale + device_offset_y;
4322 frame->x = (int)floor(px0);
4323 frame->y = (int)floor(py0);
4324 frame->width = (int)ceil(px1) - frame->x;
4325 frame->height = (int)ceil(py1) - frame->y;
4326 if(frame->width <= 0 || frame->height <= 0) return FALSE;
4327
4328 frame->canvas = _canvas_acquire(frame->width, frame->height, frame->device_scale);
4329 if(IS_NULL_PTR(frame->canvas)) return FALSE;
4331 frame->cr = cairo_create(frame->canvas);
4332
4333 /* The canvas draws in cr's coordinates, shifted so that its pixel (0, 0) is the frame's
4334 * origin: cr's matrix, then a device-space translation. The canvas has the target's device
4335 * scale, so a pixel shift is that many device units, and the target's own device offset is
4336 * taken back out. */
4337 cairo_matrix_t matrix;
4338 cairo_matrix_t shift;
4339 cairo_matrix_t canvas_matrix;
4340 cairo_get_matrix(cr, &matrix);
4341 cairo_matrix_init_translate(&shift, (device_offset_x - frame->x) / frame->device_scale,
4342 (device_offset_y - frame->y) / frame->device_scale);
4343 cairo_matrix_multiply(&canvas_matrix, &matrix, &shift);
4344 cairo_set_matrix(frame->cr, &canvas_matrix);
4345 return TRUE;
4346}
4347
4348/* Composite the pixels [x0, x1) x [y0, y1) of a view-sized @p surface onto @p cr, one pixel
4349 * to one device pixel. With the identity matrix a user unit is one device unit, and the target
4350 * maps those to pixels through its device scale and offset; the surface, carrying the same
4351 * device scale, then lands pixel on pixel. cr's clip bounds it: a redraw asked for a rectangle
4352 * pays for that rectangle. */
4353static void _canvas_composite(cairo_t *cr, const _canvas_frame_t *frame, cairo_surface_t *surface, const int x0,
4354 const int y0, const int x1, const int y1)
4355{
4356 const double s = frame->device_scale;
4357 double offset_x = 0.0;
4358 double offset_y = 0.0;
4359 cairo_surface_get_device_offset(cairo_get_target(cr), &offset_x, &offset_y);
4360 cairo_surface_flush(surface);
4361 cairo_save(cr);
4362 cairo_identity_matrix(cr);
4363 cairo_set_source_surface(cr, surface, (frame->x - offset_x) / s, (frame->y - offset_y) / s);
4364 cairo_rectangle(cr, (frame->x + x0 - offset_x) / s, (frame->y + y0 - offset_y) / s, (x1 - x0) / s,
4365 (y1 - y0) / s);
4366 cairo_fill(cr);
4367 cairo_restore(cr);
4368}
4369
4370/* Composite @p dirty of the frame's canvas onto @p cr, then clear it so the next frame starts
4371 * from transparency there. */
4372static void _canvas_end(cairo_t *cr, const _canvas_frame_t *frame, const cairo_rectangle_int_t *dirty)
4373{
4374 const int x0 = CLAMP(dirty->x, 0, frame->width);
4375 const int y0 = CLAMP(dirty->y, 0, frame->height);
4376 const int x1 = CLAMP(dirty->x + dirty->width, 0, frame->width);
4377 const int y1 = CLAMP(dirty->y + dirty->height, 0, frame->height);
4378 if(x1 <= x0 || y1 <= y0) return;
4379 _canvas_composite(cr, frame, frame->canvas, x0, y0, x1, y1);
4380 const cairo_rectangle_int_t painted = { x0, y0, x1 - x0, y1 - y0 };
4381 _canvas_clear(frame->canvas, &painted);
4382}
4383
4384/* ---------------------------------------------------------------------------------------------
4385 * The static layer.
4386 *
4387 * A pipe frame redraws the whole centre, and the overlay stroked every member of the visible
4388 * group on it: 1 to 8 ms a shape at fit zoom, 50 to 100 ms of GUI thread for a 26-shape edit,
4389 * on every pipe frame -- and a drag makes a pipe frame per motion. The members that are not the
4390 * selected one do not change between those frames: they are stroked once into this view-sized
4391 * surface and composited under the live canvas with one blit, clipped to what the redraw asked
4392 * for, until the key changes. The key is the view matrix, the group, the selection, and a
4393 * signature of every other member's outline -- its counts and every 32nd sample -- so a member
4394 * an undo moved is caught, while the selected member, which a drag rebuilds on every motion,
4395 * is not in it at all. A pan, a zoom or a change of selection pays one full stroke of the
4396 * others, which is what every frame paid before. */
4397static cairo_surface_t *_static_layer = NULL;
4398static int _static_layer_width = 0;
4400static uint64_t _static_layer_key = 0; /* 0: holds nothing */
4401
4402static uint64_t _static_layer_hash_samples(uint64_t hash, const float *const array, const int count)
4403{
4404 if(IS_NULL_PTR(array) || count <= 0) return hash;
4405 hash = dt_hash(hash, (const char *)&count, sizeof(count));
4406 for(int i = 0; i < count; i += 32) hash = dt_hash(hash, (const char *)&array[2 * i], 2 * sizeof(float));
4407 hash = dt_hash(hash, (const char *)&array[2 * (count - 1)], 2 * sizeof(float));
4408 return hash;
4409}
4410
4411static uint64_t _static_layer_key_compute(cairo_t *canvas_cr, const float zoom_scale, const dt_masks_form_t *form,
4412 const dt_masks_form_gui_t *gui)
4413{
4414 cairo_matrix_t matrix;
4415 cairo_get_matrix(canvas_cr, &matrix);
4417 uint64_t hash = 5381;
4418 hash = dt_hash(hash, (const char *)&matrix, sizeof(matrix));
4419 hash = dt_hash(hash, (const char *)&zoom_scale, sizeof(zoom_scale));
4420 hash = dt_hash(hash, (const char *)&form->formid, sizeof(form->formid));
4421 hash = dt_hash(hash, (const char *)&gui->group_selected, sizeof(gui->group_selected));
4422 hash = dt_hash(hash, (const char *)overlay, sizeof(*overlay));
4423 int index = 0;
4424 for(const GList *node = gui->points; node; node = g_list_next(node), index++)
4425 {
4426 if(index == gui->group_selected) continue;
4427 const dt_masks_form_gui_points_t *const pts = (const dt_masks_form_gui_points_t *)node->data;
4428 if(IS_NULL_PTR(pts)) continue;
4429 hash = dt_hash(hash, (const char *)&index, sizeof(index));
4430 hash = _static_layer_hash_samples(hash, pts->points, pts->points_count);
4431 hash = _static_layer_hash_samples(hash, pts->border, pts->border_count);
4432 hash = dt_hash(hash, (const char *)&pts->border_skip_count, sizeof(int));
4433 }
4434 return hash ? hash : 1;
4435}
4436
4437/* Bring the layer up to date with the members other than the selected one, stroking them only
4438 * when the key moved; @p rebuilt says whether it did, in which case the whole frame is dirty:
4439 * a member that left the selection lost its handles in the layer, and wherever a redraw asked
4440 * for less than the window the rest is stale until the next, full one, which the dirty
4441 * rectangle then asks for. Returns FALSE when there is no layer to composite. */
4442static gboolean _static_layer_ensure(const _canvas_frame_t *frame, cairo_t *canvas_cr, const float zoom_scale,
4443 dt_masks_form_t *form, dt_masks_form_gui_t *gui, gboolean *rebuilt)
4444{
4445 *rebuilt = FALSE;
4446 const uint64_t key = _static_layer_key_compute(canvas_cr, zoom_scale, form, gui);
4447 const gboolean same_size = (_static_layer_width == frame->width && _static_layer_height == frame->height);
4448 if(!IS_NULL_PTR(_static_layer) && same_size && _static_layer_key == key) return TRUE;
4449 *rebuilt = TRUE;
4450
4451 if(IS_NULL_PTR(_static_layer) || !same_size)
4452 {
4453 if(!IS_NULL_PTR(_static_layer)) cairo_surface_destroy(_static_layer);
4454 _static_layer = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, frame->width, frame->height);
4455 if(cairo_surface_status(_static_layer) != CAIRO_STATUS_SUCCESS)
4456 {
4457 cairo_surface_destroy(_static_layer);
4458 _static_layer = NULL;
4460 return FALSE;
4461 }
4462 cairo_surface_set_device_scale(_static_layer, frame->device_scale, frame->device_scale);
4463 _static_layer_width = frame->width;
4465 }
4466 else
4467 {
4468 const cairo_rectangle_int_t whole = { 0, 0, frame->width, frame->height };
4470 }
4471
4472 /* the same mapping as the canvas, so the two land pixel on pixel */
4473 cairo_matrix_t matrix;
4474 cairo_get_matrix(canvas_cr, &matrix);
4475 cairo_t *layer_cr = cairo_create(_static_layer);
4476 cairo_set_matrix(layer_cr, &matrix);
4477 dt_group_events_post_expose_except(layer_cr, zoom_scale, form, gui, gui->group_selected);
4478 cairo_destroy(layer_cr);
4480 return TRUE;
4481}
4482
4483/* A group being edited, outside a creation session, has a layer; anything else is drawn whole. */
4484static gboolean _static_layer_applies(const dt_masks_form_t *form, const dt_masks_form_gui_t *gui)
4485{
4486 return !IS_NULL_PTR(form) && (form->type & DT_MASKS_GROUP) && !gui->creation && IS_NULL_PTR(gui->creation_formids);
4487}
4488
4489/* The skip range that hides border sample @p i of @p pts, or -1. */
4490static int _overlay_skip_of(const dt_masks_form_gui_points_t *const pts, const int i)
4491{
4492 for(int k = 0; k < pts->border_skip_count; k++)
4493 if(i >= pts->border_skips[k].jump_from && i < pts->border_skips[k].resume_at) return k;
4494 return -1;
4495}
4496
4497/* One cached outline as the harness dumps it: index, border, the skip range hiding it, spine. */
4498static void _overlay_dump_outline(const dt_masks_form_gui_points_t *const pts, const char *path)
4499{
4500 FILE *f = g_fopen(path, "w");
4501 if(IS_NULL_PTR(f)) return;
4502 const int count = MIN(pts->points_count, pts->border_count);
4503 for(int i = 0; i < count; i++)
4504 fprintf(f, "%d %.2f %.2f %d %.2f %.2f\n", i, pts->border[2 * i], pts->border[2 * i + 1], _overlay_skip_of(pts, i),
4505 pts->points[2 * i], pts->points[2 * i + 1]);
4506 fclose(f);
4507}
4508
4509/* MASKS_DUMP_OVERLAY=<dir>: write what this frame drew -- the canvas before it is composited
4510 * and cleared, and every outline in the GUI cache with its skip ranges -- so a darkroom that
4511 * shows something the harness does not can be read from the files it leaves behind. Overwritten
4512 * on every frame: the last one is the one on screen. */
4513static void _overlay_dump(const _canvas_frame_t *const frame, const dt_masks_form_gui_t *const gui,
4514 const cairo_rectangle_int_t *const dirty)
4515{
4516 const char *dir = g_getenv("MASKS_DUMP_OVERLAY");
4517 if(IS_NULL_PTR(dir)) return;
4518 char *path = g_strdup_printf("%s/overlay-canvas.png", dir);
4519 cairo_surface_flush(frame->canvas);
4520 cairo_surface_write_to_png(frame->canvas, path);
4521 g_free(path);
4522 path = g_strdup_printf("%s/overlay-info.txt", dir);
4523 FILE *info = g_fopen(path, "w");
4524 g_free(path);
4525 if(!IS_NULL_PTR(info))
4526 {
4527 fprintf(info, "canvas %d %d %dx%d device_scale %.3f dirty %d %d %dx%d\n", frame->x, frame->y, frame->width,
4528 frame->height, frame->device_scale, dirty->x, dirty->y, dirty->width, dirty->height);
4529 fclose(info);
4530 }
4531 int index = 0;
4532 for(const GList *node = gui->points; node; node = g_list_next(node), index++)
4533 {
4534 const dt_masks_form_gui_points_t *const pts = (const dt_masks_form_gui_points_t *)node->data;
4535 if(IS_NULL_PTR(pts) || IS_NULL_PTR(pts->border) || IS_NULL_PTR(pts->points)) continue;
4536 path = g_strdup_printf("%s/outline-%d.txt", dir, index);
4537 _overlay_dump_outline(pts, path);
4538 g_free(path);
4539 }
4540}
4541
4542/* What the last overlay frame composited onto the view, in the view's coordinates, and how far
4543 * the pointer moved since the motion before (see _overlay_pointer_delta): together they say
4544 * which rectangle of the view a motion the masks handled can have changed. Every such motion
4545 * used to invalidate the whole centre widget, which repainted the whole image under an overlay
4546 * that had moved by a few pixels. */
4547static cairo_rectangle_int_t _overlay_damage = { 0, 0, 0, 0 };
4549
4551{
4552 if(IS_NULL_PTR(widget)) return;
4554 {
4555 gtk_widget_queue_draw(widget);
4556 return;
4557 }
4558 /* the last frame's rectangle, grown by the pointer's motion: a dragged shape moves with the
4559 * pointer, a hovered one does not move at all. Where a frame lands outside this,
4560 * _overlay_damage_record() asks for the rest. */
4561 const int grow_x = (int)ceil(fabs(_overlay_pointer_delta[0])) + 4;
4562 const int grow_y = (int)ceil(fabs(_overlay_pointer_delta[1])) + 4;
4563 gtk_widget_queue_draw_area(widget, _overlay_damage.x - grow_x, _overlay_damage.y - grow_y,
4564 _overlay_damage.width + 2 * grow_x, _overlay_damage.height + 2 * grow_y);
4565}
4566
4567/* Record the rectangle the frame composited, in the view's coordinates, and ask for whatever
4568 * of it the clip of this expose did not cover: the invalidation was an estimate made before the
4569 * frame was drawn, and a frame that outgrew it is painted in full by the next, small one. */
4570static void _overlay_damage_record(cairo_t *cr, const _canvas_frame_t *const frame, const cairo_rectangle_int_t *dirty)
4571{
4572 if(IS_NULL_PTR(dirty))
4573 {
4575 return;
4576 }
4577 const double s = frame->device_scale;
4578 const cairo_rectangle_int_t damage = { (int)floor(dirty->x / s), (int)floor(dirty->y / s),
4579 (int)ceil((dirty->x + dirty->width) / s) - (int)floor(dirty->x / s),
4580 (int)ceil((dirty->y + dirty->height) / s) - (int)floor(dirty->y / s) };
4581 _overlay_damage = damage;
4583
4584 double clip_x0 = 0.0;
4585 double clip_y0 = 0.0;
4586 double clip_x1 = 0.0;
4587 double clip_y1 = 0.0;
4588 cairo_clip_extents(cr, &clip_x0, &clip_y0, &clip_x1, &clip_y1);
4589 const gboolean covered = damage.x >= floor(clip_x0) && damage.y >= floor(clip_y0)
4590 && damage.x + damage.width <= ceil(clip_x1) && damage.y + damage.height <= ceil(clip_y1);
4591 if(!covered)
4592 gtk_widget_queue_draw_area(dt_gui_center_widget(), damage.x, damage.y, damage.width, damage.height);
4593}
4594
4595/* Map the canvas context to input space: from the viewport, or from the caller's own mapping
4596 * when it supplied one (see dt_masks_overlay_transform_t: the viewport path needs GUI state).
4597 * Returns FALSE when the viewport cannot be mapped, in which case nothing can be drawn. */
4598static gboolean _overlay_apply_transform(cairo_t *mask_draw, dt_develop_t *develop,
4600 const int height)
4601{
4602 if(IS_NULL_PTR(transform)) return dt_dev_rescale_roi_to_input(develop, mask_draw, width, height) == 0;
4603 cairo_translate(mask_draw, transform->offset_x, transform->offset_y);
4604 cairo_scale(mask_draw, transform->scale, transform->scale);
4605 return TRUE;
4606}
4607
4608/* Composite what the frame painted and remember it; a headless caller has no widget to ask a
4609 * redraw of, so only the darkroom's own frames are recorded. */
4610static void _overlay_finish(cairo_t *cr, const _canvas_frame_t *const frame, const cairo_rectangle_int_t *dirty,
4611 const gboolean darkroom_frame, const gboolean layered)
4612{
4613 /* the static members first, under the live shape; cr's clip keeps it to what was asked */
4614 if(layered && !IS_NULL_PTR(_static_layer)) _canvas_composite(cr, frame, _static_layer, 0, 0, frame->width, frame->height);
4615 if(!IS_NULL_PTR(dirty)) _canvas_end(cr, frame, dirty);
4616 if(darkroom_frame) _overlay_damage_record(cr, frame, dirty);
4617}
4618
4619/* Draw the visible form: a group member by member -- the selected member alone when the static
4620 * layer holds the others -- anything else through its own drawer. */
4621static void _overlay_draw_form(cairo_t *cr, const float zoom_scale, dt_masks_form_t *mask_form,
4622 dt_masks_form_gui_t *mask_gui, const gboolean layered)
4623{
4624 if(mask_form->type & DT_MASKS_GROUP)
4625 {
4626 if(layered)
4627 dt_group_events_post_expose_only(cr, zoom_scale, mask_form, mask_gui, mask_gui->group_selected);
4628 else
4629 dt_group_events_post_expose(cr, zoom_scale, mask_form, mask_gui);
4630 return;
4631 }
4632 if(mask_form->functions && mask_form->functions->post_expose)
4633 {
4634 const guint point_count = g_list_length(mask_form->points);
4635 mask_gui->type = mask_form->type;
4636 mask_form->functions->post_expose(cr, zoom_scale, mask_gui, 0, point_count);
4637 }
4638}
4639
4640/* What was painted this frame: the rasteriser's own record, and the bounds of what cairo drew
4641 * on top. The transform must still be in effect on @p canvas_cr, which is what the header
4642 * bounds need; a creation session paints through its own cached pattern and takes the whole
4643 * canvas. Returns FALSE when nothing was painted. */
4644static gboolean _overlay_dirty(const _canvas_frame_t *frame, const cairo_rectangle_int_t *cairo_bound,
4645 const gboolean cairo_bounded, cairo_rectangle_int_t *dirty)
4646{
4647 gboolean any = dt_stroke_raster_touched(frame->canvas, dirty);
4648 if(!cairo_bounded) return any;
4649 if(!any)
4650 {
4651 *dirty = *cairo_bound;
4652 return TRUE;
4653 }
4654 const int x1 = MAX(dirty->x + dirty->width, cairo_bound->x + cairo_bound->width);
4655 const int y1 = MAX(dirty->y + dirty->height, cairo_bound->y + cairo_bound->height);
4656 dirty->x = MIN(dirty->x, cairo_bound->x);
4657 dirty->y = MIN(dirty->y, cairo_bound->y);
4658 dirty->width = x1 - dirty->x;
4659 dirty->height = y1 - dirty->y;
4660 return TRUE;
4661}
4662
4664 int32_t width, int32_t height, int32_t pointerx, int32_t pointery,
4666{
4667 const double post_expose_start = dt_get_wtime();
4668 dt_develop_t *develop = dev;
4669 if(IS_NULL_PTR(develop)) return;
4670 dt_masks_form_t *mask_form = dt_masks_get_visible_form(develop);
4671 dt_masks_form_gui_t *mask_gui = develop->form_gui;
4672 if(IS_NULL_PTR(mask_gui)) return;
4673 if(IS_NULL_PTR(mask_form)) return;
4674
4675 int buffer_width = 0;
4676 int buffer_height = 0;
4677 dt_dev_get_processed_size(develop, &buffer_width, &buffer_height);
4678
4679 if(buffer_width < 1.0 || buffer_height < 1.0) return;
4680 const float zoom_scale = IS_NULL_PTR(transform) ? dt_dev_get_zoom_level(develop) : (float)transform->scale;
4681
4682 /* Draw into an isolated group, so that overlapping shapes composite once against the view
4683 * instead of once each.
4684 *
4685 * This used to allocate a full-window ARGB surface with cairo_surface_create_similar() on EVERY
4686 * expose and destroy it again at the end -- at ppd 2 that is a four-byte-per-pixel buffer of
4687 * four times the window's area, created, cleared, composited and freed per frame, and on an
4688 * X11 or GL target it is a server-side pixmap each time. Measured on #1158: the expose stage
4689 * that contains it costs 0.44 s while the mask drawing inside it costs 0.0014 s, and it grows
4690 * across a session -- 11 ms, 137, 221, 310 -- which is what repeated large allocation and
4691 * release looks like.
4692 *
4693 * cairo_push_group() is the same isolation with none of that: cairo sizes the group to the
4694 * current CLIP rather than the window, and takes it from its own scratch pool instead of
4695 * allocating. Every path out of here must pop what it pushed. */
4696 const double group_start = dt_get_wtime();
4698 dt_print(DT_DEBUG_MASKS, "[masks] overlay preamble took %0.04f sec\n", group_start - post_expose_start);
4699
4700 _canvas_frame_t frame = { 0 };
4701 if(!_canvas_begin(cr, width, height, &frame)) return;
4702 cairo_t *const mask_draw = frame.cr;
4704 dt_print(DT_DEBUG_MASKS, "[masks] overlay canvas %dx%d ready in %0.04f sec\n", frame.width, frame.height,
4705 dt_get_wtime() - group_start);
4706
4707 cairo_save(mask_draw);
4708
4709 if(!_overlay_apply_transform(mask_draw, develop, transform, width, height))
4710 {
4711 cairo_restore(mask_draw);
4712 cairo_destroy(mask_draw); // nothing was drawn
4713 return;
4714 }
4715 /* The density the outlines are built at is what this context shows: the transform is on,
4716 * so one device pixel spans this many image pixels. Read before the cache is tested, since
4717 * a change is a rebuild. */
4719
4720 // We update the form if needed
4721 // Add preview when creating a circle, ellipse and gradient
4722 const dt_times_t rebuild_start = { 0 };
4723 dt_get_times((dt_times_t *)&rebuild_start);
4724
4725 if(!((mask_form->type & DT_MASKS_IS_PRIMITIVE_SHAPE) && mask_gui->creation))
4726 dt_masks_gui_form_test_create(mask_form, mask_gui, module);
4727
4728 /* The rectangle cairo may paint in, and the clip that holds it to that. */
4729 cairo_rectangle_int_t cairo_bound = { 0, 0, frame.width, frame.height };
4730 const gboolean cairo_bounded = _overlay_clip_to_bound(mask_draw, mask_gui, &frame, &cairo_bound, zoom_scale);
4731
4733 dt_show_times(&rebuild_start, "[masks] overlay outline refresh");
4734
4735 const double session_start = dt_get_wtime();
4736 _masks_draw_creation_session_forms(develop, module, mask_draw, zoom_scale, mask_gui);
4738 dt_print(DT_DEBUG_MASKS, "[masks] creation session (%d shapes) drawn in %0.04f sec\n",
4739 g_list_length(mask_gui->creation_formids), dt_get_wtime() - session_start);
4740
4741 // Draw form
4742 const dt_times_t draw_start = { 0 };
4743 dt_get_times((dt_times_t *)&draw_start);
4744
4745 gboolean layer_rebuilt = FALSE;
4746 const gboolean layered = _static_layer_applies(mask_form, mask_gui)
4747 && _static_layer_ensure(&frame, mask_draw, zoom_scale, mask_form, mask_gui, &layer_rebuilt);
4748 _overlay_draw_form(mask_draw, zoom_scale, mask_form, mask_gui, layered);
4749 /* What was painted: the rasteriser's own record, and the bounds of what cairo drew on top.
4750 * The transform is still in effect here, which is what the header bounds need; a creation
4751 * session paints through its own cached pattern and takes the whole canvas. */
4752 cairo_rectangle_int_t dirty = { 0, 0, 0, 0 };
4753 const gboolean any = _overlay_dirty(&frame, &cairo_bound, cairo_bounded, &dirty);
4754 _overlay_dump(&frame, mask_gui, &dirty);
4755 cairo_restore(mask_draw);
4756 cairo_destroy(mask_draw);
4757
4759 dt_show_times(&draw_start, "[masks] overlay drawn");
4760
4761 const double composite_start = dt_get_wtime();
4762 if(layer_rebuilt)
4763 {
4764 /* the layer changed under the whole view: everything is dirty this frame */
4765 dirty.x = 0;
4766 dirty.y = 0;
4767 dirty.width = frame.width;
4768 dirty.height = frame.height;
4769 }
4770 _overlay_finish(cr, &frame, (any || layered) ? &dirty : NULL, IS_NULL_PTR(transform), layered);
4772 dt_print(DT_DEBUG_MASKS, "[masks] overlay composited (%dx%d of %dx%d) in %0.04f sec\n",
4773 any ? dirty.width : 0, any ? dirty.height : 0, frame.width, frame.height,
4774 dt_get_wtime() - composite_start);
4775}
4776
4778{
4779 /* Deliberately NOT resetting the creation session's cache here.
4780 *
4781 * dt_masks_change_form_gui() calls this every time the visible form changes, which during a
4782 * creation session is once per stroke -- and the shapes already saved in that session are
4783 * unaffected by which form is currently being edited. Tying the two together threw away their
4784 * rendering AND their outlines on every stroke: 37 of 77 cache misses in one log came from the
4785 * pattern having been destroyed rather than from anything about the shapes changing, each
4786 * costing a 20 ms re-render.
4787 *
4788 * What ends the session cache is the session ending, which
4789 * _masks_draw_creation_session_forms() handles at its top, and any change to what it describes,
4790 * which its key covers: the geometry generation, the shape count, the shape ids and the
4791 * matrix. */
4792 if(IS_NULL_PTR(develop->form_gui)) return;
4793 g_list_free_full(develop->form_gui->points, dt_masks_form_gui_points_free);
4794 develop->form_gui->points = NULL;
4796 develop->form_gui->guipoints = NULL;
4798 develop->form_gui->guipoints_payload = NULL;
4799 develop->form_gui->guipoints_count = 0;
4800 develop->form_gui->geometry_generation = 0;
4801 develop->form_gui->formid = 0;
4802 develop->form_gui->delta[0] = develop->form_gui->delta[1] = 0.0f;
4803 develop->form_gui->scrollx = develop->form_gui->scrolly = 0.0f;
4804 develop->form_gui->form_selected = develop->form_gui->border_selected = develop->form_gui->form_dragging
4805 = develop->form_gui->form_rotating = develop->form_gui->border_toggling = develop->form_gui->gradient_toggling = FALSE;
4806 develop->form_gui->source_selected = develop->form_gui->source_dragging = FALSE;
4807 develop->form_gui->pivot_selected = FALSE;
4808 develop->form_gui->node_hovered = -1;
4809 develop->form_gui->handle_hovered = -1;
4810 develop->form_gui->seg_hovered = -1;
4811 develop->form_gui->handle_border_hovered = -1;
4812 develop->form_gui->node_selected = FALSE;
4813 develop->form_gui->handle_selected = FALSE;
4814 develop->form_gui->seg_selected = FALSE;
4816 develop->form_gui->node_selected_idx = -1;
4818 = develop->form_gui->node_dragging = -1;
4822 develop->form_gui->creation_module = NULL;
4824 g_list_free(develop->form_gui->creation_formids);
4825 develop->form_gui->creation_formids = NULL;
4826 develop->form_gui->creation_last_formid = 0;
4827 develop->form_gui->node_selected = FALSE;
4828
4829 develop->form_gui->group_selected = -1;
4830 develop->form_gui->group_selected = -1;
4832 develop->form_gui->last_rebuild_ts = 0.0;
4833 develop->form_gui->last_rebuild_pos[0] = develop->form_gui->last_rebuild_pos[1] = 0.0f;
4834 develop->form_gui->rebuild_pending = FALSE;
4835 develop->form_gui->last_hit_test_pos[0] = develop->form_gui->last_hit_test_pos[1] = -1.0f;
4836 // allow to select a shape inside an iop
4837 dt_masks_select_form(develop, NULL, NULL);
4838}
4839
4841{
4843 if(!IS_NULL_PTR(old_form))
4844 {
4845 gboolean is_registered = FALSE;
4846 gboolean is_registered_for_cleanup = FALSE;
4848 for(const GList *form_node = dev->forms; form_node; form_node = g_list_next(form_node))
4849 {
4850 if(form_node->data == old_form)
4851 {
4852 is_registered = TRUE;
4853 break;
4854 }
4855 }
4856
4857 for(const GList *form_node = dev->allforms; form_node; form_node = g_list_next(form_node))
4858 {
4859 if(form_node->data == old_form)
4860 {
4861 is_registered_for_cleanup = TRUE;
4862 break;
4863 }
4864 }
4866
4867 // Free only fully orphan temporary previews. Forms tracked in either list
4868 // are owned by develop and will be released by its teardown path.
4869 //
4870 // form_visible is cleared BEFORE the release, not after. old_form IS what it points at
4871 // -- it came from dt_masks_get_visible_form(dev) at the top -- so releasing it first
4872 // leaves the field holding freed memory until dt_masks_set_visible_form() below, and
4873 // dt_masks_clear_form_gui() runs inside that window. Re-entering this function there
4874 // reads the dangling form_visible as its own old_form, finds it in neither list because
4875 // it is already gone, and releases it a second time (Sentry 140265757: SIGSEGV in
4876 // dt_masks_change_form_gui, two frames of it, from the view-leave teardown path).
4877 // The creation-exit path in this file already orders it this way.
4878 //
4879 // Released through unref rather than dt_masks_free_form(): forms are refcounted and
4880 // free_form is the destructor unref calls at zero. Calling it directly frees the form
4881 // even when a history snapshot still references it. An orphan preview holds the single
4882 // reference it was created with, so this frees it exactly as before.
4883 if(!is_registered && !is_registered_for_cleanup)
4884 {
4885 dt_masks_set_visible_form(dev, NULL);
4886 dt_masks_form_unref(old_form);
4887 }
4888 }
4889
4891 dt_masks_set_visible_form(dev, new_form);
4892}
4893
4895{
4896 dt_masks_change_form_gui(dev, NULL);
4898 dt_iop_module_t *module = dev->gui_module;
4899 if(!IS_NULL_PTR(module) && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(module->flags() & IOP_FLAGS_NO_MASKS)
4900 && !IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->blend_data))
4901 {
4902 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
4903 blend_data->masks_shown = DT_MASKS_EDIT_OFF;
4904 if(!IS_NULL_PTR(blend_data->masks_edit))
4905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), 0);
4906 }
4907}
4908
4910{
4912 for(GList *module_node = dev->iop; module_node; module_node = g_list_next(module_node))
4913 {
4914 dt_iop_module_t *module = (dt_iop_module_t *)module_node->data;
4915 if(module && (module->flags() & IOP_FLAGS_SUPPORTS_BLENDING) && !(module->flags() & IOP_FLAGS_NO_MASKS)
4916 && !IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->blend_data))
4917 {
4918 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
4919 blend_data->masks_shown = DT_MASKS_EDIT_OFF;
4920 if(!IS_NULL_PTR(blend_data->masks_edit))
4921 {
4922 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit), FALSE);
4923 gtk_widget_queue_draw(blend_data->masks_edit);
4924 }
4925 }
4926 }
4927}
4928
4929static void _menu_no_masks(struct dt_iop_module_t *module)
4930{
4931 // we drop all the forms in the iop
4932 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
4933 if(group_form) dt_masks_form_delete(module->dev, module, NULL, group_form);
4934 module->blend_params->mask_id = 0;
4935
4936 // and we update the iop
4939}
4940
4942{
4943 dt_masks_creation_mode_enter(module->dev, module, type);
4944}
4945
4946static void _menu_add_exist(dt_iop_module_t *module, int form_id)
4947{
4948 if(IS_NULL_PTR(module)) return;
4949 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, form_id);
4950 if(IS_NULL_PTR(mask_form)) return;
4951
4952 // is there already a masks group for this module ?
4953 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
4954 if(IS_NULL_PTR(group_form))
4955 {
4956 group_form = _group_create(module->dev, module, DT_MASKS_GROUP);
4957 }
4958 group_form = dt_masks_cow_touch(module->dev, group_form);
4959 // we add the form in this group
4960 dt_masks_group_add_form(module->dev, group_form, mask_form);
4961 // we save the group
4962 // and we ensure that we are in edit mode
4963
4966}
4967
4969{
4970 if(IS_NULL_PTR(module)) return;
4971
4972 // Id-keyed: renaming a module's mask after the module was renamed is a mutation like any other,
4973 // and the group may be shared with a history snapshot, so it copies on write.
4974 const dt_masks_result_t result =
4976 if(result != DT_MASKS_OK && result != DT_MASKS_UNCHANGED) return;
4977
4979}
4980
4982{
4983 // we ensure that the module has focus
4984 dt_iop_module_t *module = (dt_iop_module_t *)data;
4985 dt_iop_request_focus(module);
4986 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
4987
4988 // we determine a higher approx of the entry number
4989 const guint forms_count = g_list_length(module->dev->forms);
4990 const guint iop_count = g_list_length(module->dev->iop);
4991 guint combo_capacity = 5 + forms_count + iop_count;
4992 dt_free_align(blend_data->masks_combo_ids);
4993 blend_data->masks_combo_ids = dt_alloc_align(sizeof(int) * combo_capacity);
4994
4995 int *combo_ids = blend_data->masks_combo_ids;
4996 GtkWidget *combo = blend_data->masks_combo;
4997
4998 // we remove all the combo entries except the first one
4999 while(dt_bauhaus_combobox_length(combo) > 1)
5000 {
5002 }
5003
5004 int combo_index = 0;
5005 combo_ids[combo_index] = 0; // nothing to do for the first entry (already here)
5006 combo_index++;
5007
5008 // add existing shapes
5009 dt_pthread_rwlock_rdlock(&module->dev->masks_mutex);
5010 for(GList *form_node = module->dev->forms; form_node; form_node = g_list_next(form_node))
5011 {
5012 dt_masks_form_t *mask_form = (dt_masks_form_t *)form_node->data;
5013 if((mask_form->type & (DT_MASKS_CLONE|DT_MASKS_NON_CLONE))
5014 || mask_form->formid == module->blend_params->mask_id)
5015 {
5016 continue;
5017 }
5018
5019 // we search were this form is used in the current module
5020 int is_used = 0;
5021 dt_masks_form_t *group_form = _group_from_module(module->dev, module);
5022 if(group_form && (group_form->type & DT_MASKS_GROUP))
5023 {
5024 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
5025 {
5026 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
5027 if(group_entry->formid == mask_form->formid)
5028 {
5029 is_used = 1;
5030 break;
5031 }
5032 }
5033 }
5034 if(!is_used)
5035 {
5036 dt_bauhaus_combobox_add(combo, mask_form->name);
5037 combo_ids[combo_index] = mask_form->formid;
5038 combo_index++;
5039 }
5040 }
5041 dt_pthread_rwlock_unlock(&module->dev->masks_mutex);
5042
5043 // masks from other iops
5044 int iop_index = 1;
5045 for(GList *module_node = module->dev->iop; module_node; module_node = g_list_next(module_node))
5046 {
5047 dt_iop_module_t *other_module = (dt_iop_module_t *)module_node->data;
5048 if((other_module != module) && (other_module->flags() & IOP_FLAGS_SUPPORTS_BLENDING)
5049 && !(other_module->flags() & IOP_FLAGS_NO_MASKS))
5050 {
5051 dt_masks_form_t *group_form = _group_from_module(module->dev, other_module);
5052 if(group_form)
5053 {
5054 gchar *module_label = dt_history_item_get_name(other_module);
5055 dt_bauhaus_combobox_add(combo, g_strdup_printf(_("reuse shapes from %s"), module_label));
5056 dt_free(module_label);
5057 combo_ids[combo_index] = -1 * iop_index;
5058 combo_index++;
5059 }
5060 }
5061 iop_index++;
5062 }
5063}
5064
5066{
5067 // we get the corresponding value
5069
5070 int selection_index = dt_bauhaus_combobox_get(blend_data->masks_combo);
5071 if(selection_index == 0) return;
5072 if(selection_index > 0)
5073 {
5074 int selection_value = blend_data->masks_combo_ids[selection_index];
5075 const guint iop_count = g_list_length(module->dev->iop);
5076 // FIXME : these values should use binary enums
5077 if(selection_value == -1000000)
5078 {
5079 // delete all masks
5080 _menu_no_masks(module);
5081 }
5082 else if(selection_value == -2000001)
5083 {
5084 // add a circle shape
5086 }
5087 else if(selection_value == -2000002)
5088 {
5089 // add a path shape
5091 }
5092 else if(selection_value == -2000016)
5093 {
5094 // add a gradient shape
5096 }
5097 else if(selection_value == -2000032)
5098 {
5099 // add a gradient shape
5101 }
5102 else if(selection_value == -2000064)
5103 {
5104 // add a brush shape
5106 }
5107 else if(selection_value < 0)
5108 {
5109 // use same shapes as another iop
5110 selection_value = -1 * selection_value - 1;
5111 if(selection_value < (int)iop_count)
5112 {
5113 dt_iop_module_t *source_module
5114 = (dt_iop_module_t *)g_list_nth_data(module->dev->iop, selection_value);
5115 dt_masks_iop_use_same_as(module, source_module);
5116 // and we ensure that we are in edit mode
5117 //
5118
5120 }
5121 }
5122 else if(selection_value > 0)
5123 {
5124 // add an existing shape
5125 _menu_add_exist(module, selection_value);
5126 }
5127 else
5128 return;
5129 }
5130 // we update the combo line
5132 dt_dev_add_history_item(module->dev, module, TRUE, TRUE);
5133}
5134
5135float dt_masks_form_get_interaction_value(dt_develop_t *dev, const int group_id, const int formid,
5136 dt_masks_interaction_t interaction)
5137{
5138 if(IS_NULL_PTR(dev)) return NAN;
5139
5140 if(interaction == DT_MASKS_INTERACTION_OPACITY)
5141 {
5142 /* Opacity is a property of the MEMBERSHIP, not of the shape: the same shape referenced by two
5143 * groups carries two opacities, which is why this one needs the group id and the others do
5144 * not. */
5145 dt_masks_member_t member = { 0 };
5146 if(dt_masks_group_get_member(dev, group_id, formid, &member) != DT_MASKS_OK) return NAN;
5147 return member.opacity;
5148 }
5149
5150 dt_masks_form_t *target_form = dt_masks_get_from_id(dev, formid);
5151 if(IS_NULL_PTR(target_form) || IS_NULL_PTR(target_form->functions) || IS_NULL_PTR(target_form->functions->get_interaction_value)) return NAN;
5152
5153 return target_form->functions->get_interaction_value(target_form, interaction);
5154}
5155
5156gboolean dt_masks_form_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
5157{
5158 center[0] = 0.0f;
5159 center[1] = 0.0f;
5160 if(!IS_NULL_PTR(area)) *area = 0.0f;
5161
5162 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;
5163 return mask_form->functions->get_gravity_center(dev, mask_form, center, area);
5164}
5165
5178{
5179 if(IS_NULL_PTR(dev) || IS_NULL_PTR(mask_form)) return 1;
5180
5181 float center[2] = { 0.0f, 0.0f };
5182 float area = 0.0f;
5183 if(!dt_masks_form_get_gravity_center(dev, mask_form, center, &area)) return 1;
5184
5186 if(!dt_dev_coordinates_raw_abs_to_image_abs(dev, center, 1)) return 1;
5188
5189 dt_dev_viewport_set_center(dev, center[0], center[1]);
5192
5193 return 0;
5194}
5195
5196float dt_masks_form_set_interaction_value(dt_develop_t *dev, const int group_id, const int formid,
5197 dt_masks_interaction_t interaction,
5198 float value, dt_masks_increment_t increment, int flow,
5199 dt_masks_form_gui_t *mask_gui, dt_iop_module_t *module)
5200{
5201 if(IS_NULL_PTR(dev)) return NAN;
5202
5203 if(interaction == DT_MASKS_INTERACTION_OPACITY)
5204 {
5205 /* Read, apply the increment, write back -- all by identity. The write API owns the
5206 * copy-on-write, so nothing here holds a row across the mutation. */
5207 dt_masks_member_t member = { 0 };
5208 if(dt_masks_group_get_member(dev, group_id, formid, &member) != DT_MASKS_OK) return NAN;
5209
5210 const float target = dt_masks_apply_increment(member.opacity, value, increment, flow);
5211 const dt_masks_result_t result = dt_masks_group_set_member_opacity(dev, group_id, formid, target, &member);
5212 if(result != DT_MASKS_OK && result != DT_MASKS_UNCHANGED) return NAN;
5213
5214 dt_toast_log(_("Opacity: %3.2f%%"), member.opacity * 100.f);
5215 return member.opacity;
5216 }
5217
5218 dt_masks_form_t *target_form = dt_masks_get_from_id(dev, formid);
5219 if(IS_NULL_PTR(target_form) || !target_form->functions
5220 || !target_form->functions->set_interaction_value) return NAN;
5221
5222 // The shape's own geometry is refcounted like any other dt_masks_form_t (an undo/redo
5223 // snapshot can share it) -- touch it before the vtable call below mutates form->points in
5224 // place, or a shared shape's geometry changes behind the back of a snapshot that still
5225 // references it. Safe to re-touch on every call: target_form is re-resolved by formid each
5226 // time, never cached across slider drag steps.
5227 target_form = dt_masks_cow_touch(dev, target_form);
5228
5229 const float result = target_form->functions->set_interaction_value(target_form, interaction, value, increment,
5230 flow, mask_gui, module);
5231 if(isnan(result)) return NAN;
5232 dt_masks_form_update_gravity_center(dev, target_form);
5233 return result;
5234}
5235
5236const char * _get_mask_plugin(dt_masks_form_t *mask_form)
5237{
5238 // Internal masks are used by spots removal and retouch modules
5239 if(mask_form->type & (DT_MASKS_CLONE | DT_MASKS_NON_CLONE))
5240 return "spots";
5241 // Regular all-purpose masks
5242 else
5243 return "masks";
5244}
5245
5246float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
5247{
5248 switch(increment)
5249 {
5251 return current * powf(amount, (float)flow);
5253 return current + amount * (float)flow;
5255 default:
5256 return amount;
5257 }
5258}
5259
5260float dt_masks_apply_increment_precomputed(float current, float amount, float scale_amount, float offset_amount,
5261 dt_masks_increment_t increment)
5262{
5263 switch(increment)
5264 {
5266 return current * scale_amount;
5268 return current + offset_amount;
5270 default:
5271 return amount;
5272 }
5273}
5274
5275float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value,
5276 float value_min, float value_max,
5277 dt_masks_increment_t increment, int flow)
5278{
5279 gchar *config_key = NULL;
5280 if(!strcmp(feature, "opacity"))
5281 config_key = g_strdup_printf("plugins/darkroom/%s_opacity", _get_mask_plugin(mask_form));
5282 else
5283 config_key = g_strdup_printf("plugins/darkroom/%s/%s/%s",
5284 _get_mask_plugin(mask_form), dt_masks_type_name(mask_form->type), feature);
5285
5286 if(!g_strcmp0(feature, "rotation")) flow = (flow > 1) ? (flow - 1) * 5 : flow;
5287
5288 const float current_value = dt_conf_get_float(config_key);
5289 float updated_value = dt_masks_apply_increment(current_value, new_value, increment, flow);
5290 if(!g_strcmp0(feature, "rotation"))
5291 {
5292 // Ensure the rotation value stays within the interval [min, max)
5293 if(updated_value > value_max) updated_value = fmodf(updated_value, value_max);
5294 else if(updated_value < value_min)
5295 updated_value = value_max - fmodf(value_min - updated_value, value_max);
5296 }
5297 else updated_value = MAX(value_min, MIN(updated_value, value_max));
5298
5299 dt_conf_set_float(config_key, updated_value);
5300
5301 dt_free(config_key);
5302 return updated_value;
5303}
5304
5305float dt_masks_get_set_conf_value_with_toast(dt_masks_form_t *mask_form, const char *feature, float amount,
5306 float value_min, float value_max,
5307 dt_masks_increment_t increment, int flow,
5308 const char *toast_fmt, float toast_scale)
5309{
5310 float value = dt_masks_get_set_conf_value(mask_form, (char *)feature, amount,
5311 value_min, value_max, increment, flow);
5312 if(!IS_NULL_PTR(toast_fmt) && toast_fmt[0] != '\0')
5313 dt_toast_log(toast_fmt, value * toast_scale);
5314 return value;
5315}
5316
5318 dt_masks_form_t *mask_form, const int parentid,
5319 const dt_masks_state_t state, const float opacity)
5320{
5321 if(IS_NULL_PTR(group_form) || IS_NULL_PTR(mask_form)) return NULL;
5322 if(!(group_form->type & DT_MASKS_GROUP)) return NULL;
5323
5324 /* Either the form being added is not a group, so there is no risk, or we walk it looking for a
5325 * reference back to this group. This is the guard the hand-rolled call sites skipped. */
5326 if((mask_form->type & DT_MASKS_GROUP) && _find_in_group(dev, mask_form, group_form->formid) != 0)
5327 {
5328 dt_control_log(_("Masks can not contain themselves"));
5329 return NULL;
5330 }
5331
5332 dt_masks_form_group_t *group_entry = malloc(sizeof(dt_masks_form_group_t));
5333 if(IS_NULL_PTR(group_entry)) return NULL;
5334
5335 group_entry->formid = mask_form->formid;
5336 group_entry->parentid = parentid;
5337 group_entry->state = state;
5338 group_entry->opacity = opacity;
5339 group_form->points = g_list_append(group_form->points, group_entry);
5340
5341 /* The group's cached centre of gravity is now stale, and hit-testing reads it. The hand-rolled
5342 * sites left it stale. */
5343 dt_masks_form_update_gravity_center(dev, group_form);
5344 return group_entry;
5345}
5346
5348{
5349 if(IS_NULL_PTR(mask_form)) return NULL;
5350 if(IS_NULL_PTR(group_form)) return NULL;
5351 return dt_masks_group_add_form_with_state(dev, group_form, mask_form, group_form->formid,
5353 dt_conf_get_float("plugins/darkroom/masks/opacity"));
5354}
5355
5357{
5358 if(IS_NULL_PTR(group_form) || IS_NULL_PTR(dest_group)) return;
5359 if(!(group_form->type & DT_MASKS_GROUP) || !(dest_group->type & DT_MASKS_GROUP)) return;
5360
5361 // dest_group->points is mutated below (also across recursive calls, which all receive this
5362 // same touched pointer by value): safe to self-touch here, unlike group_form which is only
5363 // ever read/recursed into, never mutated.
5364 dest_group = dt_masks_cow_touch(dev, dest_group);
5365
5366 for(GList *group_node = group_form->points; group_node; group_node = g_list_next(group_node))
5367 {
5368 dt_masks_form_group_t *group_entry = (dt_masks_form_group_t *)group_node->data;
5369 dt_masks_form_t *mask_form = dt_masks_get_from_id(dev, group_entry->formid);
5370 if(mask_form)
5371 {
5372 if(mask_form->type & DT_MASKS_GROUP)
5373 {
5374 dt_masks_group_ungroup(dev, dest_group, mask_form);
5375 }
5376 else
5377 {
5379 new_entry->formid = group_entry->formid;
5380 new_entry->parentid = group_entry->parentid;
5381 new_entry->state = group_entry->state;
5382 new_entry->opacity = group_entry->opacity;
5383 dest_group->points = g_list_append(dest_group->points, new_entry);
5384 }
5385 }
5386 }
5387
5388 dt_masks_form_update_gravity_center(dev, dest_group);
5389}
5390
5403int dt_masks_point_in_form_exact(const float *test_points, int test_point_count,
5404 const float *form_points, int form_points_start, int form_points_count,
5405 const dt_masks_skip_range_t *skips, int skip_count)
5406{
5407 if(IS_NULL_PTR(test_points) || test_point_count <= 0 || IS_NULL_PTR(form_points)) return -1;
5408 if(form_points_count <= 2 + form_points_start) return -1;
5409 if(form_points_start < 0 || form_points_start >= form_points_count) return -1;
5410 if(IS_NULL_PTR(skips)) skip_count = 0;
5411
5412 const int start_index = form_points_start;
5413 // Once per call, not per finding: a corrupt input corrupts every test point alike, and the
5414 // point of reporting is a greppable line, not a flood.
5415 gboolean reported_bad_skip = FALSE;
5416 gboolean reported_trapped_walk = FALSE;
5417
5418 for(int test_index = 0; test_index < test_point_count; test_index++)
5419 {
5420 int intersection_count = 0;
5421 const float point_x = test_points[test_index * 2];
5422 const float point_y = test_points[test_index * 2 + 1];
5423 int visited_points = 0;
5424
5425 for(int i = form_points_start, next = start_index + 1; i < form_points_count;)
5426 {
5427 if(next < start_index || next >= form_points_count) break;
5428 if(++visited_points > form_points_count - start_index + 1)
5429 {
5430 /* Unreachable over a well-formed stream: the walk visits each index at most once and
5431 * wraps exactly once. Tripping it means the input is corrupt, and the crossing count
5432 * below is then garbage. This used to be silent -- both historical bugs of the cut
5433 * mechanism shipped as exactly this silence. */
5434 if(!reported_trapped_walk)
5435 {
5437 "[masks] point_in_form walk trapped after %d visits of %d points -- corrupt"
5438 " outline or skip ranges; hit-test result is unreliable\n",
5439 visited_points, form_points_count - form_points_start);
5440 reported_trapped_walk = TRUE;
5441 }
5442 break;
5443 }
5444
5445 /* Out-of-band self-intersection cuts: on reaching one, close the contour with a chord to
5446 * its resume point. Only a skip that moves the walk STRICTLY FORWARD is followed -- a
5447 * backward one would re-walk the span just left until the cap above fires, which is the
5448 * cycle the old in-band encoding actually produced once. Such a range is a producer bug:
5449 * ignore it and say so. */
5450 gboolean jumped = TRUE;
5451 int hops = 0;
5452 while(jumped && hops <= skip_count)
5453 {
5454 jumped = FALSE;
5455 for(int s = 0; s < skip_count; s++)
5456 {
5457 if(next != skips[s].jump_from) continue;
5458 if(skips[s].resume_at <= skips[s].jump_from || skips[s].resume_at >= form_points_count)
5459 {
5460 if(!reported_bad_skip)
5461 {
5463 "[masks] skip range [%d -> %d] does not move forward within %d points --"
5464 " ignoring it; the producer is broken\n",
5465 skips[s].jump_from, skips[s].resume_at, form_points_count);
5466 reported_bad_skip = TRUE;
5467 }
5468 }
5469 else
5470 {
5471 next = skips[s].resume_at;
5472 jumped = TRUE;
5473 hops++;
5474 }
5475 break;
5476 }
5477 }
5478 if(next >= form_points_count) break;
5479
5480 const float y1 = form_points[i * 2 + 1];
5481 const float y2 = form_points[next * 2 + 1];
5482
5483 if(isnan(form_points[next * 2]))
5484 {
5485 /* NOTHING should reach this. dt_masks_get_points_border() guarantees the outline holds
5486 * finite geometry, and everything a consumer must not use travels beside it in the
5487 * exclusion list. Both shapes of sentinel are therefore reported: a NaN x with a finite
5488 * y was the in-band jump encoding, an index smuggled through a coordinate, and decoding
5489 * one silently is how issue #1313 shipped; a bare NaN,NaN was a close-the-contour
5490 * marker, which no builder emits any longer. This walk is also reached with buffers
5491 * that never went through that entry point, which is why the check stays at all. */
5492 if(!reported_bad_skip)
5493 {
5495 "[masks] non-finite outline sample at %d of %d (y %s) -- the buffer should"
5496 " hold geometry only; treating the outline as ended\n", next, form_points_count,
5497 isnan(y2) ? "also NaN" : "finite, i.e. the old in-band jump encoding");
5498 reported_bad_skip = TRUE;
5499 }
5500 break;
5501 if(next == start_index) break;
5502 next = start_index;
5503 continue;
5504 }
5505
5506 if(((point_y <= y2 && point_y > y1) || (point_y >= y2 && point_y < y1))
5507 && (form_points[i * 2] > point_x))
5508 intersection_count++;
5509
5510 if(next == start_index) break;
5511 i = next++;
5512 // loop
5513 if(next >= form_points_count) next = start_index;
5514 }
5515
5516 if(intersection_count & 1) return test_index;
5517 }
5518
5519 return -1;
5520}
5521
5527void dt_masks_select_form(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *selected_form)
5528{
5529 const int selected_formid = IS_NULL_PTR(selected_form) ? 0 : selected_form->formid;
5530
5531 if(IS_NULL_PTR(module) && selected_formid == 0 && !IS_NULL_PTR(dev))
5532 module = dev->gui_module;
5533
5534 if(!IS_NULL_PTR(module) && module->masks_selection_changed)
5535 module->masks_selection_changed(module, selected_formid);
5536}
5537
5545void dt_masks_set_source_pos_initial_state(dt_masks_form_gui_t *mask_gui, const uint32_t key_state)
5546{
5547 if(dt_modifier_is(key_state, GDK_SHIFT_MASK | DT_PRIMARY_MASK))
5549 else if(dt_modifier_is(key_state, GDK_SHIFT_MASK))
5551 else
5552 fprintf(stderr, "[dt_masks_set_source_pos_initial_state] unknown state for setting masks position type\n");
5553
5554 // both source types record an absolute position,
5555 // for the relative type, the first time is used the position is recorded,
5556 // the second time a relative position is calculated based on that one
5557 mask_gui->pos_source[0] = mask_gui->pos[0];
5558 mask_gui->pos_source[1] = mask_gui->pos[1];
5559}
5560
5571{
5572 dt_develop_t *dev = mask_gui->dev;
5574 const float raw_width = geometry.raw_width;
5575 const float raw_height = geometry.raw_height;
5576
5577 const float xx = mask_gui->pos[0];
5578 const float yy = mask_gui->pos[1];
5579
5580 // if this is the first time the relative pos is used
5582 {
5583 // if it has not been defined by the user, set some default
5584 if(mask_gui->pos_source[0] == -1.0f && mask_gui->pos_source[1] == -1.0f)
5585 {
5586 if(mask_form->functions && mask_form->functions->initial_source_pos)
5587 {
5588 mask_form->functions->initial_source_pos(dev, raw_width, raw_height,
5589 &mask_gui->pos_source[0], &mask_gui->pos_source[1]);
5590 }
5591 else
5592 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unsupported masks type when calculating source position initial value\n");
5593
5594 // set offset to form->source
5595 mask_form->source[0] = mask_gui->pos[0] + mask_gui->pos_source[0];
5596 mask_form->source[1] = mask_gui->pos[1] + mask_gui->pos_source[1];
5598 // normalize backbuf points
5600
5601 }
5602 else
5603 {
5604 // if a position was defined by the user, use the absolute value the first time
5605 float source_points[2] = { mask_gui->pos_source[0], mask_gui->pos_source[1] };
5606 dt_dev_coordinates_image_abs_to_raw_norm(dev, source_points, 1);
5607
5608 mask_form->source[0] = source_points[0];
5609 mask_form->source[1] = source_points[1];
5610
5611 mask_gui->pos_source[0] = mask_gui->pos_source[0] - xx;
5612 mask_gui->pos_source[1] = mask_gui->pos_source[1] - yy;
5613 }
5614
5616 }
5617 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_RELATIVE)
5618 {
5619 // original pos was already defined and relative value calculated, just use it
5620 mask_form->source[0] = mask_gui->pos[0] + mask_gui->pos_source[0];
5621 mask_form->source[1] = mask_gui->pos[1] + mask_gui->pos_source[1];
5623 }
5624 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
5625 {
5626 // an absolute position was defined by the user
5627 float source_points[2] = { mask_gui->pos_source[0], mask_gui->pos_source[1] };
5628 dt_dev_coordinates_image_abs_to_raw_norm(dev, source_points, 1);
5629
5630 mask_form->source[0] = source_points[0];
5631 mask_form->source[1] = source_points[1];
5632 }
5633 else
5634 fprintf(stderr, "[dt_masks_set_source_pos_initial_value] unknown source position type\n");
5635}
5636
5642void dt_masks_calculate_source_pos_origin(dt_masks_form_gui_t *mask_gui, const float initial_xpos,
5643 const float initial_ypos, const float xpos, const float ypos,
5644 float *pos_x, float *pos_y, const int adding)
5645{
5646 float source_x = 0.0f;
5647 float source_y = 0.0f;
5649 const float raw_width = geometry.raw_width;
5650 const float raw_height = geometry.raw_height;
5652 {
5653 source_x = xpos + mask_gui->pos_source[0];
5654 source_y = ypos + mask_gui->pos_source[1];
5655 }
5657 {
5658 if(mask_gui->pos_source[0] == -1.0f && mask_gui->pos_source[1] == -1.0f)
5659 {
5660 const dt_masks_form_t *visible_form = dt_masks_get_visible_form(mask_gui->dev);
5661 if(!IS_NULL_PTR(visible_form) && visible_form->functions && visible_form->functions->initial_source_pos)
5662 {
5663 visible_form->functions->initial_source_pos(mask_gui->dev, raw_width, raw_height, &source_x, &source_y);
5664 source_x += xpos;
5665 source_y += ypos;
5666 }
5667 else
5668 fprintf(stderr, "[dt_masks_calculate_source_pos_origin] unsupported masks type when calculating source position value\n");
5669 }
5670 else
5671 {
5672 source_x = mask_gui->pos_source[0];
5673 source_y = mask_gui->pos_source[1];
5674 }
5675 }
5676 else if(mask_gui->source_pos_type == DT_MASKS_SOURCE_POS_ABSOLUTE)
5677 {
5678 // if the user is actually adding, the mask follow the cursor
5679 if(adding)
5680 {
5681 source_x = xpos + mask_gui->pos_source[0] - initial_xpos;
5682 source_y = ypos + mask_gui->pos_source[1] - initial_ypos;
5683 }
5684 else
5685 {
5686 // if not added yet set the start position
5687 source_x = mask_gui->pos_source[0];
5688 source_y = mask_gui->pos_source[1];
5689 }
5690 }
5691 else
5692 fprintf(stderr, "[dt_masks_calculate_source_pos_origin] unknown source position type for setting source position value\n");
5693
5694 *pos_x = source_x;
5695 *pos_y = source_y;
5696}
5697
5706float dt_masks_rotate_with_anchor(dt_develop_t *develop, const float anchor[2], const float center[2],
5707 dt_masks_form_gui_t *mask_gui)
5708{
5709 const float center_x = center[0];
5710 const float center_y = center[1];
5711
5712 // get the current angle
5713 const float anchor_x = anchor[0];
5714 const float anchor_y = anchor[1];
5715 const float angle_current = atan2f(anchor_y - center_y, anchor_x - center_x);
5716
5717 // get the previous angle
5718 const float delta_x = mask_gui->delta[0];
5719 const float delta_y = mask_gui->delta[1];
5720 const float angle_prev = atan2f(delta_y - center_y, delta_x - center_x);
5721
5722 // calculate the angle difference an normalize to -180 to 180 degrees
5723 float delta_angle = angle_current - angle_prev;
5724 float angle = atan2f(sinf(delta_angle), cosf(delta_angle));
5725
5726 // check if distortion inverts the axes
5727 float test_points[8] = { center_x, center_y, anchor_x , anchor_y,
5728 center_x + 10.0f, center_y, center_x, center_y + 10.0f };
5729 dt_dev_coordinates_image_abs_to_raw_abs(develop, test_points, 4);
5730 float check_angle = atan2f(test_points[7] - test_points[1], test_points[6] - test_points[0])
5731 - atan2f(test_points[5] - test_points[1], test_points[4] - test_points[0]);
5732 // Normalize to the range -180 to 180 degrees
5733 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
5734
5735 // Adjust the sign if the axes are inverted by distortion
5736 if(check_angle < 0.0f) angle = -angle;
5737
5738 // Update the delta for the next frame (old position becomes the current one)
5739 mask_gui->delta[0] = anchor_x;
5740 mask_gui->delta[1] = anchor_y;
5741
5742 return angle / M_PI * 180.0f;
5743}
5744
5751{
5752 if(IS_NULL_PTR(mask_gui)) return;
5753
5754 mask_gui->creation = FALSE;
5755}
5756
5763{
5764 if((type & DT_MASKS_ALL) == 0) return FALSE;
5765 if(IS_NULL_PTR(dev) || IS_NULL_PTR(dev->form_gui)) return FALSE;
5766 // we want to be sure that the iop has focus
5767 if(!IS_NULL_PTR(module)) dt_iop_request_focus(module);
5768
5769 dt_masks_form_t *mask_form = dt_masks_create(type);
5770 if(IS_NULL_PTR(mask_form)) return FALSE;
5771
5772 dt_masks_change_form_gui(dev, mask_form);
5773 dev->form_gui->creation = TRUE;
5774 dev->form_gui->creation_module = module;
5775 dev->form_gui->creation_type = type;
5776 dev->form_gui->creation_formids = NULL;
5778
5779 /* Whichever gesture armed the tool -- a toolbar button, the shape manager's "Add new shape ..."
5780 * entry, a shortcut -- every toolbar now shows the shape being created. Raised after the whole
5781 * creation state is written, so a handler asking is told the truth; the buttons act on
5782 * "button-press-event", not "toggled", so pressing one here cannot re-enter this function. */
5784
5785 // Give focus to central view to allow using shortcuts for mask creation right after selecting a mask type in the manager
5786 gtk_widget_grab_focus(dt_gui_center_widget());
5787 return TRUE;
5788}
5789
5796{
5797 if(IS_NULL_PTR(group_entry)) return;
5798
5799 // Apply Inverse
5800 if(apply_state == DT_MASKS_STATE_INVERSE)
5801 group_entry->state ^= DT_MASKS_STATE_INVERSE;
5802
5803 else if((apply_state & DT_MASKS_STATE_IS_COMBINE_OP) != 0)
5804 {
5805 // Reset all and apply state
5806 group_entry->state = (group_entry->state & ~DT_MASKS_STATE_IS_COMBINE_OP) | apply_state;
5807 }
5808}
5810{
5811 if(IS_NULL_PTR(module)) return;
5812 dt_iop_gui_blend_data_t *blend_data = module->gui ? (dt_iop_gui_blend_data_t *)module->gui->blend_data : NULL;
5813 if(IS_NULL_PTR(blend_data)) return;
5814
5815 dt_masks_form_t *group_form = NULL;
5816 dt_masks_form_t *mask_form = dt_masks_get_from_id(module->dev, module->blend_params->mask_id);
5817 if(value && !IS_NULL_PTR(mask_form))
5818 {
5819 group_form = dt_masks_create_ext(module->dev, DT_MASKS_GROUP);
5820 group_form->formid = 0;
5821 dt_masks_group_ungroup(module->dev, group_form, mask_form);
5822 }
5823
5824 if(blend_data) blend_data->masks_shown = value;
5825
5826 dt_masks_change_form_gui(module->dev, group_form);
5827 module->dev->form_gui->edit_mode = value;
5828 if(value && mask_form)
5829 dt_dev_masks_selection_change(module->dev, NULL, mask_form->formid, FALSE);
5830 else
5831 dt_dev_masks_selection_change(module->dev, NULL, 0, FALSE);
5832
5833 if(blend_data->masks_support)
5834 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit),
5836
5838}
5839
5840int dt_masks_form_change_opacity(dt_develop_t *dev, dt_masks_form_t *mask_form, int parent_id, int scroll_up,
5841 const int flow)
5842{
5843 if(IS_NULL_PTR(mask_form)) return 0;
5844
5845 // Read, apply the increment, write back by identity. The write API owns the copy-on-write, so
5846 // neither half of this holds a row pointer across the other -- which is what the previous
5847 // touch-then-resolve-then-mutate-in-place version had to get right by hand.
5848 dt_masks_member_t member = { 0 };
5849 if(dt_masks_group_get_member(dev, parent_id, mask_form->formid, &member) != DT_MASKS_OK) return 0;
5850
5851 const float amount = scroll_up ? 0.02f : -0.02f;
5852 const float target = dt_masks_apply_increment(member.opacity, amount, DT_MASKS_INCREMENT_OFFSET, flow);
5853
5854 const dt_masks_result_t result = dt_masks_group_set_member_opacity(dev, parent_id, mask_form->formid,
5855 target, &member);
5856 if(result != DT_MASKS_OK && result != DT_MASKS_UNCHANGED) return 0;
5857
5858 dt_toast_log(_("Opacity: %3.2f%%"), member.opacity * 100.f);
5859
5860 // UNCHANGED still counts as handled: this return value is what every shape's scrolled handler
5861 // gives back to say it consumed the event, so reporting 0 once the opacity is already pinned at
5862 // 0 or 1 would let that scroll step fall through and zoom the canvas instead.
5863 return (result == DT_MASKS_OK || result == DT_MASKS_UNCHANGED);
5864}
5865
5866
5867
5868/* ------------------------------------------------------------------------------------- */
5869/* The headless half of the diagnostic renderer.
5870 *
5871 * It lives here, and not next to dt_masks_debug_rasterise() in masks_debug.c, for one reason:
5872 * compositing the overlay needs cairo, and src/develop is kept free of every toolkit type by
5873 * tools/check_module_boundaries.sh so the pixel and params engines stay portable. This file is
5874 * already the GUI half of masks -- and it is where dt_masks_events_post_expose_with(), the one
5875 * production drawing routine this calls, already lives. There is deliberately no second
5876 * drawing path: what a regression test looks at is what the darkroom paints. */
5880static void _paint_mask_backdrop(cairo_t *cr, dt_develop_t *dev, dt_masks_form_t *form,
5881 const int width, const int height)
5882{
5883 float *const mask = dt_masks_debug_rasterise(dev, form, width, height);
5884 if(IS_NULL_PTR(mask)) return;
5885
5886 cairo_surface_t *grey = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
5887 if(cairo_surface_status(grey) == CAIRO_STATUS_SUCCESS)
5888 {
5889 cairo_surface_flush(grey);
5890 uint8_t *const pixels = cairo_image_surface_get_data(grey);
5891 const int stride = cairo_image_surface_get_stride(grey);
5892 for(int y = 0; y < height; y++)
5893 {
5894 uint32_t *const row = (uint32_t *)(pixels + (size_t)y * stride);
5895 for(int x = 0; x < width; x++)
5896 {
5897 const float v = mask[(size_t)y * width + x];
5898 const uint32_t g = (uint32_t)(CLAMPF(v, 0.0f, 1.0f) * 255.0f + 0.5f);
5899 row[x] = (g << 16) | (g << 8) | g;
5900 }
5901 }
5902 cairo_surface_mark_dirty(grey);
5903 cairo_set_source_surface(cr, grey, 0, 0);
5904 cairo_paint(cr);
5905 }
5906 cairo_surface_destroy(grey);
5907 dt_free_align(mask);
5908}
5909
5911 const dt_masks_debug_request_t *request, const char *path)
5912{
5913 if(IS_NULL_PTR(dev) || IS_NULL_PTR(request) || IS_NULL_PTR(path)) return FALSE;
5914 if(IS_NULL_PTR(form)) form = dt_masks_get_visible_form(dev);
5915 if(IS_NULL_PTR(form)) return FALSE;
5916
5917 int32_t raw_width = 0;
5918 int32_t raw_height = 0;
5919 if(!dt_dev_geometry_get_raw_size(dev, &raw_width, &raw_height) || raw_width <= 0 || raw_height <= 0)
5920 return FALSE;
5921
5922 int width = request->width > 0 ? request->width : raw_width;
5923 int height = request->height > 0 ? request->height
5924 : (int)lrint((double)width * raw_height / (double)raw_width);
5925 if(width <= 0 || height <= 0) return FALSE;
5926
5927 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
5928 if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
5929 {
5930 cairo_surface_destroy(surface);
5931 return FALSE;
5932 }
5933 cairo_t *cr = cairo_create(surface);
5934
5936 {
5937 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
5938 cairo_paint(cr);
5939 }
5940
5942 _paint_mask_backdrop(cr, dev, form, width, height);
5943
5944 if(request->draw_overlay)
5945 {
5946 /* The overlay paints with the theme palette, which is filled from GTK at startup and is
5947 * therefore all-zero -- fully transparent -- with no GUI. Drawing would "succeed" and
5948 * produce an empty picture. Give every unset entry a visible fallback so a headless render
5949 * shows the same geometry the darkroom would; the exact hues are the theme's business, and
5950 * a diagnostic only needs to be legible. Production is untouched: with a GUI up, every
5951 * entry already has a non-zero alpha and nothing here applies. */
5952 GdkRGBA *const palette = dt_widget_colors();
5953 if(!IS_NULL_PTR(palette))
5954 {
5955 for(int i = 0; i < DT_GUI_COLOR_LAST; i++)
5956 if(palette[i].alpha <= 0.0) palette[i] = (GdkRGBA){ 1.0, 1.0, 1.0, 1.0 };
5957 }
5958
5959 /* The overlay reads the visible form and the processed size off the dev, so both are
5960 * published here rather than passed -- this is the same state the darkroom holds while it
5961 * draws, which is the point: no second code path. */
5962 if(IS_NULL_PTR(dev->form_gui))
5963 {
5964 dev->form_gui = (dt_masks_form_gui_t *)calloc(1, sizeof(dt_masks_form_gui_t));
5966 }
5967 if(!IS_NULL_PTR(dev->form_gui))
5968 {
5969 dev->form_gui->dev = dev;
5970 dev->form_gui->form_visible = form;
5971 dev->form_gui->formid = 0; // force the outline cache to rebuild for this render
5972 dt_dev_geometry_set_processed_size(dev, raw_width, raw_height);
5973
5975 = { .scale = (double)width / (double)raw_width, .offset_x = 0.0, .offset_y = 0.0 };
5976 int pw = 0, ph = 0;
5977 dt_dev_get_processed_size(dev, &pw, &ph);
5978 dt_print(DT_DEBUG_ALWAYS, "[masks debug] overlay: visible=%p processed=%dx%d scale=%.4f\n",
5979 (void *)dt_masks_get_visible_form(dev), pw, ph, transform.scale);
5980 dt_masks_events_post_expose_with(dev, NULL, cr, width, height, -1, -1, &transform);
5981 }
5982 }
5983
5984 cairo_destroy(cr);
5985 cairo_surface_flush(surface);
5986 const gboolean ok = (cairo_surface_write_to_png(surface, path) == CAIRO_STATUS_SUCCESS);
5987 cairo_surface_destroy(surface);
5988
5989 if(!ok) dt_print(DT_DEBUG_ALWAYS, "[masks debug] could not write %s\n", path);
5990 return ok;
5991}
5992
6023static inline void _emit_run(cairo_t *cr, const float *const points, const int first, const int last,
6024 const double min_step2)
6025{
6026 double last_x = points[first * 2];
6027 double last_y = points[first * 2 + 1];
6028 cairo_move_to(cr, last_x, last_y);
6029
6030 for(int i = first + 1; i < last; i++)
6031 {
6032 const double x = points[i * 2];
6033 const double y = points[i * 2 + 1];
6034 const double dx = x - last_x;
6035 const double dy = y - last_y;
6036 if((dx * dx + dy * dy) < min_step2) continue;
6037 cairo_line_to(cr, x, y);
6038 last_x = x;
6039 last_y = y;
6040 }
6041
6042 /* the run's last sample always lands, so a run ends where the geometry does and not wherever
6043 * the decimation happened to stop */
6044 const double x = points[(last - 1) * 2];
6045 const double y = points[(last - 1) * 2 + 1];
6046 if(x != last_x || y != last_y) cairo_line_to(cr, x, y);
6047}
6048
6049void dt_masks_draw_outline_runs(cairo_t *cr, const float *const points, const int first, const int last,
6050 const dt_masks_skip_range_t *skips, const int skip_count)
6051{
6052 if(!cr || !points || first >= last) return;
6053
6054 /* One line_to per sample at RAW resolution is several per device pixel; emit at the resolution
6055 * the context can actually show. See dt_draw_min_emit_step(). */
6056 const double min_step = dt_draw_min_emit_step(cr);
6057 const double min_step2 = min_step * min_step;
6058
6059 const int count = IS_NULL_PTR(skips) ? 0 : skip_count;
6060
6061 int at = first;
6062 int next = 0;
6063
6064 while(at < last)
6065 {
6066 while(next < count && skips[next].resume_at <= at) next++;
6067
6068 int run_end = last;
6069 if(next < count && skips[next].jump_from < last) run_end = MAX(skips[next].jump_from, at);
6070
6071 if(run_end > at) _emit_run(cr, points, at, run_end, min_step2);
6072
6073 if(next < count && skips[next].jump_from < last)
6074 {
6075 at = MAX(skips[next].resume_at, at + 1);
6076 next++;
6077 }
6078 else
6079 break;
6080 }
6081}
6082
6084{
6085 float source_pos[2] = { 0.0f, 0.0f };
6086 dt_masks_calculate_source_pos_origin(gui, gui->pos[0], gui->pos[1], gui->pos[0], gui->pos[1],
6087 &source_pos[0], &source_pos[1], FALSE);
6088 const float center_source[2] = { source_pos[0] - gui->pos[0], source_pos[1] - gui->pos[1] };
6089
6090 preview->source_points = dt_pixelpipe_cache_alloc_align_float_cache((size_t)2 * preview->points_count, 0);
6091 if(IS_NULL_PTR(preview->source_points)) return 1;
6092
6093 for(int i = 0; i < preview->points_count; i++)
6094 {
6095 preview->source_points[i * 2] = preview->points[i * 2] + center_source[0];
6096 preview->source_points[i * 2 + 1] = preview->points[i * 2 + 1] + center_source[1];
6097 }
6098
6099 return 0;
6100}
6101
6102
6104 float **points, int *points_count,
6105 const float xs, const float ys, const int first_shifted)
6106{
6107 // every distortion that happens BEFORE the module: the TARGET outline in module input reference
6109 *points, *points_count))
6110 goto error;
6111
6112 // the source anchor, taken to the same reference, gives the shift
6113 float pts[2] = { xs, ys };
6116 goto error;
6117
6118 {
6119 const float dx = pts[0] - (*points)[0];
6120 const float dy = pts[1] - (*points)[1];
6121
6122 // a shape with handle points in its header keeps them where they are and takes the anchor
6123 // verbatim; one whose point 0 is just the centre lets the loop below carry it
6124 if(first_shifted > 0)
6125 {
6126 (*points)[0] = pts[0];
6127 (*points)[1] = pts[1];
6128 }
6129
6130 __OMP_PARALLEL_FOR_SIMD__(if(*points_count > 100) aligned(points:64))
6131 for(int i = first_shifted; i < *points_count; i++)
6132 {
6133 (*points)[i * 2] += dx;
6134 (*points)[i * 2 + 1] += dy;
6135 }
6136 }
6137
6138 // and the distortions AFTER the module: the SOURCE outline in final image reference
6140 *points, *points_count))
6141 goto error;
6142
6143 return 0;
6144
6145error:
6147 *points = NULL;
6148 *points_count = 0;
6149 return 1;
6150}
6151
6152// clang-format off
6153// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
6154// vim: shiftwidth=2 expandtab tabstop=2 cindent
6155// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
6156// clang-format on
Handle default and user-set shortcuts (accelerators)
#define DT_PRIMARY_MASK
GtkWidget * dt_gui_main_window(void)
struct dt_ui_t * dt_gui_get_ui(void)
GtkWidget * dt_gui_center_widget(void)
static double dist(double x1, double y1, double x2, double y2)
Definition ashift_lsd.c:250
static void error(char *msg)
Definition ashift_lsd.c:202
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
Definition bauhaus.c:3343
float dt_bauhaus_slider_get(GtkWidget *widget)
Definition bauhaus.c:3280
int dt_bauhaus_combobox_get(GtkWidget *widget)
Definition bauhaus.c:2136
int dt_bauhaus_combobox_length(GtkWidget *widget)
Definition bauhaus.c:1963
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
Definition bauhaus.c:3331
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
Definition bauhaus.c:1504
GtkWidget * dt_bauhaus_slider_new_with_range(dt_bauhaus_t *bh, dt_gui_module_t *self, float min, float max, float step, float defval, int digits)
Definition bauhaus.c:1632
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
Definition bauhaus.c:1909
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
Definition bauhaus.c:3407
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
Definition bauhaus.c:1824
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
Definition bauhaus.c:3423
#define DT_BAUHAUS_WIDGET(obj)
Definition bauhaus.h:62
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
Definition blend_gui.c:3700
The blending panel's GUI state and API, cut out of develop/blend.h.
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
Definition clipping.c:487
static const float x
const float f
const float v
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
Definition colorspaces.h:98
static const float const float const float min
const float max
const dt_colormatrix_t dt_aligned_pixel_t out
static const int row
const dt_colormatrix_t matrix
const float delta
void dt_conf_set_bool(const char *name, int val)
int dt_conf_get_bool(const char *name)
void dt_conf_set_float(const char *name, float val)
float dt_conf_get_float(const char *name)
Float for name, clamped to its declared bounds.
void dt_conf_set_string(const char *name, const char *val)
const char * dt_conf_get_string_const(const char *name)
Borrow the stored string for name without copying it.
void dt_control_mouse_is_dragging(gboolean state)
Definition control.c:432
void dt_toast_log(const char *msg,...)
Definition control.c:871
void dt_control_log(const char *msg,...)
Definition control.c:824
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
Definition control.c:924
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
Definition control.c:981
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
Definition control.c:407
#define dt_control_queue_cursor(cursor)
Definition control.h:140
#define dt_control_set_cursor_visible(visible)
Definition control.h:153
struct dt_control_t * dt_control_get_global(void)
Definition darktable.c:651
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
Definition darktable.c:508
struct dt_gui_gtk_t * dt_gui_get_global(void)
Definition darktable.c:523
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
Definition darktable.c:646
gboolean dt_dev_geometry_get_raw_size(const dt_develop_t *dev, int32_t *width, int32_t *height)
void dt_dev_geometry_set_processed_size(dt_develop_t *dev, const int32_t width, const int32_t height)
dt_dev_image_geometry_t dt_dev_geometry_snapshot(const dt_develop_t *dev)
#define dt_dev_add_history_item(dev, module, enable, redraw)
void dt_dev_pixelpipe_change_zoom_main(dt_develop_t *dev)
gboolean dt_dev_viewport_set_center(dt_develop_t *dev, const float center_x, const float center_y)
void dt_dev_coordinates_image_abs_to_image_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1219
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
Definition develop.c:1109
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1765
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1255
float dt_dev_get_zoom_level(const dt_develop_t *dev)
Definition develop.c:1992
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:1355
gboolean dt_dev_clamp_viewport_center(dt_develop_t *dev)
Clamp the viewport centre against the box currently visible at this zoom.
Definition develop.c:1065
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:2081
int dt_dev_distort_transform_gui(dt_develop_t *dev, const double iop_order, const int transf_direction, float *points, size_t points_count)
The GUI's bounded transform folds, composed by the geometry service.
Definition develop.c:1691
int dt_dev_coordinates_image_abs_to_raw_abs(dt_develop_t *dev, float *points, size_t points_count)
Definition develop.c:1770
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1203
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:1144
void dt_dev_coordinates_raw_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1237
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
Definition develop.c:1285
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
Definition develop.c:1645
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
Definition develop.h:111
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
Definition develop.h:108
GtkWidget * geometry
its size, under the preview
GtkWidget * preview
what the selected row actually captures
GHashTable * selected
set of checked row labels, mirrored to conf on every change
#define DT_DRAW_RADIUS_NODE_SELECTED
Definition draw.h:95
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)
Definition draw.h:952
void(* shape_draw_function_t)(struct dt_develop_t *dev, cairo_t *cr, const float *points, const int points_count, const int nb, const gboolean border, const gboolean source, const struct dt_masks_skip_range_t *skips, const int skip_count)
Definition draw.h:815
static double dt_draw_min_emit_step(cairo_t *cr)
Stroke a line with style.
Definition draw.h:931
static void dt_draw_source_shape(struct dt_develop_t *dev, 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:1085
@ DT_MASKS_DASH_ROUND
Definition draw.h:136
@ DT_MASKS_NO_DASH
Definition draw.h:134
#define DT_DRAW_SCALE_ARROW
Definition draw.h:91
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:988
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
Definition draw.h:1116
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:299
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:217
const int res
Definition dtpthread.h:351
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
Definition dtpthread.h:267
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
Definition gdkkeys.h:118
uint64_t dt_geometry_chain_generation(const dt_geometry_chain_t *chain)
How many times this chain has been rebuilt. A GUI cache key for anything derived from the composed ge...
Definition geometry.c:319
Where things are on the image, answered without a pipeline.
GdkRGBA color[]
Definition geotagging.c:541
static gboolean g_list_shorter_than(const GList *list, unsigned len)
Definition glib_utils.h:34
void dt_group_events_post_expose_except(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, const int except_pos)
Definition group.c:164
void dt_group_events_post_expose_only(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, const int pos)
Definition group.c:172
void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
Definition group.c:179
#define DT_GUI_MODULE(x)
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
Definition hash.h:55
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
Definition imageop.h:186
@ IOP_FLAGS_NO_MASKS
Definition imageop.h:194
GtkWidget * dt_iop_togglebutton_new_no_register(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
GtkWidget * dt_iop_togglebutton_new(dt_iop_module_t *self, const char *section, const gchar *label, const gchar *ctrl_label, GCallback callback, gboolean local, guint accel_key, GdkModifierType mods, DTGTKCairoPaintIconFunc paint, GtkWidget *box)
gboolean dt_mask_scroll_increases(int up)
_lib_location_type_t type
Definition location.c:1
dt_map_box_t bbox
Definition location.c:4
@ DT_DEBUG_INPUT
Definition logging.h:64
@ DT_DEBUG_PERF
Definition logging.h:55
@ DT_DEBUG_ALWAYS
Definition logging.h:49
@ DT_DEBUG_MASKS
Definition logging.h:62
int32_t dt_get_debug_flags(void)
Definition darktable.c:2085
void dt_print(dt_debug_thread_t thread, const char *msg,...) __attribute__((format(printf
Print to stdout when thread is enabled, prefixed with seconds since startup.
float *const restrict const size_t k
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
Definition macros.h:96
dt_masks_result_t dt_masks_group_get_member(dt_develop_t *dev, const int group_id, const int formid, dt_masks_member_t *out)
Read one group member by identity.
Definition masks.c:1909
dt_masks_form_t * _group_create(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_type_t group_type)
Definition masks.c:170
dt_masks_raster_result_t dt_masks_get_points_border(dt_develop_t *develop, dt_masks_form_t *mask_form, float **point_buffer, int *point_count, float **border_buffer, int *border_count, dt_masks_skip_range_t **border_skips, int *border_skip_count, int source, dt_iop_module_t *module)
Definition masks.c:238
void _check_id(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition masks.c:125
dt_masks_result_t dt_masks_group_set_name_from_module(dt_develop_t *dev, const int group_id, dt_iop_module_t *module)
Name a group after the module that renders it, by id.
Definition masks.c:1808
dt_masks_result_t dt_masks_group_set_member_opacity(dt_develop_t *dev, const int group_id, const int formid, const float opacity, dt_masks_member_t *out)
Set a group member's opacity.
Definition masks.c:2029
int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
Definition masks.c:1384
void dt_masks_form_update_gravity_center(dt_develop_t *dev, dt_masks_form_t *mask_form)
Definition masks.c:1304
dt_masks_form_t * _group_from_module(dt_develop_t *develop, dt_iop_module_t *module)
Definition masks.c:181
const char * dt_masks_type_name(const dt_masks_type_t type)
The stable, untranslated token for a shape kind: circle, ellipse, polygon, brush, gradient,...
Definition masks.c:1767
void dt_masks_form_gui_points_free(gpointer data)
Definition masks.c:112
void dt_masks_free_form(dt_masks_form_t *form)
Definition masks.c:1140
void dt_masks_iop_use_same_as(struct dt_iop_module_t *module, struct dt_iop_module_t *src)
Definition masks.c:1157
dt_masks_raster_result_t
What a rasterisation attempt produced.
Definition masks.h:313
@ DT_MASKS_RASTER_ERROR
Definition masks.h:316
@ DT_MASKS_RASTER_OK
Definition masks.h:314
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
Definition masks.c:885
@ DT_MASKS_SOURCE_POS_RELATIVE_TEMP
Definition masks.h:189
@ DT_MASKS_SOURCE_POS_RELATIVE
Definition masks.h:188
@ DT_MASKS_SOURCE_POS_ABSOLUTE
Definition masks.h:190
dt_masks_event_t
Definition masks.h:143
@ DT_MASKS_EVENT_UPDATE
Definition masks.h:147
@ DT_MASKS_EVENT_ADD
Definition masks.h:145
@ DT_MASKS_EVENT_DELETE
Definition masks.h:148
@ DT_MASKS_EVENT_REMOVE
Definition masks.h:146
@ DT_MASKS_EVENT_CHANGE
Definition masks.h:149
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
Definition masks.c:851
void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
Definition masks.c:1349
@ DT_MASKS_PRESSURE_OFF
Definition masks.h:172
void dt_masks_form_delete(dt_develop_t *dev, struct dt_iop_module_t *module, dt_masks_form_t *grp, dt_masks_form_t *form)
Definition masks.c:1196
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
Definition masks.c:909
dt_masks_points_states_t
Definition masks.h:155
@ DT_MASKS_POINT_STATE_NORMAL
Definition masks.h:156
@ DT_MASKS_POINT_STATE_USER
Definition masks.h:157
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
Definition masks.h:754
float * dt_masks_debug_rasterise(dt_develop_t *dev, dt_masks_form_t *form, const int width, const int height)
Rasterise form into a freshly allocated width*height float buffer, 0..1.
Definition masks_debug.c:37
Render a mask's rasterisation and its GUI overlay to an image file, headlessly.
@ DT_MASKS_DEBUG_BACKDROP_TRANSPARENT
Definition masks_debug.h:57
@ DT_MASKS_DEBUG_BACKDROP_RASTER
Definition masks_debug.h:59
What a mask outline needs in order to place itself, and who supplies it.
The per-shape function table, private to the masks implementation.
Ask the masks module about a shape or a group, instead of reading its structs.
static void _masks_gui_menu_item_block_activate(GtkWidget *widget, gpointer user_data)
Definition masks_gui.c:431
void dt_masks_group_ungroup(dt_develop_t *dev, dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
Definition masks_gui.c:5356
int dt_masks_preview_add_clone_source(dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
Definition masks_gui.c:6083
static gboolean _canvas_begin(cairo_t *cr, const int width, const int height, _canvas_frame_t *frame)
Definition masks_gui.c:4296
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...
Definition masks_gui.c:2214
static uint64_t _static_layer_key_compute(cairo_t *canvas_cr, const float zoom_scale, const dt_masks_form_t *form, const dt_masks_form_gui_t *gui)
Definition masks_gui.c:4411
static void _bound_include_points(cairo_t *canvas_cr, const float *const array, const int limit, const double scale, cairo_rectangle_int_t *rect, gboolean *any)
Definition masks_gui.c:4148
static int _session_sigs_count
Definition masks_gui.c:3710
static gboolean _session_bbox_from_points(const dt_masks_form_gui_t *gui, double bbox[4])
Definition masks_gui.c:3769
static void _bbox_include_array(const float *const array, const int count, double bbox[4], gboolean *any)
Union of the cached outlines, or FALSE when there is nothing to bound.
Definition masks_gui.c:3743
static void _session_bbox_invalidate(void)
Definition masks_gui.c:3805
static void _session_sigs_reset(void)
Definition masks_gui.c:3712
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.
Definition masks_gui.c:3368
static void _overlay_dump_outline(const dt_masks_form_gui_points_t *const pts, const char *path)
Definition masks_gui.c:4498
static gboolean _masks_gui_menu_item_forward_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition masks_gui.c:436
static void _canvas_composite(cairo_t *cr, const _canvas_frame_t *frame, cairo_surface_t *surface, const int x0, const int y0, const int x1, const int y1)
Definition masks_gui.c:4353
void dt_masks_gui_set_dragging(dt_masks_form_gui_t *gui)
Definition masks_gui.c:1449
GtkWidget * dt_masks_gui_add_interaction_slider(GtkWidget *menu, const char *label, dt_develop_t *dev, const int group_id, const int formid, dt_masks_interaction_t interaction, dt_masks_increment_t increment, float min, float max, float step, float value, int digits, const char *format, float factor, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
Append a bauhaus-slider menu item to a mask context menu, bound to one shape interaction (size,...
Definition masks_gui.c:519
static int _overlay_skip_of(const dt_masks_form_gui_points_t *const pts, const int i)
Definition masks_gui.c:4490
static int _canvas_height
Definition masks_gui.c:4040
static _session_outline_sig_t * _session_sigs
Definition masks_gui.c:3709
void dt_masks_clear_form_gui(dt_develop_t *develop)
Definition masks_gui.c:4777
static void _menu_add_exist(dt_iop_module_t *module, int form_id)
Definition masks_gui.c:4946
static gboolean _dt_masks_events_should_update_hover_on_move(dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:1703
void dt_masks_scroll_mapping_set(dt_masks_scroll_modifier_t modifier, dt_masks_interaction_t interaction)
Definition masks_gui.c:3140
static gboolean _overlay_apply_transform(cairo_t *mask_draw, dt_develop_t *develop, const dt_masks_overlay_transform_t *transform, const int width, const int height)
Definition masks_gui.c:4598
gboolean dt_masks_reset_bezier_ctrl_points(struct dt_iop_module_t *module, struct dt_masks_form_t *mask_form, struct dt_masks_form_gui_t *mask_gui, const int form_index, const struct dt_masks_form_gui_points_t *gui_points, const int node_index, dt_masks_points_states_t *state)
Definition masks_gui.c:1123
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)
Definition masks_gui.c:2020
void dt_masks_events_post_expose(dt_develop_t *dev, struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery)
Definition masks_gui.c:4017
void dt_masks_gui_populate_interaction_sliders(GtkWidget *menu, dt_develop_t *dev, dt_masks_form_t *form, const int group_id, dt_masks_form_gui_t *gui, dt_iop_module_t *module)
Append the full set of shape-parameter sliders (size/fading/rotation or curvature/fade/rotation depen...
Definition masks_gui.c:798
static uint64_t _session_pattern_key
Definition masks_gui.c:3673
GtkWidget * dt_masks_shape_menu_item_new(GtkWidget *menu, const dt_masks_type_t type, GCallback activate_callback, gpointer user_data)
Append a menu entry for one shape type: its toolbar icon, then its toolbar label.
Definition masks_gui.c:210
static void _masks_move_up_down_callback(gpointer user_data, const int up)
Definition masks_gui.c:724
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.
Definition masks_gui.c:1157
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 ...
Definition masks_gui.c:5275
static void _menu_no_masks(struct dt_iop_module_t *module)
Definition masks_gui.c:4929
gboolean dt_masks_toggle_bezier_node_type(struct dt_iop_module_t *module, struct dt_masks_form_t *mask_form, struct dt_masks_form_gui_t *mask_gui, const int form_index, const struct dt_masks_form_gui_points_t *gui_points, const int node_index, float node[2], float ctrl1[2], float ctrl2[2], dt_masks_points_states_t *state)
Definition masks_gui.c:1095
static gboolean _overlay_pointer_valid
Definition masks_gui.c:2822
int dt_masks_events_button_released(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, int button, uint32_t state)
Definition masks_gui.c:2906
void dt_masks_gui_points_update_bbox(dt_masks_form_gui_points_t *gp)
Definition masks_gui.c:2421
int dt_masks_group_index_from_formid(const dt_masks_form_t *group_form, int form_id)
Find any group that currently references formid, searching every top-level group in dev->forms and th...
Definition masks_gui.c:1388
void dt_masks_gui_form_remove(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
Definition masks_gui.c:2433
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
Definition masks_gui.c:4840
static const char *const _interaction_conf_values[DT_MASKS_INTERACTION_LAST]
Definition masks_gui.c:3120
static void _set_cursor_shape(dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2676
gboolean dt_masks_form_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
Definition masks_gui.c:5156
static void _canvas_cairo_bound(cairo_t *canvas_cr, const dt_masks_form_gui_t *gui, cairo_rectangle_int_t *rect, gboolean *any)
Definition masks_gui.c:4216
static uint64_t _session_gui_generation
Definition masks_gui.c:3654
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)
Definition masks_gui.c:2456
static gboolean _masks_shape_button_pressed(GtkWidget *button, GdkEventButton *event, gpointer user_data)
Definition masks_gui.c:239
static int _masks_shape_button_index(const dt_masks_shape_buttons_data_t *data, GtkWidget *button)
Definition masks_gui.c:118
int dt_masks_gui_outline_step(const dt_develop_t *dev)
The density the dev's GUI shows its outlines at, in image pixels between consecutive samples: what th...
Definition masks_gui.c:1908
void dt_masks_gui_reset_dragging(dt_masks_form_gui_t *gui)
Definition masks_gui.c:1463
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)
Definition masks_gui.c:2715
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)
Definition masks_gui.c:1976
int dt_masks_events_button_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int button, int event_type, uint32_t state)
Definition masks_gui.c:2961
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:602
static uint64_t _session_pattern_hash(cairo_t *cr, const dt_masks_form_gui_t *creation_gui, const uint64_t generation, const int count)
What the cached rendering is a rendering OF.
Definition masks_gui.c:3811
static gboolean _session_gui_inited
Definition masks_gui.c:3653
static int _masks_gui_form_group_use_count(const dt_develop_t *dev, const int formid)
Definition masks_gui.c:2094
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.
Definition masks_gui.c:1522
static int _canvas_width
Definition masks_gui.c:4039
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.
Definition masks_gui.c:1600
gboolean dt_masks_is_anything_selected(const dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2658
void dt_masks_soft_reset_form_gui(dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:1877
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.
Definition masks_gui.c:2796
const char * dt_masks_interaction_alias_name(dt_masks_interaction_t interaction)
The other word for a property, when a shape family spells it its own way.
Definition masks_gui.c:3183
static void _emit_run(cairo_t *cr, const float *const points, const int first, const int last, const double min_step2)
Append a shape's outline to cr as the COMPLEMENT of its exclusion list.
Definition masks_gui.c:6023
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.
Definition masks_gui.c:1539
static void _session_bound_include(cairo_t *canvas_cr, const float zoom_scale, cairo_rectangle_int_t *rect, gboolean *any)
Definition masks_gui.c:4243
int dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
Definition masks_gui.c:2824
void dt_masks_gui_set_outline_density(dt_masks_form_gui_t *mask_gui, const double image_px_per_device_px)
Definition masks_gui.c:1901
static void _menu_add_shape(struct dt_iop_module_t *module, dt_masks_type_t type)
Definition masks_gui.c:4941
static void _masks_gui_interaction_apply_value(dt_masks_gui_interaction_slider_t *data, float value)
Definition masks_gui.c:407
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2505
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.
Definition masks_gui.c:1568
dt_masks_form_group_t * dt_masks_form_group_from_parentid(dt_develop_t *dev, int parent_id, int form_id)
Return the group entry for a (parent, form) pair.
Definition masks_gui.c:1489
const char * dt_masks_interaction_name(dt_masks_interaction_t interaction)
Definition masks_gui.c:3165
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)
Definition masks_gui.c:2123
void dt_masks_events_post_expose_with(dt_develop_t *dev, struct dt_iop_module_t *module, cairo_t *cr, int32_t width, int32_t height, int32_t pointerx, int32_t pointery, const dt_masks_overlay_transform_t *transform)
Draw the mask overlay with an explicit mapping; transform NULL means the viewport's.
Definition masks_gui.c:4663
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
Definition masks_gui.c:113
static void _masks_movedown_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:751
static gboolean _overlay_damage_valid
Definition masks_gui.c:4548
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.
Definition masks_gui.c:5706
static void _masks_gui_interaction_slider_changed(GtkWidget *widget, gpointer user_data)
Definition masks_gui.c:511
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
Definition masks_gui.c:308
static void _canvas_clear(cairo_surface_t *canvas, const cairo_rectangle_int_t *rect)
Definition masks_gui.c:4067
static const dt_masks_shape_button_def_t _masks_shape_button_defs[]
Definition masks_gui.c:81
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)
Definition masks_gui.c:1719
static void _overlay_draw_form(cairo_t *cr, const float zoom_scale, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, const gboolean layered)
Definition masks_gui.c:4621
static gboolean _masks_shape_icon_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
Definition masks_gui.c:190
static cairo_pattern_t * _session_pattern
Definition masks_gui.c:3672
static void _masks_moveup_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:746
static void _masks_shape_buttons_destroy(GtkWidget *widget, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:293
void _masks_gui_delete_node_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:679
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...
Definition masks_gui.c:3282
static void _session_pattern_reset(void)
Drop the session outlines. Safe to call when nothing is cached.
Definition masks_gui.c:3798
static cairo_surface_t * _static_layer
Definition masks_gui.c:4397
void dt_masks_group_entry_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.
Definition masks_gui.c:5795
dt_masks_form_group_t * dt_masks_group_add_form(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form)
Definition masks_gui.c:5347
static const char *const _scroll_conf_keys[DT_MASKS_SCROLL_MODIFIER_LAST]
Definition masks_gui.c:3111
static double _overlay_pointer[2]
Definition masks_gui.c:2820
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.
Definition masks_gui.c:2050
static gboolean _session_clip(cairo_t *cr, const float zoom_scale)
Clip to the session's area, with room for the stroke's own width.
Definition masks_gui.c:3783
#define masks_gtk_menu_item_new_bold(label, selected, state, icon)
Definition masks_gui.c:780
static gboolean _masks_gui_resolve_selected_shape(dt_masks_form_gui_t *gui, dt_iop_module_t **module, dt_masks_form_t **sel, int *parentid, int *formid)
Definition masks_gui.c:634
static gboolean _masks_shape_button_is_current_creation(dt_develop_t *dev, const dt_masks_shape_buttons_data_t *data, const int button_index)
Definition masks_gui.c:129
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
Definition masks_gui.c:1408
int dt_masks_events_mouse_leave(struct dt_iop_module_t *module)
Definition masks_gui.c:2648
static cairo_surface_t * _canvas_acquire(const int width, const int height, const double device_scale)
Definition masks_gui.c:4042
gboolean dt_masks_gui_remove(struct dt_iop_module_t *module, dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, const int parent_id)
remove a mask shape or node form from the GUI. This function is used with a popupmenu "Delete" action...
Definition masks_gui.c:2378
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 gboolean round_ends)
Definition masks_gui.c:3483
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.
Definition masks_gui.c:1794
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
Definition masks_gui.c:1475
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....
Definition masks_gui.c:2235
static void _overlay_dump(const _canvas_frame_t *const frame, const dt_masks_form_gui_t *const gui, const cairo_rectangle_int_t *const dirty)
Definition masks_gui.c:4513
void dt_masks_reset_show_masks_icons(dt_develop_t *dev)
Definition masks_gui.c:4909
static gboolean _static_layer_ensure(const _canvas_frame_t *frame, cairo_t *canvas_cr, const float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui, gboolean *rebuilt)
Definition masks_gui.c:4442
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.
Definition masks_gui.c:3307
static gboolean _static_layer_applies(const dt_masks_form_t *form, const dt_masks_form_gui_t *gui)
Definition masks_gui.c:4484
static cairo_surface_t * _canvas
Definition masks_gui.c:4038
static gboolean header_reach_wanted(const int nodes, const int points_count)
Definition masks_gui.c:4132
int dt_masks_gui_confirm_delete_form_dialog(const char *form_name)
Definition masks_gui.c:578
void dt_masks_shape_buttons_sync_all(void)
Make every shape toolbar show the shape currently being created, if any.
Definition masks_gui.c:176
void dt_masks_iop_combo_populate(GtkWidget *widget, void *data)
Definition masks_gui.c:4981
static void _overlay_damage_record(cairo_t *cr, const _canvas_frame_t *const frame, const cairo_rectangle_int_t *dirty)
Definition masks_gui.c:4570
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.
Definition masks_gui.c:5642
const char * dt_masks_scroll_modifier_name(dt_masks_scroll_modifier_t modifier)
Definition masks_gui.c:3149
gboolean dt_masks_debug_write_png(dt_develop_t *dev, dt_masks_form_t *form, const dt_masks_debug_request_t *request, const char *path)
Render form per request and write it to path as a PNG.
Definition masks_gui.c:5910
static void _masks_gui_remove_from_group_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:656
const char * _get_mask_plugin(dt_masks_form_t *mask_form)
Definition masks_gui.c:5236
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
Definition masks_gui.c:5246
int dt_masks_events_mouse_enter(struct dt_iop_module_t *module)
Definition masks_gui.c:2653
static double _overlay_pointer_delta[2]
Definition masks_gui.c:2821
static int _dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
Definition masks_gui.c:2851
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.
Definition masks_gui.c:5260
static int _dt_masks_events_update_hover(dt_masks_form_t *dispatch_form, dt_masks_form_gui_t *mask_gui, const int form_index)
Definition masks_gui.c:1711
static void _masks_shape_buttons_deactivate(GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:94
static void _bound_include_headers(cairo_t *canvas_cr, const dt_masks_form_gui_points_t *const pts, const int nodes, const double scale, cairo_rectangle_int_t *rect, gboolean *any)
Definition masks_gui.c:4162
void dt_masks_draw_outline_runs(cairo_t *cr, const float *const points, const int first, const int last, const dt_masks_skip_range_t *skips, const int skip_count)
Definition masks_gui.c:6049
static const dt_masks_shape_button_def_t * _masks_shape_button_def(const dt_masks_type_t type)
Definition masks_gui.c:181
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.
Definition masks_gui.c:1741
void dt_masks_gui_cleanup(dt_develop_t *dev)
Definition masks_gui.c:1436
static double _session_bbox[4]
Definition masks_gui.c:3683
static cairo_rectangle_int_t _overlay_damage
Definition masks_gui.c:4547
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...
Definition masks_gui.c:2242
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.
Definition masks_gui.c:5570
static void _masks_shape_buttons_deactivate_signal(gpointer instance, GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:107
void dt_masks_reset_form_gui(dt_develop_t *dev)
Definition masks_gui.c:4894
GtkWidget * dt_masks_create_menu(dt_masks_form_gui_t *gui, dt_masks_form_t *form, const dt_masks_form_group_t *formgroup, const float pzx, const float pzy)
Definition masks_gui.c:865
#define DT_MASKS_SHAPE_BUTTON_COUNT
Definition masks_gui.c:58
static void _paint_mask_backdrop(cairo_t *cr, dt_develop_t *dev, dt_masks_form_t *form, const int width, const int height)
Definition masks_gui.c:5880
void dt_masks_set_visible_form(dt_develop_t *dev, dt_masks_form_t *form)
Definition masks_gui.c:1414
static void _masks_gui_full_delete_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:668
static void _session_gui_reset(void)
Definition masks_gui.c:3833
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.
Definition masks_gui.c:1760
int dt_masks_events_key_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, GdkEventKey *event)
Definition masks_gui.c:3040
static void _masks_operation_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:758
void dt_masks_iop_value_changed_callback(GtkWidget *widget, struct dt_iop_module_t *module)
Definition masks_gui.c:5065
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.
Definition masks_gui.c:5177
void dt_masks_overlay_queue_redraw(GtkWidget *widget)
Definition masks_gui.c:4550
void dt_masks_remove_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
Definition masks_gui.c:2515
dt_masks_interaction_t dt_masks_scroll_get_interaction(uint32_t key_state)
Resolve a wheel event's key state into the property it edits.
Definition masks_gui.c:3196
static gboolean _session_bbox_valid
Definition masks_gui.c:3682
gboolean dt_masks_creation_mode_enter(dt_develop_t *dev, dt_iop_module_t *module, const dt_masks_type_t type)
Enter mask creation mode for a given shape type.
Definition masks_gui.c:5762
dt_masks_form_group_t * dt_masks_form_group_find_entry(dt_masks_form_t *group_form, const int form_id, int *out_index)
Find the group-membership entry for form_id inside group_form's own points list (not recursive into s...
Definition masks_gui.c:1368
static void _masks_gui_exit_creation_callback(GtkWidget *menu, gpointer user_data)
Definition masks_gui.c:716
static int _outline_nodes(dt_develop_t *dev, const dt_masks_form_t *form, const int index)
Definition masks_gui.c:4201
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, const dt_masks_skip_range_t *skips, int skip_count)
Check whether any 2D point in pts[] lies inside the form points[].
Definition masks_gui.c:5403
static void _masks_shape_buttons_sync(dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:151
static int _session_gui_count
Definition masks_gui.c:3655
static void _masks_gui_interaction_commit(dt_masks_gui_interaction_slider_t *data)
Definition masks_gui.c:398
static int _session_sigs_take(const dt_masks_form_gui_t *gui, _session_outline_sig_t *out, const int max)
Take the signature of every cached outline.
Definition masks_gui.c:3719
static void _masks_shape_buttons_sync_signal(gpointer instance __attribute__((unused)), dt_masks_shape_buttons_data_t *data)
Definition masks_gui.c:170
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)
Definition masks_gui.c:3845
static int _static_layer_width
Definition masks_gui.c:4398
static gboolean _overlay_dirty(const _canvas_frame_t *frame, const cairo_rectangle_int_t *cairo_bound, const gboolean cairo_bounded, cairo_rectangle_int_t *dirty)
Definition masks_gui.c:4644
int dt_masks_events_mouse_scrolled(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, int scroll_up, uint32_t key_state, int scrolling_delta)
Definition masks_gui.c:3218
static int _static_layer_height
Definition masks_gui.c:4399
static dt_masks_form_gui_t _session_gui
Draw completed shapes from the current creation session.
Definition masks_gui.c:3652
gboolean dt_masks_is_anything_hovered(const dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2668
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.
Definition masks_gui.c:1183
void dt_masks_init_form_gui(dt_develop_t *dev, dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:1847
static void _overlay_finish(cairo_t *cr, const _canvas_frame_t *const frame, const cairo_rectangle_int_t *dirty, const gboolean darkroom_frame, const gboolean layered)
Definition masks_gui.c:4610
void dt_masks_gui_init(dt_develop_t *dev)
Definition masks_gui.c:1420
void dt_masks_creation_mode_quit(dt_masks_form_gui_t *mask_gui)
Exit mask creation mode, restoring cursor visibility and resetting GUI state.
Definition masks_gui.c:5750
void dt_masks_group_update_name(dt_iop_module_t *module)
Definition masks_gui.c:4968
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
Definition masks_gui.c:5809
static uint64_t _static_layer_key
Definition masks_gui.c:4400
static uint64_t _static_layer_hash_samples(uint64_t hash, const float *const array, const int count)
Definition masks_gui.c:4402
dt_masks_interaction_t dt_masks_scroll_mapping_get(dt_masks_scroll_modifier_t modifier)
Definition masks_gui.c:3127
int dt_masks_form_change_opacity(dt_develop_t *dev, dt_masks_form_t *mask_form, int parent_id, int scroll_up, const int flow)
Definition masks_gui.c:5840
static void _canvas_end(cairo_t *cr, const _canvas_frame_t *frame, const cairo_rectangle_int_t *dirty)
Definition masks_gui.c:4372
static double _canvas_device_scale(cairo_t *cr)
Definition masks_gui.c:4138
float dt_masks_form_set_interaction_value(dt_develop_t *dev, const int group_id, const int formid, 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)
Definition masks_gui.c:5196
int dt_masks_points_shift_to_source(dt_develop_t *dev, const dt_iop_module_t *module, float **points, int *points_count, const float xs, const float ys, const int first_shifted)
Definition masks_gui.c:6103
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.
Definition masks_gui.c:5545
static void _rect_include(cairo_rectangle_int_t *rect, gboolean *any, const double x, const double y, const int margin)
Definition masks_gui.c:4082
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)
Definition masks_gui.c:1914
float dt_masks_form_get_interaction_value(dt_develop_t *dev, const int group_id, const int formid, dt_masks_interaction_t interaction)
Definition masks_gui.c:5135
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.
Definition masks_gui.c:5305
dt_masks_form_group_t * dt_masks_group_add_form_with_state(dt_develop_t *dev, dt_masks_form_t *group_form, dt_masks_form_t *mask_form, const int parentid, const dt_masks_state_t state, const float opacity)
Definition masks_gui.c:5317
void dt_masks_select_form(dt_develop_t *dev, 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.
Definition masks_gui.c:5527
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *mask_gui)
Definition masks_gui.c:2250
static void _gui_points_bbox_extend(float *const bbox, const float *const samples, const int count)
Definition masks_gui.c:2408
static double _bound_header_reach(cairo_t *canvas_cr, const dt_masks_form_gui_points_t *const pts, const int nodes, const double scale)
Definition masks_gui.c:4178
static gboolean _overlay_clip_to_bound(cairo_t *mask_draw, const dt_masks_form_gui_t *gui, const _canvas_frame_t *const frame, cairo_rectangle_int_t *bound, const float zoom_scale)
Definition masks_gui.c:4269
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.
Definition masks_gui.c:2525
The interactive half of the masks subsystem: the editing state (dt_masks_form_gui_t),...
static int dt_masks_gui_selected_segment_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:250
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:235
@ DT_MASKS_SHAPE_INDEX_BRUSH
Definition masks_gui.h:480
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
Definition masks_gui.h:478
@ DT_MASKS_SHAPE_INDEX_CIRCLE
Definition masks_gui.h:479
@ DT_MASKS_SHAPE_INDEX_GRADIENT
Definition masks_gui.h:476
@ DT_MASKS_SHAPE_INDEX_POLYGON
Definition masks_gui.h:477
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_gui.h:933
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_handle, int *inside_source, float *dist, void *user_data)
Shape-specific callback for inside/border/segment hit testing.
Definition masks_gui.h:926
dt_masks_scroll_modifier_t
Definition masks_gui.h:774
@ DT_MASKS_SCROLL_SHIFT
Definition masks_gui.h:776
@ DT_MASKS_SCROLL_PRIMARY
Definition masks_gui.h:777
@ DT_MASKS_SCROLL_PLAIN
Definition masks_gui.h:775
@ DT_MASKS_SCROLL_PRIMARY_SHIFT
Definition masks_gui.h:778
@ DT_MASKS_SCROLL_MODIFIER_LAST
Definition masks_gui.h:779
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_gui.h:904
#define menu_item_set_fake_accel(menu_item, keyval, mods)
Definition masks_gui.h:958
static gboolean dt_masks_gui_was_anything_selected(const dt_masks_form_gui_t *gui)
Definition masks_gui.h:230
static gboolean dt_masks_gui_should_hit_test(dt_masks_form_gui_t *gui)
Definition masks_gui.h:288
@ DT_MASKS_SHAPE_BUTTONS_GRADIENT
Definition masks_gui.h:496
@ DT_MASKS_SHAPE_BUTTONS_CIRCLE
Definition masks_gui.h:488
@ DT_MASKS_SHAPE_BUTTONS_BRUSH
Definition masks_gui.h:494
@ DT_MASKS_SHAPE_BUTTONS_ELLIPSE
Definition masks_gui.h:490
@ DT_MASKS_SHAPE_BUTTONS_POLYGON
Definition masks_gui.h:492
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_gui.h:918
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_gui.h:911
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)
dt_masks_edit_mode_t
@ DT_MASKS_EDIT_OFF
@ DT_MASKS_EDIT_FULL
dt_masks_state_t
Definition masks_types.h:87
@ DT_MASKS_STATE_DIFFERENCE
Definition masks_types.h:94
@ DT_MASKS_STATE_INVERSE
Definition masks_types.h:91
@ DT_MASKS_STATE_INTERSECTION
Definition masks_types.h:93
@ DT_MASKS_STATE_IS_COMBINE_OP
Definition masks_types.h:98
@ DT_MASKS_STATE_SHOW
Definition masks_types.h:90
@ DT_MASKS_STATE_EXCLUSION
Definition masks_types.h:95
@ DT_MASKS_STATE_UNION
Definition masks_types.h:92
@ DT_MASKS_STATE_USE
Definition masks_types.h:89
dt_masks_type_t
Definition masks_types.h:62
@ DT_MASKS_NON_CLONE
Definition masks_types.h:71
@ DT_MASKS_ALL
Definition masks_types.h:73
@ DT_MASKS_POLYGON
Definition masks_types.h:65
@ DT_MASKS_BRUSH
Definition masks_types.h:70
@ DT_MASKS_IS_PATH_SHAPE
Definition masks_types.h:81
@ DT_MASKS_IS_OPEN_SHAPE
Definition masks_types.h:77
@ DT_MASKS_ELLIPSE
Definition masks_types.h:69
@ DT_MASKS_CLONE
Definition masks_types.h:67
@ DT_MASKS_GRADIENT
Definition masks_types.h:68
@ DT_MASKS_NONE
Definition masks_types.h:63
@ DT_MASKS_CIRCLE
Definition masks_types.h:64
@ DT_MASKS_GROUP
Definition masks_types.h:66
@ DT_MASKS_IS_PRIMITIVE_SHAPE
Definition masks_types.h:82
@ DT_MASKS_IS_CLOSED_SHAPE
Definition masks_types.h:76
@ DT_MASKS_IS_RETOUCHE
Definition masks_types.h:79
dt_masks_interaction_t
@ DT_MASKS_INTERACTION_OPACITY
@ DT_MASKS_INTERACTION_SIZE
@ DT_MASKS_INTERACTION_FADING
@ DT_MASKS_INTERACTION_LAST
@ DT_MASKS_INTERACTION_UNDEF
@ DT_MASKS_INTERACTION_ROTATION
dt_masks_result_t
What a request that may change the masks model did.
@ DT_MASKS_OK
@ DT_MASKS_UNCHANGED
dt_masks_increment_t
@ DT_MASKS_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
@ DT_MASKS_INCREMENT_ABSOLUTE
static float f_inv_sqrtf(const float x)
Definition math.h:476
#define CLAMPF(a, mn, mx)
Definition math.h:91
#define M_PI
Definition math.h:47
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
Definition mem_alloc.h:214
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
Definition mem_alloc.h:171
GtkWidget * ctx_gtk_menu_item_new_with_markup(const char *label, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:170
GtkWidget * ctx_gtk_menu_item_new_with_markup_and_pixbuf(const char *label, GdkPixbuf *icon, GtkWidget *menu, void(*activate_callback)(GtkWidget *widget, gpointer user_data), gpointer user_data)
Definition menu.c:215
char * key
uint32_t width
Definition mipmap_cache.c:0
uint32_t height
Definition mipmap_cache.c:1
size_t size
Definition mipmap_cache.c:3
#define __OMP_PARALLEL_FOR_SIMD__(...)
Definition openmp.h:96
const float factor
Definition pdf.h:91
#define dt_pixelpipe_cache_alloc_align_float_cache(pixels, id)
#define dt_pixelpipe_cache_free_align(mem)
void copy(TYPE *dest, TYPE *source, size_t num_el)
Copy a flat buffer used to initialize test matrices.
int main()
Definition prova.c:47
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
Definition signal.h:407
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
Definition signal.h:386
struct dt_control_signal_t * dt_control_signal_get_global(void)
Definition darktable.c:616
@ DT_SIGNAL_MASK_CHANGED
Definition signal.h:315
@ DT_SIGNAL_MASK_SHAPE_BUTTONS_SYNC
Definition signal.h:320
@ DT_SIGNAL_MASK_SHAPE_BUTTONS_DEACTIVATE
Definition signal.h:316
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
Definition signal.h:396
float dt_aligned_pixel_simd_t __attribute__((vector_size(16), aligned(16)))
Apply one channel's tone curve to each of the three colour channels, or pass the channel through unto...
Definition simd.h:55
static const dt_aligned_pixel_simd_t value
Definition simd.h:144
const float uint32_t state[4]
void dt_stroke_raster_touched_reset(cairo_surface_t *surface)
gboolean dt_stroke_raster_touched(cairo_surface_t *surface, cairo_rectangle_int_t *touched)
unsigned __int64 uint64_t
Definition strptime.c:75
double device_scale
Definition masks_gui.c:4128
cairo_surface_t * canvas
Definition masks_gui.c:4122
Objective facts about the image a dev is working on.
GList * iop
Definition develop.h:269
struct dt_iop_module_t * gui_module
Definition develop.h:170
gboolean darkroom_skip_mouse_events
Definition develop.h:482
GList * allforms
Definition develop.h:312
struct dt_masks_form_gui_t * form_gui
Definition develop.h:310
dt_pthread_rwlock_t masks_mutex
Definition develop.h:316
struct dt_geometry_chain_t * geometry_chain
Definition develop.h:491
GList * forms
Definition develop.h:304
GtkWidget * masks_combo
Definition blend_gui.h:138
struct dt_develop_blend_params_t * blend_params
Definition imageop.h:349
struct dt_iop_module_gui_t * gui
Definition imageop.h:346
struct dt_develop_t * dev
Definition imageop.h:311
What to render. Zeroed means: raster only, at full image resolution, on black.
Definition masks_debug.h:64
dt_masks_debug_backdrop_t backdrop
Definition masks_debug.h:67
dt_masks_skip_range_t * border_skips
Definition masks_gui.h:70
gboolean rebuild_pending
Definition masks_gui.h:173
gboolean node_selected
Definition masks_gui.h:145
gboolean handle_border_selected
Definition masks_gui.h:148
gboolean border_toggling
Definition masks_gui.h:163
dt_masks_dynbuf_t * guipoints_payload
Definition masks_gui.h:110
gboolean source_selected
Definition masks_gui.h:153
dt_masks_type_t creation_type
Definition masks_gui.h:182
uint64_t geometry_generation
Definition masks_gui.h:200
gboolean source_dragging
Definition masks_gui.h:161
dt_masks_edit_mode_t edit_mode
Definition masks_gui.h:138
dt_iop_module_t * creation_module
Definition masks_gui.h:180
gboolean creation_closing_form
Definition masks_gui.h:179
GList * creation_formids
Definition masks_gui.h:184
dt_masks_pressure_sensitivity_t pressure_sensitivity
Definition masks_gui.h:188
gboolean seg_selected
Definition masks_gui.h:147
gboolean form_dragging
Definition masks_gui.h:160
gboolean gradient_toggling
Definition masks_gui.h:164
dt_masks_type_t type
Definition masks_gui.h:103
dt_masks_form_t * form_visible
Definition masks_gui.h:105
gboolean form_selected
Definition masks_gui.h:151
gboolean handle_selected
Definition masks_gui.h:146
gboolean form_rotating
Definition masks_gui.h:162
dt_masks_dynbuf_t * guipoints
Definition masks_gui.h:110
gboolean border_selected
Definition masks_gui.h:152
float last_rebuild_pos[2]
Definition masks_gui.h:172
gboolean pivot_selected
Definition masks_gui.h:154
float last_hit_test_pos[2]
Definition masks_gui.h:176
struct dt_develop_t * dev
Definition masks_gui.h:101
const dt_masks_functions_t * functions
Definition masks.h:255
dt_masks_type_t type
Definition masks.h:254
gboolean gravity_center_valid
Definition masks.h:270
float source[2]
Definition masks.h:261
char name[128]
Definition masks.h:277
GList * points
Definition masks.h:253
float gravity_center[2]
Definition masks.h:265
gboolean uses_bezier_points_layout
Definition masks.h:257
float(* get_interaction_value)(const struct dt_masks_form_t *form, dt_masks_interaction_t interaction)
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)
void(* initial_source_pos)(struct dt_develop_t *dev, const float iwd, const float iht, float *x, float *y)
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)
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)
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)
void(* set_form_name)(struct dt_masks_form_t *const form, const size_t nb)
int(* populate_context_menu)(GtkWidget *menu, struct dt_masks_form_t *form, struct dt_masks_form_gui_t *gui, const float pzx, const float pzy)
int(* update_hover)(struct dt_masks_form_t *form, struct dt_masks_form_gui_t *gui, int index)
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)
void(* set_hint_message)(const struct dt_masks_form_gui_t *const gui, const struct dt_masks_form_t *const form, char *const __restrict__ msgbuf, const size_t msgbuf_len)
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)
gboolean(* get_gravity_center)(struct dt_develop_t *dev, const struct dt_masks_form_t *form, float center[2], float *area)
void(* post_expose)(cairo_t *cr, float zoom_scale, struct dt_masks_form_gui_t *gui, int index, int num_points)
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_handle, int *inside_source, float *dist)
void(* init_ctrl_points)(struct dt_masks_form_t *form)
struct dt_masks_gui_center_point_t::@27 main
struct dt_masks_gui_center_point_t::@28 source
dt_iop_module_t *dt_masks_interaction_t interaction
Definition masks_gui.c:385
dt_masks_form_gui_t * gui
Definition masks_gui.c:383
dt_masks_increment_t increment
Definition masks_gui.c:386
One shape's membership of one group, BY VALUE.
How the overlay maps image coordinates onto its cairo target.
Definition masks_gui.h:598
DTGTKCairoPaintIconFunc paint
Definition masks_gui.c:70
dt_iop_module_t * creation_module
Definition masks_gui.h:516
dt_masks_shape_buttons_flags_t register_flags
Definition masks_gui.h:521
dt_masks_shape_buttons_type_f form_type
Definition masks_gui.h:525
dt_masks_shape_buttons_start_f can_start
Definition masks_gui.h:524
dt_masks_shape_buttons_notify_f exited
Definition masks_gui.h:527
struct dt_develop_t * dev
Definition masks_gui.h:514
dt_masks_shape_buttons_notify_f started
Definition masks_gui.h:526
dt_iop_module_t * owner_module
Definition masks_gui.h:515
dt_masks_shape_buttons_flags_t flags
Definition masks_gui.h:520
dt_masks_shape_buttons_config_t config
Definition masks_gui.c:78
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
double x
double width
double y
typedef double((*spd)(unsigned long int wavelength, double TempK))
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29
void dt_show_times(const dt_times_t *start, const char *prefix)
Definition darktable.c:2138
static void dt_get_times(dt_times_t *t)
Definition times.h:50
static double dt_get_wtime(void)
Definition times.h:43
GtkWidget * dtgtk_togglebutton_new(DTGTKCairoPaintIconFunc paint, gint paintflags, void *paintdata)
GdkRGBA * dt_widget_colors(void)
gboolean dt_gui_widgets_suppressed(void)
const dt_widget_overlay_color_t * dt_widget_overlay_color(void)
static gboolean dt_modifier_is(GdkModifierType state, const GdkModifierType desired_modifier_mask)
@ DT_GUI_COLOR_LAST
#define DT_GUI_MOUSE_EFFECT_RADIUS
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
void dtgtk_cairo_paint_masks_circle(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_inverse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_brush(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_difference(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_ellipse(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
Paint a 45 deg-rotated ellipse that touches the unit square boundaries.
void dtgtk_cairo_paint_masks_gradient(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_union(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_exclusion(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_polygon(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void dtgtk_cairo_paint_masks_intersection(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)
void(* DTGTKCairoPaintIconFunc)(cairo_t *cr, gint x, gint y, gint w, gint h, gint flags, void *data)