summaryrefslogtreecommitdiffstats
path: root/app/vectors/gimpbezierstroke.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/vectors/gimpbezierstroke.c2309
1 files changed, 2309 insertions, 0 deletions
diff --git a/app/vectors/gimpbezierstroke.c b/app/vectors/gimpbezierstroke.c
new file mode 100644
index 0000000..3cfe735
--- /dev/null
+++ b/app/vectors/gimpbezierstroke.c
@@ -0,0 +1,2309 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpbezierstroke.c
+ * Copyright (C) 2002 Simon Budig <simon@gimp.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include <cairo.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimpbezierdesc.h"
+#include "core/gimpcoords.h"
+#include "core/gimpcoords-interpolate.h"
+
+#include "gimpanchor.h"
+#include "gimpbezierstroke.h"
+
+
+/* local prototypes */
+
+static gdouble
+ gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+static gdouble
+ gimp_bezier_stroke_segment_nearest_point_get
+ (const GimpCoords *beziercoords,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos,
+ gint depth);
+static gdouble
+ gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos);
+static gdouble
+ gimp_bezier_stroke_segment_nearest_tangent_get
+ (const GimpCoords *beziercoords,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos);
+static void
+ gimp_bezier_stroke_anchor_move_relative
+ (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_move_absolute
+ (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor);
+static gboolean
+ gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static void
+ gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void
+ gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+static void gimp_bezier_stroke_close (GimpStroke *stroke);
+
+static GimpStroke *
+ gimp_bezier_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+static gboolean
+ gimp_bezier_stroke_anchor_is_insertable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static GimpAnchor *
+ gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static gboolean
+ gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+static gboolean
+ gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+static GArray *
+ gimp_bezier_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *closed);
+static GimpBezierDesc *
+ gimp_bezier_stroke_make_bezier (GimpStroke *stroke);
+static void gimp_bezier_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+static void gimp_bezier_stroke_finalize (GObject *object);
+
+
+static GList * gimp_bezier_stroke_get_anchor_listitem
+ (GList *list);
+
+
+G_DEFINE_TYPE (GimpBezierStroke, gimp_bezier_stroke, GIMP_TYPE_STROKE)
+
+#define parent_class gimp_bezier_stroke_parent_class
+
+
+static void
+gimp_bezier_stroke_class_init (GimpBezierStrokeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpStrokeClass *stroke_class = GIMP_STROKE_CLASS (klass);
+
+ object_class->finalize = gimp_bezier_stroke_finalize;
+
+ stroke_class->nearest_point_get = gimp_bezier_stroke_nearest_point_get;
+ stroke_class->nearest_tangent_get = gimp_bezier_stroke_nearest_tangent_get;
+ stroke_class->nearest_intersection_get = NULL;
+ stroke_class->anchor_move_relative = gimp_bezier_stroke_anchor_move_relative;
+ stroke_class->anchor_move_absolute = gimp_bezier_stroke_anchor_move_absolute;
+ stroke_class->anchor_convert = gimp_bezier_stroke_anchor_convert;
+ stroke_class->anchor_delete = gimp_bezier_stroke_anchor_delete;
+ stroke_class->point_is_movable = gimp_bezier_stroke_point_is_movable;
+ stroke_class->point_move_relative = gimp_bezier_stroke_point_move_relative;
+ stroke_class->point_move_absolute = gimp_bezier_stroke_point_move_absolute;
+ stroke_class->close = gimp_bezier_stroke_close;
+ stroke_class->open = gimp_bezier_stroke_open;
+ stroke_class->anchor_is_insertable = gimp_bezier_stroke_anchor_is_insertable;
+ stroke_class->anchor_insert = gimp_bezier_stroke_anchor_insert;
+ stroke_class->is_extendable = gimp_bezier_stroke_is_extendable;
+ stroke_class->extend = gimp_bezier_stroke_extend;
+ stroke_class->connect_stroke = gimp_bezier_stroke_connect_stroke;
+ stroke_class->interpolate = gimp_bezier_stroke_interpolate;
+ stroke_class->make_bezier = gimp_bezier_stroke_make_bezier;
+ stroke_class->transform = gimp_bezier_stroke_transform;
+}
+
+static void
+gimp_bezier_stroke_init (GimpBezierStroke *stroke)
+{
+}
+
+static void
+gimp_bezier_stroke_finalize (GObject *object)
+{
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+/* Bezier specific functions */
+
+GimpStroke *
+gimp_bezier_stroke_new (void)
+{
+ return g_object_new (GIMP_TYPE_BEZIER_STROKE, NULL);
+}
+
+
+GimpStroke *
+gimp_bezier_stroke_new_from_coords (const GimpCoords *coords,
+ gint n_coords,
+ gboolean closed)
+{
+ GimpStroke *stroke;
+ GimpAnchor *last_anchor;
+ gint count;
+
+ g_return_val_if_fail (coords != NULL, NULL);
+ g_return_val_if_fail (n_coords >= 3, NULL);
+ g_return_val_if_fail ((n_coords % 3) == 0, NULL);
+
+ stroke = gimp_bezier_stroke_new ();
+
+ last_anchor = NULL;
+
+ for (count = 0; count < n_coords; count++)
+ last_anchor = gimp_bezier_stroke_extend (stroke,
+ &coords[count],
+ last_anchor,
+ EXTEND_SIMPLE);
+
+ if (closed)
+ gimp_stroke_close (stroke);
+
+ return stroke;
+}
+
+static void
+gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ GList *list;
+ GList *list2;
+ gint i;
+
+ /* Anchors always are surrounded by two handles that have to
+ * be deleted too
+ */
+
+ list2 = g_queue_find (stroke->anchors, anchor);
+ list = g_list_previous (list2);
+
+ for (i = 0; i < 3; i++)
+ {
+ g_return_if_fail (list != NULL);
+
+ list2 = g_list_next (list);
+ gimp_anchor_free (list->data);
+ g_queue_delete_link (stroke->anchors, list);
+ list = list2;
+ }
+}
+
+static GimpStroke *
+gimp_bezier_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ GList *list;
+ GList *list2;
+ GimpStroke *new_stroke = NULL;
+
+ list = g_queue_find (stroke->anchors, end_anchor);
+
+ g_return_val_if_fail (list != NULL && list->next != NULL, NULL);
+
+ list = g_list_next (list); /* protect the handle... */
+
+ list2 = list->next;
+ list->next = NULL;
+
+ if (list2 != NULL)
+ {
+ GList *tail = stroke->anchors->tail;
+
+ stroke->anchors->tail = list;
+ stroke->anchors->length -= g_list_length (list2);
+
+ list2->prev = NULL;
+
+ if (stroke->closed)
+ {
+ GList *l;
+
+ for (l = tail; l; l = g_list_previous (l))
+ g_queue_push_head (stroke->anchors, l->data);
+
+ g_list_free (list2);
+ }
+ else
+ {
+ new_stroke = gimp_bezier_stroke_new ();
+ new_stroke->anchors->head = list2;
+ new_stroke->anchors->tail = g_list_last (list2);
+ new_stroke->anchors->length = g_list_length (list2);
+ }
+ }
+
+ stroke->closed = FALSE;
+ g_object_notify (G_OBJECT (stroke), "closed");
+
+ return new_stroke;
+}
+
+static gboolean
+gimp_bezier_stroke_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return (g_queue_find (stroke->anchors, predec) != NULL);
+}
+
+
+static GimpAnchor *
+gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ GList *segment_start;
+ GList *list;
+ GList *list2;
+ GimpCoords subdivided[8];
+ GimpCoords beziercoords[4];
+ gint i;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ if (! segment_start)
+ return NULL;
+
+ list = segment_start;
+
+ for (i = 0; i <= 3; i++)
+ {
+ beziercoords[i] = GIMP_ANCHOR (list->data)->position;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ subdivided[0] = beziercoords[0];
+ subdivided[6] = beziercoords[3];
+
+ gimp_coords_mix (1-position, &(beziercoords[0]),
+ position, &(beziercoords[1]),
+ &(subdivided[1]));
+
+ gimp_coords_mix (1-position, &(beziercoords[1]),
+ position, &(beziercoords[2]),
+ &(subdivided[7]));
+
+ gimp_coords_mix (1-position, &(beziercoords[2]),
+ position, &(beziercoords[3]),
+ &(subdivided[5]));
+
+ gimp_coords_mix (1-position, &(subdivided[1]),
+ position, &(subdivided[7]),
+ &(subdivided[2]));
+
+ gimp_coords_mix (1-position, &(subdivided[7]),
+ position, &(subdivided[5]),
+ &(subdivided[4]));
+
+ gimp_coords_mix (1-position, &(subdivided[2]),
+ position, &(subdivided[4]),
+ &(subdivided[3]));
+
+ /* subdivided 0-6 contains the bezier segment subdivided at <position> */
+
+ list = segment_start;
+
+ for (i = 0; i <= 6; i++)
+ {
+ if (i >= 2 && i <= 4)
+ {
+ list2 = g_list_append (NULL,
+ gimp_anchor_new ((i == 3 ?
+ GIMP_ANCHOR_ANCHOR:
+ GIMP_ANCHOR_CONTROL),
+ &(subdivided[i])));
+ /* insert it *before* list manually. */
+ list2->next = list;
+ list2->prev = list->prev;
+ if (list->prev)
+ list->prev->next = list2;
+ list->prev = list2;
+
+ list = list2;
+
+ if (i == 3)
+ segment_start = list;
+ }
+ else
+ {
+ GIMP_ANCHOR (list->data)->position = subdivided[i];
+ }
+
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ stroke->anchors->head = g_list_first (list);
+ stroke->anchors->tail = g_list_last (list);
+ stroke->anchors->length += 3;
+
+ return GIMP_ANCHOR (segment_start->data);
+}
+
+
+static gboolean
+gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return (g_queue_find (stroke->anchors, predec) != NULL);
+}
+
+
+static void
+gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords offsetcoords[2];
+ GList *segment_start;
+ GList *list;
+ gint i;
+ gdouble feel_good;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ g_return_if_fail (segment_start != NULL);
+
+ /* dragging close to endpoints just moves the handle related to
+ * the endpoint. Just make sure that feel_good is in the range from
+ * 0 to 1. The 1.0 / 6.0 and 5.0 / 6.0 are duplicated in
+ * tools/gimpvectortool.c.
+ */
+ if (position <= 1.0 / 6.0)
+ feel_good = 0;
+ else if (position <= 0.5)
+ feel_good = (pow((6 * position - 1) / 2.0, 3)) / 2;
+ else if (position <= 5.0 / 6.0)
+ feel_good = (1 - pow((6 * (1-position) - 1) / 2.0, 3)) / 2 + 0.5;
+ else
+ feel_good = 1;
+
+ gimp_coords_scale ((1-feel_good)/(3*position*
+ (1-position)*(1-position)),
+ deltacoord,
+ &(offsetcoords[0]));
+ gimp_coords_scale (feel_good/(3*position*position*(1-position)),
+ deltacoord,
+ &(offsetcoords[1]));
+
+ list = segment_start;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+
+ for (i = 0; i <= 1; i++)
+ {
+ gimp_stroke_anchor_move_relative (stroke, GIMP_ANCHOR (list->data),
+ &(offsetcoords[i]), feature);
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+}
+
+
+static void
+gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords deltacoord;
+ GimpCoords tmp1, tmp2, abs_pos;
+ GimpCoords beziercoords[4];
+ GList *segment_start;
+ GList *list;
+ gint i;
+
+ segment_start = g_queue_find (stroke->anchors, predec);
+
+ g_return_if_fail (segment_start != NULL);
+
+ list = segment_start;
+
+ for (i = 0; i <= 3; i++)
+ {
+ beziercoords[i] = GIMP_ANCHOR (list->data)->position;
+ list = g_list_next (list);
+ if (! list)
+ list = stroke->anchors->head;
+ }
+
+ gimp_coords_mix ((1-position)*(1-position)*(1-position), &(beziercoords[0]),
+ 3*(1-position)*(1-position)*position, &(beziercoords[1]),
+ &tmp1);
+ gimp_coords_mix (3*(1-position)*position*position, &(beziercoords[2]),
+ position*position*position, &(beziercoords[3]),
+ &tmp2);
+ gimp_coords_add (&tmp1, &tmp2, &abs_pos);
+
+ gimp_coords_difference (coord, &abs_pos, &deltacoord);
+
+ gimp_bezier_stroke_point_move_relative (stroke, predec, position,
+ &deltacoord, feature);
+}
+
+static void
+gimp_bezier_stroke_close (GimpStroke *stroke)
+{
+ GList *start;
+ GList *end;
+ GimpAnchor *anchor;
+
+ start = stroke->anchors->head;
+ end = stroke->anchors->tail;
+
+ g_return_if_fail (start->next != NULL && end->prev != NULL);
+
+ if (start->next != end->prev)
+ {
+ if (gimp_coords_equal (&(GIMP_ANCHOR (start->next->data)->position),
+ &(GIMP_ANCHOR (start->data)->position)) &&
+ gimp_coords_equal (&(GIMP_ANCHOR (start->data)->position),
+ &(GIMP_ANCHOR (end->data)->position)) &&
+ gimp_coords_equal (&(GIMP_ANCHOR (end->data)->position),
+ &(GIMP_ANCHOR (end->prev->data)->position)))
+ {
+ /* redundant segment */
+
+ gimp_anchor_free (stroke->anchors->tail->data);
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ gimp_anchor_free (stroke->anchors->tail->data);
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ anchor = stroke->anchors->tail->data;
+ g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
+
+ gimp_anchor_free (stroke->anchors->head->data);
+ stroke->anchors->head->data = anchor;
+ }
+ }
+
+ GIMP_STROKE_CLASS (parent_class)->close (stroke);
+}
+
+static gdouble
+gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ gdouble min_dist, dist, pos;
+ GimpCoords point = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
+ GimpCoords segmentcoords[4];
+ GList *anchorlist;
+ GimpAnchor *segment_start;
+ GimpAnchor *segment_end = NULL;
+ GimpAnchor *anchor;
+ gint count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1.0;
+
+ count = 0;
+ min_dist = -1;
+ pos = 0;
+
+ for (anchorlist = stroke->anchors->head;
+ GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ segment_start = anchorlist->data;
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ segment_end = anchorlist->data;
+ dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords,
+ coord, precision,
+ &point, &pos,
+ 10);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (ret_point)
+ *ret_point = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+
+ segment_start = anchorlist->data;
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ if (stroke->closed && stroke->anchors->head)
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+
+ anchorlist = g_list_next (anchorlist);
+
+ if (anchorlist)
+ {
+ segment_end = GIMP_ANCHOR (anchorlist->data);
+ segmentcoords[3] = segment_end->position;
+ }
+
+ dist = gimp_bezier_stroke_segment_nearest_point_get (segmentcoords,
+ coord, precision,
+ &point, &pos,
+ 10);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (ret_point)
+ *ret_point = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+ }
+
+ return min_dist;
+}
+
+
+static gdouble
+gimp_bezier_stroke_segment_nearest_point_get (const GimpCoords *beziercoords,
+ const GimpCoords *coord,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos,
+ gint depth)
+{
+ /*
+ * beziercoords has to contain four GimpCoords with the four control points
+ * of the bezier segment. We subdivide it at the parameter 0.5.
+ */
+
+ GimpCoords subdivided[8];
+ gdouble dist1, dist2;
+ GimpCoords point1, point2;
+ gdouble pos1, pos2;
+
+ gimp_coords_difference (&beziercoords[1], &beziercoords[0], &point1);
+ gimp_coords_difference (&beziercoords[3], &beziercoords[2], &point2);
+
+ if (! depth || (gimp_coords_bezier_is_straight (beziercoords, precision) &&
+ gimp_coords_length_squared (&point1) < precision &&
+ gimp_coords_length_squared (&point2) < precision))
+ {
+ GimpCoords line, dcoord;
+ gdouble length2, scalar;
+ gint i;
+
+ gimp_coords_difference (&(beziercoords[3]),
+ &(beziercoords[0]),
+ &line);
+
+ gimp_coords_difference (coord,
+ &(beziercoords[0]),
+ &dcoord);
+
+ length2 = gimp_coords_scalarprod (&line, &line);
+ scalar = gimp_coords_scalarprod (&line, &dcoord) / length2;
+
+ scalar = CLAMP (scalar, 0.0, 1.0);
+
+ /* lines look the same as bezier curves where the handles
+ * sit on the anchors, however, they are parametrized
+ * differently. Hence we have to do some weird approximation. */
+
+ pos1 = pos2 = 0.5;
+
+ for (i = 0; i <= 15; i++)
+ {
+ pos2 *= 0.5;
+
+ if (3 * pos1 * pos1 * (1-pos1) + pos1 * pos1 * pos1 < scalar)
+ pos1 += pos2;
+ else
+ pos1 -= pos2;
+ }
+
+ *ret_pos = pos1;
+
+ gimp_coords_mix (1.0, &(beziercoords[0]),
+ scalar, &line,
+ ret_point);
+
+ gimp_coords_difference (coord, ret_point, &dcoord);
+
+ return gimp_coords_length (&dcoord);
+ }
+
+ /* ok, we have to subdivide */
+
+ subdivided[0] = beziercoords[0];
+ subdivided[6] = beziercoords[3];
+
+ /* if (!depth) g_printerr ("Hit recursion depth limit!\n"); */
+
+ gimp_coords_average (&(beziercoords[0]), &(beziercoords[1]),
+ &(subdivided[1]));
+
+ gimp_coords_average (&(beziercoords[1]), &(beziercoords[2]),
+ &(subdivided[7]));
+
+ gimp_coords_average (&(beziercoords[2]), &(beziercoords[3]),
+ &(subdivided[5]));
+
+ gimp_coords_average (&(subdivided[1]), &(subdivided[7]),
+ &(subdivided[2]));
+
+ gimp_coords_average (&(subdivided[7]), &(subdivided[5]),
+ &(subdivided[4]));
+
+ gimp_coords_average (&(subdivided[2]), &(subdivided[4]),
+ &(subdivided[3]));
+
+ /*
+ * We now have the coordinates of the two bezier segments in
+ * subdivided [0-3] and subdivided [3-6]
+ */
+
+ dist1 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[0]),
+ coord, precision,
+ &point1, &pos1,
+ depth - 1);
+
+ dist2 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[3]),
+ coord, precision,
+ &point2, &pos2,
+ depth - 1);
+
+ if (dist1 <= dist2)
+ {
+ *ret_point = point1;
+ *ret_pos = 0.5 * pos1;
+ return dist1;
+ }
+ else
+ {
+ *ret_point = point2;
+ *ret_pos = 0.5 + 0.5 * pos2;
+ return dist2;
+ }
+}
+
+
+static gdouble
+gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ gdouble min_dist, dist, pos;
+ GimpCoords point;
+ GimpCoords segmentcoords[4];
+ GList *anchorlist;
+ GimpAnchor *segment_start;
+ GimpAnchor *segment_end = NULL;
+ GimpAnchor *anchor;
+ gint count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1.0;
+
+ count = 0;
+ min_dist = -1;
+
+ for (anchorlist = stroke->anchors->head;
+ GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ segment_start = anchorlist->data;
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ segment_end = anchorlist->data;
+ dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords,
+ coord1, coord2,
+ precision,
+ &point, &pos);
+
+ if (dist >= 0 && (dist < min_dist || min_dist < 0))
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (nearest)
+ *nearest = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+
+ segment_start = anchorlist->data;
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ if (stroke->closed && ! g_queue_is_empty (stroke->anchors))
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+
+ anchorlist = g_list_next (anchorlist);
+
+ if (anchorlist)
+ {
+ segment_end = GIMP_ANCHOR (anchorlist->data);
+ segmentcoords[3] = segment_end->position;
+ }
+
+ dist = gimp_bezier_stroke_segment_nearest_tangent_get (segmentcoords,
+ coord1, coord2,
+ precision,
+ &point, &pos);
+
+ if (dist >= 0 && (dist < min_dist || min_dist < 0))
+ {
+ min_dist = dist;
+
+ if (ret_pos)
+ *ret_pos = pos;
+ if (nearest)
+ *nearest = point;
+ if (ret_segment_start)
+ *ret_segment_start = segment_start;
+ if (ret_segment_end)
+ *ret_segment_end = segment_end;
+ }
+ }
+
+ return min_dist;
+}
+
+static gdouble
+gimp_bezier_stroke_segment_nearest_tangent_get (const GimpCoords *beziercoords,
+ const GimpCoords *coord1,
+ const GimpCoords *coord2,
+ gdouble precision,
+ GimpCoords *ret_point,
+ gdouble *ret_pos)
+{
+ GArray *ret_coords;
+ GArray *ret_params;
+ GimpCoords dir, line, dcoord, min_point;
+ gdouble min_dist = -1;
+ gdouble dist, length2, scalar, ori, ori2;
+ gint i;
+
+ gimp_coords_difference (coord2, coord1, &line);
+
+ ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+ ret_params = g_array_new (FALSE, FALSE, sizeof (gdouble));
+
+ g_printerr ("(%.2f, %.2f)-(%.2f,%.2f): ", coord1->x, coord1->y,
+ coord2->x, coord2->y);
+
+ gimp_coords_interpolate_bezier (beziercoords, precision,
+ ret_coords, ret_params);
+
+ g_return_val_if_fail (ret_coords->len == ret_params->len, -1.0);
+
+ if (ret_coords->len < 2)
+ return -1;
+
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, 1),
+ &g_array_index (ret_coords, GimpCoords, 0),
+ &dir);
+ ori = dir.x * line.y - dir.y * line.x;
+
+ for (i = 2; i < ret_coords->len; i++)
+ {
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
+ &g_array_index (ret_coords, GimpCoords, i-1),
+ &dir);
+ ori2 = dir.x * line.y - dir.y * line.x;
+
+ if (ori * ori2 <= 0)
+ {
+ gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
+ coord1,
+ &dcoord);
+
+ length2 = gimp_coords_scalarprod (&line, &line);
+ scalar = gimp_coords_scalarprod (&line, &dcoord) / length2;
+
+ if (scalar >= 0 && scalar <= 1)
+ {
+ gimp_coords_mix (1.0, coord1,
+ scalar, &line,
+ &min_point);
+ gimp_coords_difference (&min_point,
+ &g_array_index (ret_coords, GimpCoords, i),
+ &dcoord);
+ dist = gimp_coords_length (&dcoord);
+
+ if (dist < min_dist || min_dist < 0)
+ {
+ min_dist = dist;
+ *ret_point = g_array_index (ret_coords, GimpCoords, i);
+ *ret_pos = g_array_index (ret_params, gdouble, i);
+ }
+ }
+ }
+ ori = ori2;
+ }
+
+ if (min_dist < 0)
+ g_printerr ("-\n");
+ else
+ g_printerr ("%f: (%.2f, %.2f) /%.3f/\n", min_dist,
+ (*ret_point).x, (*ret_point).y, *ret_pos);
+
+ g_array_free (ret_coords, TRUE);
+ g_array_free (ret_params, TRUE);
+
+ return min_dist;
+}
+
+static gboolean
+gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ GList *listneighbor;
+ gint loose_end;
+
+ if (stroke->closed)
+ return FALSE;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return TRUE;
+
+ /* assure that there is a neighbor specified */
+ g_return_val_if_fail (neighbor != NULL, FALSE);
+
+ loose_end = 0;
+ listneighbor = stroke->anchors->tail;
+
+ /* Check if the neighbor is at an end of the control points */
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = 1;
+ }
+ else
+ {
+ listneighbor = g_list_first (stroke->anchors->head);
+
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = -1;
+ }
+ else
+ {
+ /*
+ * It isn't. If we are on a handle go to the nearest
+ * anchor and see if we can find an end from it.
+ * Yes, this is tedious.
+ */
+
+ listneighbor = g_queue_find (stroke->anchors, neighbor);
+
+ if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL)
+ {
+ if (listneighbor->prev &&
+ GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->prev;
+ }
+ else if (listneighbor->next &&
+ GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->next;
+ }
+ else
+ {
+ loose_end = 0;
+ listneighbor = NULL;
+ }
+ }
+
+ if (listneighbor)
+ /* we found a suitable ANCHOR_ANCHOR now, lets
+ * search for its loose end.
+ */
+ {
+ if (listneighbor->prev &&
+ listneighbor->prev->prev == NULL)
+ {
+ loose_end = -1;
+ }
+ else if (listneighbor->next &&
+ listneighbor->next->next == NULL)
+ {
+ loose_end = 1;
+ }
+ }
+ }
+ }
+
+ return (loose_end != 0);
+}
+
+GimpAnchor *
+gimp_bezier_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ GimpAnchor *anchor = NULL;
+ GList *listneighbor;
+ gint loose_end, control_count;
+
+ if (g_queue_is_empty (stroke->anchors))
+ {
+ /* assure that there is no neighbor specified */
+ g_return_val_if_fail (neighbor == NULL, NULL);
+
+ anchor = gimp_anchor_new (GIMP_ANCHOR_CONTROL, coords);
+
+ g_queue_push_tail (stroke->anchors, anchor);
+
+ switch (extend_mode)
+ {
+ case EXTEND_SIMPLE:
+ break;
+
+ case EXTEND_EDITABLE:
+ anchor = gimp_bezier_stroke_extend (stroke,
+ coords, anchor,
+ EXTEND_SIMPLE);
+
+ /* we return the GIMP_ANCHOR_ANCHOR */
+ gimp_bezier_stroke_extend (stroke,
+ coords, anchor,
+ EXTEND_SIMPLE);
+
+ break;
+
+ default:
+ anchor = NULL;
+ }
+
+ return anchor;
+ }
+ else
+ {
+ /* assure that there is a neighbor specified */
+ g_return_val_if_fail (neighbor != NULL, NULL);
+
+ loose_end = 0;
+ listneighbor = stroke->anchors->tail;
+
+ /* Check if the neighbor is at an end of the control points */
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = 1;
+ }
+ else
+ {
+ listneighbor = stroke->anchors->head;
+
+ if (listneighbor->data == neighbor)
+ {
+ loose_end = -1;
+ }
+ else
+ {
+ /*
+ * It isn't. If we are on a handle go to the nearest
+ * anchor and see if we can find an end from it.
+ * Yes, this is tedious.
+ */
+
+ listneighbor = g_queue_find (stroke->anchors, neighbor);
+
+ if (listneighbor && neighbor->type == GIMP_ANCHOR_CONTROL)
+ {
+ if (listneighbor->prev &&
+ GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->prev;
+ }
+ else if (listneighbor->next &&
+ GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ listneighbor = listneighbor->next;
+ }
+ else
+ {
+ loose_end = 0;
+ listneighbor = NULL;
+ }
+ }
+
+ if (listneighbor)
+ /* we found a suitable ANCHOR_ANCHOR now, lets
+ * search for its loose end.
+ */
+ {
+ if (listneighbor->next &&
+ listneighbor->next->next == NULL)
+ {
+ loose_end = 1;
+ listneighbor = listneighbor->next;
+ }
+ else if (listneighbor->prev &&
+ listneighbor->prev->prev == NULL)
+ {
+ loose_end = -1;
+ listneighbor = listneighbor->prev;
+ }
+ }
+ }
+ }
+
+ if (loose_end)
+ {
+ GimpAnchorType type;
+
+ /* We have to detect the type of the point to add... */
+
+ control_count = 0;
+
+ if (loose_end == 1)
+ {
+ while (listneighbor &&
+ GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ control_count++;
+ listneighbor = listneighbor->prev;
+ }
+ }
+ else
+ {
+ while (listneighbor &&
+ GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ control_count++;
+ listneighbor = listneighbor->next;
+ }
+ }
+
+ switch (extend_mode)
+ {
+ case EXTEND_SIMPLE:
+ switch (control_count)
+ {
+ case 0:
+ type = GIMP_ANCHOR_CONTROL;
+ break;
+ case 1:
+ if (listneighbor) /* only one handle in the path? */
+ type = GIMP_ANCHOR_CONTROL;
+ else
+ type = GIMP_ANCHOR_ANCHOR;
+ break;
+ case 2:
+ type = GIMP_ANCHOR_ANCHOR;
+ break;
+ default:
+ g_warning ("inconsistent bezier curve: "
+ "%d successive control handles", control_count);
+ type = GIMP_ANCHOR_ANCHOR;
+ }
+
+ anchor = gimp_anchor_new (type, coords);
+
+ if (loose_end == 1)
+ g_queue_push_tail (stroke->anchors, anchor);
+
+ if (loose_end == -1)
+ g_queue_push_head (stroke->anchors, anchor);
+ break;
+
+ case EXTEND_EDITABLE:
+ switch (control_count)
+ {
+ case 0:
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ &(neighbor->position),
+ neighbor,
+ EXTEND_SIMPLE);
+ case 1:
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ neighbor,
+ EXTEND_SIMPLE);
+ case 2:
+ anchor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ neighbor,
+ EXTEND_SIMPLE);
+
+ neighbor = gimp_bezier_stroke_extend (stroke,
+ coords,
+ anchor,
+ EXTEND_SIMPLE);
+ break;
+ default:
+ g_warning ("inconsistent bezier curve: "
+ "%d successive control handles", control_count);
+ }
+ }
+
+ return anchor;
+ }
+
+ return NULL;
+ }
+}
+
+static gboolean
+gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ GList *list1;
+ GList *list2;
+
+ list1 = g_queue_find (stroke->anchors, anchor);
+ list1 = gimp_bezier_stroke_get_anchor_listitem (list1);
+ list2 = g_queue_find (extension->anchors, neighbor);
+ list2 = gimp_bezier_stroke_get_anchor_listitem (list2);
+
+ g_return_val_if_fail (list1 != NULL && list2 != NULL, FALSE);
+
+ if (stroke == extension)
+ {
+ g_return_val_if_fail ((list1->prev && list1->prev->prev == NULL &&
+ list2->next && list2->next->next == NULL) ||
+ (list1->next && list1->next->next == NULL &&
+ list2->prev && list2->prev->prev == NULL), FALSE);
+ gimp_stroke_close (stroke);
+ return TRUE;
+ }
+
+ if (list1->prev && list1->prev->prev == NULL)
+ {
+ g_queue_reverse (stroke->anchors);
+ }
+
+ g_return_val_if_fail (list1->next && list1->next->next == NULL, FALSE);
+
+ if (list2->next && list2->next->next == NULL)
+ {
+ g_queue_reverse (extension->anchors);
+ }
+
+ g_return_val_if_fail (list2->prev && list2->prev->prev == NULL, FALSE);
+
+ for (list1 = extension->anchors->head; list1; list1 = g_list_next (list1))
+ g_queue_push_tail (stroke->anchors, list1->data);
+
+ g_queue_clear (extension->anchors);
+
+ return TRUE;
+}
+
+
+static void
+gimp_bezier_stroke_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords delta, coord1, coord2;
+ GList *anchor_list;
+
+ delta = *deltacoord;
+ delta.pressure = 0;
+ delta.xtilt = 0;
+ delta.ytilt = 0;
+ delta.wheel = 0;
+
+ gimp_coords_add (&(anchor->position), &delta, &coord1);
+ anchor->position = coord1;
+
+ anchor_list = g_queue_find (stroke->anchors, anchor);
+ g_return_if_fail (anchor_list != NULL);
+
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (g_list_previous (anchor_list))
+ {
+ coord2 = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
+ gimp_coords_add (&coord2, &delta, &coord1);
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position = coord1;
+ }
+
+ if (g_list_next (anchor_list))
+ {
+ coord2 = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
+ gimp_coords_add (&coord2, &delta, &coord1);
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->position = coord1;
+ }
+ }
+ else
+ {
+ if (feature == GIMP_ANCHOR_FEATURE_SYMMETRIC)
+ {
+ GList *neighbour = NULL, *opposite = NULL;
+
+ /* search for opposite control point. Sigh. */
+ neighbour = g_list_previous (anchor_list);
+ if (neighbour &&
+ GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ opposite = g_list_previous (neighbour);
+ }
+ else
+ {
+ neighbour = g_list_next (anchor_list);
+ if (neighbour &&
+ GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
+ {
+ opposite = g_list_next (neighbour);
+ }
+ }
+ if (opposite &&
+ GIMP_ANCHOR (opposite->data)->type == GIMP_ANCHOR_CONTROL)
+ {
+ gimp_coords_difference (&(GIMP_ANCHOR (neighbour->data)->position),
+ &(anchor->position), &delta);
+ gimp_coords_add (&(GIMP_ANCHOR (neighbour->data)->position),
+ &delta, &coord1);
+ GIMP_ANCHOR (opposite->data)->position = coord1;
+ }
+ }
+ }
+}
+
+
+static void
+gimp_bezier_stroke_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ GimpCoords deltacoord;
+
+ gimp_coords_difference (coord, &anchor->position, &deltacoord);
+ gimp_bezier_stroke_anchor_move_relative (stroke, anchor,
+ &deltacoord, feature);
+}
+
+static void
+gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ GList *anchor_list;
+
+ anchor_list = g_queue_find (stroke->anchors, anchor);
+
+ g_return_if_fail (anchor_list != NULL);
+
+ switch (feature)
+ {
+ case GIMP_ANCHOR_FEATURE_EDGE:
+ if (anchor->type == GIMP_ANCHOR_ANCHOR)
+ {
+ if (g_list_previous (anchor_list))
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position =
+ anchor->position;
+
+ if (g_list_next (anchor_list))
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->position =
+ anchor->position;
+ }
+ else
+ {
+ if (g_list_previous (anchor_list) &&
+ GIMP_ANCHOR (g_list_previous (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
+ anchor->position = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
+ if (g_list_next (anchor_list) &&
+ GIMP_ANCHOR (g_list_next (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
+ anchor->position = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
+ }
+
+ break;
+
+ default:
+ g_warning ("gimp_bezier_stroke_anchor_convert: "
+ "unimplemented anchor conversion %d\n", feature);
+ }
+}
+
+
+static GimpBezierDesc *
+gimp_bezier_stroke_make_bezier (GimpStroke *stroke)
+{
+ GArray *points;
+ GArray *cmd_array;
+ GimpBezierDesc *bezdesc;
+ cairo_path_data_t pathdata;
+ gint num_cmds, i;
+
+ points = gimp_stroke_control_points_get (stroke, NULL);
+
+ g_return_val_if_fail (points && points->len % 3 == 0, NULL);
+ if (points->len < 3)
+ return NULL;
+
+ /* Moveto + (n-1) * curveto + (if closed) curveto + closepath */
+ num_cmds = 2 + (points->len / 3 - 1) * 4;
+ if (stroke->closed)
+ num_cmds += 1 + 4;
+
+ cmd_array = g_array_sized_new (FALSE, FALSE,
+ sizeof (cairo_path_data_t),
+ num_cmds);
+
+ pathdata.header.type = CAIRO_PATH_MOVE_TO;
+ pathdata.header.length = 2;
+ g_array_append_val (cmd_array, pathdata);
+ pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ for (i = 2; i+2 < points->len; i += 3)
+ {
+ pathdata.header.type = CAIRO_PATH_CURVE_TO;
+ pathdata.header.length = 4;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i+1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i+1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i+2).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i+2).position.y;
+ g_array_append_val (cmd_array, pathdata);
+ }
+
+ if (stroke->closed)
+ {
+ pathdata.header.type = CAIRO_PATH_CURVE_TO;
+ pathdata.header.length = 4;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, i).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, 0).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 0).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x;
+ pathdata.point.y = g_array_index (points, GimpAnchor, 1).position.y;
+ g_array_append_val (cmd_array, pathdata);
+
+ pathdata.header.type = CAIRO_PATH_CLOSE_PATH;
+ pathdata.header.length = 1;
+ g_array_append_val (cmd_array, pathdata);
+ }
+
+ if (cmd_array->len != num_cmds)
+ g_printerr ("miscalculated path cmd length! (%d vs. %d)\n",
+ cmd_array->len, num_cmds);
+
+ bezdesc = gimp_bezier_desc_new ((cairo_path_data_t *) cmd_array->data,
+ cmd_array->len);
+ g_array_free (points, TRUE);
+ g_array_free (cmd_array, FALSE);
+
+ return bezdesc;
+}
+
+
+static GArray *
+gimp_bezier_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ GArray *ret_coords;
+ GimpAnchor *anchor;
+ GList *anchorlist;
+ GimpCoords segmentcoords[4];
+ gint count;
+ gboolean need_endpoint = FALSE;
+
+ if (g_queue_is_empty (stroke->anchors))
+ {
+ if (ret_closed)
+ *ret_closed = FALSE;
+ return NULL;
+ }
+
+ ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ count = 0;
+
+ for (anchorlist = stroke->anchors->head;
+ anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ for ( ; anchorlist; anchorlist = g_list_next (anchorlist))
+ {
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ gimp_coords_interpolate_bezier (segmentcoords, precision,
+ ret_coords, NULL);
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ need_endpoint = TRUE;
+ }
+ }
+
+ if (stroke->closed && ! g_queue_is_empty (stroke->anchors))
+ {
+ anchorlist = stroke->anchors->head;
+
+ while (count < 3)
+ {
+ segmentcoords[count] = GIMP_ANCHOR (anchorlist->data)->position;
+ count++;
+ }
+ anchorlist = g_list_next (anchorlist);
+ if (anchorlist)
+ segmentcoords[3] = GIMP_ANCHOR (anchorlist->data)->position;
+
+ gimp_coords_interpolate_bezier (segmentcoords, precision,
+ ret_coords, NULL);
+ need_endpoint = TRUE;
+
+ }
+
+ if (need_endpoint)
+ ret_coords = g_array_append_val (ret_coords, segmentcoords[3]);
+
+ if (ret_closed)
+ *ret_closed = stroke->closed;
+
+ if (ret_coords->len == 0)
+ {
+ g_array_free (ret_coords, TRUE);
+ ret_coords = NULL;
+ }
+
+ return ret_coords;
+}
+
+
+static void
+gimp_bezier_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ GimpStroke *first_stroke = NULL;
+ GimpStroke *last_stroke = NULL;
+ GList *anchorlist;
+ GimpAnchor *anchor;
+ GimpCoords segmentcoords[4];
+ GQueue *transformed[2];
+ gint n_transformed;
+ gint count;
+ gboolean first;
+ gboolean last;
+
+ /* if there's no need for clipping, use the default implementation */
+ if (! ret_strokes ||
+ gimp_matrix3_is_affine (matrix) ||
+ g_queue_is_empty (stroke->anchors))
+ {
+ GIMP_STROKE_CLASS (parent_class)->transform (stroke, matrix, ret_strokes);
+
+ return;
+ }
+
+ /* transform the individual segments */
+ count = 0;
+ first = TRUE;
+ last = FALSE;
+
+ /* find the first non-control anchor */
+ for (anchorlist = stroke->anchors->head;
+ anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_ANCHOR_ANCHOR;
+ anchorlist = g_list_next (anchorlist));
+
+ for ( ; anchorlist || stroke->closed; anchorlist = g_list_next (anchorlist))
+ {
+ /* wrap around if 'stroke' is closed, so that we transform the final
+ * segment
+ */
+ if (! anchorlist)
+ {
+ anchorlist = stroke->anchors->head;
+ last = TRUE;
+ }
+
+ anchor = anchorlist->data;
+
+ segmentcoords[count] = anchor->position;
+ count++;
+
+ if (count == 4)
+ {
+ gboolean start_in;
+ gboolean end_in;
+ gint i;
+
+ gimp_transform_bezier_coords (matrix, segmentcoords,
+ transformed, &n_transformed,
+ &start_in, &end_in);
+
+ for (i = 0; i < n_transformed; i++)
+ {
+ GimpStroke *s = NULL;
+ GList *list;
+ gint j;
+
+ if (i == 0 && start_in)
+ {
+ /* current stroke is connected to last stroke */
+ s = last_stroke;
+ }
+ else if (last_stroke)
+ {
+ /* current stroke is not connected to last stroke. finalize
+ * last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+ }
+
+ for (list = transformed[i]->head; list; list = g_list_next (list))
+ {
+ GimpCoords *transformedcoords = list->data;
+
+ if (! s)
+ {
+ /* start a new stroke */
+ s = gimp_bezier_stroke_new ();
+
+ g_queue_push_tail (s->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &transformedcoords[0]));
+
+ g_queue_push_tail (ret_strokes, s);
+
+ j = 0;
+ }
+ else
+ {
+ /* continue an existing stroke, skipping the first anchor,
+ * which is the same as the last anchor of the last stroke
+ */
+ j = 1;
+ }
+
+ for (; j < 4; j++)
+ {
+ GimpAnchorType type;
+
+ if (j == 0 || j == 3)
+ type = GIMP_ANCHOR_ANCHOR;
+ else
+ type = GIMP_ANCHOR_CONTROL;
+
+ g_queue_push_tail (s->anchors,
+ gimp_anchor_new (type,
+ &transformedcoords[j]));
+ }
+
+ g_free (transformedcoords);
+ }
+
+ g_queue_free (transformed[i]);
+
+ /* if the current stroke is an initial segment of 'stroke',
+ * remember it, so that we can possibly connect it to the last
+ * stroke later.
+ */
+ if (i == 0 && start_in && first)
+ first_stroke = s;
+
+ last_stroke = s;
+ first = FALSE;
+ }
+
+ if (! end_in && last_stroke)
+ {
+ /* the next stroke is not connected to the last stroke. finalize
+ * the last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+
+ last_stroke = NULL;
+ }
+
+ if (last)
+ break;
+
+ segmentcoords[0] = segmentcoords[3];
+ count = 1;
+ }
+ }
+
+ /* if the last stroke is a final segment of 'stroke'... */
+ if (last_stroke)
+ {
+ /* ... and the first stroke is an initial segment of 'stroke', and
+ * 'stroke' is closed ...
+ */
+ if (first_stroke && stroke->closed)
+ {
+ /* connect the first and last strokes */
+
+ /* remove the first anchor, which is a synthetic control point */
+ gimp_anchor_free (g_queue_pop_head (first_stroke->anchors));
+ /* remove the last anchor, which is the same anchor point as the
+ * first anchor
+ */
+ gimp_anchor_free (g_queue_pop_tail (last_stroke->anchors));
+
+ if (first_stroke == last_stroke)
+ {
+ /* the result is a single stroke. move the last anchor, which is
+ * an orphan control point, to the front, to fill in the removed
+ * control point of the first anchor, and close the stroke.
+ */
+ g_queue_push_head (first_stroke->anchors,
+ g_queue_pop_tail (first_stroke->anchors));
+
+ first_stroke->closed = TRUE;
+ }
+ else
+ {
+ /* the result is multiple strokes. prepend the last stroke to
+ * the first stroke, and discard it.
+ */
+ while ((anchor = g_queue_pop_tail (last_stroke->anchors)))
+ g_queue_push_head (first_stroke->anchors, anchor);
+
+ g_object_unref (g_queue_pop_tail (ret_strokes));
+ }
+ }
+ else
+ {
+ /* otherwise, the first and last strokes are not connected. finalize
+ * the last stroke.
+ */
+ anchor = g_queue_peek_tail (last_stroke->anchors);
+
+ g_queue_push_tail (last_stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &anchor->position));
+ }
+ }
+}
+
+
+GimpStroke *
+gimp_bezier_stroke_new_moveto (const GimpCoords *start)
+{
+ GimpStroke *stroke = gimp_bezier_stroke_new ();
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ start));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ start));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ start));
+ return stroke;
+}
+
+void
+gimp_bezier_stroke_lineto (GimpStroke *stroke,
+ const GimpCoords *end)
+{
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+void
+gimp_bezier_stroke_conicto (GimpStroke *stroke,
+ const GimpCoords *control,
+ const GimpCoords *end)
+{
+ GimpCoords start, coords;
+
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_get_length (stroke->anchors) > 1);
+
+ start = GIMP_ANCHOR (stroke->anchors->tail->prev->data)->position;
+
+ gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, &start, &coords);
+
+ GIMP_ANCHOR (stroke->anchors->tail->data)->position = coords;
+
+ gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, end, &coords);
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ &coords));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+void
+gimp_bezier_stroke_cubicto (GimpStroke *stroke,
+ const GimpCoords *control1,
+ const GimpCoords *control2,
+ const GimpCoords *end)
+{
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
+ g_return_if_fail (stroke->closed == FALSE);
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ GIMP_ANCHOR (stroke->anchors->tail->data)->position = *control1;
+
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ control2));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
+ end));
+ g_queue_push_tail (stroke->anchors,
+ gimp_anchor_new (GIMP_ANCHOR_CONTROL,
+ end));
+}
+
+static gdouble
+arcto_circleparam (gdouble h,
+ gdouble *y)
+{
+ gdouble t0 = 0.5;
+ gdouble dt = 0.25;
+ gdouble pt0;
+ gdouble y01, y12, y23, y012, y123, y0123; /* subdividing y[] */
+
+ while (dt >= 0.00001)
+ {
+ pt0 = ( y[0] * (1-t0) * (1-t0) * (1-t0) +
+ 3 * y[1] * (1-t0) * (1-t0) * t0 +
+ 3 * y[2] * (1-t0) * t0 * t0 +
+ y[3] * t0 * t0 * t0 );
+
+ if (pt0 > h)
+ t0 = t0 - dt;
+ else if (pt0 < h)
+ t0 = t0 + dt;
+ else
+ break;
+ dt = dt/2;
+ }
+
+ y01 = y[0] * (1-t0) + y[1] * t0;
+ y12 = y[1] * (1-t0) + y[2] * t0;
+ y23 = y[2] * (1-t0) + y[3] * t0;
+ y012 = y01 * (1-t0) + y12 * t0;
+ y123 = y12 * (1-t0) + y23 * t0;
+ y0123 = y012 * (1-t0) + y123 * t0;
+
+ y[0] = y0123; y[1] = y123; y[2] = y23; /* y[3] unchanged */
+
+ return t0;
+}
+
+static void
+arcto_subdivide (gdouble t,
+ gint part,
+ GimpCoords *p)
+{
+ GimpCoords p01, p12, p23, p012, p123, p0123;
+
+ gimp_coords_mix (1-t, &(p[0]), t, &(p[1]), &p01 );
+ gimp_coords_mix (1-t, &(p[1]), t, &(p[2]), &p12 );
+ gimp_coords_mix (1-t, &(p[2]), t, &(p[3]), &p23 );
+ gimp_coords_mix (1-t, &p01 , t, &p12 , &p012 );
+ gimp_coords_mix (1-t, &p12 , t, &p23 , &p123 );
+ gimp_coords_mix (1-t, &p012 , t, &p123 , &p0123);
+
+ if (part == 0)
+ {
+ /* p[0] unchanged */
+ p[1] = p01;
+ p[2] = p012;
+ p[3] = p0123;
+ }
+ else
+ {
+ p[0] = p0123;
+ p[1] = p123;
+ p[2] = p23;
+ /* p[3] unchanged */
+ }
+}
+
+static void
+arcto_ellipsesegment (gdouble radius_x,
+ gdouble radius_y,
+ gdouble phi0,
+ gdouble phi1,
+ GimpCoords *ellips)
+{
+ const GimpCoords template = GIMP_COORDS_DEFAULT_VALUES;
+ const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0;
+
+ gdouble phi_s, phi_e;
+ gdouble y[4];
+ gdouble h0, h1;
+ gdouble t0, t1;
+
+ g_return_if_fail (ellips != NULL);
+
+ y[0] = 0.0;
+ y[1] = circlemagic;
+ y[2] = 1.0;
+ y[3] = 1.0;
+
+ ellips[0] = template;
+ ellips[1] = template;
+ ellips[2] = template;
+ ellips[3] = template;
+
+ if (phi0 < phi1)
+ {
+ phi_s = floor (phi0 / G_PI_2) * G_PI_2;
+ while (phi_s < 0) phi_s += 2 * G_PI;
+ phi_e = phi_s + G_PI_2;
+ }
+ else
+ {
+ phi_e = floor (phi1 / G_PI_2) * G_PI_2;
+ while (phi_e < 0) phi_e += 2 * G_PI;
+ phi_s = phi_e + G_PI_2;
+ }
+
+ h0 = sin (fabs (phi0-phi_s));
+ h1 = sin (fabs (phi1-phi_s));
+
+ ellips[0].x = cos (phi_s); ellips[0].y = sin (phi_s);
+ ellips[3].x = cos (phi_e); ellips[3].y = sin (phi_e);
+
+ gimp_coords_mix (1, &(ellips[0]), circlemagic, &(ellips[3]), &(ellips[1]));
+ gimp_coords_mix (circlemagic, &(ellips[0]), 1, &(ellips[3]), &(ellips[2]));
+
+ if (h0 > y[0])
+ {
+ t0 = arcto_circleparam (h0, y); /* also subdivides y[] at t0 */
+ arcto_subdivide (t0, 1, ellips);
+ }
+
+ if (h1 < y[3])
+ {
+ t1 = arcto_circleparam (h1, y);
+ arcto_subdivide (t1, 0, ellips);
+ }
+
+ ellips[0].x *= radius_x ; ellips[0].y *= radius_y;
+ ellips[1].x *= radius_x ; ellips[1].y *= radius_y;
+ ellips[2].x *= radius_x ; ellips[2].y *= radius_y;
+ ellips[3].x *= radius_x ; ellips[3].y *= radius_y;
+}
+
+void
+gimp_bezier_stroke_arcto (GimpStroke *bez_stroke,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle_rad,
+ gboolean large_arc,
+ gboolean sweep,
+ const GimpCoords *end)
+{
+ GimpCoords start;
+ GimpCoords middle; /* between start and end */
+ GimpCoords trans_delta;
+ GimpCoords trans_center;
+ GimpCoords tmp_center;
+ GimpCoords center;
+ GimpCoords ellips[4]; /* control points of untransformed ellipse segment */
+ GimpCoords ctrl[4]; /* control points of next bezier segment */
+
+ GimpMatrix3 anglerot;
+
+ gdouble lambda;
+ gdouble phi0, phi1, phi2;
+ gdouble tmpx, tmpy;
+
+ g_return_if_fail (GIMP_IS_BEZIER_STROKE (bez_stroke));
+ g_return_if_fail (bez_stroke->closed == FALSE);
+ g_return_if_fail (g_queue_get_length (bez_stroke->anchors) > 1);
+
+ if (radius_x == 0 || radius_y == 0)
+ {
+ gimp_bezier_stroke_lineto (bez_stroke, end);
+ return;
+ }
+
+ start = GIMP_ANCHOR (bez_stroke->anchors->tail->prev->data)->position;
+
+ gimp_matrix3_identity (&anglerot);
+ gimp_matrix3_rotate (&anglerot, -angle_rad);
+
+ gimp_coords_mix (0.5, &start, -0.5, end, &trans_delta);
+ gimp_matrix3_transform_point (&anglerot,
+ trans_delta.x, trans_delta.y,
+ &tmpx, &tmpy);
+ trans_delta.x = tmpx;
+ trans_delta.y = tmpy;
+
+ lambda = (SQR (trans_delta.x) / SQR (radius_x) +
+ SQR (trans_delta.y) / SQR (radius_y));
+
+ if (lambda < 0.00001)
+ {
+ /* don't bother with it - endpoint is too close to startpoint */
+ return;
+ }
+
+ trans_center = trans_delta;
+
+ if (lambda > 1.0)
+ {
+ /* The radii are too small for a matching ellipse. We expand them
+ * so that they fit exactly (center of the ellipse between the
+ * start- and endpoint
+ */
+ radius_x *= sqrt (lambda);
+ radius_y *= sqrt (lambda);
+ trans_center.x = 0.0;
+ trans_center.y = 0.0;
+ }
+ else
+ {
+ gdouble factor = sqrt ((1.0 - lambda) / lambda);
+
+ trans_center.x = trans_delta.y * radius_x / radius_y * factor;
+ trans_center.y = - trans_delta.x * radius_y / radius_x * factor;
+ }
+
+ if ((large_arc && sweep) || (!large_arc && !sweep))
+ {
+ trans_center.x *= -1;
+ trans_center.y *= -1;
+ }
+
+ gimp_matrix3_identity (&anglerot);
+ gimp_matrix3_rotate (&anglerot, angle_rad);
+
+ tmp_center = trans_center;
+ gimp_matrix3_transform_point (&anglerot,
+ tmp_center.x, tmp_center.y,
+ &tmpx, &tmpy);
+ tmp_center.x = tmpx;
+ tmp_center.y = tmpy;
+
+ gimp_coords_mix (0.5, &start, 0.5, end, &middle);
+ gimp_coords_add (&tmp_center, &middle, &center);
+
+ phi1 = atan2 ((trans_delta.y - trans_center.y) / radius_y,
+ (trans_delta.x - trans_center.x) / radius_x);
+
+ phi2 = atan2 ((- trans_delta.y - trans_center.y) / radius_y,
+ (- trans_delta.x - trans_center.x) / radius_x);
+
+ if (phi1 < 0)
+ phi1 += 2 * G_PI;
+
+ if (phi2 < 0)
+ phi2 += 2 * G_PI;
+
+ if (sweep)
+ {
+ while (phi2 < phi1)
+ phi2 += 2 * G_PI;
+
+ phi0 = floor (phi1 / G_PI_2) * G_PI_2;
+
+ while (phi0 < phi2)
+ {
+ arcto_ellipsesegment (radius_x, radius_y,
+ MAX (phi0, phi1), MIN (phi0 + G_PI_2, phi2),
+ ellips);
+
+ gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
+ &tmpx, &tmpy);
+ ellips[0].x = tmpx; ellips[0].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
+ &tmpx, &tmpy);
+ ellips[1].x = tmpx; ellips[1].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
+ &tmpx, &tmpy);
+ ellips[2].x = tmpx; ellips[2].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
+ &tmpx, &tmpy);
+ ellips[3].x = tmpx; ellips[3].y = tmpy;
+
+ gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
+ gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
+ gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
+
+ gimp_bezier_stroke_cubicto (bez_stroke,
+ &(ctrl[1]), &(ctrl[2]), &(ctrl[3]));
+ phi0 += G_PI_2;
+ }
+ }
+ else
+ {
+ while (phi1 < phi2)
+ phi1 += 2 * G_PI;
+
+ phi0 = ceil (phi1 / G_PI_2) * G_PI_2;
+
+ while (phi0 > phi2)
+ {
+ arcto_ellipsesegment (radius_x, radius_y,
+ MIN (phi0, phi1), MAX (phi0 - G_PI_2, phi2),
+ ellips);
+
+ gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
+ &tmpx, &tmpy);
+ ellips[0].x = tmpx; ellips[0].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
+ &tmpx, &tmpy);
+ ellips[1].x = tmpx; ellips[1].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
+ &tmpx, &tmpy);
+ ellips[2].x = tmpx; ellips[2].y = tmpy;
+ gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
+ &tmpx, &tmpy);
+ ellips[3].x = tmpx; ellips[3].y = tmpy;
+
+ gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
+ gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
+ gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
+
+ gimp_bezier_stroke_cubicto (bez_stroke,
+ &(ctrl[1]), &(ctrl[2]), &(ctrl[3]));
+ phi0 -= G_PI_2;
+ }
+ }
+}
+
+GimpStroke *
+gimp_bezier_stroke_new_ellipse (const GimpCoords *center,
+ gdouble radius_x,
+ gdouble radius_y,
+ gdouble angle)
+{
+ GimpStroke *stroke;
+ GimpCoords p1 = *center;
+ GimpCoords p2 = *center;
+ GimpCoords p3 = *center;
+ GimpCoords dx = { 0, };
+ GimpCoords dy = { 0, };
+ const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0;
+ GimpAnchor *handle;
+
+ dx.x = radius_x * cos (angle);
+ dx.y = - radius_x * sin (angle);
+ dy.x = radius_y * sin (angle);
+ dy.y = radius_y * cos (angle);
+
+ gimp_coords_mix (1.0, center, 1.0, &dx, &p1);
+ stroke = gimp_bezier_stroke_new_moveto (&p1);
+
+ handle = g_queue_peek_head (stroke->anchors);
+ gimp_coords_mix (1.0, &p1, -circlemagic, &dy, &handle->position);
+
+ gimp_coords_mix (1.0, &p1, circlemagic, &dy, &p1);
+ gimp_coords_mix (1.0, center, 1.0, &dy, &p3);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dx, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p1);
+ gimp_coords_mix (1.0, center, -1.0, &dx, &p3);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dy, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dy, &p1);
+ gimp_coords_mix (1.0, center, -1.0, &dy, &p3);
+ gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p2);
+ gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
+
+ handle = g_queue_peek_tail (stroke->anchors);
+ gimp_coords_mix (1.0, &p3, circlemagic, &dx, &handle->position);
+
+ gimp_stroke_close (stroke);
+
+ return stroke;
+}
+
+
+/* helper function to get the associated anchor of a listitem */
+
+static GList *
+gimp_bezier_stroke_get_anchor_listitem (GList *list)
+{
+ if (!list)
+ return NULL;
+
+ if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list;
+
+ if (list->prev && GIMP_ANCHOR (list->prev->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list->prev;
+
+ if (list->next && GIMP_ANCHOR (list->next->data)->type == GIMP_ANCHOR_ANCHOR)
+ return list->next;
+
+ g_return_val_if_fail (/* bezier stroke inconsistent! */ FALSE, NULL);
+
+ return NULL;
+}