summaryrefslogtreecommitdiffstats
path: root/app/vectors/gimpstroke.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--app/vectors/gimpstroke.c1430
1 files changed, 1430 insertions, 0 deletions
diff --git a/app/vectors/gimpstroke.c b/app/vectors/gimpstroke.c
new file mode 100644
index 0000000..13db273
--- /dev/null
+++ b/app/vectors/gimpstroke.c
@@ -0,0 +1,1430 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpstroke.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 <cairo.h>
+#include <gegl.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "vectors-types.h"
+
+#include "core/gimp-memsize.h"
+#include "core/gimpcoords.h"
+#include "core/gimpparamspecs.h"
+#include "core/gimp-transform-utils.h"
+
+#include "gimpanchor.h"
+#include "gimpstroke.h"
+
+enum
+{
+ PROP_0,
+ PROP_CONTROL_POINTS,
+ PROP_CLOSED
+};
+
+/* Prototypes */
+
+static void gimp_stroke_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_stroke_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_stroke_finalize (GObject *object);
+
+static gint64 gimp_stroke_get_memsize (GimpObject *object,
+ gint64 *gui_size);
+
+static GimpAnchor * gimp_stroke_real_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord);
+static GimpAnchor * gimp_stroke_real_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev);
+static void gimp_stroke_real_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive);
+static void gimp_stroke_real_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor);
+static gboolean gimp_stroke_real_point_is_movable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static void gimp_stroke_real_point_move_relative
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature);
+static void gimp_stroke_real_point_move_absolute
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature);
+
+static void gimp_stroke_real_close (GimpStroke *stroke);
+static GimpStroke * gimp_stroke_real_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor);
+static gboolean gimp_stroke_real_anchor_is_insertable
+ (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+static GimpAnchor * gimp_stroke_real_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position);
+
+static gboolean gimp_stroke_real_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor);
+
+static GimpAnchor * gimp_stroke_real_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode);
+
+gboolean gimp_stroke_real_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor);
+
+
+static gboolean gimp_stroke_real_is_empty (GimpStroke *stroke);
+
+static gdouble gimp_stroke_real_get_length (GimpStroke *stroke,
+ gdouble precision);
+static gdouble gimp_stroke_real_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord);
+static GArray * gimp_stroke_real_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *closed);
+static GimpStroke * gimp_stroke_real_duplicate (GimpStroke *stroke);
+static GimpBezierDesc * gimp_stroke_real_make_bezier (GimpStroke *stroke);
+
+static void gimp_stroke_real_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y);
+static void gimp_stroke_real_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y);
+static void gimp_stroke_real_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle);
+static void gimp_stroke_real_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis);
+static void gimp_stroke_real_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2);
+static void gimp_stroke_real_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes);
+
+static GList * gimp_stroke_real_get_draw_anchors (GimpStroke *stroke);
+static GList * gimp_stroke_real_get_draw_controls (GimpStroke *stroke);
+static GArray * gimp_stroke_real_get_draw_lines (GimpStroke *stroke);
+static GArray * gimp_stroke_real_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed);
+static gboolean gimp_stroke_real_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope);
+
+
+G_DEFINE_TYPE (GimpStroke, gimp_stroke, GIMP_TYPE_OBJECT)
+
+#define parent_class gimp_stroke_parent_class
+
+
+static void
+gimp_stroke_class_init (GimpStrokeClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
+ GParamSpec *param_spec;
+
+ object_class->finalize = gimp_stroke_finalize;
+ object_class->get_property = gimp_stroke_get_property;
+ object_class->set_property = gimp_stroke_set_property;
+
+ gimp_object_class->get_memsize = gimp_stroke_get_memsize;
+
+ klass->changed = NULL;
+ klass->removed = NULL;
+
+ klass->anchor_get = gimp_stroke_real_anchor_get;
+ klass->anchor_get_next = gimp_stroke_real_anchor_get_next;
+ klass->anchor_select = gimp_stroke_real_anchor_select;
+ klass->anchor_move_relative = gimp_stroke_real_anchor_move_relative;
+ klass->anchor_move_absolute = gimp_stroke_real_anchor_move_absolute;
+ klass->anchor_convert = gimp_stroke_real_anchor_convert;
+ klass->anchor_delete = gimp_stroke_real_anchor_delete;
+
+ klass->point_is_movable = gimp_stroke_real_point_is_movable;
+ klass->point_move_relative = gimp_stroke_real_point_move_relative;
+ klass->point_move_absolute = gimp_stroke_real_point_move_absolute;
+
+ klass->nearest_point_get = NULL;
+ klass->nearest_tangent_get = NULL;
+ klass->nearest_intersection_get = NULL;
+ klass->close = gimp_stroke_real_close;
+ klass->open = gimp_stroke_real_open;
+ klass->anchor_is_insertable = gimp_stroke_real_anchor_is_insertable;
+ klass->anchor_insert = gimp_stroke_real_anchor_insert;
+ klass->is_extendable = gimp_stroke_real_is_extendable;
+ klass->extend = gimp_stroke_real_extend;
+ klass->connect_stroke = gimp_stroke_real_connect_stroke;
+
+ klass->is_empty = gimp_stroke_real_is_empty;
+ klass->get_length = gimp_stroke_real_get_length;
+ klass->get_distance = gimp_stroke_real_get_distance;
+ klass->get_point_at_dist = gimp_stroke_real_get_point_at_dist;
+ klass->interpolate = gimp_stroke_real_interpolate;
+
+ klass->duplicate = gimp_stroke_real_duplicate;
+ klass->make_bezier = gimp_stroke_real_make_bezier;
+
+ klass->translate = gimp_stroke_real_translate;
+ klass->scale = gimp_stroke_real_scale;
+ klass->rotate = gimp_stroke_real_rotate;
+ klass->flip = gimp_stroke_real_flip;
+ klass->flip_free = gimp_stroke_real_flip_free;
+ klass->transform = gimp_stroke_real_transform;
+
+
+ klass->get_draw_anchors = gimp_stroke_real_get_draw_anchors;
+ klass->get_draw_controls = gimp_stroke_real_get_draw_controls;
+ klass->get_draw_lines = gimp_stroke_real_get_draw_lines;
+ klass->control_points_get = gimp_stroke_real_control_points_get;
+
+ param_spec = g_param_spec_boxed ("gimp-anchor",
+ "Gimp Anchor",
+ "The control points of a Stroke",
+ GIMP_TYPE_ANCHOR,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property (object_class, PROP_CONTROL_POINTS,
+ gimp_param_spec_value_array ("control-points",
+ "Control Points",
+ "This is an ValueArray "
+ "with the initial "
+ "control points of "
+ "the new Stroke",
+ param_spec,
+ GIMP_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_CLOSED,
+ g_param_spec_boolean ("closed",
+ "Close Flag",
+ "this flag indicates "
+ "whether the stroke "
+ "is closed or not",
+ FALSE,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gimp_stroke_init (GimpStroke *stroke)
+{
+ stroke->anchors = g_queue_new ();
+}
+
+static void
+gimp_stroke_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+ GimpValueArray *val_array;
+ gint length;
+ gint i;
+
+ switch (property_id)
+ {
+ case PROP_CLOSED:
+ stroke->closed = g_value_get_boolean (value);
+ break;
+
+ case PROP_CONTROL_POINTS:
+ g_return_if_fail (g_queue_is_empty (stroke->anchors));
+ g_return_if_fail (value != NULL);
+
+ val_array = g_value_get_boxed (value);
+
+ if (val_array == NULL)
+ return;
+
+ length = gimp_value_array_length (val_array);
+
+ for (i = 0; i < length; i++)
+ {
+ GValue *item = gimp_value_array_index (val_array, i);
+
+ g_return_if_fail (G_VALUE_HOLDS (item, GIMP_TYPE_ANCHOR));
+ g_queue_push_tail (stroke->anchors, g_value_dup_boxed (item));
+ }
+
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_stroke_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+
+ switch (property_id)
+ {
+ case PROP_CLOSED:
+ g_value_set_boolean (value, stroke->closed);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_stroke_finalize (GObject *object)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+
+ g_queue_free_full (stroke->anchors, (GDestroyNotify) gimp_anchor_free);
+ stroke->anchors = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gint64
+gimp_stroke_get_memsize (GimpObject *object,
+ gint64 *gui_size)
+{
+ GimpStroke *stroke = GIMP_STROKE (object);
+ gint64 memsize = 0;
+
+ memsize += gimp_g_queue_get_memsize (stroke->anchors, sizeof (GimpAnchor));
+
+ return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
+ gui_size);
+}
+
+void
+gimp_stroke_set_ID (GimpStroke *stroke,
+ gint id)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (stroke->ID == 0 /* we don't want changing IDs... */);
+
+ stroke->ID = id;
+}
+
+gint
+gimp_stroke_get_ID (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), -1);
+
+ return stroke->ID;
+}
+
+
+GimpAnchor *
+gimp_stroke_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_get (stroke, coord);
+}
+
+
+gdouble
+gimp_stroke_nearest_point_get (GimpStroke *stroke,
+ const GimpCoords *coord,
+ const gdouble precision,
+ GimpCoords *ret_point,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coord != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_point_get (stroke,
+ coord,
+ precision,
+ ret_point,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+gdouble
+gimp_stroke_nearest_tangent_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *coords2,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coords1 != NULL, FALSE);
+ g_return_val_if_fail (coords2 != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_tangent_get (stroke,
+ coords1,
+ coords2,
+ precision,
+ nearest,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+gdouble
+gimp_stroke_nearest_intersection_get (GimpStroke *stroke,
+ const GimpCoords *coords1,
+ const GimpCoords *direction,
+ gdouble precision,
+ GimpCoords *nearest,
+ GimpAnchor **ret_segment_start,
+ GimpAnchor **ret_segment_end,
+ gdouble *ret_pos)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (coords1 != NULL, FALSE);
+ g_return_val_if_fail (direction != NULL, FALSE);
+
+ if (GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get)
+ return GIMP_STROKE_GET_CLASS (stroke)->nearest_intersection_get (stroke,
+ coords1,
+ direction,
+ precision,
+ nearest,
+ ret_segment_start,
+ ret_segment_end,
+ ret_pos);
+ return -1;
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_get (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ gdouble dx, dy;
+ gdouble mindist = -1;
+ GList *anchors;
+ GList *list;
+ GimpAnchor *anchor = NULL;
+
+ anchors = gimp_stroke_get_draw_controls (stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
+ dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
+
+ if (mindist < 0 || mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ anchor = GIMP_ANCHOR (list->data);
+ }
+ }
+
+ g_list_free (anchors);
+
+ anchors = gimp_stroke_get_draw_anchors (stroke);
+
+ for (list = anchors; list; list = g_list_next (list))
+ {
+ dx = coord->x - GIMP_ANCHOR (list->data)->position.x;
+ dy = coord->y - GIMP_ANCHOR (list->data)->position.y;
+
+ if (mindist < 0 || mindist > dx * dx + dy * dy)
+ {
+ mindist = dx * dx + dy * dy;
+ anchor = GIMP_ANCHOR (list->data);
+ }
+ }
+
+ g_list_free (anchors);
+
+ return anchor;
+}
+
+
+GimpAnchor *
+gimp_stroke_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_get_next (stroke, prev);
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_get_next (GimpStroke *stroke,
+ const GimpAnchor *prev)
+{
+ GList *list;
+
+ if (prev)
+ {
+ list = g_queue_find (stroke->anchors, prev);
+ if (list)
+ list = g_list_next (list);
+ }
+ else
+ {
+ list = stroke->anchors->head;
+ }
+
+ if (list)
+ return GIMP_ANCHOR (list->data);
+
+ return NULL;
+}
+
+
+void
+gimp_stroke_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_select (stroke, anchor,
+ selected, exclusive);
+}
+
+static void
+gimp_stroke_real_anchor_select (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ gboolean selected,
+ gboolean exclusive)
+{
+ GList *list = stroke->anchors->head;
+
+ if (exclusive)
+ {
+ while (list)
+ {
+ GIMP_ANCHOR (list->data)->selected = FALSE;
+ list = g_list_next (list);
+ }
+ }
+
+ list = g_queue_find (stroke->anchors, anchor);
+
+ if (list)
+ GIMP_ANCHOR (list->data)->selected = selected;
+}
+
+
+void
+gimp_stroke_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor != NULL);
+ g_return_if_fail (g_queue_find (stroke->anchors, anchor));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_move_relative (stroke, anchor,
+ delta, feature);
+}
+
+static void
+gimp_stroke_real_anchor_move_relative (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *delta,
+ GimpAnchorFeatureType feature)
+{
+ anchor->position.x += delta->x;
+ anchor->position.y += delta->y;
+}
+
+
+void
+gimp_stroke_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor != NULL);
+ g_return_if_fail (g_queue_find (stroke->anchors, anchor));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_move_absolute (stroke, anchor,
+ coord, feature);
+}
+
+static void
+gimp_stroke_real_anchor_move_absolute (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ anchor->position.x = coord->x;
+ anchor->position.y = coord->y;
+}
+
+gboolean
+gimp_stroke_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->point_is_movable (stroke, predec,
+ position);
+}
+
+
+static gboolean
+gimp_stroke_real_point_is_movable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ return FALSE;
+}
+
+
+void
+gimp_stroke_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->point_move_relative (stroke, predec,
+ position, deltacoord,
+ feature);
+}
+
+
+static void
+gimp_stroke_real_point_move_relative (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *deltacoord,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_point_move_relative: default implementation\n");
+}
+
+
+void
+gimp_stroke_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->point_move_absolute (stroke, predec,
+ position, coord,
+ feature);
+}
+
+static void
+gimp_stroke_real_point_move_absolute (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position,
+ const GimpCoords *coord,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_point_move_absolute: default implementation\n");
+}
+
+
+void
+gimp_stroke_close (GimpStroke *stroke)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
+
+ GIMP_STROKE_GET_CLASS (stroke)->close (stroke);
+}
+
+static void
+gimp_stroke_real_close (GimpStroke *stroke)
+{
+ stroke->closed = TRUE;
+ g_object_notify (G_OBJECT (stroke), "closed");
+}
+
+
+void
+gimp_stroke_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_convert (stroke, anchor, feature);
+}
+
+static void
+gimp_stroke_real_anchor_convert (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpAnchorFeatureType feature)
+{
+ g_printerr ("gimp_stroke_anchor_convert: default implementation\n");
+}
+
+
+void
+gimp_stroke_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+ g_return_if_fail (anchor && anchor->type == GIMP_ANCHOR_ANCHOR);
+
+ GIMP_STROKE_GET_CLASS (stroke)->anchor_delete (stroke, anchor);
+}
+
+static void
+gimp_stroke_real_anchor_delete (GimpStroke *stroke,
+ GimpAnchor *anchor)
+{
+ g_printerr ("gimp_stroke_anchor_delete: default implementation\n");
+}
+
+GimpStroke *
+gimp_stroke_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (end_anchor &&
+ end_anchor->type == GIMP_ANCHOR_ANCHOR, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->open (stroke, end_anchor);
+}
+
+static GimpStroke *
+gimp_stroke_real_open (GimpStroke *stroke,
+ GimpAnchor *end_anchor)
+{
+ g_printerr ("gimp_stroke_open: default implementation\n");
+ return NULL;
+}
+
+gboolean
+gimp_stroke_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_is_insertable (stroke,
+ predec,
+ position);
+}
+
+static gboolean
+gimp_stroke_real_anchor_is_insertable (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return FALSE;
+}
+
+GimpAnchor *
+gimp_stroke_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (predec->type == GIMP_ANCHOR_ANCHOR, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->anchor_insert (stroke,
+ predec, position);
+}
+
+static GimpAnchor *
+gimp_stroke_real_anchor_insert (GimpStroke *stroke,
+ GimpAnchor *predec,
+ gdouble position)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return NULL;
+}
+
+
+gboolean
+gimp_stroke_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->is_extendable (stroke, neighbor);
+}
+
+static gboolean
+gimp_stroke_real_is_extendable (GimpStroke *stroke,
+ GimpAnchor *neighbor)
+{
+ return FALSE;
+}
+
+
+GimpAnchor *
+gimp_stroke_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+ g_return_val_if_fail (!stroke->closed, NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->extend (stroke, coords,
+ neighbor, extend_mode);
+}
+
+static GimpAnchor *
+gimp_stroke_real_extend (GimpStroke *stroke,
+ const GimpCoords *coords,
+ GimpAnchor *neighbor,
+ GimpVectorExtendMode extend_mode)
+{
+ g_printerr ("gimp_stroke_extend: default implementation\n");
+ return NULL;
+}
+
+gboolean
+gimp_stroke_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+ g_return_val_if_fail (GIMP_IS_STROKE (extension), FALSE);
+ g_return_val_if_fail (stroke->closed == FALSE &&
+ extension->closed == FALSE, FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->connect_stroke (stroke, anchor,
+ extension, neighbor);
+}
+
+gboolean
+gimp_stroke_real_connect_stroke (GimpStroke *stroke,
+ GimpAnchor *anchor,
+ GimpStroke *extension,
+ GimpAnchor *neighbor)
+{
+ g_printerr ("gimp_stroke_connect_stroke: default implementation\n");
+ return FALSE;
+}
+
+gboolean
+gimp_stroke_is_empty (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->is_empty (stroke);
+}
+
+static gboolean
+gimp_stroke_real_is_empty (GimpStroke *stroke)
+{
+ return g_queue_is_empty (stroke->anchors);
+}
+
+
+gdouble
+gimp_stroke_get_length (GimpStroke *stroke,
+ gdouble precision)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_length (stroke, precision);
+}
+
+static gdouble
+gimp_stroke_real_get_length (GimpStroke *stroke,
+ gdouble precision)
+{
+ GArray *points;
+ gint i;
+ gdouble length;
+ GimpCoords difference;
+
+ if (g_queue_is_empty (stroke->anchors))
+ return -1;
+
+ points = gimp_stroke_interpolate (stroke, precision, NULL);
+ if (points == NULL)
+ return -1;
+
+ length = 0;
+
+ for (i = 0; i < points->len - 1; i++ )
+ {
+ gimp_coords_difference (&(g_array_index (points, GimpCoords, i)),
+ &(g_array_index (points, GimpCoords, i+1)),
+ &difference);
+ length += gimp_coords_length (&difference);
+ }
+
+ g_array_free(points, TRUE);
+
+ return length;
+}
+
+
+gdouble
+gimp_stroke_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), 0.0);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_distance (stroke, coord);
+}
+
+static gdouble
+gimp_stroke_real_get_distance (GimpStroke *stroke,
+ const GimpCoords *coord)
+{
+ g_printerr ("gimp_stroke_get_distance: default implementation\n");
+
+ return 0.0;
+}
+
+
+GArray *
+gimp_stroke_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->interpolate (stroke, precision,
+ ret_closed);
+}
+
+static GArray *
+gimp_stroke_real_interpolate (GimpStroke *stroke,
+ gdouble precision,
+ gboolean *ret_closed)
+{
+ g_printerr ("gimp_stroke_interpolate: default implementation\n");
+
+ return NULL;
+}
+
+GimpStroke *
+gimp_stroke_duplicate (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->duplicate (stroke);
+}
+
+static GimpStroke *
+gimp_stroke_real_duplicate (GimpStroke *stroke)
+{
+ GimpStroke *new_stroke;
+ GList *list;
+
+ new_stroke = g_object_new (G_TYPE_FROM_INSTANCE (stroke),
+ "name", gimp_object_get_name (stroke),
+ NULL);
+
+ new_stroke->anchors = g_queue_copy (stroke->anchors);
+
+ for (list = new_stroke->anchors->head; list; list = g_list_next (list))
+ {
+ list->data = gimp_anchor_copy (GIMP_ANCHOR (list->data));
+ }
+
+ new_stroke->closed = stroke->closed;
+ /* we do *not* copy the ID! */
+
+ return new_stroke;
+}
+
+
+GimpBezierDesc *
+gimp_stroke_make_bezier (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->make_bezier (stroke);
+}
+
+static GimpBezierDesc *
+gimp_stroke_real_make_bezier (GimpStroke *stroke)
+{
+ g_printerr ("gimp_stroke_make_bezier: default implementation\n");
+
+ return NULL;
+}
+
+
+void
+gimp_stroke_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->translate (stroke, offset_x, offset_y);
+}
+
+static void
+gimp_stroke_real_translate (GimpStroke *stroke,
+ gdouble offset_x,
+ gdouble offset_y)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ anchor->position.x += offset_x;
+ anchor->position.y += offset_y;
+ }
+}
+
+
+void
+gimp_stroke_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->scale (stroke, scale_x, scale_y);
+}
+
+static void
+gimp_stroke_real_scale (GimpStroke *stroke,
+ gdouble scale_x,
+ gdouble scale_y)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ anchor->position.x *= scale_x;
+ anchor->position.y *= scale_y;
+ }
+}
+
+void
+gimp_stroke_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->rotate (stroke, center_x, center_y, angle);
+}
+
+static void
+gimp_stroke_real_rotate (GimpStroke *stroke,
+ gdouble center_x,
+ gdouble center_y,
+ gdouble angle)
+{
+ GimpMatrix3 matrix;
+
+ angle = angle / 180.0 * G_PI;
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_rotate_center (&matrix, center_x, center_y, angle);
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+void
+gimp_stroke_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->flip (stroke, flip_type, axis);
+}
+
+static void
+gimp_stroke_real_flip (GimpStroke *stroke,
+ GimpOrientationType flip_type,
+ gdouble axis)
+{
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_flip (&matrix, flip_type, axis);
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+void
+gimp_stroke_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->flip_free (stroke, x1, y1, x2, y2);
+}
+
+static void
+gimp_stroke_real_flip_free (GimpStroke *stroke,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ /* x, y, width and height parameter in gimp_transform_matrix_flip_free are unused */
+ GimpMatrix3 matrix;
+
+ gimp_matrix3_identity (&matrix);
+ gimp_transform_matrix_flip_free (&matrix, x1, y1, x2, y2);
+
+ gimp_stroke_transform (stroke, &matrix, NULL);
+}
+
+/* transforms 'stroke' by 'matrix'. due to clipping, the transformation may
+ * result in multiple strokes.
+ *
+ * if 'ret_strokes' is not NULL, the transformed strokes are appended to the
+ * queue, and 'stroke' is left in an unspecified state. one of the resulting
+ * strokes may alias 'stroke'.
+ *
+ * if 'ret_strokes' is NULL, the transformation is performed in-place. if the
+ * transformation results in multiple strokes (which, atm, can only happen for
+ * non-affine transformation), the result is undefined.
+ */
+void
+gimp_stroke_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ g_return_if_fail (GIMP_IS_STROKE (stroke));
+
+ GIMP_STROKE_GET_CLASS (stroke)->transform (stroke, matrix, ret_strokes);
+}
+
+static void
+gimp_stroke_real_transform (GimpStroke *stroke,
+ const GimpMatrix3 *matrix,
+ GQueue *ret_strokes)
+{
+ GList *list;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ gimp_matrix3_transform_point (matrix,
+ anchor->position.x,
+ anchor->position.y,
+ &anchor->position.x,
+ &anchor->position.y);
+ }
+
+ if (ret_strokes)
+ {
+ stroke->ID = 0;
+
+ g_queue_push_tail (ret_strokes, g_object_ref (stroke));
+ }
+}
+
+
+GList *
+gimp_stroke_get_draw_anchors (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_anchors (stroke);
+}
+
+static GList *
+gimp_stroke_real_get_draw_anchors (GimpStroke *stroke)
+{
+ GList *list;
+ GList *ret_list = NULL;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR)
+ ret_list = g_list_prepend (ret_list, list->data);
+ }
+
+ return g_list_reverse (ret_list);
+}
+
+
+GList *
+gimp_stroke_get_draw_controls (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_controls (stroke);
+}
+
+static GList *
+gimp_stroke_real_get_draw_controls (GimpStroke *stroke)
+{
+ GList *list;
+ GList *ret_list = NULL;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ if (anchor->type == GIMP_ANCHOR_CONTROL)
+ {
+ GimpAnchor *next = list->next ? list->next->data : NULL;
+ GimpAnchor *prev = list->prev ? list->prev->data : NULL;
+
+ if (next && next->type == GIMP_ANCHOR_ANCHOR && next->selected)
+ {
+ /* Ok, this is a hack.
+ * The idea is to give control points at the end of a
+ * stroke a higher priority for the interactive tool.
+ */
+ if (prev)
+ ret_list = g_list_prepend (ret_list, anchor);
+ else
+ ret_list = g_list_append (ret_list, anchor);
+ }
+ else if (prev && prev->type == GIMP_ANCHOR_ANCHOR && prev->selected)
+ {
+ /* same here... */
+ if (next)
+ ret_list = g_list_prepend (ret_list, anchor);
+ else
+ ret_list = g_list_append (ret_list, anchor);
+ }
+ }
+ }
+
+ return g_list_reverse (ret_list);
+}
+
+
+GArray *
+gimp_stroke_get_draw_lines (GimpStroke *stroke)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_draw_lines (stroke);
+}
+
+static GArray *
+gimp_stroke_real_get_draw_lines (GimpStroke *stroke)
+{
+ GList *list;
+ GArray *ret_lines = NULL;
+ gint count = 0;
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ GimpAnchor *anchor = list->data;
+
+ if (anchor->type == GIMP_ANCHOR_ANCHOR && anchor->selected)
+ {
+ if (list->next)
+ {
+ GimpAnchor *next = list->next->data;
+
+ if (count == 0)
+ ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ ret_lines = g_array_append_val (ret_lines, anchor->position);
+ ret_lines = g_array_append_val (ret_lines, next->position);
+ count += 1;
+ }
+
+ if (list->prev)
+ {
+ GimpAnchor *prev = list->prev->data;
+
+ if (count == 0)
+ ret_lines = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
+
+ ret_lines = g_array_append_val (ret_lines, anchor->position);
+ ret_lines = g_array_append_val (ret_lines, prev->position);
+ count += 1;
+ }
+ }
+ }
+
+ return ret_lines;
+}
+
+GArray *
+gimp_stroke_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), NULL);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->control_points_get (stroke,
+ ret_closed);
+}
+
+static GArray *
+gimp_stroke_real_control_points_get (GimpStroke *stroke,
+ gboolean *ret_closed)
+{
+ guint num_anchors;
+ GArray *ret_array;
+ GList *list;
+
+ num_anchors = g_queue_get_length (stroke->anchors);
+ ret_array = g_array_sized_new (FALSE, FALSE,
+ sizeof (GimpAnchor), num_anchors);
+
+ for (list = stroke->anchors->head; list; list = g_list_next (list))
+ {
+ g_array_append_vals (ret_array, list->data, 1);
+ }
+
+ if (ret_closed)
+ *ret_closed = stroke->closed;
+
+ return ret_array;
+}
+
+gboolean
+gimp_stroke_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope)
+{
+ g_return_val_if_fail (GIMP_IS_STROKE (stroke), FALSE);
+
+ return GIMP_STROKE_GET_CLASS (stroke)->get_point_at_dist (stroke,
+ dist,
+ precision,
+ position,
+ slope);
+}
+
+
+static gboolean
+gimp_stroke_real_get_point_at_dist (GimpStroke *stroke,
+ gdouble dist,
+ gdouble precision,
+ GimpCoords *position,
+ gdouble *slope)
+{
+ GArray *points;
+ gint i;
+ gdouble length;
+ gdouble segment_length;
+ gboolean ret = FALSE;
+ GimpCoords difference;
+
+ points = gimp_stroke_interpolate (stroke, precision, NULL);
+ if (points == NULL)
+ return ret;
+
+ length = 0;
+ for (i=0; i < points->len - 1; i++)
+ {
+ gimp_coords_difference (&(g_array_index (points, GimpCoords , i)),
+ &(g_array_index (points, GimpCoords , i+1)),
+ &difference);
+ segment_length = gimp_coords_length (&difference);
+
+ if (segment_length == 0 || length + segment_length < dist )
+ {
+ length += segment_length;
+ }
+ else
+ {
+ /* x = x1 + (x2 - x1 ) u */
+ /* x = x1 (1-u) + u x2 */
+
+ gdouble u = (dist - length) / segment_length;
+
+ gimp_coords_mix (1 - u, &(g_array_index (points, GimpCoords , i)),
+ u, &(g_array_index (points, GimpCoords , i+1)),
+ position);
+
+ if (difference.x == 0)
+ *slope = G_MAXDOUBLE;
+ else
+ *slope = difference.y / difference.x;
+
+ ret = TRUE;
+ break;
+ }
+ }
+
+ g_array_free (points, TRUE);
+
+ return ret;
+}