diff --git a/po/darktable.pot b/po/darktable.pot index 5ce8af60a78a..49f0b105c2ee 100644 --- a/po/darktable.pot +++ b/po/darktable.pot @@ -8965,7 +8965,7 @@ msgstr "" #: ../src/develop/masks/path.c:3360 msgid "" -"node curvature: drag\n" +"node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click" msgstr "" diff --git a/po/de.po b/po/de.po index 1efe2309cd82..b273707864f7 100644 --- a/po/de.po +++ b/po/de.po @@ -9572,10 +9572,11 @@ msgstr "" #: ../src/develop/masks/path.c:3360 msgid "" -"node curvature: drag\n" +"node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click" msgstr "" -"Knoten Krümmung: ziehen\n" +"Knoten Krümmung: Ziehen, Einzelnen Kontrollpunkt bewegen: " +"Shift+Ziehen,\n" "Krümmung zurücksetzen: Rechtsklick" #: ../src/develop/masks/path.c:3365 diff --git a/po/en@truecase.po b/po/en@truecase.po index 90e49a47b987..84f7b1beb8ca 100644 --- a/po/en@truecase.po +++ b/po/en@truecase.po @@ -9421,12 +9421,12 @@ msgstr "" "Move node: drag, Remove node: right-click\n" "Switch smooth/sharp mode: Ctrl+click" -#: ../src/develop/masks/path.c:3360 +#: ../src/develop/masks/path.c:3360#: ../src/develop/masks/path.c:3360 msgid "" -"node curvature: drag\n" +"node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click" msgstr "" -"Node curvature: drag\n" +"Node Curvature: drag, Move single handle: shift+drag\n" "Reset curvature: right-click" #: ../src/develop/masks/path.c:3365 diff --git a/po/fr.po b/po/fr.po index be8e727724da..484565ceacba 100644 --- a/po/fr.po +++ b/po/fr.po @@ -9575,10 +9575,11 @@ msgstr "" #: ../src/develop/masks/path.c:3360 msgid "" -"node curvature: drag\n" +"node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click" msgstr "" -"Courbe du nœud : déplacer\n" +"Courbe du nœud : glisser, Déplacer un seul point de contrôle : " +"Maj+glisser\n" "Réinitialiser la courbe : clic droit" #: ../src/develop/masks/path.c:3365 diff --git a/po/it.po b/po/it.po index a62c9bd49f66..777d06fa533d 100644 --- a/po/it.po +++ b/po/it.po @@ -9652,10 +9652,11 @@ msgstr "" #: ../src/develop/masks/path.c:3360 msgid "" -"node curvature: drag\n" +"node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click" msgstr "" -"nodo curvatura: trascina\n" +"nodo curvatura: trascina, muovere punto di controllo singolo: " +"maiusc+trascina\n" "azzera curvatura: clic pulsante destro" #: ../src/develop/masks/path.c:3365 diff --git a/src/develop/masks.h b/src/develop/masks.h index 5335969ac5e0..9029a029f584 100644 --- a/src/develop/masks.h +++ b/src/develop/masks.h @@ -120,6 +120,15 @@ typedef enum dt_masks_source_pos_type_t DT_MASKS_SOURCE_POS_ABSOLUTE = 2 } dt_masks_source_pos_type_t; +/* selected Bézier control point for path*/ +typedef enum dt_masks_path_ctrl_t +{ + DT_MASKS_PATH_CRTL_NONE = -1, + DT_MASKS_PATH_CTRL1 = 1, + DT_MASKS_PATH_CTRL2 = 2 + +} dt_masks_path_ctrl_t; + /** structure used to store 1 point for a circle */ typedef struct dt_masks_point_circle_t { @@ -367,6 +376,7 @@ typedef struct dt_masks_form_gui_t int point_selected; int point_edited; int feather_selected; + dt_masks_path_ctrl_t bezier_ctrl; // For paths, this selects a Bézier control point. int seg_selected; int point_border_selected; int source_pos_type; @@ -378,6 +388,7 @@ typedef struct dt_masks_form_gui_t gboolean gradient_toggling; int point_dragging; int feather_dragging; + gboolean bezier_single; // User drags a single control point. int seg_dragging; int point_border_dragging; @@ -827,6 +838,15 @@ float dt_masks_dynbuf_get(dt_masks_dynbuf_t *a, const int offset) return (a->buffer[a->pos + offset]); } +static inline +float dt_masks_dynbuf_get_absolute(dt_masks_dynbuf_t *a, const int position) +{ + assert(a != NULL); + assert(position >= 0); + assert((long)a->pos > position); + return (a->buffer[position]); +} + static inline void dt_masks_dynbuf_set(dt_masks_dynbuf_t *a, const int offset, const float value) { @@ -837,6 +857,15 @@ void dt_masks_dynbuf_set(dt_masks_dynbuf_t *a, const int offset, const float val a->buffer[a->pos + offset] = value; } +static inline +void dt_masks_dynbuf_set_absolute(dt_masks_dynbuf_t *a, const int position, const float value) +{ + assert(a != NULL); + assert(position >= 0); + assert((long)a->pos > position); + a->buffer[position] = value; +} + static inline float *dt_masks_dynbuf_buffer(dt_masks_dynbuf_t *a) { diff --git a/src/develop/masks/path.c b/src/develop/masks/path.c index 18ca80ecf56d..65aa39bb7c90 100644 --- a/src/develop/masks/path.c +++ b/src/develop/masks/path.c @@ -122,53 +122,28 @@ static void _path_border_get_XY(const float p0x, *yb = (*yc) - rad * dx * l; } -/** get feather extremity from the control point n°2 */ -/** the values should be in orthonormal space */ -static void _path_ctrl2_to_feather(const float ptx, - const float pty, - const float ctrlx, - const float ctrly, - float *fx, - float *fy, - const gboolean clockwise) +void _update_bezier_ctrl_points(dt_masks_point_path_t *point, float iwidth, float iheight, + float new_ctrl[2], dt_masks_path_ctrl_t ctrl_select, bool ctrl_single) { - if(clockwise) + float icorner[2] = { point->corner[0] * iwidth, point->corner[1] * iheight }; + if(ctrl_select == DT_MASKS_PATH_CTRL1) { - *fx = ptx + ctrly - pty; - *fy = pty + ptx - ctrlx; - } - else - { - *fx = ptx - ctrly + pty; - *fy = pty - ptx + ctrlx; - } -} - -/** get bezier control points from feather extremity */ -/** the values should be in orthonormal space */ -static void _path_feather_to_ctrl(const float ptx, - const float pty, - const float fx, - const float fy, - float *ctrl1x, - float *ctrl1y, - float *ctrl2x, - float *ctrl2y, - const gboolean clockwise) -{ - if(clockwise) - { - *ctrl2x = ptx + pty - fy; - *ctrl2y = pty + fx - ptx; - *ctrl1x = ptx - pty + fy; - *ctrl1y = pty - fx + ptx; + point->ctrl1[0] = new_ctrl[0] / iwidth; + point->ctrl1[1] = new_ctrl[1] / iheight; + if (!ctrl_single) { + point->ctrl2[0] = (icorner[0] - (new_ctrl[0] - icorner[0])) / iwidth; + point->ctrl2[1] = (icorner[1] - (new_ctrl[1] - icorner[1])) / iheight; + } } else { - *ctrl1x = ptx + pty - fy; - *ctrl1y = pty + fx - ptx; - *ctrl2x = ptx - pty + fy; - *ctrl2y = pty - fx + ptx; + assert(ctrl_select == DT_MASKS_PATH_CTRL2); + if (!ctrl_single) { + point->ctrl1[0] = (icorner[0] - (new_ctrl[0] - icorner[0])) / iwidth; + point->ctrl1[1] = (icorner[1] - (new_ctrl[1] - icorner[1])) / iheight; + } + point->ctrl2[0] = new_ctrl[0] / iwidth; + point->ctrl2[1] = new_ctrl[1] / iheight; } } @@ -533,12 +508,13 @@ static int _path_find_self_intersection(dt_masks_dynbuf_t *inter, // from border shape extrema (here x_max) int lastx = border[(posextr[1] - 1) * 2]; int lasty = border[(posextr[1] - 1) * 2 + 1]; + int nb_border_points = border_count - _nb_ctrl_point(nb_corners); for(int ii = _nb_ctrl_point(nb_corners); ii < border_count; ii++) { // we want to loop from one border extremity int i = ii - _nb_ctrl_point(nb_corners) + posextr[1]; - if(i >= border_count) i = i - border_count + _nb_ctrl_point(nb_corners); + if(i >= border_count) i = i - nb_border_points; if(inter_count >= nb_corners * 4) break; @@ -594,22 +570,54 @@ static int _path_find_self_intersection(dt_masks_dynbuf_t *inter, // one of the shape extrema if(inter_count > 0) { - if((v[k] - i) - * ((int)dt_masks_dynbuf_get(inter, -2) - - (int)dt_masks_dynbuf_get(inter, -1)) > 0 - && (int)dt_masks_dynbuf_get(inter, -2) >= v[k] - && (int)dt_masks_dynbuf_get(inter, -1) <= i) + int curr_start_ordered = v[k] >= posextr[1] ? v[k] : v[k] + nb_border_points; + int curr_end_ordered = i >= posextr[1] ? i : i + nb_border_points; + + int prev_start; + int prev_end; + int prev_start_ordered; + int prev_end_ordered; + + // Loop over all previously found self-intersections + int n; + for (n = 0; n < inter_count; n++) { - // we find an self-intersection portion which include the last one - // we just update it - dt_masks_dynbuf_set(inter, -2, v[k]); - dt_masks_dynbuf_set(inter, -1, i); + prev_start = dt_masks_dynbuf_get_absolute(inter, n * 2); + prev_end = dt_masks_dynbuf_get_absolute(inter, n * 2 + 1); + + prev_start_ordered = prev_start >= posextr[1] ? prev_start : prev_start + nb_border_points; + prev_end_ordered = prev_end >= posextr[1] ? prev_end : prev_end + nb_border_points; + + assert(prev_start_ordered <= prev_end_ordered); + assert(curr_start_ordered <= curr_end_ordered); + assert(prev_end_ordered <= curr_end_ordered); + + if (prev_start_ordered <= curr_start_ordered + && curr_start_ordered <= prev_end_ordered) + { + // If the new self-intersection starts in a previous self-intersection, + // there is nothing to do (the start is on a segment that does not exist, + // so it makes no sense to cut). + break; + } + if (curr_start_ordered <= prev_start_ordered + && prev_end_ordered <= curr_end_ordered) + { + // The new self-intersection fully contains an old one. + // Update the old intersection. + dt_masks_dynbuf_set_absolute(inter, n * 2, v[k]); + dt_masks_dynbuf_set_absolute(inter, n * 2 + 1, i); + break; + } + } - else + + if (n == inter_count + && prev_end_ordered < curr_start_ordered) { - // we find a new self-intersection portion + // We have a new self-intersection that does not overlap the last one. dt_masks_dynbuf_add_2(inter, v[k], i); - inter_count++; + inter_count++; } } else @@ -1125,7 +1133,7 @@ static int _path_events_mouse_scrolled(struct dt_iop_module_t *module, // resize a shape even if on a node or segment if(gui->form_selected || gui->point_selected >= 0 - || gui->feather_selected >= 0 + || gui->feather_selected >= 0 // bezier control points || gui->seg_selected >= 0 || gui->point_border_selected >= 0) { @@ -1535,9 +1543,10 @@ static int _path_events_button_pressed(struct dt_iop_module_t *module, dt_control_queue_redraw_center(); return 1; } - else if(gui->feather_selected >= 0) + else if(gui->feather_selected >= 0) // Bézier control point { gui->feather_dragging = gui->feather_selected; + gui->bezier_single = dt_modifier_is(state, GDK_SHIFT_MASK); dt_control_queue_redraw_center(); return 1; } @@ -1651,7 +1660,7 @@ static int _path_events_button_pressed(struct dt_iop_module_t *module, return 1; } - else if(which == 3 && gui->feather_selected >= 0) + else if(which == 3 && gui->feather_selected >= 0) // right-click to reset Bézier controls { dt_masks_point_path_t *point = (dt_masks_point_path_t *)g_list_nth_data(form->points, gui->feather_selected); @@ -1814,7 +1823,7 @@ static int _path_events_button_released(struct dt_iop_module_t *module, return 1; } - else if(gui->feather_dragging >= 0) + else if(gui->feather_dragging >= 0) // Bézier control point { dt_masks_point_path_t *point = (dt_masks_point_path_t *)g_list_nth_data(form->points, gui->feather_dragging); @@ -1822,16 +1831,8 @@ static int _path_events_button_released(struct dt_iop_module_t *module, float pts[2] = { pzx * wd, pzy * ht }; dt_dev_distort_backtransform(darktable.develop, pts, 1); - float p1x, p1y, p2x, p2y; - _path_feather_to_ctrl(point->corner[0] * iwidth, - point->corner[1] * iheight, - pts[0], pts[1], - &p1x, &p1y, &p2x, &p2y, gpt->clockwise); - point->ctrl1[0] = p1x / iwidth; - point->ctrl1[1] = p1y / iheight; - point->ctrl2[0] = p2x / iwidth; - point->ctrl2[1] = p2y / iheight; - + _update_bezier_ctrl_points(point, iwidth, iheight, pts, gui->bezier_ctrl, gui->bezier_single); + gui->bezier_single = FALSE; point->state = DT_MASKS_POINT_STATE_USER; _path_init_ctrl_points(form); @@ -1980,15 +1981,7 @@ static int _path_events_mouse_moved(struct dt_iop_module_t *module, dt_masks_point_path_t *point = (dt_masks_point_path_t *)g_list_nth_data(form->points, gui->feather_dragging); - float p1x, p1y, p2x, p2y; - _path_feather_to_ctrl(point->corner[0] * iwidth, - point->corner[1] * iheight, - pts[0], pts[1], - &p1x, &p1y, &p2x, &p2y, gpt->clockwise); - point->ctrl1[0] = p1x / iwidth; - point->ctrl1[1] = p1y / iheight; - point->ctrl2[0] = p2x / iwidth; - point->ctrl2[1] = p2y / iheight; + _update_bezier_ctrl_points(point, iwidth, iheight, pts, gui->bezier_ctrl, gui->bezier_single); point->state = DT_MASKS_POINT_STATE_USER; _path_init_ctrl_points(form); @@ -2074,23 +2067,29 @@ static int _path_events_mouse_moved(struct dt_iop_module_t *module, if((gui->group_selected == index) && gui->point_edited >= 0) { - const int k = gui->point_edited; + const int k = gui->point_edited; // we only select feather if the point is not "sharp" if(gpt->points[k * 6 + 2] != gpt->points[k * 6 + 4] && gpt->points[k * 6 + 3] != gpt->points[k * 6 + 5]) { - float ffx, ffy; - _path_ctrl2_to_feather(gpt->points[k * 6 + 2], - gpt->points[k * 6 + 3], - gpt->points[k * 6 + 4], - gpt->points[k * 6 + 5], - &ffx, &ffy, gpt->clockwise); - if(pzx - ffx > -as - && pzx - ffx < as - && pzy - ffy > -as - && pzy - ffy < as) + if(pzx - gpt->points[k * 6] > -as + && pzx - gpt->points[k * 6] < as + && pzy - gpt->points[k * 6 + 1] > -as + && pzy - gpt->points[k * 6 + 1] < as) + { + gui->feather_selected = k; + gui->bezier_ctrl = DT_MASKS_PATH_CTRL1; + dt_control_queue_redraw_center(); + return 1; + } + + if(pzx - gpt->points[k * 6 + 4] > -as + && pzx - gpt->points[k * 6 + 4] < as + && pzy - gpt->points[k * 6 + 5] > -as + && pzy - gpt->points[k * 6 + 5] < as) { gui->feather_selected = k; + gui->bezier_ctrl = DT_MASKS_PATH_CTRL2; dt_control_queue_redraw_center(); return 1; } @@ -2215,32 +2214,22 @@ static void _path_events_post_expose(cairo_t *cr, gpt->points[k * 6 + 3]); } - // draw feathers + // draw Bézier control points if((gui->group_selected == index) && gui->point_edited >= 0) { const int k = gui->point_edited; - // uncomment this part if you want to see "real" control points - /* + cairo_move_to(cr, gpt->points[k*6+2], gpt->points[k*6+3]); cairo_line_to(cr, gpt->points[k*6], gpt->points[k*6+1]); - cairo_stroke(cr); + dt_masks_line_stroke(cr, TRUE, FALSE, FALSE, zoom_scale); + dt_masks_draw_ctrl(cr, gpt->points[k*6], gpt->points[k*6+1], zoom_scale, + k == gui->feather_dragging || k == gui->feather_selected); + + cairo_move_to(cr, gpt->points[k*6+2], gpt->points[k*6+3]); cairo_line_to(cr, gpt->points[k*6+4], gpt->points[k*6+5]); - cairo_stroke(cr); - */ - - float ffx = 0.0f, ffy = 0.0f; - _path_ctrl2_to_feather(gpt->points[k * 6 + 2], - gpt->points[k * 6 + 3], - gpt->points[k * 6 + 4], - gpt->points[k * 6 + 5], - &ffx, &ffy, gpt->clockwise); - cairo_move_to(cr, gpt->points[k * 6 + 2], gpt->points[k * 6 + 3]); - cairo_line_to(cr, ffx, ffy); - dt_masks_line_stroke(cr, TRUE, FALSE, FALSE, zoom_scale); - - dt_masks_draw_ctrl(cr, ffx, ffy, zoom_scale, + dt_masks_draw_ctrl(cr, gpt->points[k*6+4], gpt->points[k*6+5], zoom_scale, k == gui->feather_dragging || k == gui->feather_selected); } @@ -3357,7 +3346,7 @@ static void _path_set_hint_message(const dt_masks_form_gui_t *const gui, msgbuf_len); else if(gui->feather_selected >= 0) g_strlcat(msgbuf, - _("node curvature: drag\n" + _("node curvature: drag, move single handle: shift+drag\n" "reset curvature: right-click"), msgbuf_len); else if(gui->seg_selected >= 0)