56#include <glib/gstdio.h>
58#define DT_MASKS_SHAPE_BUTTON_COUNT 5
102 if(GTK_IS_TOGGLE_BUTTON(button) && button != active_button)
103 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
FALSE);
131 const int button_index)
137 if(!(visible_form->
type & data->
types[button_index]))
return FALSE;
162 if(!GTK_IS_TOGGLE_BUTTON(button))
continue;
165 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) != active)
166 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), active);
184 for(
size_t i = 0;
i < count;
i++)
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);
201 gtk_widget_get_allocation(widget, &alloc);
205 paint(cr, pad, pad, alloc.width - 2 * pad, alloc.height - 2 * pad, 0, NULL);
211 GCallback activate_callback, gpointer user_data)
214 if(
IS_NULL_PTR(def) || !GTK_IS_MENU_SHELL(menu))
return NULL;
216 GtkWidget *menu_item = gtk_menu_item_new();
218 GtkWidget *icon = gtk_drawing_area_new();
222 gtk_widget_set_size_request(icon,
size,
size);
223 gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
227 gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
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);
234 if(activate_callback) g_signal_connect(G_OBJECT(menu_item),
"activate", activate_callback, user_data);
246 if(button_index < 0)
return FALSE;
273 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
TRUE);
317 gtk_widget_set_halign(data->
box, GTK_ALIGN_END);
318 gtk_widget_set_valign(data->
box, GTK_ALIGN_CENTER);
324 for(
size_t i = 0;
i < button_defs_count;
i++)
327 if(!(config->
flags & def->
flag))
continue;
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);
355 gtk_widget_set_can_focus(button,
FALSE);
356 g_object_set_data(G_OBJECT(button),
"dt-masks-shape-buttons-data", data);
421 if(fabsf(
delta) < 1e-6f)
return;
424 const float scale = exp2f(
delta);
433 g_signal_stop_emission_by_name(widget,
"activate");
441 GdkEvent *
copy = gdk_event_copy(event);
444 double x = 0.0, y = 0.0;
445 gboolean has_coords =
FALSE;
448 case GDK_BUTTON_PRESS:
449 case GDK_2BUTTON_PRESS:
450 case GDK_3BUTTON_PRESS:
451 case GDK_BUTTON_RELEASE:
456 case GDK_MOTION_NOTIFY:
473 if(gtk_widget_translate_coordinates(widget, data->
slider, (
int)
x, (
int)y, &sx, &sy))
477 case GDK_BUTTON_PRESS:
478 case GDK_2BUTTON_PRESS:
479 case GDK_3BUTTON_PRESS:
480 case GDK_BUTTON_RELEASE:
484 case GDK_MOTION_NOTIFY:
498 GdkWindow *slider_window = gtk_widget_get_window(data->
slider);
501 if(
copy->any.window) g_object_unref(
copy->any.window);
502 copy->any.window = g_object_ref(slider_window);
507 gdk_event_free(
copy);
520 const int group_id,
const int formid,
522 float min,
float max,
float step,
float value,
int digits,
523 const char *format,
float factor,
526 GtkWidget *menu_item = gtk_menu_item_new();
529 gtk_widget_set_can_focus(menu_item,
FALSE);
530 g_signal_connect(G_OBJECT(menu_item),
"activate",
532 gtk_widget_add_events(menu_item, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
533 | GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK);
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);
547 gtk_widget_set_can_focus(slider,
TRUE);
554 data->module =
module;
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",
564 g_signal_connect(G_OBJECT(menu_item),
"button-release-event",
566 g_signal_connect(G_OBJECT(menu_item),
"motion-notify-event",
568 g_signal_connect(G_OBJECT(menu_item),
"scroll-event",
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);
582 GtkWidget *dialog = gtk_message_dialog_new(
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?"));
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);
596 const int response = gtk_dialog_run(GTK_DIALOG(dialog));
597 gtk_widget_destroy(dialog);
607 GtkWidget *dialog = gtk_message_dialog_new(
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."));
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);
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);
629 return response == GTK_RESPONSE_YES;
661 int parentid = 0, formid = 0;
673 int parentid = 0, formid = 0;
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"));
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"));
780#define masks_gtk_menu_item_new_bold(label, selected, state, icon) \
782 gchar *op_label = g_strdup(label); \
783 menu_item = ctx_gtk_check_menu_item_new_with_markup_and_pixbuf(op_label, icon, \
785 _masks_operation_callback, gui, \
787 ((state) == DT_MASKS_STATE_INVERSE)); \
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)); \
818 if(!isfinite(rotation)) rotation = 0.0f;
819 if(rotation > 180.0f) rotation -= 360.0f;
823 isfinite(curvature) ? curvature : 0.0f, 3,
"%", 50.0f,
827 isfinite(fade) ? fade : 1.0f, 3,
"%", 100.0f,
831 rotation, 1,
"\302\260", 1.0f,
843 isfinite(fading) ? fading : 1.0f, 3,
"%", 100.0f,
849 if(!isfinite(rotation)) rotation = 0.0f;
850 if(rotation > 180.0f) rotation -= 360.0f;
854 rotation, 1,
"\302\260", 1.0f,
861 isfinite(opacity) ? opacity : 1.0f, 3,
"%", 100.0f,
866 const float pzx,
const float pzy)
872 gtk_style_context_add_class(gtk_widget_get_style_context(menu),
"dt-masks-context-menu");
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);
899 gboolean form_found =
FALSE;
902 for(GList *fpts = grp->
points; fpts; fpts = g_list_next(fpts))
915 guint list_length = (form_found && grp) ? g_list_length(grp->
points) : 0;
919 gchar *form_name = NULL;
921 form_name = g_strdup(form->
name);
925 form_name = g_strdup(_(
"New "));
929 form_name = g_strconcat(form_name, _(
"circle"), NULL);
932 form_name = g_strconcat(form_name, _(
"ellipse"), NULL);
935 form_name = g_strconcat(form_name, _(
"polygon"), NULL);
938 form_name = g_strconcat(form_name, _(
"brush"), NULL);
941 form_name = g_strconcat(form_name, _(
"gradient"), NULL);
944 form_name = g_strconcat(form_name, _(
"mask"), NULL);
948 form_name = g_strdup(_(
"Unknown shape"));
954 gchar *item_str = NULL;
958 item_str = g_strdup_printf(
"%s %d - ", gui->
node_hovered >= 0 ? _(
"Node") : _(
"Segment"), item_index);
961 item_str = g_strdup(
"");
967 GdkPixbuf *composed_icon = NULL;
972 const int base_w = gdk_pixbuf_get_width(icon);
973 const int base_h = gdk_pixbuf_get_height(icon);
976 const int out_w = base_w + inv_w;
977 const int out_h =
MAX(base_h, inv_h);
979 composed_icon = gdk_pixbuf_new(GDK_COLORSPACE_RGB,
TRUE, 8, out_w, out_h);
982 gdk_pixbuf_fill(composed_icon, 0x00000000);
983 gdk_pixbuf_copy_area(icon, 0, 0, base_w, base_h, composed_icon, 0, 0);
985 icon = composed_icon;
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);
995 gtk_widget_set_sensitive(menu_item,
FALSE);
1000 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1006 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1013 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1020 if(!
IS_NULL_PTR(module) && module->populate_masks_context_menu)
1021 if(module->populate_masks_context_menu(module, menu, form->
formid, pzx, pzy))
1023 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_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);
1038 gtk_menu_shell_append(GTK_MENU_SHELL(sub_menu), gtk_separator_menu_item_new());
1048 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1054 gtk_widget_set_sensitive(menu_item, (form_pos > 0));
1056 gtk_widget_set_sensitive(menu_item, (form_pos < list_length - 1));
1058 gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
1079 gtk_widget_set_sensitive(menu_item, gui->
form_selected >= 0);
1082 gtk_widget_set_sensitive(menu_item, gui->
form_selected >= 0);
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);
1090 gtk_widget_show_all(menu);
1098 const int form_index,
1100 const int node_index,
1101 float node[2],
float ctrl1[2],
float ctrl2[2],
1114 ctrl1[0] = ctrl2[0] = node[0];
1115 ctrl1[1] = ctrl2[1] = node[1];
1126 const int form_index,
1128 const int node_index,
1158 const float center_x,
const float center_y,
1159 const float squared_radius)
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;
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];
1239 const int node_count = (node_count_override >= 0) ? node_count_override
1240 : (int)g_list_length(mask_form->
points);
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;
1245 const gboolean has_selected_node = (node_count > 0) && can_test_nodes
1247 && selected_node >= first_node_index && selected_node < node_count;
1249 if(has_selected_node)
1252 float handle_x = NAN;
1253 float handle_y = NAN;
1255 && border_handle_cb(gui_points, node_count, selected_node, &handle_x, &handle_y, user_data)
1265 curve_handle_cb(gui_points, selected_node, &handle_x, &handle_y, user_data);
1276 if(node_position_cb)
1278 node_position_cb(gui_points, selected_node, &node_x, &node_y, user_data);
1280 else if(has_bezier_layout)
1282 node_x = gui_points->
points[selected_node * 6 + 2];
1283 node_y = gui_points->
points[selected_node * 6 + 3];
1285 if(!isnan(node_x) && !isnan(node_y)
1295 for(
int node_index = first_node_index; node_index < node_count; node_index++)
1299 if(node_position_cb)
1301 node_position_cb(gui_points, node_index, &node_x, &node_y, user_data);
1303 else if(has_bezier_layout)
1305 node_x = gui_points->
points[node_index * 6 + 2];
1306 node_y = gui_points->
points[node_index * 6 + 3];
1308 if(!isnan(node_x) && !isnan(node_y)
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);
1333 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1340 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1343 if(near_segment >= 0)
1345 if(near_segment < node_count)
1352 if(post_select_cb) post_select_cb(mask_gui, inside, inside_border, inside_source, user_data);
1357 return need_refresh_anyway;
1370 if(out_index) *out_index = -1;
1375 for(GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node))
1378 if(group_entry && group_entry->
formid == form_id)
1380 if(out_index) *out_index = index;
1393 for(
const GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node))
1548 if(
IS_NULL_PTR(selected_group_entry))
return NULL;
1550 if(selected_group_entry->
parentid > 0)
1555 selected_group_entry->
formid);
1556 if(resolved_group_entry)
return resolved_group_entry;
1559 return selected_group_entry;
1571 int *parent_id,
int *form_index)
1573 if(group_entry) *group_entry = NULL;
1574 if(parent_id) *parent_id = 0;
1575 if(form_index) *form_index = 0;
1582 if(
IS_NULL_PTR(selected_group_entry))
return NULL;
1587 if(group_entry) *group_entry = selected_group_entry;
1588 if(parent_id) *parent_id = selected_group_entry->
parentid;
1591 return selected_form;
1607 const float cursor_x = mask_gui->
pos[0];
1608 const float cursor_y = mask_gui->
pos[1];
1611 int locked_formid = -1;
1616 if(prev_border_selected && prev_group_selected >= 0)
1622 const gboolean has_border_lock_candidate = selected_group_entry
1627 if(has_border_lock_candidate)
1631 int inside_border = 0;
1632 int near_handle = -1;
1633 int inside_source = 0;
1634 float dist = FLT_MAX;
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;
1643 if(prev_group_selected >= 0)
1647 int selected_index = -1;
1648 float best_dist = FLT_MAX;
1651 for(GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node), index++)
1655 if(locked_formid >= 0 && group_entry->
formid != locked_formid)
continue;
1661 int inside_border = 0;
1662 int near_handle = -1;
1663 int inside_source = 0;
1664 float dist = FLT_MAX;
1667 &inside, &inside_border, &near_handle, &inside_source, &
dist);
1669 const gboolean is_selected_form = (prev_group_selected == index);
1670 const gboolean hit_border = (inside_border || near_handle >= 0);
1673 if(!is_selected_form && hit_border && !is_open_shape)
1676 if(inside || hit_border || inside_source)
1683 const float center_dist2 = dx * dx + dy * dy;
1684 const float combined_dist2 =
dist * center_dist2;
1685 if(combined_dist2 < best_dist)
1687 selected_form = form;
1688 selected_index = index;
1689 best_dist = combined_dist2;
1712 const int form_index)
1721 const int form_index)
1727 int inside_border = 0;
1728 int near_handle = -1;
1729 int inside_source = 0;
1730 float dist = FLT_MAX;
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;
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))
1763 const int form_index,
const int button)
1765 if(button != 1)
return FALSE;
1798 char message[512] =
"";
1805 const int form_type = mask_form->
type;
1808 int selected_form_id = 0;
1813 if(!
IS_NULL_PTR(selected_group_entry)) selected_form_id = selected_group_entry->
formid;
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,
1840 "[masks] hint end: sel=%p has_set_hint=%d msg_len=%" G_GSIZE_FORMAT
" msg='%s'\n",
1841 (
void *)selected_form,
1843 strlen(message), message);
1844 return message[0] !=
'\0';
1851 mask_gui->
dev = dev;
1853 mask_gui->
pos[0] = mask_gui->
pos[1] = -1.0f;
1891 mask_gui->
delta[0] = mask_gui->
delta[1] = 0.0f;
1904 const double span = isfinite(image_px_per_device_px) ? floor(image_px_per_device_px) : 1.0;
1921 const int gui_points_count = g_list_length(mask_gui->
points);
1922 if(gui_points_count == form_index)
1926 mask_gui->
points = g_list_append(mask_gui->
points, gui_points_new);
1928 else if(gui_points_count < form_index)
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));
1961 NULL, NULL, NULL, NULL,
TRUE, module)
1978 float posx,
float posy)
1990 const double min_delta_time = 1.0 / 60.0;
1991 const float min_dist2 = 4.0f;
1997 const gboolean force_rebuild
2005 if(elapsed_time < min_delta_time && (delta_x * delta_x + delta_y * delta_y) < min_dist2)
2027 mask_form->
points = g_list_remove(mask_form->
points, brush_node);
2054 if(parent_id <= 0)
return 1;
2064 const int edit_mode = mask_gui->
edit_mode;
2070 for(GList *forms = visible_form->
points; forms; forms = g_list_next(forms))
2075 visible_form->
points = g_list_remove(visible_form->
points, group_entry);
2099 for(GList *form_node = dev->
forms; form_node; form_node = g_list_next(form_node))
2104 for(GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node))
2107 if(group_entry && group_entry->
formid == formid)
2110 if(count > 1)
goto done;
2127 int next_form_index = -1;
2128 int next_formid = 0;
2132 const int group_length = g_list_length(visible_form->
points);
2142 GList *next_node = g_list_next(selected_node);
2143 if(
IS_NULL_PTR(next_node)) next_node = g_list_previous(selected_node);
2153 int signal_parent_id = 0;
2160 signal_parent_id = parent_id;
2172 const int edit_mode = mask_gui->
edit_mode;
2177 for(GList *forms = visible_form->
points; forms; forms = g_list_next(forms))
2182 visible_form->
points = g_list_remove(visible_form->
points, group_entry);
2202 if(
res && next_formid > 0)
2221 const gboolean internal_masks
2223 && ((
module->flags() & IOP_FLAGS_INTERNAL_MASKS) == IOP_FLAGS_INTERNAL_MASKS);
2225 int response = GTK_RESPONSE_YES;
2226 if(use_count <= 1 && !internal_masks)
2229 if(response == GTK_RESPONSE_CANCEL)
return FALSE;
2327 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->
masks_edit),
TRUE);
2328 gtk_widget_queue_draw(blend_data->
masks_edit);
2336 if(selected_index >= 0)
2357 else if(last_formid > 0)
2400 else if(parent_id > 0)
2410 for(
int i = 0;
i < count;
i++)
2412 const float x = samples[2 *
i];
2413 const float y = samples[2 *
i + 1];
2424 gp->
bbox[0] = FLT_MAX;
2425 gp->
bbox[1] = -FLT_MAX;
2426 gp->
bbox[2] = FLT_MAX;
2427 gp->
bbox[3] = -FLT_MAX;
2445 gui_points->
points = NULL;
2447 gui_points->
border = NULL;
2451 gui_points->
source = NULL;
2467 const char *verdict =
"reuse";
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",
2472 (
unsigned long)live_generation, live_step, verdict);
2489 for(GList *group_node = mask_form->
points; group_node; group_node = g_list_next(group_node))
2511 develop->
forms = g_list_append(develop->
forms, mask_form);
2518 develop->
forms = g_list_remove(develop->
forms, mask_form);
2532 guint form_count = 0;
2536 for(GList *form_node = develop->
forms; form_node; form_node = g_list_next(form_node))
2539 if(existing_form->
type == mask_form->
type) form_count++;
2543 gboolean name_exists =
FALSE;
2549 name_exists =
FALSE;
2556 for(GList *form_node = develop->
forms; form_node; form_node = g_list_next(form_node))
2559 if(!strcmp(existing_form->
name, mask_form->
name))
2567 }
while(name_exists);
2598 group_form->
points = g_list_append(group_form->
points, group_entry);
2716 const uint32_t
state,
2717 const gboolean shape_was_selected)
2720 if(button != 1 && button != 3)
return;
2729 const gboolean prev_node_selected = mask_gui->
node_selected;
2731 const gboolean prev_form_selected = mask_gui->
form_selected;
2751 if(prev_node_selected)
2760 if(prev_node_selected)
2779 if(button != 1)
return;
2782 if(!shape_was_selected)
return;
2800 float point[2] = { (float)
x, (
float)y };
2815 double y,
double pressure,
int which);
2848 return moved_result;
2885 dispatch_form, parent_id, mask_gui, form_index);
2935 state, dispatch_form, parent_id, mask_gui, form_index);
2944 if(selected_group_entry)
2949 if(mask_gui && !mask_gui->
creation && button == 1)
2962 int button,
int event_type, uint32_t
state)
2996 gboolean return_val =
FALSE;
2999 button, event_type,
state,
3000 dispatch_form, parent_id, mask_gui, form_index);
3006 ? (prev_group_selected >= 0 && prev_group_selected == form_index)
3007 : prev_any_selected;
3020 if(button == 3 && !return_val)
3031 gtk_menu_popup_at_pointer(GTK_MENU(menu), NULL);
3047 gboolean return_value =
FALSE;
3065 parent_id, mask_gui, form_index);
3084 case GDK_KEY_Escape:
3089 case GDK_KEY_Delete:
3105 return return_value;
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" };
3120 = {
"none",
"size",
"fading",
"opacity",
"rotation" };
3154 return N_(
"Shift+Scroll");
3156 return N_(
"Ctrl+Scroll");
3158 return N_(
"Ctrl+Shift+Scroll");
3161 return N_(
"Scroll");
3172 return N_(
"Fading");
3174 return N_(
"Opacity");
3176 return N_(
"Rotation");
3179 return N_(
"Nothing");
3188 return N_(
"Curvature");
3198 const GdkModifierType
state = (GdkModifierType)key_state;
3219 int scroll_up, uint32_t key_state,
int scrolling_delta)
3235 int scroll_flow = (scrolling_delta < 0) ? -scrolling_delta : scrolling_delta;
3265 scroll_increases ? 1 : 0, scroll_flow,
3266 key_state, dispatch_form, parent_id, mask_gui, form_index,
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,
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]);
3308 const float *points,
const int points_count,
const float zoom_scale,
3310 const gboolean is_closed_shape,
3311 const float offset_factor,
3315 result[0] = ray_1[0];
3316 result[1] = ray_1[1];
3318 const int available_points = points_count - first_pt;
3319 if(available_points < 2)
return;
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;
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;
3334 const int segment_count = (available_points - 1) + ((is_closed_shape) ? 1 : 0);
3335 for(
int seg = 0; seg < segment_count; seg++)
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];
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;
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;
3357 const float ix = ray2_x + ray_param * dir_x;
3358 const float iy = ray2_y + ray_param * dir_y;
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;
3369 const int node_count,
const float zoom_scale,
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 };
3385 int first_point_index = 2;
3387 first_point_index = 10;
3389 first_point_index = node_count * 3;
3395 attach_points_count /= 2;
3396 attach_source_count /= 2;
3399 float head[2] = { 0.0f, 0.0f };
3400 float tail[2] = { 0.0f, 0.0f };
3404 zoom_scale, first_point_index, is_closed_shape, 1.f, head);
3408 zoom_scale, first_point_index, is_closed_shape, 0.5f, tail);
3412 gboolean draw_tail =
TRUE;
3421 const float pts[4] = { tail[0], tail[1], source[0], source[1] };
3427 const float origin_pt[2] = {
main[0],
main[1] };
3435 head[0] = 0.5f * (
main[0] + source[0]);
3436 head[1] = 0.5f * (
main[1] + source[1]);
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;
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]);
3457 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 1);
3460 cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1);
3464 cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1);
3474 const int rendered_node_count = node_count + !mask_gui->
creation;
3475 const gboolean shape_selected = (mask_gui->
group_selected == form_index)
3479 rendered_node_count, draw_shape_func);
3484 const float *points,
const int points_count,
const int node_count,
3485 const float zoom_scale,
const gboolean round_ends)
3488 if(node_count <= 0 || points_count <= node_count * 3 + 6)
return;
3490 const int total_coords = points_count * 2;
3491 if(total_coords <= (node_count * 6 + 1))
return;
3493 const gboolean group_selected = (mask_gui->
group_selected == form_index);
3499 const int last_segment_index = (round_ends || mask_gui->
creation) ? node_count - 2 : node_count - 1;
3519 const gboolean all_selected = group_selected
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];
3528 cairo_move_to(cr, start_x, start_y);
3529 cairo_line_to(cr, start_x, start_y);
3532 cairo_move_to(cr, end_x, end_y);
3533 cairo_line_to(cr, end_x, end_y);
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]);
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];
3558 for(
int point_index = node_count * 3; point_index < points_count; point_index++)
3560 const int coord_index = point_index * 2;
3561 if((coord_index + 1) >= total_coords)
break;
3563 const double coord_x = points[coord_index];
3564 const double coord_y = points[coord_index + 1];
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;
3571 cairo_line_to(cr, coord_x, coord_y);
3576 const int segment_coord_index = show_segment_index * 6;
3577 if((segment_coord_index + 3) >= total_coords)
continue;
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)
3587 cairo_line_to(cr, coord_x, coord_y);
3592 const gboolean seg_is_selected = group_selected
3594 == current_segment_index);
3595 const gboolean all_selected = group_selected
3599 if(mask_gui->
creation && current_segment_index == node_count - 2)
3603 CAIRO_LINE_CAP_BUTT);
3605 show_segment_index = (show_segment_index + 1) % node_count;
3606 current_segment_index++;
3614 if(current_segment_index > last_segment_index)
break;
3617 cairo_move_to(cr, coord_x, coord_y);
3722 for(
const GList *node = gui->
points; node && taken <
max; node = g_list_next(node))
3736 out[taken].first[0] =
out[taken].first[1] =
out[taken].last[0] =
out[taken].last[1] = 0.0f;
3746 for(
int i = 0;
i < count;
i++)
3748 const double x = array[2 *
i];
3749 const double y = array[2 *
i + 1];
3771 gboolean any =
FALSE;
3772 for(
const GList *node = gui->
points; node; node = g_list_next(node))
3787 const double margin = (zoom_scale > 1e-6f) ? (16.0 / zoom_scale) : 0.0;
3812 const uint64_t generation,
const int count)
3815 cairo_get_matrix(cr, &
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))
3827 const int formid = GPOINTER_TO_INT(node->data);
3828 hash =
dt_hash(hash, (
const char *)&formid,
sizeof(formid));
3830 return hash ? hash : 1;
3846 cairo_t *cr,
const float zoom_scale,
3901 gboolean outlines_moved =
TRUE;
3920 outlines_moved =
FALSE;
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",
3941 if(clipped) cairo_restore(cr);
3958 const gboolean clipped = !rebuild &&
_session_clip(cr, zoom_scale);
3959 cairo_push_group(cr);
3964 for(GList *formid_node = creation_gui->
creation_formids; formid_node; formid_node = g_list_next(formid_node))
3966 const int formid = GPOINTER_TO_INT(formid_node->data);
3978 const guint point_count = g_list_length(session_form->
points);
3989 rebuild ?
"rebuilt in" :
"cached,", built - shape_start,
dt_get_wtime() - built);
4003 if(clipped) cairo_restore(cr);
4018 int32_t
width, int32_t
height, int32_t pointerx, int32_t pointery)
4046 cairo_surface_destroy(
_canvas);
4052 if(cairo_surface_status(
_canvas) != CAIRO_STATUS_SUCCESS)
4054 cairo_surface_destroy(
_canvas);
4061 cairo_surface_set_device_scale(
_canvas, device_scale, device_scale);
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);
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);
4082static void _rect_include(cairo_rectangle_int_t *
rect, gboolean *any,
const double x,
const double y,
const int margin)
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;
4093 rect->height = y1 - y0;
4134 return nodes > 0 && points_count >= nodes * 3;
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;
4149 const double scale, cairo_rectangle_int_t *
rect, gboolean *any)
4151 for(
int i = 0;
i < limit;
i++)
4153 double x = array[2 *
i];
4154 double y = array[2 *
i + 1];
4155 cairo_user_to_device(canvas_cr, &
x, &y);
4163 const double scale, cairo_rectangle_int_t *
rect, gboolean *any)
4167 for(
int a = 0; a < 3; a++)
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);
4183 for(
int k = 0;
k < nodes;
k++)
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++)
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);
4224 for(
const GList *node = gui->
points; node; node = g_list_next(node), index++)
4238 rect->height += 2 * margin;
4248 const double margin = (zoom_scale > 1e-6f) ? (16.0 / zoom_scale) : 0.0;
4251 for(
int c = 0; c < 4; c++)
4253 double x = xs[c & 1];
4254 double y = ys[c >> 1];
4255 cairo_user_to_device(canvas_cr, &
x, &y);
4271 const float zoom_scale)
4275 cairo_rectangle_int_t session = { 0 };
4276 gboolean session_bounded =
FALSE;
4279 if(session_bounded) *bound = session;
4282 gboolean bounded =
FALSE;
4284 if(!bounded)
return FALSE;
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);
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);
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;
4331 frame->
cr = cairo_create(frame->
canvas);
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,
4343 cairo_matrix_multiply(&canvas_matrix, &
matrix, &shift);
4344 cairo_set_matrix(frame->
cr, &canvas_matrix);
4354 const int y0,
const int x1,
const int y1)
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);
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,
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;
4380 const cairo_rectangle_int_t painted = { x0, y0, x1 - x0, y1 - y0 };
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));
4415 cairo_get_matrix(canvas_cr, &
matrix);
4419 hash =
dt_hash(hash, (
const char *)&zoom_scale,
sizeof(zoom_scale));
4422 hash =
dt_hash(hash, (
const char *)overlay,
sizeof(*overlay));
4424 for(
const GList *node = gui->
points; node; node = g_list_next(node), index++)
4429 hash =
dt_hash(hash, (
const char *)&index,
sizeof(index));
4434 return hash ? hash : 1;
4455 if(cairo_surface_status(
_static_layer) != CAIRO_STATUS_SUCCESS)
4468 const cairo_rectangle_int_t whole = { 0, 0, frame->
width, frame->
height };
4474 cairo_get_matrix(canvas_cr, &
matrix);
4476 cairo_set_matrix(layer_cr, &
matrix);
4478 cairo_destroy(layer_cr);
4500 FILE *
f = g_fopen(path,
"w");
4503 for(
int i = 0;
i < count;
i++)
4514 const cairo_rectangle_int_t *
const dirty)
4516 const char *dir = g_getenv(
"MASKS_DUMP_OVERLAY");
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);
4522 path = g_strdup_printf(
"%s/overlay-info.txt", dir);
4523 FILE *info = g_fopen(path,
"w");
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);
4532 for(
const GList *node = gui->
points; node; node = g_list_next(node), index++)
4536 path = g_strdup_printf(
"%s/outline-%d.txt", dir, index);
4555 gtk_widget_queue_draw(widget);
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) };
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);
4592 gtk_widget_queue_draw_area(
dt_gui_center_widget(), damage.x, damage.y, damage.width, damage.height);
4611 const gboolean darkroom_frame,
const gboolean layered)
4634 const guint point_count = g_list_length(mask_form->
points);
4645 const gboolean cairo_bounded, cairo_rectangle_int_t *dirty)
4648 if(!cairo_bounded)
return any;
4651 *dirty = *cairo_bound;
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;
4664 int32_t
width, int32_t
height, int32_t pointerx, int32_t pointery,
4675 int buffer_width = 0;
4676 int buffer_height = 0;
4679 if(buffer_width < 1.0 || buffer_height < 1.0)
return;
4702 cairo_t *
const mask_draw = frame.
cr;
4707 cairo_save(mask_draw);
4711 cairo_restore(mask_draw);
4712 cairo_destroy(mask_draw);
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);
4733 dt_show_times(&rebuild_start,
"[masks] overlay outline refresh");
4745 gboolean layer_rebuilt =
FALSE;
4752 cairo_rectangle_int_t dirty = { 0, 0, 0, 0 };
4753 const gboolean any =
_overlay_dirty(&frame, &cairo_bound, cairo_bounded, &dirty);
4755 cairo_restore(mask_draw);
4756 cairo_destroy(mask_draw);
4767 dirty.width = frame.
width;
4768 dirty.height = frame.
height;
4773 any ? dirty.width : 0, any ? dirty.height : 0, frame.
width, frame.
height,
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))
4850 if(form_node->data == old_form)
4852 is_registered =
TRUE;
4857 for(
const GList *form_node = dev->
allforms; form_node; form_node = g_list_next(form_node))
4859 if(form_node->data == old_form)
4861 is_registered_for_cleanup =
TRUE;
4883 if(!is_registered && !is_registered_for_cleanup)
4905 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->
masks_edit), 0);
4912 for(GList *module_node = dev->
iop; module_node; module_node = g_list_next(module_node))
4916 && !IS_NULL_PTR(module->gui) && !IS_NULL_PTR(module->gui->blend_data))
4918 dt_iop_gui_blend_data_t *blend_data = (dt_iop_gui_blend_data_t *)module->gui->blend_data;
4922 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->masks_edit),
FALSE);
4923 gtk_widget_queue_draw(blend_data->masks_edit);
4934 module->blend_params->mask_id = 0;
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;
5004 int combo_index = 0;
5005 combo_ids[combo_index] = 0;
5010 for(GList *form_node = module->dev->forms; form_node; form_node = g_list_next(form_node))
5014 || mask_form->
formid == module->blend_params->mask_id)
5024 for(GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node))
5037 combo_ids[combo_index] = mask_form->
formid;
5045 for(GList *module_node = module->dev->iop; module_node; module_node = g_list_next(module_node))
5057 combo_ids[combo_index] = -1 * iop_index;
5071 if(selection_index == 0)
return;
5072 if(selection_index > 0)
5075 const guint iop_count = g_list_length(module->
dev->
iop);
5077 if(selection_value == -1000000)
5082 else if(selection_value == -2000001)
5087 else if(selection_value == -2000002)
5092 else if(selection_value == -2000016)
5097 else if(selection_value == -2000032)
5102 else if(selection_value == -2000064)
5107 else if(selection_value < 0)
5110 selection_value = -1 * selection_value - 1;
5111 if(selection_value < (
int)iop_count)
5122 else if(selection_value > 0)
5181 float center[2] = { 0.0f, 0.0f };
5230 flow, mask_gui, module);
5231 if(isnan(result))
return NAN;
5251 return current * powf(amount, (
float)flow);
5253 return current + amount * (float)flow;
5266 return current * scale_amount;
5268 return current + offset_amount;
5276 float value_min,
float value_max,
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));
5283 config_key = g_strdup_printf(
"plugins/darkroom/%s/%s/%s",
5286 if(!g_strcmp0(feature,
"rotation")) flow = (flow > 1) ? (flow - 1) * 5 : flow;
5290 if(!g_strcmp0(feature,
"rotation"))
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);
5297 else updated_value =
MAX(value_min,
MIN(updated_value, value_max));
5302 return updated_value;
5306 float value_min,
float value_max,
5308 const char *toast_fmt,
float toast_scale)
5311 value_min, value_max, increment, flow);
5312 if(!
IS_NULL_PTR(toast_fmt) && toast_fmt[0] !=
'\0')
5338 group_entry->
opacity = opacity;
5339 group_form->
points = g_list_append(group_form->
points, group_entry);
5366 for(GList *group_node = group_form->
points; group_node; group_node = g_list_next(group_node))
5383 dest_group->
points = g_list_append(dest_group->
points, new_entry);
5404 const float *form_points,
int form_points_start,
int form_points_count,
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;
5412 const int start_index = form_points_start;
5415 gboolean reported_bad_skip =
FALSE;
5416 gboolean reported_trapped_walk =
FALSE;
5418 for(
int test_index = 0; test_index < test_point_count; test_index++)
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;
5425 for(
int i = form_points_start, next = start_index + 1;
i < form_points_count;)
5427 if(next < start_index || next >= form_points_count)
break;
5428 if(++visited_points > form_points_count - start_index + 1)
5434 if(!reported_trapped_walk)
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;
5450 gboolean jumped =
TRUE;
5452 while(jumped && hops <= skip_count)
5455 for(
int s = 0; s < skip_count; s++)
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)
5460 if(!reported_bad_skip)
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;
5478 if(next >= form_points_count)
break;
5480 const float y1 = form_points[
i * 2 + 1];
5481 const float y2 = form_points[next * 2 + 1];
5483 if(isnan(form_points[next * 2]))
5492 if(!reported_bad_skip)
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;
5501 if(next == start_index)
break;
5506 if(((point_y <= y2 && point_y > y1) || (point_y >= y2 && point_y < y1))
5507 && (form_points[
i * 2] > point_x))
5508 intersection_count++;
5510 if(next == start_index)
break;
5513 if(next >= form_points_count) next = start_index;
5516 if(intersection_count & 1)
return test_index;
5529 const int selected_formid =
IS_NULL_PTR(selected_form) ? 0 : selected_form->
formid;
5532 module = dev->gui_module;
5534 if(!
IS_NULL_PTR(module) && module->masks_selection_changed)
5535 module->masks_selection_changed(module, selected_formid);
5552 fprintf(stderr,
"[dt_masks_set_source_pos_initial_state] unknown state for setting masks position type\n");
5574 const float raw_width =
geometry.raw_width;
5575 const float raw_height =
geometry.raw_height;
5577 const float xx = mask_gui->
pos[0];
5578 const float yy = mask_gui->
pos[1];
5592 fprintf(stderr,
"[dt_masks_set_source_pos_initial_value] unsupported masks type when calculating source position initial value\n");
5608 mask_form->
source[0] = source_points[0];
5609 mask_form->
source[1] = source_points[1];
5630 mask_form->
source[0] = source_points[0];
5631 mask_form->
source[1] = source_points[1];
5634 fprintf(stderr,
"[dt_masks_set_source_pos_initial_value] unknown source position type\n");
5643 const float initial_ypos,
const float xpos,
const float ypos,
5644 float *pos_x,
float *pos_y,
const int adding)
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;
5668 fprintf(stderr,
"[dt_masks_calculate_source_pos_origin] unsupported masks type when calculating source position value\n");
5681 source_x = xpos + mask_gui->
pos_source[0] - initial_xpos;
5682 source_y = ypos + mask_gui->
pos_source[1] - initial_ypos;
5692 fprintf(stderr,
"[dt_masks_calculate_source_pos_origin] unknown source position type for setting source position value\n");
5709 const float center_x = center[0];
5710 const float center_y = center[1];
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);
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);
5723 float delta_angle = angle_current - angle_prev;
5724 float angle = atan2f(sinf(delta_angle), cosf(delta_angle));
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 };
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]);
5733 check_angle = atan2f(sinf(check_angle), cosf(check_angle));
5736 if(check_angle < 0.0f) angle = -angle;
5739 mask_gui->
delta[0] = anchor_x;
5740 mask_gui->
delta[1] = anchor_y;
5742 return angle /
M_PI * 180.0f;
5806 group_entry->
state = (group_entry->
state & ~DT_MASKS_STATE_IS_COMBINE_OP) | apply_state;
5812 dt_iop_gui_blend_data_t *blend_data =
module->gui ? (dt_iop_gui_blend_data_t *)module->gui->blend_data : NULL;
5827 module->dev->form_gui->edit_mode = value;
5828 if(
value && mask_form)
5834 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(blend_data->
masks_edit),
5851 const float amount = scroll_up ? 0.02f : -0.02f;
5886 cairo_surface_t *grey = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
width,
height);
5887 if(cairo_surface_status(grey) == CAIRO_STATUS_SUCCESS)
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++)
5894 uint32_t *
const row = (uint32_t *)(pixels + (
size_t)y * stride);
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;
5902 cairo_surface_mark_dirty(grey);
5903 cairo_set_source_surface(cr, grey, 0, 0);
5906 cairo_surface_destroy(grey);
5917 int32_t raw_width = 0;
5918 int32_t raw_height = 0;
5924 : (int)lrint((
double)
width * raw_height / (
double)raw_width);
5927 cairo_surface_t *surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
width,
height);
5928 if(cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS)
5930 cairo_surface_destroy(surface);
5933 cairo_t *cr = cairo_create(surface);
5937 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
5956 if(palette[
i].alpha <= 0.0) palette[
i] = (GdkRGBA){ 1.0, 1.0, 1.0, 1.0 };
5975 = { .
scale = (
double)
width / (
double)raw_width, .offset_x = 0.0, .offset_y = 0.0 };
5985 cairo_surface_flush(surface);
5986 const gboolean ok = (cairo_surface_write_to_png(surface, path) == CAIRO_STATUS_SUCCESS);
5987 cairo_surface_destroy(surface);
6023static inline void _emit_run(cairo_t *cr,
const float *
const points,
const int first,
const int last,
6024 const double min_step2)
6026 double last_x = points[first * 2];
6027 double last_y = points[first * 2 + 1];
6028 cairo_move_to(cr, last_x, last_y);
6030 for(
int i = first + 1;
i < last;
i++)
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);
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);
6052 if(!cr || !points || first >= last)
return;
6057 const double min_step2 = min_step * min_step;
6059 const int count =
IS_NULL_PTR(skips) ? 0 : skip_count;
6066 while(next < count && skips[next].resume_at <= at) next++;
6069 if(next < count && skips[next].jump_from < last) run_end =
MAX(skips[next].jump_from, at);
6071 if(run_end > at)
_emit_run(cr, points, at, run_end, min_step2);
6073 if(next < count && skips[next].jump_from < last)
6075 at =
MAX(skips[next].resume_at, at + 1);
6085 float source_pos[2] = { 0.0f, 0.0f };
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] };
6093 for(
int i = 0;
i <
preview->points_count;
i++)
6096 preview->source_points[
i * 2 + 1] =
preview->points[
i * 2 + 1] + center_source[1];
6104 float **points,
int *points_count,
6105 const float xs,
const float ys,
const int first_shifted)
6109 *points, *points_count))
6113 float pts[2] = { xs, ys };
6119 const float dx = pts[0] - (*points)[0];
6120 const float dy = pts[1] - (*points)[1];
6124 if(first_shifted > 0)
6126 (*points)[0] = pts[0];
6127 (*points)[1] = pts[1];
6131 for(
int i = first_shifted;
i < *points_count;
i++)
6133 (*points)[
i * 2] += dx;
6134 (*points)[
i * 2 + 1] += dy;
6140 *points, *points_count))
Handle default and user-set shortcuts (accelerators)
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)
static void error(char *msg)
void dt_bauhaus_slider_set_digits(GtkWidget *widget, int val)
float dt_bauhaus_slider_get(GtkWidget *widget)
int dt_bauhaus_combobox_get(GtkWidget *widget)
int dt_bauhaus_combobox_length(GtkWidget *widget)
void dt_bauhaus_slider_set(GtkWidget *widget, float pos)
void dt_bauhaus_widget_set_label(GtkWidget *widget, const char *label)
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)
void dt_bauhaus_combobox_remove_at(GtkWidget *widget, int pos)
void dt_bauhaus_slider_set_format(GtkWidget *widget, const char *format)
void dt_bauhaus_combobox_add(GtkWidget *widget, const char *text)
void dt_bauhaus_slider_set_factor(GtkWidget *widget, float factor)
#define DT_BAUHAUS_WIDGET(obj)
void dt_iop_gui_blend_masks_update(dt_iop_module_t *module)
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)
struct _GtkWidget GtkWidget
GtkWidget, opaque, spelled exactly as GTK spells it.
static const float const float const float min
const dt_colormatrix_t dt_aligned_pixel_t out
const dt_colormatrix_t matrix
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)
void dt_toast_log(const char *msg,...)
void dt_control_log(const char *msg,...)
void dt_control_queue_redraw_center()
Request a redraw of the centre view.
void dt_control_hinter_message(const struct dt_control_t *s, const char *message)
void dt_control_queue_cursor_by_name(const char *curs_str)
Queue a GTK named cursor for the next cursor commit.
#define dt_control_queue_cursor(cursor)
#define dt_control_set_cursor_visible(visible)
struct dt_control_t * dt_control_get_global(void)
void * dt_alloc_align(size_t size)
Allocate cacheline-aligned memory.
struct dt_gui_gtk_t * dt_gui_get_global(void)
struct dt_bauhaus_t * dt_bauhaus_get_global(void)
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)
void dt_dev_get_processed_size(const dt_develop_t *dev, int *procw, int *proch)
int dt_dev_coordinates_raw_abs_to_image_abs(dt_develop_t *dev, float *points, size_t points_count)
void dt_dev_coordinates_raw_norm_to_raw_abs(dt_develop_t *dev, float *points, size_t num_points)
float dt_dev_get_zoom_level(const dt_develop_t *dev)
void dt_dev_masks_selection_change(dt_develop_t *dev, struct dt_iop_module_t *module, const int selectid, const int throw_event)
gboolean dt_dev_clamp_viewport_center(dt_develop_t *dev)
Clamp the viewport centre against the box currently visible at this zoom.
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.
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.
int dt_dev_coordinates_image_abs_to_raw_abs(dt_develop_t *dev, float *points, size_t points_count)
void dt_dev_coordinates_image_norm_to_image_abs(dt_develop_t *dev, float *points, size_t num_points)
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.
void dt_dev_coordinates_raw_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
void dt_dev_coordinates_image_abs_to_raw_norm(dt_develop_t *dev, float *points, size_t num_points)
gchar * dt_history_item_get_name(const struct dt_iop_module_t *module)
@ DT_DEV_TRANSFORM_DIR_BACK_EXCL
@ DT_DEV_TRANSFORM_DIR_FORW_INCL
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
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)
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)
static double dt_draw_min_emit_step(cairo_t *cr)
Stroke a line with style.
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)
#define DT_DRAW_SCALE_ARROW
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...
static GdkPixbuf * dt_draw_get_pixbuf_from_cairo(DTGTKCairoPaintIconFunc paint, const int width, const int height)
static int dt_pthread_rwlock_wrlock(dt_pthread_rwlock_t *rwlock) ACQUIRE(rwlock) NO_THREAD_SAFETY_ANALYSIS
static int dt_pthread_rwlock_unlock(dt_pthread_rwlock_t *rwlock) RELEASE_GENERIC(rwlock) NO_THREAD_SAFETY_ANALYSIS
static int dt_pthread_rwlock_rdlock(dt_pthread_rwlock_t *rwlock) ACQUIRE_SHARED(rwlock) NO_THREAD_SAFETY_ANALYSIS
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
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...
Where things are on the image, answered without a pipeline.
static gboolean g_list_shorter_than(const GList *list, unsigned len)
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)
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)
void dt_group_events_post_expose(cairo_t *cr, float zoom_scale, dt_masks_form_t *form, dt_masks_form_gui_t *gui)
static uint64_t dt_hash(uint64_t hash, const char *str, size_t size)
void dt_iop_request_focus(dt_iop_module_t *module)
Move darkroom focus to module, or clear it with NULL.
@ IOP_FLAGS_SUPPORTS_BLENDING
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
int32_t dt_get_debug_flags(void)
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...
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.
dt_masks_form_t * _group_create(dt_develop_t *develop, dt_iop_module_t *module, dt_masks_type_t group_type)
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)
void _check_id(dt_develop_t *dev, dt_masks_form_t *mask_form)
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.
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.
int _find_in_group(dt_develop_t *dev, dt_masks_form_t *group_form, int form_id)
void dt_masks_form_update_gravity_center(dt_develop_t *dev, dt_masks_form_t *mask_form)
dt_masks_form_t * _group_from_module(dt_develop_t *develop, dt_iop_module_t *module)
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,...
void dt_masks_form_gui_points_free(gpointer data)
void dt_masks_free_form(dt_masks_form_t *form)
void dt_masks_iop_use_same_as(struct dt_iop_module_t *module, struct dt_iop_module_t *src)
dt_masks_raster_result_t
What a rasterisation attempt produced.
dt_masks_form_t * dt_masks_create_ext(dt_develop_t *dev, dt_masks_type_t type)
@ DT_MASKS_SOURCE_POS_RELATIVE_TEMP
@ DT_MASKS_SOURCE_POS_RELATIVE
@ DT_MASKS_SOURCE_POS_ABSOLUTE
dt_masks_form_t * dt_masks_create(dt_masks_type_t type)
void dt_masks_form_move(dt_masks_form_t *grp, int formid, int up)
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)
dt_masks_form_t * dt_masks_get_from_id(dt_develop_t *dev, int id)
@ DT_MASKS_POINT_STATE_NORMAL
@ DT_MASKS_POINT_STATE_USER
static void dt_masks_dynbuf_free(dt_masks_dynbuf_t *a)
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.
Render a mask's rasterisation and its GUI overlay to an image file, headlessly.
@ DT_MASKS_DEBUG_BACKDROP_TRANSPARENT
@ DT_MASKS_DEBUG_BACKDROP_RASTER
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)
void dt_masks_group_ungroup(dt_develop_t *dev, dt_masks_form_t *dest_group, dt_masks_form_t *group_form)
int dt_masks_preview_add_clone_source(dt_masks_form_gui_t *gui, dt_masks_preview_buffers_t *preview)
static gboolean _canvas_begin(cairo_t *cr, const int width, const int height, _canvas_frame_t *frame)
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...
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)
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)
static int _session_sigs_count
static gboolean _session_bbox_from_points(const dt_masks_form_gui_t *gui, double bbox[4])
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.
static void _session_bbox_invalidate(void)
static void _session_sigs_reset(void)
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.
static void _overlay_dump_outline(const dt_masks_form_gui_points_t *const pts, const char *path)
static gboolean _masks_gui_menu_item_forward_event(GtkWidget *widget, GdkEvent *event, gpointer user_data)
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)
void dt_masks_gui_set_dragging(dt_masks_form_gui_t *gui)
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,...
static int _overlay_skip_of(const dt_masks_form_gui_points_t *const pts, const int i)
static int _canvas_height
static _session_outline_sig_t * _session_sigs
void dt_masks_clear_form_gui(dt_develop_t *develop)
static void _menu_add_exist(dt_iop_module_t *module, int form_id)
static gboolean _dt_masks_events_should_update_hover_on_move(dt_masks_form_gui_t *mask_gui)
void dt_masks_scroll_mapping_set(dt_masks_scroll_modifier_t modifier, dt_masks_interaction_t interaction)
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)
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)
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)
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)
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...
static uint64_t _session_pattern_key
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.
static void _masks_move_up_down_callback(gpointer user_data, const int up)
gboolean dt_masks_point_is_within_radius(const float point_x, const float point_y, const float center_x, const float center_y, const float squared_radius)
Check whether a point lies within a squared radius of a center.
float dt_masks_get_set_conf_value(dt_masks_form_t *mask_form, char *feature, float new_value, float value_min, float value_max, dt_masks_increment_t increment, int flow)
Change a numerical property of a mask shape, either by in/de-crementing the current value or setting ...
static void _menu_no_masks(struct dt_iop_module_t *module)
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)
static gboolean _overlay_pointer_valid
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)
void dt_masks_gui_points_update_bbox(dt_masks_form_gui_points_t *gp)
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...
void dt_masks_gui_form_remove(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index)
void dt_masks_change_form_gui(dt_develop_t *dev, dt_masks_form_t *new_form)
static const char *const _interaction_conf_values[DT_MASKS_INTERACTION_LAST]
static void _set_cursor_shape(dt_masks_form_gui_t *mask_gui)
gboolean dt_masks_form_get_gravity_center(dt_develop_t *dev, const dt_masks_form_t *mask_form, float center[2], float *area)
static void _canvas_cairo_bound(cairo_t *canvas_cr, const dt_masks_form_gui_t *gui, cairo_rectangle_int_t *rect, gboolean *any)
static uint64_t _session_gui_generation
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)
static gboolean _masks_shape_button_pressed(GtkWidget *button, GdkEventButton *event, gpointer user_data)
static int _masks_shape_button_index(const dt_masks_shape_buttons_data_t *data, GtkWidget *button)
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...
void dt_masks_gui_reset_dragging(dt_masks_form_gui_t *gui)
static void _apply_gui_button_pressed_state(dt_masks_form_gui_t *mask_gui, const int button, const uint32_t state, const gboolean shape_was_selected)
gboolean dt_masks_gui_form_create_throttled(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, dt_iop_module_t *module, float posx, float posy)
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)
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....
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.
static gboolean _session_gui_inited
static int _masks_gui_form_group_use_count(const dt_develop_t *dev, const int formid)
dt_masks_form_group_t * dt_masks_form_get_selected_group(const dt_masks_form_t *mask_form, const dt_masks_form_gui_t *mask_gui)
Get the selected group entry from the GUI selection index.
static gboolean _dt_masks_events_group_update_selection(dt_masks_form_t *group_form, dt_masks_form_gui_t *mask_gui)
Update group selection from the current cached cursor before leaf dispatch.
gboolean dt_masks_is_anything_selected(const dt_masks_form_gui_t *mask_gui)
void dt_masks_soft_reset_form_gui(dt_masks_form_gui_t *mask_gui)
static void _dt_masks_events_set_current_pos(const double x, const double y, dt_masks_form_gui_t *mask_gui)
Convert the GTK/Cairo widget cursor once for the full mask event chain.
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.
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.
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.
static void _session_bound_include(cairo_t *canvas_cr, const float zoom_scale, cairo_rectangle_int_t *rect, gboolean *any)
int dt_masks_events_mouse_moved(dt_develop_t *dev, struct dt_iop_module_t *module, double x, double y, double pressure, int which)
void dt_masks_gui_set_outline_density(dt_masks_form_gui_t *mask_gui, const double image_px_per_device_px)
static void _menu_add_shape(struct dt_iop_module_t *module, dt_masks_type_t type)
static void _masks_gui_interaction_apply_value(dt_masks_gui_interaction_slider_t *data, float value)
void dt_masks_append_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
static dt_masks_form_t * _dt_masks_events_get_dispatch_form(dt_masks_form_t *visible_form, const dt_masks_form_gui_t *mask_gui, dt_masks_form_group_t **group_entry, int *parent_id, int *form_index)
Resolve the concrete form that should receive an event.
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.
const char * dt_masks_interaction_name(dt_masks_interaction_t interaction)
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)
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.
void dt_masks_shape_buttons_deactivate_all(GtkWidget *active_button)
static void _masks_movedown_callback(GtkWidget *menu, gpointer user_data)
static gboolean _overlay_damage_valid
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.
static void _masks_gui_interaction_slider_changed(GtkWidget *widget, gpointer user_data)
GtkWidget * dt_masks_shape_buttons_create(const dt_masks_shape_buttons_config_t *config)
Build a synchronized toolbar for creating masks shapes.
static void _canvas_clear(cairo_surface_t *canvas, const cairo_rectangle_int_t *rect)
static const dt_masks_shape_button_def_t _masks_shape_button_defs[]
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)
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)
static gboolean _masks_shape_icon_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
static cairo_pattern_t * _session_pattern
static void _masks_moveup_callback(GtkWidget *menu, gpointer user_data)
static void _masks_shape_buttons_destroy(GtkWidget *widget, dt_masks_shape_buttons_data_t *data)
void _masks_gui_delete_node_callback(GtkWidget *menu, gpointer user_data)
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...
static void _session_pattern_reset(void)
Drop the session outlines. Safe to call when nothing is cached.
static cairo_surface_t * _static_layer
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.
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)
static const char *const _scroll_conf_keys[DT_MASKS_SCROLL_MODIFIER_LAST]
static double _overlay_pointer[2]
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.
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.
#define masks_gtk_menu_item_new_bold(label, selected, state, icon)
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)
static gboolean _masks_shape_button_is_current_creation(dt_develop_t *dev, const dt_masks_shape_buttons_data_t *data, const int button_index)
dt_masks_form_t * dt_masks_get_visible_form(const dt_develop_t *dev)
Return the currently visible form used by the masks GUI.
int dt_masks_events_mouse_leave(struct dt_iop_module_t *module)
static cairo_surface_t * _canvas_acquire(const int width, const int height, const double device_scale)
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...
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)
static gboolean _set_hinter_message(dt_masks_form_gui_t *mask_gui, const dt_masks_form_t *mask_form)
Build and display the on-canvas hint message for masks interactions.
gboolean dt_masks_gui_is_dragging(const dt_masks_form_gui_t *gui)
gboolean dt_masks_remove_shape_from_group(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
Remove the form from its current group only, keeping it around unused for potential reuse....
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)
void dt_masks_reset_show_masks_icons(dt_develop_t *dev)
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)
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.
static gboolean _static_layer_applies(const dt_masks_form_t *form, const dt_masks_form_gui_t *gui)
static cairo_surface_t * _canvas
static gboolean header_reach_wanted(const int nodes, const int points_count)
int dt_masks_gui_confirm_delete_form_dialog(const char *form_name)
void dt_masks_shape_buttons_sync_all(void)
Make every shape toolbar show the shape currently being created, if any.
void dt_masks_iop_combo_populate(GtkWidget *widget, void *data)
static void _overlay_damage_record(cairo_t *cr, const _canvas_frame_t *const frame, const cairo_rectangle_int_t *dirty)
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.
const char * dt_masks_scroll_modifier_name(dt_masks_scroll_modifier_t modifier)
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.
static void _masks_gui_remove_from_group_callback(GtkWidget *menu, gpointer user_data)
const char * _get_mask_plugin(dt_masks_form_t *mask_form)
float dt_masks_apply_increment(float current, float amount, dt_masks_increment_t increment, int flow)
Apply a scroll increment to a scalar value.
int dt_masks_events_mouse_enter(struct dt_iop_module_t *module)
static double _overlay_pointer_delta[2]
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)
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.
static int _dt_masks_events_update_hover(dt_masks_form_t *dispatch_form, dt_masks_form_gui_t *mask_gui, const int form_index)
static void _masks_shape_buttons_deactivate(GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
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)
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)
static const dt_masks_shape_button_def_t * _masks_shape_button_def(const dt_masks_type_t type)
static gboolean _dt_masks_events_group_blocks_motion(dt_masks_form_gui_t *mask_gui)
Consume the initial drag motion used to disambiguate scrolling vs dragging in groups.
void dt_masks_gui_cleanup(dt_develop_t *dev)
static double _session_bbox[4]
static cairo_rectangle_int_t _overlay_damage
gboolean dt_masks_delete_shape(struct dt_iop_module_t *module, dt_masks_form_t *sel, int parent_id, dt_masks_form_gui_t *mask_gui, int form_id)
Permanently delete the form from every group and from dev->forms. Gated by dt_masks_gui_confirm_perma...
void dt_masks_set_source_pos_initial_value(dt_masks_form_gui_t *mask_gui, dt_masks_form_t *mask_form)
Initialize the clone source position based on current GUI state.
static void _masks_shape_buttons_deactivate_signal(gpointer instance, GtkWidget *active_button, dt_masks_shape_buttons_data_t *data)
void dt_masks_reset_form_gui(dt_develop_t *dev)
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)
#define DT_MASKS_SHAPE_BUTTON_COUNT
static void _paint_mask_backdrop(cairo_t *cr, dt_develop_t *dev, dt_masks_form_t *form, const int width, const int height)
void dt_masks_set_visible_form(dt_develop_t *dev, dt_masks_form_t *form)
static void _masks_gui_full_delete_callback(GtkWidget *menu, gpointer user_data)
static void _session_gui_reset(void)
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.
int dt_masks_events_key_pressed(dt_develop_t *dev, struct dt_iop_module_t *module, GdkEventKey *event)
static void _masks_operation_callback(GtkWidget *menu, gpointer user_data)
void dt_masks_iop_value_changed_callback(GtkWidget *widget, struct dt_iop_module_t *module)
int dt_masks_center_view_on_form(dt_develop_t *dev, const dt_masks_form_t *mask_form)
Center the darkroom ROI on a mask form gravity center.
void dt_masks_overlay_queue_redraw(GtkWidget *widget)
void dt_masks_remove_form(dt_develop_t *develop, dt_masks_form_t *mask_form)
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.
static gboolean _session_bbox_valid
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.
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...
static void _masks_gui_exit_creation_callback(GtkWidget *menu, gpointer user_data)
static int _outline_nodes(dt_develop_t *dev, const dt_masks_form_t *form, const int index)
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[].
static void _masks_shape_buttons_sync(dt_masks_shape_buttons_data_t *data)
static int _session_gui_count
static void _masks_gui_interaction_commit(dt_masks_gui_interaction_slider_t *data)
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.
static void _masks_shape_buttons_sync_signal(gpointer instance __attribute__((unused)), dt_masks_shape_buttons_data_t *data)
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)
static int _static_layer_width
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)
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)
static int _static_layer_height
static dt_masks_form_gui_t _session_gui
Draw completed shapes from the current creation session.
gboolean dt_masks_is_anything_hovered(const dt_masks_form_gui_t *mask_gui)
int dt_masks_find_closest_handle_common(dt_masks_form_t *mask_form, dt_masks_form_gui_t *mask_gui, int form_index, int node_count_override, dt_masks_border_handle_fn border_handle_cb, dt_masks_curve_handle_fn curve_handle_cb, dt_masks_node_position_fn node_position_cb, dt_masks_distance_fn distance_cb, dt_masks_post_select_fn post_select_cb, void *user_data)
Centralized hit-testing for node/handle/segment selection across shapes.
void dt_masks_init_form_gui(dt_develop_t *dev, dt_masks_form_gui_t *mask_gui)
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)
void dt_masks_gui_init(dt_develop_t *dev)
void dt_masks_creation_mode_quit(dt_masks_form_gui_t *mask_gui)
Exit mask creation mode, restoring cursor visibility and resetting GUI state.
void dt_masks_group_update_name(dt_iop_module_t *module)
void dt_masks_set_edit_mode(struct dt_iop_module_t *module, dt_masks_edit_mode_t value)
static uint64_t _static_layer_key
static uint64_t _static_layer_hash_samples(uint64_t hash, const float *const array, const int count)
dt_masks_interaction_t dt_masks_scroll_mapping_get(dt_masks_scroll_modifier_t modifier)
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)
static void _canvas_end(cairo_t *cr, const _canvas_frame_t *frame, const cairo_rectangle_int_t *dirty)
static double _canvas_device_scale(cairo_t *cr)
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)
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)
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.
static void _rect_include(cairo_rectangle_int_t *rect, gboolean *any, const double x, const double y, const int margin)
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)
float dt_masks_form_get_interaction_value(dt_develop_t *dev, const int group_id, const int formid, dt_masks_interaction_t interaction)
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.
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)
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.
gboolean dt_masks_form_exit_creation(dt_iop_module_t *module, dt_masks_form_gui_t *mask_gui)
static void _gui_points_bbox_extend(float *const bbox, const float *const samples, const int count)
static double _bound_header_reach(cairo_t *canvas_cr, const dt_masks_form_gui_points_t *const pts, const int nodes, const double scale)
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)
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.
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)
static int dt_masks_gui_selected_node_index(const dt_masks_form_gui_t *gui)
@ DT_MASKS_SHAPE_INDEX_BRUSH
@ DT_MASKS_SHAPE_INDEX_ELLIPSE
@ DT_MASKS_SHAPE_INDEX_CIRCLE
@ DT_MASKS_SHAPE_INDEX_GRADIENT
@ DT_MASKS_SHAPE_INDEX_POLYGON
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.
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.
dt_masks_scroll_modifier_t
@ DT_MASKS_SCROLL_PRIMARY
@ DT_MASKS_SCROLL_PRIMARY_SHIFT
@ DT_MASKS_SCROLL_MODIFIER_LAST
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.
#define menu_item_set_fake_accel(menu_item, keyval, mods)
static gboolean dt_masks_gui_was_anything_selected(const dt_masks_form_gui_t *gui)
static gboolean dt_masks_gui_should_hit_test(dt_masks_form_gui_t *gui)
@ DT_MASKS_SHAPE_BUTTONS_GRADIENT
@ DT_MASKS_SHAPE_BUTTONS_CIRCLE
@ DT_MASKS_SHAPE_BUTTONS_BRUSH
@ DT_MASKS_SHAPE_BUTTONS_ELLIPSE
@ DT_MASKS_SHAPE_BUTTONS_POLYGON
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.
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.
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_STATE_DIFFERENCE
@ DT_MASKS_STATE_INTERSECTION
@ DT_MASKS_STATE_IS_COMBINE_OP
@ DT_MASKS_STATE_EXCLUSION
@ DT_MASKS_IS_PRIMITIVE_SHAPE
@ DT_MASKS_IS_CLOSED_SHAPE
@ 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_INCREMENT_SCALE
@ DT_MASKS_INCREMENT_OFFSET
@ DT_MASKS_INCREMENT_ABSOLUTE
static float f_inv_sqrtf(const float x)
#define CLAMPF(a, mn, mx)
#define dt_free_align(ptr)
Release memory from dt_alloc_align() and set ptr to NULL.
#define dt_free(ptr)
g_free() ptr and set it to NULL, skipping both if it is already NULL.
#define __OMP_PARALLEL_FOR_SIMD__(...)
#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.
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
#define DT_DEBUG_CONTROL_SIGNAL_RAISE(ctlsig, signal,...)
struct dt_control_signal_t * dt_control_signal_get_global(void)
@ DT_SIGNAL_MASK_SHAPE_BUTTONS_SYNC
@ DT_SIGNAL_MASK_SHAPE_BUTTONS_DEACTIVATE
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
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...
static const dt_aligned_pixel_simd_t value
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
Objective facts about the image a dev is working on.
struct dt_iop_module_t * gui_module
gboolean darkroom_skip_mouse_events
struct dt_masks_form_gui_t * form_gui
dt_pthread_rwlock_t masks_mutex
struct dt_geometry_chain_t * geometry_chain
struct dt_develop_blend_params_t * blend_params
struct dt_iop_module_gui_t * gui
struct dt_develop_t * dev
What to render. Zeroed means: raster only, at full image resolution, on black.
dt_masks_debug_backdrop_t backdrop
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
dt_masks_form_gui_t * gui
dt_masks_increment_t increment
One shape's membership of one group, BY VALUE.
One cut in a shape's border outline: while walking the border buffer forward, on reaching index jump_...
typedef double((*spd)(unsigned long int wavelength, double TempK))
void dt_show_times(const dt_times_t *start, const char *prefix)
static void dt_get_times(dt_times_t *t)
static double dt_get_wtime(void)