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)