/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimpstroke.c * Copyright (C) 2002 Simon Budig * * 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 . */ #include "config.h" #include #include #include #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); g_queue_free_full (new_stroke->anchors, (GDestroyNotify) gimp_anchor_free); 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; }