/* 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 * * pikavectors.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 "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "vectors-types.h" #include "core/pika.h" #include "core/pika-transform-utils.h" #include "core/pikabezierdesc.h" #include "core/pikachannel-select.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "core/pikadrawable-fill.h" #include "core/pikadrawable-stroke.h" #include "core/pikaerror.h" #include "core/pikaimage.h" #include "core/pikaimage-undo-push.h" #include "core/pikapaintinfo.h" #include "core/pikastrokeoptions.h" #include "paint/pikapaintcore-stroke.h" #include "paint/pikapaintoptions.h" #include "pikaanchor.h" #include "pikastroke.h" #include "pikavectors.h" #include "pikavectors-preview.h" #include "pika-intl.h" enum { FREEZE, THAW, LAST_SIGNAL }; static void pika_vectors_finalize (GObject *object); static gint64 pika_vectors_get_memsize (PikaObject *object, gint64 *gui_size); static gboolean pika_vectors_is_attached (PikaItem *item); static PikaItemTree * pika_vectors_get_tree (PikaItem *item); static gboolean pika_vectors_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height); static PikaItem * pika_vectors_duplicate (PikaItem *item, GType new_type); static void pika_vectors_convert (PikaItem *item, PikaImage *dest_image, GType old_type); static void pika_vectors_translate (PikaItem *item, gdouble offset_x, gdouble offset_y, gboolean push_undo); static void pika_vectors_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interp_type, PikaProgress *progress); static void pika_vectors_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y); static void pika_vectors_flip (PikaItem *item, PikaContext *context, PikaOrientationType flip_type, gdouble axis, gboolean clip_result); static void pika_vectors_rotate (PikaItem *item, PikaContext *context, PikaRotationType rotate_type, gdouble center_x, gdouble center_y, gboolean clip_result); static void pika_vectors_transform (PikaItem *item, PikaContext *context, const PikaMatrix3 *matrix, PikaTransformDirection direction, PikaInterpolationType interp_type, PikaTransformResize clip_result, PikaProgress *progress); static PikaTransformResize pika_vectors_get_clip (PikaItem *item, PikaTransformResize clip_result); static gboolean pika_vectors_fill (PikaItem *item, PikaDrawable *drawable, PikaFillOptions *fill_options, gboolean push_undo, PikaProgress *progress, GError **error); static gboolean pika_vectors_stroke (PikaItem *item, PikaDrawable *drawable, PikaStrokeOptions *stroke_options, gboolean push_undo, PikaProgress *progress, GError **error); static void pika_vectors_to_selection (PikaItem *item, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y); static void pika_vectors_real_freeze (PikaVectors *vectors); static void pika_vectors_real_thaw (PikaVectors *vectors); static void pika_vectors_real_stroke_add (PikaVectors *vectors, PikaStroke *stroke); static void pika_vectors_real_stroke_remove (PikaVectors *vectors, PikaStroke *stroke); static PikaStroke * pika_vectors_real_stroke_get (PikaVectors *vectors, const PikaCoords *coord); static PikaStroke *pika_vectors_real_stroke_get_next(PikaVectors *vectors, PikaStroke *prev); static gdouble pika_vectors_real_stroke_get_length (PikaVectors *vectors, PikaStroke *prev); static PikaAnchor * pika_vectors_real_anchor_get (PikaVectors *vectors, const PikaCoords *coord, PikaStroke **ret_stroke); static void pika_vectors_real_anchor_delete (PikaVectors *vectors, PikaAnchor *anchor); static gdouble pika_vectors_real_get_length (PikaVectors *vectors, const PikaAnchor *start); static gdouble pika_vectors_real_get_distance (PikaVectors *vectors, const PikaCoords *coord); static gint pika_vectors_real_interpolate (PikaVectors *vectors, PikaStroke *stroke, gdouble precision, gint max_points, PikaCoords *ret_coords); static PikaBezierDesc * pika_vectors_make_bezier (PikaVectors *vectors); static PikaBezierDesc * pika_vectors_real_make_bezier (PikaVectors *vectors); G_DEFINE_TYPE (PikaVectors, pika_vectors, PIKA_TYPE_ITEM) #define parent_class pika_vectors_parent_class static guint pika_vectors_signals[LAST_SIGNAL] = { 0 }; static void pika_vectors_class_init (PikaVectorsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaObjectClass *pika_object_class = PIKA_OBJECT_CLASS (klass); PikaViewableClass *viewable_class = PIKA_VIEWABLE_CLASS (klass); PikaItemClass *item_class = PIKA_ITEM_CLASS (klass); pika_vectors_signals[FREEZE] = g_signal_new ("freeze", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PikaVectorsClass, freeze), NULL, NULL, NULL, G_TYPE_NONE, 0); pika_vectors_signals[THAW] = g_signal_new ("thaw", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaVectorsClass, thaw), NULL, NULL, NULL, G_TYPE_NONE, 0); object_class->finalize = pika_vectors_finalize; pika_object_class->get_memsize = pika_vectors_get_memsize; viewable_class->get_new_preview = pika_vectors_get_new_preview; viewable_class->default_icon_name = "pika-path"; item_class->is_attached = pika_vectors_is_attached; item_class->get_tree = pika_vectors_get_tree; item_class->bounds = pika_vectors_bounds; item_class->duplicate = pika_vectors_duplicate; item_class->convert = pika_vectors_convert; item_class->translate = pika_vectors_translate; item_class->scale = pika_vectors_scale; item_class->resize = pika_vectors_resize; item_class->flip = pika_vectors_flip; item_class->rotate = pika_vectors_rotate; item_class->transform = pika_vectors_transform; item_class->get_clip = pika_vectors_get_clip; item_class->fill = pika_vectors_fill; item_class->stroke = pika_vectors_stroke; item_class->to_selection = pika_vectors_to_selection; item_class->default_name = _("Path"); item_class->rename_desc = C_("undo-type", "Rename Path"); item_class->translate_desc = C_("undo-type", "Move Path"); item_class->scale_desc = C_("undo-type", "Scale Path"); item_class->resize_desc = C_("undo-type", "Resize Path"); item_class->flip_desc = C_("undo-type", "Flip Path"); item_class->rotate_desc = C_("undo-type", "Rotate Path"); item_class->transform_desc = C_("undo-type", "Transform Path"); item_class->fill_desc = C_("undo-type", "Fill Path"); item_class->stroke_desc = C_("undo-type", "Stroke Path"); item_class->to_selection_desc = C_("undo-type", "Path to Selection"); item_class->reorder_desc = C_("undo-type", "Reorder Path"); item_class->raise_desc = C_("undo-type", "Raise Path"); item_class->raise_to_top_desc = C_("undo-type", "Raise Path to Top"); item_class->lower_desc = C_("undo-type", "Lower Path"); item_class->lower_to_bottom_desc = C_("undo-type", "Lower Path to Bottom"); item_class->raise_failed = _("Path cannot be raised higher."); item_class->lower_failed = _("Path cannot be lowered more."); klass->freeze = pika_vectors_real_freeze; klass->thaw = pika_vectors_real_thaw; klass->stroke_add = pika_vectors_real_stroke_add; klass->stroke_remove = pika_vectors_real_stroke_remove; klass->stroke_get = pika_vectors_real_stroke_get; klass->stroke_get_next = pika_vectors_real_stroke_get_next; klass->stroke_get_length = pika_vectors_real_stroke_get_length; klass->anchor_get = pika_vectors_real_anchor_get; klass->anchor_delete = pika_vectors_real_anchor_delete; klass->get_length = pika_vectors_real_get_length; klass->get_distance = pika_vectors_real_get_distance; klass->interpolate = pika_vectors_real_interpolate; klass->make_bezier = pika_vectors_real_make_bezier; } static void pika_vectors_init (PikaVectors *vectors) { pika_item_set_visible (PIKA_ITEM (vectors), FALSE, FALSE); vectors->strokes = g_queue_new (); vectors->stroke_to_list = g_hash_table_new (g_direct_hash, g_direct_equal); vectors->last_stroke_id = 0; vectors->freeze_count = 0; vectors->precision = 0.2; vectors->bezier_desc = NULL; vectors->bounds_valid = FALSE; } static void pika_vectors_finalize (GObject *object) { PikaVectors *vectors = PIKA_VECTORS (object); if (vectors->bezier_desc) { pika_bezier_desc_free (vectors->bezier_desc); vectors->bezier_desc = NULL; } if (vectors->strokes) { g_queue_free_full (vectors->strokes, (GDestroyNotify) g_object_unref); vectors->strokes = NULL; } if (vectors->stroke_to_list) { g_hash_table_destroy (vectors->stroke_to_list); vectors->stroke_to_list = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static gint64 pika_vectors_get_memsize (PikaObject *object, gint64 *gui_size) { PikaVectors *vectors; GList *list; gint64 memsize = 0; vectors = PIKA_VECTORS (object); for (list = vectors->strokes->head; list; list = g_list_next (list)) memsize += (pika_object_get_memsize (PIKA_OBJECT (list->data), gui_size) + sizeof (GList)); return memsize + PIKA_OBJECT_CLASS (parent_class)->get_memsize (object, gui_size); } static gboolean pika_vectors_is_attached (PikaItem *item) { PikaImage *image = pika_item_get_image (item); return (PIKA_IS_IMAGE (image) && pika_container_have (pika_image_get_vectors (image), PIKA_OBJECT (item))); } static PikaItemTree * pika_vectors_get_tree (PikaItem *item) { if (pika_item_is_attached (item)) { PikaImage *image = pika_item_get_image (item); return pika_image_get_vectors_tree (image); } return NULL; } static gboolean pika_vectors_bounds (PikaItem *item, gdouble *x, gdouble *y, gdouble *width, gdouble *height) { PikaVectors *vectors = PIKA_VECTORS (item); if (! vectors->bounds_valid) { PikaStroke *stroke; vectors->bounds_empty = TRUE; vectors->bounds_x1 = vectors->bounds_x2 = 0.0; vectors->bounds_y1 = vectors->bounds_y2 = 0.0; for (stroke = pika_vectors_stroke_get_next (vectors, NULL); stroke; stroke = pika_vectors_stroke_get_next (vectors, stroke)) { GArray *stroke_coords; gboolean closed; stroke_coords = pika_stroke_interpolate (stroke, 1.0, &closed); if (stroke_coords) { PikaCoords point; gint i; if (vectors->bounds_empty && stroke_coords->len > 0) { point = g_array_index (stroke_coords, PikaCoords, 0); vectors->bounds_x1 = vectors->bounds_x2 = point.x; vectors->bounds_y1 = vectors->bounds_y2 = point.y; vectors->bounds_empty = FALSE; } for (i = 0; i < stroke_coords->len; i++) { point = g_array_index (stroke_coords, PikaCoords, i); vectors->bounds_x1 = MIN (vectors->bounds_x1, point.x); vectors->bounds_y1 = MIN (vectors->bounds_y1, point.y); vectors->bounds_x2 = MAX (vectors->bounds_x2, point.x); vectors->bounds_y2 = MAX (vectors->bounds_y2, point.y); } g_array_free (stroke_coords, TRUE); } } vectors->bounds_valid = TRUE; } *x = vectors->bounds_x1; *y = vectors->bounds_y1; *width = vectors->bounds_x2 - vectors->bounds_x1; *height = vectors->bounds_y2 - vectors->bounds_y1; return ! vectors->bounds_empty; } static PikaItem * pika_vectors_duplicate (PikaItem *item, GType new_type) { PikaItem *new_item; g_return_val_if_fail (g_type_is_a (new_type, PIKA_TYPE_VECTORS), NULL); new_item = PIKA_ITEM_CLASS (parent_class)->duplicate (item, new_type); if (PIKA_IS_VECTORS (new_item)) { PikaVectors *vectors = PIKA_VECTORS (item); PikaVectors *new_vectors = PIKA_VECTORS (new_item); pika_vectors_copy_strokes (vectors, new_vectors); } return new_item; } static void pika_vectors_convert (PikaItem *item, PikaImage *dest_image, GType old_type) { pika_item_set_size (item, pika_image_get_width (dest_image), pika_image_get_height (dest_image)); PIKA_ITEM_CLASS (parent_class)->convert (item, dest_image, old_type); } static void pika_vectors_translate (PikaItem *item, gdouble offset_x, gdouble offset_y, gboolean push_undo) { PikaVectors *vectors = PIKA_VECTORS (item); GList *list; pika_vectors_freeze (vectors); if (push_undo) pika_image_undo_push_vectors_mod (pika_item_get_image (item), _("Move Path"), vectors); for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_translate (stroke, offset_x, offset_y); } pika_vectors_thaw (vectors); } static void pika_vectors_scale (PikaItem *item, gint new_width, gint new_height, gint new_offset_x, gint new_offset_y, PikaInterpolationType interpolation_type, PikaProgress *progress) { PikaVectors *vectors = PIKA_VECTORS (item); PikaImage *image = pika_item_get_image (item); GList *list; pika_vectors_freeze (vectors); if (pika_item_is_attached (item)) pika_image_undo_push_vectors_mod (image, NULL, vectors); for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_scale (stroke, (gdouble) new_width / (gdouble) pika_item_get_width (item), (gdouble) new_height / (gdouble) pika_item_get_height (item)); pika_stroke_translate (stroke, new_offset_x, new_offset_y); } PIKA_ITEM_CLASS (parent_class)->scale (item, pika_image_get_width (image), pika_image_get_height (image), 0, 0, interpolation_type, progress); pika_vectors_thaw (vectors); } static void pika_vectors_resize (PikaItem *item, PikaContext *context, PikaFillType fill_type, gint new_width, gint new_height, gint offset_x, gint offset_y) { PikaVectors *vectors = PIKA_VECTORS (item); PikaImage *image = pika_item_get_image (item); GList *list; pika_vectors_freeze (vectors); if (pika_item_is_attached (item)) pika_image_undo_push_vectors_mod (image, NULL, vectors); for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_translate (stroke, offset_x, offset_y); } PIKA_ITEM_CLASS (parent_class)->resize (item, context, fill_type, pika_image_get_width (image), pika_image_get_height (image), 0, 0); pika_vectors_thaw (vectors); } static void pika_vectors_flip (PikaItem *item, PikaContext *context, PikaOrientationType flip_type, gdouble axis, gboolean clip_result) { PikaVectors *vectors = PIKA_VECTORS (item); GList *list; PikaMatrix3 matrix; pika_matrix3_identity (&matrix); pika_transform_matrix_flip (&matrix, flip_type, axis); pika_vectors_freeze (vectors); pika_image_undo_push_vectors_mod (pika_item_get_image (item), _("Flip Path"), vectors); for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_transform (stroke, &matrix, NULL); } pika_vectors_thaw (vectors); } static void pika_vectors_rotate (PikaItem *item, PikaContext *context, PikaRotationType rotate_type, gdouble center_x, gdouble center_y, gboolean clip_result) { PikaVectors *vectors = PIKA_VECTORS (item); GList *list; PikaMatrix3 matrix; pika_matrix3_identity (&matrix); pika_transform_matrix_rotate (&matrix, rotate_type, center_x, center_y); pika_vectors_freeze (vectors); pika_image_undo_push_vectors_mod (pika_item_get_image (item), _("Rotate Path"), vectors); for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_transform (stroke, &matrix, NULL); } pika_vectors_thaw (vectors); } static void pika_vectors_transform (PikaItem *item, PikaContext *context, const PikaMatrix3 *matrix, PikaTransformDirection direction, PikaInterpolationType interpolation_type, PikaTransformResize clip_result, PikaProgress *progress) { PikaVectors *vectors = PIKA_VECTORS (item); PikaMatrix3 local_matrix; GQueue strokes; GList *list; pika_vectors_freeze (vectors); pika_image_undo_push_vectors_mod (pika_item_get_image (item), _("Transform Path"), vectors); local_matrix = *matrix; if (direction == PIKA_TRANSFORM_BACKWARD) pika_matrix3_invert (&local_matrix); g_queue_init (&strokes); while (! g_queue_is_empty (vectors->strokes)) { PikaStroke *stroke = g_queue_peek_head (vectors->strokes); g_object_ref (stroke); pika_vectors_stroke_remove (vectors, stroke); pika_stroke_transform (stroke, &local_matrix, &strokes); g_object_unref (stroke); } vectors->last_stroke_id = 0; for (list = strokes.head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_vectors_stroke_add (vectors, stroke); g_object_unref (stroke); } g_queue_clear (&strokes); pika_vectors_thaw (vectors); } static PikaTransformResize pika_vectors_get_clip (PikaItem *item, PikaTransformResize clip_result) { return PIKA_TRANSFORM_RESIZE_ADJUST; } static gboolean pika_vectors_fill (PikaItem *item, PikaDrawable *drawable, PikaFillOptions *fill_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaVectors *vectors = PIKA_VECTORS (item); if (g_queue_is_empty (vectors->strokes)) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Not enough points to fill")); return FALSE; } return pika_drawable_fill_vectors (drawable, fill_options, vectors, push_undo, error); } static gboolean pika_vectors_stroke (PikaItem *item, PikaDrawable *drawable, PikaStrokeOptions *stroke_options, gboolean push_undo, PikaProgress *progress, GError **error) { PikaVectors *vectors = PIKA_VECTORS (item); gboolean retval = FALSE; if (g_queue_is_empty (vectors->strokes)) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Not enough points to stroke")); return FALSE; } switch (pika_stroke_options_get_method (stroke_options)) { case PIKA_STROKE_LINE: retval = pika_drawable_stroke_vectors (drawable, stroke_options, vectors, push_undo, error); break; case PIKA_STROKE_PAINT_METHOD: { PikaPaintInfo *paint_info; PikaPaintCore *core; PikaPaintOptions *paint_options; gboolean emulate_dynamics; paint_info = pika_context_get_paint_info (PIKA_CONTEXT (stroke_options)); core = g_object_new (paint_info->paint_type, NULL); paint_options = pika_stroke_options_get_paint_options (stroke_options); emulate_dynamics = pika_stroke_options_get_emulate_dynamics (stroke_options); retval = pika_paint_core_stroke_vectors (core, drawable, paint_options, emulate_dynamics, vectors, push_undo, error); g_object_unref (core); } break; default: g_return_val_if_reached (FALSE); } return retval; } static void pika_vectors_to_selection (PikaItem *item, PikaChannelOps op, gboolean antialias, gboolean feather, gdouble feather_radius_x, gdouble feather_radius_y) { PikaVectors *vectors = PIKA_VECTORS (item); PikaImage *image = pika_item_get_image (item); pika_channel_select_vectors (pika_image_get_mask (image), PIKA_ITEM_GET_CLASS (item)->to_selection_desc, vectors, op, antialias, feather, feather_radius_x, feather_radius_x, TRUE); } static void pika_vectors_real_freeze (PikaVectors *vectors) { /* release cached bezier representation */ if (vectors->bezier_desc) { pika_bezier_desc_free (vectors->bezier_desc); vectors->bezier_desc = NULL; } /* invalidate bounds */ vectors->bounds_valid = FALSE; } static void pika_vectors_real_thaw (PikaVectors *vectors) { pika_viewable_invalidate_preview (PIKA_VIEWABLE (vectors)); } /* public functions */ PikaVectors * pika_vectors_new (PikaImage *image, const gchar *name) { PikaVectors *vectors; g_return_val_if_fail (PIKA_IS_IMAGE (image), NULL); vectors = PIKA_VECTORS (pika_item_new (PIKA_TYPE_VECTORS, image, name, 0, 0, pika_image_get_width (image), pika_image_get_height (image))); return vectors; } PikaVectors * pika_vectors_get_parent (PikaVectors *vectors) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); return PIKA_VECTORS (pika_viewable_get_parent (PIKA_VIEWABLE (vectors))); } void pika_vectors_freeze (PikaVectors *vectors) { g_return_if_fail (PIKA_IS_VECTORS (vectors)); vectors->freeze_count++; if (vectors->freeze_count == 1) g_signal_emit (vectors, pika_vectors_signals[FREEZE], 0); } void pika_vectors_thaw (PikaVectors *vectors) { g_return_if_fail (PIKA_IS_VECTORS (vectors)); g_return_if_fail (vectors->freeze_count > 0); vectors->freeze_count--; if (vectors->freeze_count == 0) g_signal_emit (vectors, pika_vectors_signals[THAW], 0); } void pika_vectors_copy_strokes (PikaVectors *src_vectors, PikaVectors *dest_vectors) { g_return_if_fail (PIKA_IS_VECTORS (src_vectors)); g_return_if_fail (PIKA_IS_VECTORS (dest_vectors)); pika_vectors_freeze (dest_vectors); g_queue_free_full (dest_vectors->strokes, (GDestroyNotify) g_object_unref); dest_vectors->strokes = g_queue_new (); g_hash_table_remove_all (dest_vectors->stroke_to_list); dest_vectors->last_stroke_id = 0; pika_vectors_add_strokes (src_vectors, dest_vectors); pika_vectors_thaw (dest_vectors); } void pika_vectors_add_strokes (PikaVectors *src_vectors, PikaVectors *dest_vectors) { GList *stroke; g_return_if_fail (PIKA_IS_VECTORS (src_vectors)); g_return_if_fail (PIKA_IS_VECTORS (dest_vectors)); pika_vectors_freeze (dest_vectors); for (stroke = src_vectors->strokes->head; stroke != NULL; stroke = g_list_next (stroke)) { PikaStroke *newstroke = pika_stroke_duplicate (stroke->data); g_queue_push_tail (dest_vectors->strokes, newstroke); /* Also add to {stroke: GList node} map */ g_hash_table_insert (dest_vectors->stroke_to_list, newstroke, g_queue_peek_tail_link (dest_vectors->strokes)); dest_vectors->last_stroke_id++; pika_stroke_set_id (newstroke, dest_vectors->last_stroke_id); } pika_vectors_thaw (dest_vectors); } void pika_vectors_stroke_add (PikaVectors *vectors, PikaStroke *stroke) { g_return_if_fail (PIKA_IS_VECTORS (vectors)); g_return_if_fail (PIKA_IS_STROKE (stroke)); pika_vectors_freeze (vectors); PIKA_VECTORS_GET_CLASS (vectors)->stroke_add (vectors, stroke); pika_vectors_thaw (vectors); } static void pika_vectors_real_stroke_add (PikaVectors *vectors, PikaStroke *stroke) { /* * Don't prepend into vector->strokes. See ChangeLog 2003-05-21 * --Mitch */ g_queue_push_tail (vectors->strokes, g_object_ref (stroke)); /* Also add to {stroke: GList node} map */ g_hash_table_insert (vectors->stroke_to_list, stroke, g_queue_peek_tail_link (vectors->strokes)); vectors->last_stroke_id++; pika_stroke_set_id (stroke, vectors->last_stroke_id); } void pika_vectors_stroke_remove (PikaVectors *vectors, PikaStroke *stroke) { g_return_if_fail (PIKA_IS_VECTORS (vectors)); g_return_if_fail (PIKA_IS_STROKE (stroke)); pika_vectors_freeze (vectors); PIKA_VECTORS_GET_CLASS (vectors)->stroke_remove (vectors, stroke); pika_vectors_thaw (vectors); } static void pika_vectors_real_stroke_remove (PikaVectors *vectors, PikaStroke *stroke) { GList *list = g_hash_table_lookup (vectors->stroke_to_list, stroke); if (list) { g_queue_delete_link (vectors->strokes, list); g_hash_table_remove (vectors->stroke_to_list, stroke); g_object_unref (stroke); } } gint pika_vectors_get_n_strokes (PikaVectors *vectors) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0); return g_queue_get_length (vectors->strokes); } PikaStroke * pika_vectors_stroke_get (PikaVectors *vectors, const PikaCoords *coord) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); return PIKA_VECTORS_GET_CLASS (vectors)->stroke_get (vectors, coord); } static PikaStroke * pika_vectors_real_stroke_get (PikaVectors *vectors, const PikaCoords *coord) { PikaStroke *minstroke = NULL; gdouble mindist = G_MAXDOUBLE; GList *list; for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; PikaAnchor *anchor = pika_stroke_anchor_get (stroke, coord); if (anchor) { gdouble dx = coord->x - anchor->position.x; gdouble dy = coord->y - anchor->position.y; if (mindist > dx * dx + dy * dy) { mindist = dx * dx + dy * dy; minstroke = stroke; } } } return minstroke; } PikaStroke * pika_vectors_stroke_get_by_id (PikaVectors *vectors, gint id) { GList *list; g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); for (list = vectors->strokes->head; list; list = g_list_next (list)) { if (pika_stroke_get_id (list->data) == id) return list->data; } return NULL; } PikaStroke * pika_vectors_stroke_get_next (PikaVectors *vectors, PikaStroke *prev) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); return PIKA_VECTORS_GET_CLASS (vectors)->stroke_get_next (vectors, prev); } static PikaStroke * pika_vectors_real_stroke_get_next (PikaVectors *vectors, PikaStroke *prev) { if (! prev) { return g_queue_peek_head (vectors->strokes); } else { GList *stroke = g_hash_table_lookup (vectors->stroke_to_list, prev); g_return_val_if_fail (stroke != NULL, NULL); return stroke->next ? stroke->next->data : NULL; } } gdouble pika_vectors_stroke_get_length (PikaVectors *vectors, PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0.0); g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0); return PIKA_VECTORS_GET_CLASS (vectors)->stroke_get_length (vectors, stroke); } static gdouble pika_vectors_real_stroke_get_length (PikaVectors *vectors, PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0.0); g_return_val_if_fail (PIKA_IS_STROKE (stroke), 0.0); return pika_stroke_get_length (stroke, vectors->precision); } PikaAnchor * pika_vectors_anchor_get (PikaVectors *vectors, const PikaCoords *coord, PikaStroke **ret_stroke) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); return PIKA_VECTORS_GET_CLASS (vectors)->anchor_get (vectors, coord, ret_stroke); } static PikaAnchor * pika_vectors_real_anchor_get (PikaVectors *vectors, const PikaCoords *coord, PikaStroke **ret_stroke) { PikaAnchor *minanchor = NULL; gdouble mindist = -1; GList *list; for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; PikaAnchor *anchor = pika_stroke_anchor_get (stroke, coord); if (anchor) { gdouble dx = coord->x - anchor->position.x; gdouble dy = coord->y - anchor->position.y; if (mindist > dx * dx + dy * dy || mindist < 0) { mindist = dx * dx + dy * dy; minanchor = anchor; if (ret_stroke) *ret_stroke = stroke; } } } return minanchor; } void pika_vectors_anchor_delete (PikaVectors *vectors, PikaAnchor *anchor) { g_return_if_fail (PIKA_IS_VECTORS (vectors)); g_return_if_fail (anchor != NULL); PIKA_VECTORS_GET_CLASS (vectors)->anchor_delete (vectors, anchor); } static void pika_vectors_real_anchor_delete (PikaVectors *vectors, PikaAnchor *anchor) { } void pika_vectors_anchor_select (PikaVectors *vectors, PikaStroke *target_stroke, PikaAnchor *anchor, gboolean selected, gboolean exclusive) { GList *list; for (list = vectors->strokes->head; list; list = g_list_next (list)) { PikaStroke *stroke = list->data; pika_stroke_anchor_select (stroke, stroke == target_stroke ? anchor : NULL, selected, exclusive); } } gdouble pika_vectors_get_length (PikaVectors *vectors, const PikaAnchor *start) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0.0); return PIKA_VECTORS_GET_CLASS (vectors)->get_length (vectors, start); } static gdouble pika_vectors_real_get_length (PikaVectors *vectors, const PikaAnchor *start) { g_printerr ("pika_vectors_get_length: default implementation\n"); return 0; } gdouble pika_vectors_get_distance (PikaVectors *vectors, const PikaCoords *coord) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0.0); return PIKA_VECTORS_GET_CLASS (vectors)->get_distance (vectors, coord); } static gdouble pika_vectors_real_get_distance (PikaVectors *vectors, const PikaCoords *coord) { g_printerr ("pika_vectors_get_distance: default implementation\n"); return 0; } gint pika_vectors_interpolate (PikaVectors *vectors, PikaStroke *stroke, gdouble precision, gint max_points, PikaCoords *ret_coords) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), 0); return PIKA_VECTORS_GET_CLASS (vectors)->interpolate (vectors, stroke, precision, max_points, ret_coords); } static gint pika_vectors_real_interpolate (PikaVectors *vectors, PikaStroke *stroke, gdouble precision, gint max_points, PikaCoords *ret_coords) { g_printerr ("pika_vectors_interpolate: default implementation\n"); return 0; } const PikaBezierDesc * pika_vectors_get_bezier (PikaVectors *vectors) { g_return_val_if_fail (PIKA_IS_VECTORS (vectors), NULL); if (! vectors->bezier_desc) { vectors->bezier_desc = pika_vectors_make_bezier (vectors); } return vectors->bezier_desc; } static PikaBezierDesc * pika_vectors_make_bezier (PikaVectors *vectors) { return PIKA_VECTORS_GET_CLASS (vectors)->make_bezier (vectors); } static PikaBezierDesc * pika_vectors_real_make_bezier (PikaVectors *vectors) { PikaStroke *stroke; GArray *cmd_array; PikaBezierDesc *ret_bezdesc = NULL; cmd_array = g_array_new (FALSE, FALSE, sizeof (cairo_path_data_t)); for (stroke = pika_vectors_stroke_get_next (vectors, NULL); stroke; stroke = pika_vectors_stroke_get_next (vectors, stroke)) { PikaBezierDesc *bezdesc = pika_stroke_make_bezier (stroke); if (bezdesc) { cmd_array = g_array_append_vals (cmd_array, bezdesc->data, bezdesc->num_data); pika_bezier_desc_free (bezdesc); } } if (cmd_array->len > 0) ret_bezdesc = pika_bezier_desc_new ((cairo_path_data_t *) cmd_array->data, cmd_array->len); g_array_free (cmd_array, FALSE); return ret_bezdesc; }