/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikastroke.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 "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "vectors-types.h" #include "core/pika-memsize.h" #include "core/pikacoords.h" #include "core/pikaparamspecs.h" #include "core/pika-transform-utils.h" #include "pikaanchor.h" #include "pikastroke.h" enum { PROP_0, PROP_CONTROL_POINTS, PROP_CLOSED }; /* Prototypes */ static void pika_stroke_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_stroke_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_stroke_finalize (GObject *object); static gint64 pika_stroke_get_memsize (PikaObject *object, gint64 *gui_size); static PikaAnchor * pika_stroke_real_anchor_get (PikaStroke *stroke, const PikaCoords *coord); static PikaAnchor * pika_stroke_real_anchor_get_next (PikaStroke *stroke, const PikaAnchor *prev); static void pika_stroke_real_anchor_select (PikaStroke *stroke, PikaAnchor *anchor, gboolean selected, gboolean exclusive); static void pika_stroke_real_anchor_move_relative (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *delta, PikaAnchorFeatureType feature); static void pika_stroke_real_anchor_move_absolute (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *delta, PikaAnchorFeatureType feature); static void pika_stroke_real_anchor_convert (PikaStroke *stroke, PikaAnchor *anchor, PikaAnchorFeatureType feature); static void pika_stroke_real_anchor_delete (PikaStroke *stroke, PikaAnchor *anchor); static gboolean pika_stroke_real_point_is_movable (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static void pika_stroke_real_point_move_relative (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *deltacoord, PikaAnchorFeatureType feature); static void pika_stroke_real_point_move_absolute (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *coord, PikaAnchorFeatureType feature); static void pika_stroke_real_close (PikaStroke *stroke); static PikaStroke * pika_stroke_real_open (PikaStroke *stroke, PikaAnchor *end_anchor); static gboolean pika_stroke_real_anchor_is_insertable (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static PikaAnchor * pika_stroke_real_anchor_insert (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static gboolean pika_stroke_real_is_extendable (PikaStroke *stroke, PikaAnchor *neighbor); static PikaAnchor * pika_stroke_real_extend (PikaStroke *stroke, const PikaCoords *coords, PikaAnchor *neighbor, PikaVectorExtendMode extend_mode); gboolean pika_stroke_real_connect_stroke (PikaStroke *stroke, PikaAnchor *anchor, PikaStroke *extension, PikaAnchor *neighbor); static gboolean pika_stroke_real_is_empty (PikaStroke *stroke); static gboolean pika_stroke_real_reverse (PikaStroke *stroke); static gboolean pika_stroke_real_shift_start (PikaStroke *stroke, PikaAnchor *anchor); static gdouble pika_stroke_real_get_length (PikaStroke *stroke, gdouble precision); static gdouble pika_stroke_real_get_distance (PikaStroke *stroke, const PikaCoords *coord); static GArray * pika_stroke_real_interpolate (PikaStroke *stroke, gdouble precision, gboolean *closed); static PikaStroke * pika_stroke_real_duplicate (PikaStroke *stroke); static PikaBezierDesc * pika_stroke_real_make_bezier (PikaStroke *stroke); static void pika_stroke_real_translate (PikaStroke *stroke, gdouble offset_x, gdouble offset_y); static void pika_stroke_real_scale (PikaStroke *stroke, gdouble scale_x, gdouble scale_y); static void pika_stroke_real_rotate (PikaStroke *stroke, gdouble center_x, gdouble center_y, gdouble angle); static void pika_stroke_real_flip (PikaStroke *stroke, PikaOrientationType flip_type, gdouble axis); static void pika_stroke_real_flip_free (PikaStroke *stroke, gdouble x1, gdouble y1, gdouble x2, gdouble y2); static void pika_stroke_real_transform (PikaStroke *stroke, const PikaMatrix3 *matrix, GQueue *ret_strokes); static GList * pika_stroke_real_get_draw_anchors (PikaStroke *stroke); static GList * pika_stroke_real_get_draw_controls (PikaStroke *stroke); static GArray * pika_stroke_real_get_draw_lines (PikaStroke *stroke); static GArray * pika_stroke_real_control_points_get (PikaStroke *stroke, gboolean *ret_closed); static gboolean pika_stroke_real_get_point_at_dist (PikaStroke *stroke, gdouble dist, gdouble precision, PikaCoords *position, gdouble *slope); G_DEFINE_TYPE (PikaStroke, pika_stroke, PIKA_TYPE_OBJECT) #define parent_class pika_stroke_parent_class static void pika_stroke_class_init (PikaStrokeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); GParamSpec *param_spec; object_class->finalize = pika_stroke_finalize; object_class->get_property = pika_stroke_get_property; object_class->set_property = pika_stroke_set_property; pika_object_class->get_memsize = pika_stroke_get_memsize; klass->changed = NULL; klass->removed = NULL; klass->anchor_get = pika_stroke_real_anchor_get; klass->anchor_get_next = pika_stroke_real_anchor_get_next; klass->anchor_select = pika_stroke_real_anchor_select; klass->anchor_move_relative = pika_stroke_real_anchor_move_relative; klass->anchor_move_absolute = pika_stroke_real_anchor_move_absolute; klass->anchor_convert = pika_stroke_real_anchor_convert; klass->anchor_delete = pika_stroke_real_anchor_delete; klass->point_is_movable = pika_stroke_real_point_is_movable; klass->point_move_relative = pika_stroke_real_point_move_relative; klass->point_move_absolute = pika_stroke_real_point_move_absolute; klass->nearest_point_get = NULL; klass->nearest_tangent_get = NULL; klass->nearest_intersection_get = NULL; klass->close = pika_stroke_real_close; klass->open = pika_stroke_real_open; klass->anchor_is_insertable = pika_stroke_real_anchor_is_insertable; klass->anchor_insert = pika_stroke_real_anchor_insert; klass->is_extendable = pika_stroke_real_is_extendable; klass->extend = pika_stroke_real_extend; klass->connect_stroke = pika_stroke_real_connect_stroke; klass->is_empty = pika_stroke_real_is_empty; klass->reverse = pika_stroke_real_reverse; klass->shift_start = pika_stroke_real_shift_start; klass->get_length = pika_stroke_real_get_length; klass->get_distance = pika_stroke_real_get_distance; klass->get_point_at_dist = pika_stroke_real_get_point_at_dist; klass->interpolate = pika_stroke_real_interpolate; klass->duplicate = pika_stroke_real_duplicate; klass->make_bezier = pika_stroke_real_make_bezier; klass->translate = pika_stroke_real_translate; klass->scale = pika_stroke_real_scale; klass->rotate = pika_stroke_real_rotate; klass->flip = pika_stroke_real_flip; klass->flip_free = pika_stroke_real_flip_free; klass->transform = pika_stroke_real_transform; klass->get_draw_anchors = pika_stroke_real_get_draw_anchors; klass->get_draw_controls = pika_stroke_real_get_draw_controls; klass->get_draw_lines = pika_stroke_real_get_draw_lines; klass->control_points_get = pika_stroke_real_control_points_get; param_spec = g_param_spec_boxed ("pika-anchor", "Pika Anchor", "The control points of a Stroke", PIKA_TYPE_ANCHOR, PIKA_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_CONTROL_POINTS, pika_param_spec_value_array ("control-points", "Control Points", "This is an ValueArray " "with the initial " "control points of " "the new Stroke", param_spec, PIKA_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, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void pika_stroke_init (PikaStroke *stroke) { stroke->anchors = g_queue_new (); } static void pika_stroke_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaStroke *stroke = PIKA_STROKE (object); PikaValueArray *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 = pika_value_array_length (val_array); for (i = 0; i < length; i++) { GValue *item = pika_value_array_index (val_array, i); g_return_if_fail (G_VALUE_HOLDS (item, PIKA_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 pika_stroke_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaStroke *stroke = PIKA_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 pika_stroke_finalize (GObject *object) { PikaStroke *stroke = PIKA_STROKE (object); g_queue_free_full (stroke->anchors, (GDestroyNotify) pika_anchor_free); stroke->anchors = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } static gint64 pika_stroke_get_memsize (PikaObject *object, gint64 *gui_size) { PikaStroke *stroke = PIKA_STROKE (object); gint64 memsize = 0; memsize += pika_g_queue_get_memsize (stroke->anchors, sizeof (PikaAnchor)); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } void pika_stroke_set_id (PikaStroke *stroke, gint id) { g_return_if_fail (PIKA_IS_STROKE (stroke)); g_return_if_fail (stroke->id == 0 /* we don't want changing IDs... */); stroke->id = id; } gint pika_stroke_get_id (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), -1); return stroke->id; } PikaAnchor * pika_stroke_anchor_get (PikaStroke *stroke, const PikaCoords *coord) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->anchor_get (stroke, coord); } gdouble pika_stroke_nearest_point_get (PikaStroke *stroke, const PikaCoords *coord, const gdouble precision, PikaCoords *ret_point, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); g_return_val_if_fail (coord != NULL, FALSE); if (PIKA_STROKE_GET_CLASS (stroke)->nearest_point_get) return PIKA_STROKE_GET_CLASS (stroke)->nearest_point_get (stroke, coord, precision, ret_point, ret_segment_start, ret_segment_end, ret_pos); return -1; } gdouble pika_stroke_nearest_tangent_get (PikaStroke *stroke, const PikaCoords *coords1, const PikaCoords *coords2, gdouble precision, PikaCoords *nearest, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); g_return_val_if_fail (coords1 != NULL, FALSE); g_return_val_if_fail (coords2 != NULL, FALSE); if (PIKA_STROKE_GET_CLASS (stroke)->nearest_tangent_get) return PIKA_STROKE_GET_CLASS (stroke)->nearest_tangent_get (stroke, coords1, coords2, precision, nearest, ret_segment_start, ret_segment_end, ret_pos); return -1; } gdouble pika_stroke_nearest_intersection_get (PikaStroke *stroke, const PikaCoords *coords1, const PikaCoords *direction, gdouble precision, PikaCoords *nearest, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); g_return_val_if_fail (coords1 != NULL, FALSE); g_return_val_if_fail (direction != NULL, FALSE); if (PIKA_STROKE_GET_CLASS (stroke)->nearest_intersection_get) return PIKA_STROKE_GET_CLASS (stroke)->nearest_intersection_get (stroke, coords1, direction, precision, nearest, ret_segment_start, ret_segment_end, ret_pos); return -1; } static PikaAnchor * pika_stroke_real_anchor_get (PikaStroke *stroke, const PikaCoords *coord) { gdouble dx, dy; gdouble mindist = -1; GList *anchors; GList *list; PikaAnchor *anchor = NULL; anchors = pika_stroke_get_draw_controls (stroke); for (list = anchors; list; list = g_list_next (list)) { dx = coord->x - PIKA_ANCHOR (list->data)->position.x; dy = coord->y - PIKA_ANCHOR (list->data)->position.y; if (mindist < 0 || mindist > dx * dx + dy * dy) { mindist = dx * dx + dy * dy; anchor = PIKA_ANCHOR (list->data); } } g_list_free (anchors); anchors = pika_stroke_get_draw_anchors (stroke); for (list = anchors; list; list = g_list_next (list)) { dx = coord->x - PIKA_ANCHOR (list->data)->position.x; dy = coord->y - PIKA_ANCHOR (list->data)->position.y; if (mindist < 0 || mindist > dx * dx + dy * dy) { mindist = dx * dx + dy * dy; anchor = PIKA_ANCHOR (list->data); } } g_list_free (anchors); return anchor; } PikaAnchor * pika_stroke_anchor_get_next (PikaStroke *stroke, const PikaAnchor *prev) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->anchor_get_next (stroke, prev); } static PikaAnchor * pika_stroke_real_anchor_get_next (PikaStroke *stroke, const PikaAnchor *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 PIKA_ANCHOR (list->data); return NULL; } void pika_stroke_anchor_select (PikaStroke *stroke, PikaAnchor *anchor, gboolean selected, gboolean exclusive) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->anchor_select (stroke, anchor, selected, exclusive); } static void pika_stroke_real_anchor_select (PikaStroke *stroke, PikaAnchor *anchor, gboolean selected, gboolean exclusive) { GList *list = stroke->anchors->head; if (exclusive) { while (list) { PIKA_ANCHOR (list->data)->selected = FALSE; list = g_list_next (list); } } list = g_queue_find (stroke->anchors, anchor); if (list) PIKA_ANCHOR (list->data)->selected = selected; } void pika_stroke_anchor_move_relative (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *delta, PikaAnchorFeatureType feature) { g_return_if_fail (PIKA_IS_STROKE (stroke)); g_return_if_fail (anchor != NULL); g_return_if_fail (g_queue_find (stroke->anchors, anchor)); PIKA_STROKE_GET_CLASS (stroke)->anchor_move_relative (stroke, anchor, delta, feature); } static void pika_stroke_real_anchor_move_relative (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *delta, PikaAnchorFeatureType feature) { anchor->position.x += delta->x; anchor->position.y += delta->y; } void pika_stroke_anchor_move_absolute (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *coord, PikaAnchorFeatureType feature) { g_return_if_fail (PIKA_IS_STROKE (stroke)); g_return_if_fail (anchor != NULL); g_return_if_fail (g_queue_find (stroke->anchors, anchor)); PIKA_STROKE_GET_CLASS (stroke)->anchor_move_absolute (stroke, anchor, coord, feature); } static void pika_stroke_real_anchor_move_absolute (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *coord, PikaAnchorFeatureType feature) { anchor->position.x = coord->x; anchor->position.y = coord->y; } gboolean pika_stroke_point_is_movable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->point_is_movable (stroke, predec, position); } static gboolean pika_stroke_real_point_is_movable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { return FALSE; } void pika_stroke_point_move_relative (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *deltacoord, PikaAnchorFeatureType feature) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->point_move_relative (stroke, predec, position, deltacoord, feature); } static void pika_stroke_real_point_move_relative (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *deltacoord, PikaAnchorFeatureType feature) { g_printerr ("pika_stroke_point_move_relative: default implementation\n"); } void pika_stroke_point_move_absolute (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *coord, PikaAnchorFeatureType feature) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->point_move_absolute (stroke, predec, position, coord, feature); } static void pika_stroke_real_point_move_absolute (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *coord, PikaAnchorFeatureType feature) { g_printerr ("pika_stroke_point_move_absolute: default implementation\n"); } void pika_stroke_close (PikaStroke *stroke) { g_return_if_fail (PIKA_IS_STROKE (stroke)); g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); PIKA_STROKE_GET_CLASS (stroke)->close (stroke); } static void pika_stroke_real_close (PikaStroke *stroke) { stroke->closed = TRUE; g_object_notify (G_OBJECT (stroke), "closed"); } void pika_stroke_anchor_convert (PikaStroke *stroke, PikaAnchor *anchor, PikaAnchorFeatureType feature) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->anchor_convert (stroke, anchor, feature); } static void pika_stroke_real_anchor_convert (PikaStroke *stroke, PikaAnchor *anchor, PikaAnchorFeatureType feature) { g_printerr ("pika_stroke_anchor_convert: default implementation\n"); } void pika_stroke_anchor_delete (PikaStroke *stroke, PikaAnchor *anchor) { g_return_if_fail (PIKA_IS_STROKE (stroke)); g_return_if_fail (anchor && anchor->type == PIKA_ANCHOR_ANCHOR); PIKA_STROKE_GET_CLASS (stroke)->anchor_delete (stroke, anchor); } static void pika_stroke_real_anchor_delete (PikaStroke *stroke, PikaAnchor *anchor) { g_printerr ("pika_stroke_anchor_delete: default implementation\n"); } PikaStroke * pika_stroke_open (PikaStroke *stroke, PikaAnchor *end_anchor) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); g_return_val_if_fail (end_anchor && end_anchor->type == PIKA_ANCHOR_ANCHOR, NULL); return PIKA_STROKE_GET_CLASS (stroke)->open (stroke, end_anchor); } static PikaStroke * pika_stroke_real_open (PikaStroke *stroke, PikaAnchor *end_anchor) { g_printerr ("pika_stroke_open: default implementation\n"); return NULL; } gboolean pika_stroke_anchor_is_insertable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->anchor_is_insertable (stroke, predec, position); } static gboolean pika_stroke_real_anchor_is_insertable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return FALSE; } PikaAnchor * pika_stroke_anchor_insert (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); g_return_val_if_fail (predec->type == PIKA_ANCHOR_ANCHOR, NULL); return PIKA_STROKE_GET_CLASS (stroke)->anchor_insert (stroke, predec, position); } static PikaAnchor * pika_stroke_real_anchor_insert (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return NULL; } gboolean pika_stroke_is_extendable (PikaStroke *stroke, PikaAnchor *neighbor) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->is_extendable (stroke, neighbor); } static gboolean pika_stroke_real_is_extendable (PikaStroke *stroke, PikaAnchor *neighbor) { return FALSE; } PikaAnchor * pika_stroke_extend (PikaStroke *stroke, const PikaCoords *coords, PikaAnchor *neighbor, PikaVectorExtendMode extend_mode) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); g_return_val_if_fail (!stroke->closed, NULL); return PIKA_STROKE_GET_CLASS (stroke)->extend (stroke, coords, neighbor, extend_mode); } static PikaAnchor * pika_stroke_real_extend (PikaStroke *stroke, const PikaCoords *coords, PikaAnchor *neighbor, PikaVectorExtendMode extend_mode) { g_printerr ("pika_stroke_extend: default implementation\n"); return NULL; } gboolean pika_stroke_connect_stroke (PikaStroke *stroke, PikaAnchor *anchor, PikaStroke *extension, PikaAnchor *neighbor) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); g_return_val_if_fail (PIKA_IS_STROKE (extension), FALSE); g_return_val_if_fail (stroke->closed == FALSE && extension->closed == FALSE, FALSE); return PIKA_STROKE_GET_CLASS (stroke)->connect_stroke (stroke, anchor, extension, neighbor); } gboolean pika_stroke_real_connect_stroke (PikaStroke *stroke, PikaAnchor *anchor, PikaStroke *extension, PikaAnchor *neighbor) { g_printerr ("pika_stroke_connect_stroke: default implementation\n"); return FALSE; } gboolean pika_stroke_is_empty (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->is_empty (stroke); } static gboolean pika_stroke_real_is_empty (PikaStroke *stroke) { return g_queue_is_empty (stroke->anchors); } gboolean pika_stroke_reverse (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->reverse (stroke); } static gboolean pika_stroke_real_reverse (PikaStroke *stroke) { g_queue_reverse (stroke->anchors); /* keep the first node the same for closed strokes */ if (stroke->closed && stroke->anchors->length > 0) g_queue_push_head_link (stroke->anchors, g_queue_pop_tail_link (stroke->anchors)); return TRUE; } gboolean pika_stroke_shift_start (PikaStroke *stroke, PikaAnchor *new_start) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); g_return_val_if_fail (new_start != NULL, FALSE); return PIKA_STROKE_GET_CLASS (stroke)->shift_start (stroke, new_start); } static gboolean pika_stroke_real_shift_start (PikaStroke *stroke, PikaAnchor *new_start) { GList *link; link = g_queue_find (stroke->anchors, new_start); if (!link) return FALSE; if (link == stroke->anchors->head) return TRUE; stroke->anchors->tail->next = stroke->anchors->head; stroke->anchors->head->prev = stroke->anchors->tail; stroke->anchors->tail = link->prev; stroke->anchors->head = link; stroke->anchors->tail->next = NULL; stroke->anchors->head->prev = NULL; return TRUE; } gdouble pika_stroke_get_length (PikaStroke *stroke, gdouble precision) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0); return PIKA_STROKE_GET_CLASS (stroke)->get_length (stroke, precision); } static gdouble pika_stroke_real_get_length (PikaStroke *stroke, gdouble precision) { GArray *points; gint i; gdouble length; PikaCoords difference; if (g_queue_is_empty (stroke->anchors)) return -1; points = pika_stroke_interpolate (stroke, precision, NULL); if (points == NULL) return -1; length = 0; for (i = 0; i < points->len - 1; i++ ) { pika_coords_difference (&(g_array_index (points, PikaCoords, i)), &(g_array_index (points, PikaCoords, i+1)), &difference); length += pika_coords_length (&difference); } g_array_free(points, TRUE); return length; } gdouble pika_stroke_get_distance (PikaStroke *stroke, const PikaCoords *coord) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0); return PIKA_STROKE_GET_CLASS (stroke)->get_distance (stroke, coord); } static gdouble pika_stroke_real_get_distance (PikaStroke *stroke, const PikaCoords *coord) { g_printerr ("pika_stroke_get_distance: default implementation\n"); return 0.0; } GArray * pika_stroke_interpolate (PikaStroke *stroke, gdouble precision, gboolean *ret_closed) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->interpolate (stroke, precision, ret_closed); } static GArray * pika_stroke_real_interpolate (PikaStroke *stroke, gdouble precision, gboolean *ret_closed) { g_printerr ("pika_stroke_interpolate: default implementation\n"); return NULL; } PikaStroke * pika_stroke_duplicate (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->duplicate (stroke); } static PikaStroke * pika_stroke_real_duplicate (PikaStroke *stroke) { PikaStroke *new_stroke; GList *list; new_stroke = g_object_new (G_TYPE_FROM_INSTANCE (stroke), "name", pika_object_get_name (stroke), NULL); g_queue_free_full (new_stroke->anchors, (GDestroyNotify) pika_anchor_free); new_stroke->anchors = g_queue_copy (stroke->anchors); for (list = new_stroke->anchors->head; list; list = g_list_next (list)) { list->data = pika_anchor_copy (PIKA_ANCHOR (list->data)); } new_stroke->closed = stroke->closed; /* we do *not* copy the ID! */ return new_stroke; } PikaBezierDesc * pika_stroke_make_bezier (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->make_bezier (stroke); } static PikaBezierDesc * pika_stroke_real_make_bezier (PikaStroke *stroke) { g_printerr ("pika_stroke_make_bezier: default implementation\n"); return NULL; } void pika_stroke_translate (PikaStroke *stroke, gdouble offset_x, gdouble offset_y) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->translate (stroke, offset_x, offset_y); } static void pika_stroke_real_translate (PikaStroke *stroke, gdouble offset_x, gdouble offset_y) { GList *list; for (list = stroke->anchors->head; list; list = g_list_next (list)) { PikaAnchor *anchor = list->data; anchor->position.x += offset_x; anchor->position.y += offset_y; } } void pika_stroke_scale (PikaStroke *stroke, gdouble scale_x, gdouble scale_y) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->scale (stroke, scale_x, scale_y); } static void pika_stroke_real_scale (PikaStroke *stroke, gdouble scale_x, gdouble scale_y) { GList *list; for (list = stroke->anchors->head; list; list = g_list_next (list)) { PikaAnchor *anchor = list->data; anchor->position.x *= scale_x; anchor->position.y *= scale_y; } } void pika_stroke_rotate (PikaStroke *stroke, gdouble center_x, gdouble center_y, gdouble angle) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->rotate (stroke, center_x, center_y, angle); } static void pika_stroke_real_rotate (PikaStroke *stroke, gdouble center_x, gdouble center_y, gdouble angle) { PikaMatrix3 matrix; angle = angle / 180.0 * G_PI; pika_matrix3_identity (&matrix); pika_transform_matrix_rotate_center (&matrix, center_x, center_y, angle); pika_stroke_transform (stroke, &matrix, NULL); } void pika_stroke_flip (PikaStroke *stroke, PikaOrientationType flip_type, gdouble axis) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->flip (stroke, flip_type, axis); } static void pika_stroke_real_flip (PikaStroke *stroke, PikaOrientationType flip_type, gdouble axis) { PikaMatrix3 matrix; pika_matrix3_identity (&matrix); pika_transform_matrix_flip (&matrix, flip_type, axis); pika_stroke_transform (stroke, &matrix, NULL); } void pika_stroke_flip_free (PikaStroke *stroke, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->flip_free (stroke, x1, y1, x2, y2); } static void pika_stroke_real_flip_free (PikaStroke *stroke, gdouble x1, gdouble y1, gdouble x2, gdouble y2) { /* x, y, width and height parameter in pika_transform_matrix_flip_free are unused */ PikaMatrix3 matrix; pika_matrix3_identity (&matrix); pika_transform_matrix_flip_free (&matrix, x1, y1, x2, y2); pika_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 pika_stroke_transform (PikaStroke *stroke, const PikaMatrix3 *matrix, GQueue *ret_strokes) { g_return_if_fail (PIKA_IS_STROKE (stroke)); PIKA_STROKE_GET_CLASS (stroke)->transform (stroke, matrix, ret_strokes); } static void pika_stroke_real_transform (PikaStroke *stroke, const PikaMatrix3 *matrix, GQueue *ret_strokes) { GList *list; for (list = stroke->anchors->head; list; list = g_list_next (list)) { PikaAnchor *anchor = list->data; pika_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 * pika_stroke_get_draw_anchors (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->get_draw_anchors (stroke); } static GList * pika_stroke_real_get_draw_anchors (PikaStroke *stroke) { GList *list; GList *ret_list = NULL; for (list = stroke->anchors->head; list; list = g_list_next (list)) { if (PIKA_ANCHOR (list->data)->type == PIKA_ANCHOR_ANCHOR) ret_list = g_list_prepend (ret_list, list->data); } return g_list_reverse (ret_list); } GList * pika_stroke_get_draw_controls (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->get_draw_controls (stroke); } static GList * pika_stroke_real_get_draw_controls (PikaStroke *stroke) { GList *list; GList *ret_list = NULL; for (list = stroke->anchors->head; list; list = g_list_next (list)) { PikaAnchor *anchor = list->data; if (anchor->type == PIKA_ANCHOR_CONTROL) { PikaAnchor *next = list->next ? list->next->data : NULL; PikaAnchor *prev = list->prev ? list->prev->data : NULL; if (next && next->type == PIKA_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 == PIKA_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 * pika_stroke_get_draw_lines (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->get_draw_lines (stroke); } static GArray * pika_stroke_real_get_draw_lines (PikaStroke *stroke) { GList *list; GArray *ret_lines = NULL; gint count = 0; for (list = stroke->anchors->head; list; list = g_list_next (list)) { PikaAnchor *anchor = list->data; if (anchor->type == PIKA_ANCHOR_ANCHOR && anchor->selected) { if (list->next) { PikaAnchor *next = list->next->data; if (count == 0) ret_lines = g_array_new (FALSE, FALSE, sizeof (PikaCoords)); 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) { PikaAnchor *prev = list->prev->data; if (count == 0) ret_lines = g_array_new (FALSE, FALSE, sizeof (PikaCoords)); 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 * pika_stroke_control_points_get (PikaStroke *stroke, gboolean *ret_closed) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), NULL); return PIKA_STROKE_GET_CLASS (stroke)->control_points_get (stroke, ret_closed); } static GArray * pika_stroke_real_control_points_get (PikaStroke *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 (PikaAnchor), 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 pika_stroke_get_point_at_dist (PikaStroke *stroke, gdouble dist, gdouble precision, PikaCoords *position, gdouble *slope) { g_return_val_if_fail (PIKA_IS_STROKE (stroke), FALSE); return PIKA_STROKE_GET_CLASS (stroke)->get_point_at_dist (stroke, dist, precision, position, slope); } static gboolean pika_stroke_real_get_point_at_dist (PikaStroke *stroke, gdouble dist, gdouble precision, PikaCoords *position, gdouble *slope) { GArray *points; gint i; gdouble length; gdouble segment_length; gboolean ret = FALSE; PikaCoords difference; points = pika_stroke_interpolate (stroke, precision, NULL); if (points == NULL) return ret; length = 0; for (i=0; i < points->len - 1; i++) { pika_coords_difference (&(g_array_index (points, PikaCoords , i)), &(g_array_index (points, PikaCoords , i+1)), &difference); segment_length = pika_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; pika_coords_mix (1 - u, &(g_array_index (points, PikaCoords , i)), u, &(g_array_index (points, PikaCoords , 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; }