diff options
Diffstat (limited to '')
-rw-r--r-- | app/vectors/gimpstroke.c | 1430 |
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; +} |