/* 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 * * pikabezierstroke.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 "libpikamath/pikamath.h" #include "vectors-types.h" #include "core/pika-transform-utils.h" #include "core/pikabezierdesc.h" #include "core/pikacoords.h" #include "core/pikacoords-interpolate.h" #include "pikaanchor.h" #include "pikabezierstroke.h" /* local prototypes */ static gdouble pika_bezier_stroke_nearest_point_get (PikaStroke *stroke, const PikaCoords *coord, gdouble precision, PikaCoords *ret_point, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos); static gdouble pika_bezier_stroke_segment_nearest_point_get (const PikaCoords *beziercoords, const PikaCoords *coord, gdouble precision, PikaCoords *ret_point, gdouble *ret_pos, gint depth); static gdouble pika_bezier_stroke_nearest_tangent_get (PikaStroke *stroke, const PikaCoords *coord1, const PikaCoords *coord2, gdouble precision, PikaCoords *nearest, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos); static gdouble pika_bezier_stroke_segment_nearest_tangent_get (const PikaCoords *beziercoords, const PikaCoords *coord1, const PikaCoords *coord2, gdouble precision, PikaCoords *ret_point, gdouble *ret_pos); static void pika_bezier_stroke_anchor_move_relative (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *deltacoord, PikaAnchorFeatureType feature); static void pika_bezier_stroke_anchor_move_absolute (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *coord, PikaAnchorFeatureType feature); static void pika_bezier_stroke_anchor_convert (PikaStroke *stroke, PikaAnchor *anchor, PikaAnchorFeatureType feature); static void pika_bezier_stroke_anchor_delete (PikaStroke *stroke, PikaAnchor *anchor); static gboolean pika_bezier_stroke_point_is_movable (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static void pika_bezier_stroke_point_move_relative (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *deltacoord, PikaAnchorFeatureType feature); static void pika_bezier_stroke_point_move_absolute (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *coord, PikaAnchorFeatureType feature); static void pika_bezier_stroke_close (PikaStroke *stroke); static PikaStroke * pika_bezier_stroke_open (PikaStroke *stroke, PikaAnchor *end_anchor); static gboolean pika_bezier_stroke_anchor_is_insertable (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static PikaAnchor * pika_bezier_stroke_anchor_insert (PikaStroke *stroke, PikaAnchor *predec, gdouble position); static gboolean pika_bezier_stroke_is_extendable (PikaStroke *stroke, PikaAnchor *neighbor); static gboolean pika_bezier_stroke_connect_stroke (PikaStroke *stroke, PikaAnchor *anchor, PikaStroke *extension, PikaAnchor *neighbor); static gboolean pika_bezier_stroke_reverse (PikaStroke *stroke); static gboolean pika_bezier_stroke_shift_start (PikaStroke *stroke, PikaAnchor *anchor); static GArray * pika_bezier_stroke_interpolate (PikaStroke *stroke, gdouble precision, gboolean *closed); static PikaBezierDesc * pika_bezier_stroke_make_bezier (PikaStroke *stroke); static void pika_bezier_stroke_transform (PikaStroke *stroke, const PikaMatrix3 *matrix, GQueue *ret_strokes); static void pika_bezier_stroke_finalize (GObject *object); static GList * pika_bezier_stroke_get_anchor_listitem (GList *list); G_DEFINE_TYPE (PikaBezierStroke, pika_bezier_stroke, PIKA_TYPE_STROKE) #define parent_class pika_bezier_stroke_parent_class static void pika_bezier_stroke_class_init (PikaBezierStrokeClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaStrokeClass *stroke_class = PIKA_STROKE_CLASS (klass); object_class->finalize = pika_bezier_stroke_finalize; stroke_class->nearest_point_get = pika_bezier_stroke_nearest_point_get; stroke_class->nearest_tangent_get = pika_bezier_stroke_nearest_tangent_get; stroke_class->nearest_intersection_get = NULL; stroke_class->anchor_move_relative = pika_bezier_stroke_anchor_move_relative; stroke_class->anchor_move_absolute = pika_bezier_stroke_anchor_move_absolute; stroke_class->anchor_convert = pika_bezier_stroke_anchor_convert; stroke_class->anchor_delete = pika_bezier_stroke_anchor_delete; stroke_class->point_is_movable = pika_bezier_stroke_point_is_movable; stroke_class->point_move_relative = pika_bezier_stroke_point_move_relative; stroke_class->point_move_absolute = pika_bezier_stroke_point_move_absolute; stroke_class->close = pika_bezier_stroke_close; stroke_class->open = pika_bezier_stroke_open; stroke_class->anchor_is_insertable = pika_bezier_stroke_anchor_is_insertable; stroke_class->anchor_insert = pika_bezier_stroke_anchor_insert; stroke_class->is_extendable = pika_bezier_stroke_is_extendable; stroke_class->extend = pika_bezier_stroke_extend; stroke_class->connect_stroke = pika_bezier_stroke_connect_stroke; stroke_class->reverse = pika_bezier_stroke_reverse; stroke_class->shift_start = pika_bezier_stroke_shift_start; stroke_class->interpolate = pika_bezier_stroke_interpolate; stroke_class->make_bezier = pika_bezier_stroke_make_bezier; stroke_class->transform = pika_bezier_stroke_transform; } static void pika_bezier_stroke_init (PikaBezierStroke *stroke) { } static void pika_bezier_stroke_finalize (GObject *object) { G_OBJECT_CLASS (parent_class)->finalize (object); } /* Bezier specific functions */ PikaStroke * pika_bezier_stroke_new (void) { return g_object_new (PIKA_TYPE_BEZIER_STROKE, NULL); } PikaStroke * pika_bezier_stroke_new_from_coords (const PikaCoords *coords, gint n_coords, gboolean closed) { PikaStroke *stroke; PikaAnchor *last_anchor; gint count; g_return_val_if_fail (coords != NULL, NULL); g_return_val_if_fail (n_coords >= 3, NULL); g_return_val_if_fail ((n_coords % 3) == 0, NULL); stroke = pika_bezier_stroke_new (); last_anchor = NULL; for (count = 0; count < n_coords; count++) last_anchor = pika_bezier_stroke_extend (stroke, &coords[count], last_anchor, EXTEND_SIMPLE); if (closed) pika_stroke_close (stroke); return stroke; } static void pika_bezier_stroke_anchor_delete (PikaStroke *stroke, PikaAnchor *anchor) { GList *list; GList *list2; gint i; /* Anchors always are surrounded by two handles that have to * be deleted too */ list2 = g_queue_find (stroke->anchors, anchor); list = g_list_previous (list2); for (i = 0; i < 3; i++) { g_return_if_fail (list != NULL); list2 = g_list_next (list); pika_anchor_free (list->data); g_queue_delete_link (stroke->anchors, list); list = list2; } } static PikaStroke * pika_bezier_stroke_open (PikaStroke *stroke, PikaAnchor *end_anchor) { GList *list; GList *list2; PikaStroke *new_stroke = NULL; list = g_queue_find (stroke->anchors, end_anchor); g_return_val_if_fail (list != NULL && list->next != NULL, NULL); list = g_list_next (list); /* protect the handle... */ list2 = list->next; list->next = NULL; if (list2 != NULL) { GList *tail = stroke->anchors->tail; stroke->anchors->tail = list; stroke->anchors->length -= g_list_length (list2); list2->prev = NULL; if (stroke->closed) { GList *l; for (l = tail; l; l = g_list_previous (l)) g_queue_push_head (stroke->anchors, l->data); g_list_free (list2); } else { new_stroke = pika_bezier_stroke_new (); new_stroke->anchors->head = list2; new_stroke->anchors->tail = g_list_last (list2); new_stroke->anchors->length = g_list_length (list2); } } stroke->closed = FALSE; g_object_notify (G_OBJECT (stroke), "closed"); return new_stroke; } static gboolean pika_bezier_stroke_anchor_is_insertable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { return (g_queue_find (stroke->anchors, predec) != NULL); } static PikaAnchor * pika_bezier_stroke_anchor_insert (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { GList *segment_start; GList *list; GList *list2; PikaCoords subdivided[8]; PikaCoords beziercoords[4]; gint i; segment_start = g_queue_find (stroke->anchors, predec); if (! segment_start) return NULL; list = segment_start; for (i = 0; i <= 3; i++) { beziercoords[i] = PIKA_ANCHOR (list->data)->position; list = g_list_next (list); if (! list) list = stroke->anchors->head; } subdivided[0] = beziercoords[0]; subdivided[6] = beziercoords[3]; pika_coords_mix (1-position, &(beziercoords[0]), position, &(beziercoords[1]), &(subdivided[1])); pika_coords_mix (1-position, &(beziercoords[1]), position, &(beziercoords[2]), &(subdivided[7])); pika_coords_mix (1-position, &(beziercoords[2]), position, &(beziercoords[3]), &(subdivided[5])); pika_coords_mix (1-position, &(subdivided[1]), position, &(subdivided[7]), &(subdivided[2])); pika_coords_mix (1-position, &(subdivided[7]), position, &(subdivided[5]), &(subdivided[4])); pika_coords_mix (1-position, &(subdivided[2]), position, &(subdivided[4]), &(subdivided[3])); /* subdivided 0-6 contains the bezier segment subdivided at */ list = segment_start; for (i = 0; i <= 6; i++) { if (i >= 2 && i <= 4) { list2 = g_list_append (NULL, pika_anchor_new ((i == 3 ? PIKA_ANCHOR_ANCHOR: PIKA_ANCHOR_CONTROL), &(subdivided[i]))); /* insert it *before* list manually. */ list2->next = list; list2->prev = list->prev; if (list->prev) list->prev->next = list2; list->prev = list2; list = list2; if (i == 3) segment_start = list; } else { PIKA_ANCHOR (list->data)->position = subdivided[i]; } list = g_list_next (list); if (! list) list = stroke->anchors->head; } stroke->anchors->head = g_list_first (list); stroke->anchors->tail = g_list_last (list); stroke->anchors->length += 3; return PIKA_ANCHOR (segment_start->data); } static gboolean pika_bezier_stroke_point_is_movable (PikaStroke *stroke, PikaAnchor *predec, gdouble position) { return (g_queue_find (stroke->anchors, predec) != NULL); } static void pika_bezier_stroke_point_move_relative (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *deltacoord, PikaAnchorFeatureType feature) { PikaCoords offsetcoords[2]; GList *segment_start; GList *list; gint i; gdouble feel_good; segment_start = g_queue_find (stroke->anchors, predec); g_return_if_fail (segment_start != NULL); /* dragging close to endpoints just moves the handle related to * the endpoint. Just make sure that feel_good is in the range from * 0 to 1. The 1.0 / 6.0 and 5.0 / 6.0 are duplicated in * tools/pikavectortool.c. */ if (position <= 1.0 / 6.0) feel_good = 0; else if (position <= 0.5) feel_good = (pow((6 * position - 1) / 2.0, 3)) / 2; else if (position <= 5.0 / 6.0) feel_good = (1 - pow((6 * (1-position) - 1) / 2.0, 3)) / 2 + 0.5; else feel_good = 1; pika_coords_scale ((1-feel_good)/(3*position* (1-position)*(1-position)), deltacoord, &(offsetcoords[0])); pika_coords_scale (feel_good/(3*position*position*(1-position)), deltacoord, &(offsetcoords[1])); list = segment_start; list = g_list_next (list); if (! list) list = stroke->anchors->head; for (i = 0; i <= 1; i++) { pika_stroke_anchor_move_relative (stroke, PIKA_ANCHOR (list->data), &(offsetcoords[i]), feature); list = g_list_next (list); if (! list) list = stroke->anchors->head; } } static void pika_bezier_stroke_point_move_absolute (PikaStroke *stroke, PikaAnchor *predec, gdouble position, const PikaCoords *coord, PikaAnchorFeatureType feature) { PikaCoords deltacoord; PikaCoords tmp1, tmp2, abs_pos; PikaCoords beziercoords[4]; GList *segment_start; GList *list; gint i; segment_start = g_queue_find (stroke->anchors, predec); g_return_if_fail (segment_start != NULL); list = segment_start; for (i = 0; i <= 3; i++) { beziercoords[i] = PIKA_ANCHOR (list->data)->position; list = g_list_next (list); if (! list) list = stroke->anchors->head; } pika_coords_mix ((1-position)*(1-position)*(1-position), &(beziercoords[0]), 3*(1-position)*(1-position)*position, &(beziercoords[1]), &tmp1); pika_coords_mix (3*(1-position)*position*position, &(beziercoords[2]), position*position*position, &(beziercoords[3]), &tmp2); pika_coords_add (&tmp1, &tmp2, &abs_pos); pika_coords_difference (coord, &abs_pos, &deltacoord); pika_bezier_stroke_point_move_relative (stroke, predec, position, &deltacoord, feature); } static void pika_bezier_stroke_close (PikaStroke *stroke) { GList *start; GList *end; PikaAnchor *anchor; start = stroke->anchors->head; end = stroke->anchors->tail; g_return_if_fail (start->next != NULL && end->prev != NULL); if (start->next != end->prev) { if (pika_coords_equal (&(PIKA_ANCHOR (start->next->data)->position), &(PIKA_ANCHOR (start->data)->position)) && pika_coords_equal (&(PIKA_ANCHOR (start->data)->position), &(PIKA_ANCHOR (end->data)->position)) && pika_coords_equal (&(PIKA_ANCHOR (end->data)->position), &(PIKA_ANCHOR (end->prev->data)->position))) { /* redundant segment */ pika_anchor_free (stroke->anchors->tail->data); g_queue_delete_link (stroke->anchors, stroke->anchors->tail); pika_anchor_free (stroke->anchors->tail->data); g_queue_delete_link (stroke->anchors, stroke->anchors->tail); anchor = stroke->anchors->tail->data; g_queue_delete_link (stroke->anchors, stroke->anchors->tail); pika_anchor_free (stroke->anchors->head->data); stroke->anchors->head->data = anchor; } } PIKA_STROKE_CLASS (parent_class)->close (stroke); } static gdouble pika_bezier_stroke_nearest_point_get (PikaStroke *stroke, const PikaCoords *coord, gdouble precision, PikaCoords *ret_point, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos) { gdouble min_dist, dist, pos; PikaCoords point = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 }; PikaCoords segmentcoords[4]; GList *anchorlist; PikaAnchor *segment_start; PikaAnchor *segment_end = NULL; PikaAnchor *anchor; gint count; if (g_queue_is_empty (stroke->anchors)) return -1.0; count = 0; min_dist = -1; pos = 0; for (anchorlist = stroke->anchors->head; PIKA_ANCHOR (anchorlist->data)->type != PIKA_ANCHOR_ANCHOR; anchorlist = g_list_next (anchorlist)); segment_start = anchorlist->data; for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) { anchor = anchorlist->data; segmentcoords[count] = anchor->position; count++; if (count == 4) { segment_end = anchorlist->data; dist = pika_bezier_stroke_segment_nearest_point_get (segmentcoords, coord, precision, &point, &pos, 10); if (dist < min_dist || min_dist < 0) { min_dist = dist; if (ret_pos) *ret_pos = pos; if (ret_point) *ret_point = point; if (ret_segment_start) *ret_segment_start = segment_start; if (ret_segment_end) *ret_segment_end = segment_end; } segment_start = anchorlist->data; segmentcoords[0] = segmentcoords[3]; count = 1; } } if (stroke->closed && stroke->anchors->head) { anchorlist = stroke->anchors->head; while (count < 3) { segmentcoords[count] = PIKA_ANCHOR (anchorlist->data)->position; count++; } anchorlist = g_list_next (anchorlist); if (anchorlist) { segment_end = PIKA_ANCHOR (anchorlist->data); segmentcoords[3] = segment_end->position; } dist = pika_bezier_stroke_segment_nearest_point_get (segmentcoords, coord, precision, &point, &pos, 10); if (dist < min_dist || min_dist < 0) { min_dist = dist; if (ret_pos) *ret_pos = pos; if (ret_point) *ret_point = point; if (ret_segment_start) *ret_segment_start = segment_start; if (ret_segment_end) *ret_segment_end = segment_end; } } return min_dist; } static gdouble pika_bezier_stroke_segment_nearest_point_get (const PikaCoords *beziercoords, const PikaCoords *coord, gdouble precision, PikaCoords *ret_point, gdouble *ret_pos, gint depth) { /* * beziercoords has to contain four PikaCoords with the four control points * of the bezier segment. We subdivide it at the parameter 0.5. */ PikaCoords subdivided[8]; gdouble dist1, dist2; PikaCoords point1, point2; gdouble pos1, pos2; pika_coords_difference (&beziercoords[1], &beziercoords[0], &point1); pika_coords_difference (&beziercoords[3], &beziercoords[2], &point2); if (! depth || (pika_coords_bezier_is_straight (beziercoords, precision) && pika_coords_length_squared (&point1) < precision && pika_coords_length_squared (&point2) < precision)) { PikaCoords line, dcoord; gdouble length2, scalar; gint i; pika_coords_difference (&(beziercoords[3]), &(beziercoords[0]), &line); pika_coords_difference (coord, &(beziercoords[0]), &dcoord); length2 = pika_coords_scalarprod (&line, &line); scalar = pika_coords_scalarprod (&line, &dcoord) / length2; scalar = CLAMP (scalar, 0.0, 1.0); /* lines look the same as bezier curves where the handles * sit on the anchors, however, they are parametrized * differently. Hence we have to do some weird approximation. */ pos1 = pos2 = 0.5; for (i = 0; i <= 15; i++) { pos2 *= 0.5; if (3 * pos1 * pos1 * (1-pos1) + pos1 * pos1 * pos1 < scalar) pos1 += pos2; else pos1 -= pos2; } *ret_pos = pos1; pika_coords_mix (1.0, &(beziercoords[0]), scalar, &line, ret_point); pika_coords_difference (coord, ret_point, &dcoord); return pika_coords_length (&dcoord); } /* ok, we have to subdivide */ subdivided[0] = beziercoords[0]; subdivided[6] = beziercoords[3]; /* if (!depth) g_printerr ("Hit recursion depth limit!\n"); */ pika_coords_average (&(beziercoords[0]), &(beziercoords[1]), &(subdivided[1])); pika_coords_average (&(beziercoords[1]), &(beziercoords[2]), &(subdivided[7])); pika_coords_average (&(beziercoords[2]), &(beziercoords[3]), &(subdivided[5])); pika_coords_average (&(subdivided[1]), &(subdivided[7]), &(subdivided[2])); pika_coords_average (&(subdivided[7]), &(subdivided[5]), &(subdivided[4])); pika_coords_average (&(subdivided[2]), &(subdivided[4]), &(subdivided[3])); /* * We now have the coordinates of the two bezier segments in * subdivided [0-3] and subdivided [3-6] */ dist1 = pika_bezier_stroke_segment_nearest_point_get (&(subdivided[0]), coord, precision, &point1, &pos1, depth - 1); dist2 = pika_bezier_stroke_segment_nearest_point_get (&(subdivided[3]), coord, precision, &point2, &pos2, depth - 1); if (dist1 <= dist2) { *ret_point = point1; *ret_pos = 0.5 * pos1; return dist1; } else { *ret_point = point2; *ret_pos = 0.5 + 0.5 * pos2; return dist2; } } static gdouble pika_bezier_stroke_nearest_tangent_get (PikaStroke *stroke, const PikaCoords *coord1, const PikaCoords *coord2, gdouble precision, PikaCoords *nearest, PikaAnchor **ret_segment_start, PikaAnchor **ret_segment_end, gdouble *ret_pos) { gdouble min_dist, dist, pos; PikaCoords point; PikaCoords segmentcoords[4]; GList *anchorlist; PikaAnchor *segment_start; PikaAnchor *segment_end = NULL; PikaAnchor *anchor; gint count; if (g_queue_is_empty (stroke->anchors)) return -1.0; count = 0; min_dist = -1; for (anchorlist = stroke->anchors->head; PIKA_ANCHOR (anchorlist->data)->type != PIKA_ANCHOR_ANCHOR; anchorlist = g_list_next (anchorlist)); segment_start = anchorlist->data; for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) { anchor = anchorlist->data; segmentcoords[count] = anchor->position; count++; if (count == 4) { segment_end = anchorlist->data; dist = pika_bezier_stroke_segment_nearest_tangent_get (segmentcoords, coord1, coord2, precision, &point, &pos); if (dist >= 0 && (dist < min_dist || min_dist < 0)) { min_dist = dist; if (ret_pos) *ret_pos = pos; if (nearest) *nearest = point; if (ret_segment_start) *ret_segment_start = segment_start; if (ret_segment_end) *ret_segment_end = segment_end; } segment_start = anchorlist->data; segmentcoords[0] = segmentcoords[3]; count = 1; } } if (stroke->closed && ! g_queue_is_empty (stroke->anchors)) { anchorlist = stroke->anchors->head; while (count < 3) { segmentcoords[count] = PIKA_ANCHOR (anchorlist->data)->position; count++; } anchorlist = g_list_next (anchorlist); if (anchorlist) { segment_end = PIKA_ANCHOR (anchorlist->data); segmentcoords[3] = segment_end->position; } dist = pika_bezier_stroke_segment_nearest_tangent_get (segmentcoords, coord1, coord2, precision, &point, &pos); if (dist >= 0 && (dist < min_dist || min_dist < 0)) { min_dist = dist; if (ret_pos) *ret_pos = pos; if (nearest) *nearest = point; if (ret_segment_start) *ret_segment_start = segment_start; if (ret_segment_end) *ret_segment_end = segment_end; } } return min_dist; } static gdouble pika_bezier_stroke_segment_nearest_tangent_get (const PikaCoords *beziercoords, const PikaCoords *coord1, const PikaCoords *coord2, gdouble precision, PikaCoords *ret_point, gdouble *ret_pos) { GArray *ret_coords; GArray *ret_params; PikaCoords dir, line, dcoord, min_point; gdouble min_dist = -1; gdouble dist, length2, scalar, ori, ori2; gint i; pika_coords_difference (coord2, coord1, &line); ret_coords = g_array_new (FALSE, FALSE, sizeof (PikaCoords)); ret_params = g_array_new (FALSE, FALSE, sizeof (gdouble)); g_printerr ("(%.2f, %.2f)-(%.2f,%.2f): ", coord1->x, coord1->y, coord2->x, coord2->y); pika_coords_interpolate_bezier (beziercoords, precision, ret_coords, ret_params); g_return_val_if_fail (ret_coords->len == ret_params->len, -1.0); if (ret_coords->len < 2) return -1; pika_coords_difference (&g_array_index (ret_coords, PikaCoords, 1), &g_array_index (ret_coords, PikaCoords, 0), &dir); ori = dir.x * line.y - dir.y * line.x; for (i = 2; i < ret_coords->len; i++) { pika_coords_difference (&g_array_index (ret_coords, PikaCoords, i), &g_array_index (ret_coords, PikaCoords, i-1), &dir); ori2 = dir.x * line.y - dir.y * line.x; if (ori * ori2 <= 0) { pika_coords_difference (&g_array_index (ret_coords, PikaCoords, i), coord1, &dcoord); length2 = pika_coords_scalarprod (&line, &line); scalar = pika_coords_scalarprod (&line, &dcoord) / length2; if (scalar >= 0 && scalar <= 1) { pika_coords_mix (1.0, coord1, scalar, &line, &min_point); pika_coords_difference (&min_point, &g_array_index (ret_coords, PikaCoords, i), &dcoord); dist = pika_coords_length (&dcoord); if (dist < min_dist || min_dist < 0) { min_dist = dist; *ret_point = g_array_index (ret_coords, PikaCoords, i); *ret_pos = g_array_index (ret_params, gdouble, i); } } } ori = ori2; } if (min_dist < 0) g_printerr ("-\n"); else g_printerr ("%f: (%.2f, %.2f) /%.3f/\n", min_dist, (*ret_point).x, (*ret_point).y, *ret_pos); g_array_free (ret_coords, TRUE); g_array_free (ret_params, TRUE); return min_dist; } static gboolean pika_bezier_stroke_is_extendable (PikaStroke *stroke, PikaAnchor *neighbor) { GList *listneighbor; gint loose_end; if (stroke->closed) return FALSE; if (g_queue_is_empty (stroke->anchors)) return TRUE; /* assure that there is a neighbor specified */ g_return_val_if_fail (neighbor != NULL, FALSE); loose_end = 0; listneighbor = stroke->anchors->tail; /* Check if the neighbor is at an end of the control points */ if (listneighbor->data == neighbor) { loose_end = 1; } else { listneighbor = g_list_first (stroke->anchors->head); if (listneighbor->data == neighbor) { loose_end = -1; } else { /* * It isn't. If we are on a handle go to the nearest * anchor and see if we can find an end from it. * Yes, this is tedious. */ listneighbor = g_queue_find (stroke->anchors, neighbor); if (listneighbor && neighbor->type == PIKA_ANCHOR_CONTROL) { if (listneighbor->prev && PIKA_ANCHOR (listneighbor->prev->data)->type == PIKA_ANCHOR_ANCHOR) { listneighbor = listneighbor->prev; } else if (listneighbor->next && PIKA_ANCHOR (listneighbor->next->data)->type == PIKA_ANCHOR_ANCHOR) { listneighbor = listneighbor->next; } else { loose_end = 0; listneighbor = NULL; } } if (listneighbor) /* we found a suitable ANCHOR_ANCHOR now, lets * search for its loose end. */ { if (listneighbor->prev && listneighbor->prev->prev == NULL) { loose_end = -1; } else if (listneighbor->next && listneighbor->next->next == NULL) { loose_end = 1; } } } } return (loose_end != 0); } PikaAnchor * pika_bezier_stroke_extend (PikaStroke *stroke, const PikaCoords *coords, PikaAnchor *neighbor, PikaVectorExtendMode extend_mode) { PikaAnchor *anchor = NULL; GList *listneighbor; gint loose_end, control_count; if (g_queue_is_empty (stroke->anchors)) { /* assure that there is no neighbor specified */ g_return_val_if_fail (neighbor == NULL, NULL); anchor = pika_anchor_new (PIKA_ANCHOR_CONTROL, coords); g_queue_push_tail (stroke->anchors, anchor); switch (extend_mode) { case EXTEND_SIMPLE: break; case EXTEND_EDITABLE: anchor = pika_bezier_stroke_extend (stroke, coords, anchor, EXTEND_SIMPLE); /* we return the PIKA_ANCHOR_ANCHOR */ pika_bezier_stroke_extend (stroke, coords, anchor, EXTEND_SIMPLE); break; default: anchor = NULL; } return anchor; } else { /* assure that there is a neighbor specified */ g_return_val_if_fail (neighbor != NULL, NULL); loose_end = 0; listneighbor = stroke->anchors->tail; /* Check if the neighbor is at an end of the control points */ if (listneighbor->data == neighbor) { loose_end = 1; } else { listneighbor = stroke->anchors->head; if (listneighbor->data == neighbor) { loose_end = -1; } else { /* * It isn't. If we are on a handle go to the nearest * anchor and see if we can find an end from it. * Yes, this is tedious. */ listneighbor = g_queue_find (stroke->anchors, neighbor); if (listneighbor && neighbor->type == PIKA_ANCHOR_CONTROL) { if (listneighbor->prev && PIKA_ANCHOR (listneighbor->prev->data)->type == PIKA_ANCHOR_ANCHOR) { listneighbor = listneighbor->prev; } else if (listneighbor->next && PIKA_ANCHOR (listneighbor->next->data)->type == PIKA_ANCHOR_ANCHOR) { listneighbor = listneighbor->next; } else { loose_end = 0; listneighbor = NULL; } } if (listneighbor) /* we found a suitable ANCHOR_ANCHOR now, lets * search for its loose end. */ { if (listneighbor->next && listneighbor->next->next == NULL) { loose_end = 1; listneighbor = listneighbor->next; } else if (listneighbor->prev && listneighbor->prev->prev == NULL) { loose_end = -1; listneighbor = listneighbor->prev; } } } } if (loose_end) { PikaAnchorType type; /* We have to detect the type of the point to add... */ control_count = 0; if (loose_end == 1) { while (listneighbor && PIKA_ANCHOR (listneighbor->data)->type == PIKA_ANCHOR_CONTROL) { control_count++; listneighbor = listneighbor->prev; } } else { while (listneighbor && PIKA_ANCHOR (listneighbor->data)->type == PIKA_ANCHOR_CONTROL) { control_count++; listneighbor = listneighbor->next; } } switch (extend_mode) { case EXTEND_SIMPLE: switch (control_count) { case 0: type = PIKA_ANCHOR_CONTROL; break; case 1: if (listneighbor) /* only one handle in the path? */ type = PIKA_ANCHOR_CONTROL; else type = PIKA_ANCHOR_ANCHOR; break; case 2: type = PIKA_ANCHOR_ANCHOR; break; default: g_warning ("inconsistent bezier curve: " "%d successive control handles", control_count); type = PIKA_ANCHOR_ANCHOR; } anchor = pika_anchor_new (type, coords); if (loose_end == 1) g_queue_push_tail (stroke->anchors, anchor); if (loose_end == -1) g_queue_push_head (stroke->anchors, anchor); break; case EXTEND_EDITABLE: switch (control_count) { case 0: neighbor = pika_bezier_stroke_extend (stroke, &(neighbor->position), neighbor, EXTEND_SIMPLE); case 1: neighbor = pika_bezier_stroke_extend (stroke, coords, neighbor, EXTEND_SIMPLE); case 2: anchor = pika_bezier_stroke_extend (stroke, coords, neighbor, EXTEND_SIMPLE); neighbor = pika_bezier_stroke_extend (stroke, coords, anchor, EXTEND_SIMPLE); break; default: g_warning ("inconsistent bezier curve: " "%d successive control handles", control_count); } } return anchor; } return NULL; } } static gboolean pika_bezier_stroke_connect_stroke (PikaStroke *stroke, PikaAnchor *anchor, PikaStroke *extension, PikaAnchor *neighbor) { GList *list1; GList *list2; list1 = g_queue_find (stroke->anchors, anchor); list1 = pika_bezier_stroke_get_anchor_listitem (list1); list2 = g_queue_find (extension->anchors, neighbor); list2 = pika_bezier_stroke_get_anchor_listitem (list2); g_return_val_if_fail (list1 != NULL && list2 != NULL, FALSE); if (stroke == extension) { g_return_val_if_fail ((list1->prev && list1->prev->prev == NULL && list2->next && list2->next->next == NULL) || (list1->next && list1->next->next == NULL && list2->prev && list2->prev->prev == NULL), FALSE); pika_stroke_close (stroke); return TRUE; } if (list1->prev && list1->prev->prev == NULL) { g_queue_reverse (stroke->anchors); } g_return_val_if_fail (list1->next && list1->next->next == NULL, FALSE); if (list2->next && list2->next->next == NULL) { g_queue_reverse (extension->anchors); } g_return_val_if_fail (list2->prev && list2->prev->prev == NULL, FALSE); for (list1 = extension->anchors->head; list1; list1 = g_list_next (list1)) g_queue_push_tail (stroke->anchors, list1->data); g_queue_clear (extension->anchors); return TRUE; } static gboolean pika_bezier_stroke_reverse (PikaStroke *stroke) { g_return_val_if_fail (PIKA_IS_BEZIER_STROKE (stroke), FALSE); g_queue_reverse (stroke->anchors); /* keep the first nodegroup the same for closed strokes */ if (stroke->closed && stroke->anchors->length >= 3) { g_queue_push_head_link (stroke->anchors, g_queue_pop_tail_link (stroke->anchors)); g_queue_push_head_link (stroke->anchors, g_queue_pop_tail_link (stroke->anchors)); g_queue_push_head_link (stroke->anchors, g_queue_pop_tail_link (stroke->anchors)); } return TRUE; } static gboolean pika_bezier_stroke_shift_start (PikaStroke *stroke, PikaAnchor *new_start) { GList *link; g_return_val_if_fail (PIKA_IS_BEZIER_STROKE (stroke), FALSE); g_return_val_if_fail (new_start != NULL, FALSE); g_return_val_if_fail (new_start->type == PIKA_ANCHOR_ANCHOR, FALSE); link = g_queue_find (stroke->anchors, new_start); if (!link) return FALSE; /* the preceding control anchor will be the new head */ link = g_list_previous (link); 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; } static void pika_bezier_stroke_anchor_move_relative (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *deltacoord, PikaAnchorFeatureType feature) { PikaCoords delta, coord1, coord2; GList *anchor_list; delta = *deltacoord; delta.pressure = 0; delta.xtilt = 0; delta.ytilt = 0; delta.wheel = 0; pika_coords_add (&(anchor->position), &delta, &coord1); anchor->position = coord1; anchor_list = g_queue_find (stroke->anchors, anchor); g_return_if_fail (anchor_list != NULL); if (anchor->type == PIKA_ANCHOR_ANCHOR) { if (g_list_previous (anchor_list)) { coord2 = PIKA_ANCHOR (g_list_previous (anchor_list)->data)->position; pika_coords_add (&coord2, &delta, &coord1); PIKA_ANCHOR (g_list_previous (anchor_list)->data)->position = coord1; } if (g_list_next (anchor_list)) { coord2 = PIKA_ANCHOR (g_list_next (anchor_list)->data)->position; pika_coords_add (&coord2, &delta, &coord1); PIKA_ANCHOR (g_list_next (anchor_list)->data)->position = coord1; } } else { if (feature == PIKA_ANCHOR_FEATURE_SYMMETRIC) { GList *neighbour = NULL, *opposite = NULL; /* search for opposite control point. Sigh. */ neighbour = g_list_previous (anchor_list); if (neighbour && PIKA_ANCHOR (neighbour->data)->type == PIKA_ANCHOR_ANCHOR) { opposite = g_list_previous (neighbour); } else { neighbour = g_list_next (anchor_list); if (neighbour && PIKA_ANCHOR (neighbour->data)->type == PIKA_ANCHOR_ANCHOR) { opposite = g_list_next (neighbour); } } if (opposite && PIKA_ANCHOR (opposite->data)->type == PIKA_ANCHOR_CONTROL) { pika_coords_difference (&(PIKA_ANCHOR (neighbour->data)->position), &(anchor->position), &delta); pika_coords_add (&(PIKA_ANCHOR (neighbour->data)->position), &delta, &coord1); PIKA_ANCHOR (opposite->data)->position = coord1; } } } } static void pika_bezier_stroke_anchor_move_absolute (PikaStroke *stroke, PikaAnchor *anchor, const PikaCoords *coord, PikaAnchorFeatureType feature) { PikaCoords deltacoord; pika_coords_difference (coord, &anchor->position, &deltacoord); pika_bezier_stroke_anchor_move_relative (stroke, anchor, &deltacoord, feature); } static void pika_bezier_stroke_anchor_convert (PikaStroke *stroke, PikaAnchor *anchor, PikaAnchorFeatureType feature) { GList *anchor_list; anchor_list = g_queue_find (stroke->anchors, anchor); g_return_if_fail (anchor_list != NULL); switch (feature) { case PIKA_ANCHOR_FEATURE_EDGE: if (anchor->type == PIKA_ANCHOR_ANCHOR) { if (g_list_previous (anchor_list)) PIKA_ANCHOR (g_list_previous (anchor_list)->data)->position = anchor->position; if (g_list_next (anchor_list)) PIKA_ANCHOR (g_list_next (anchor_list)->data)->position = anchor->position; } else { if (g_list_previous (anchor_list) && PIKA_ANCHOR (g_list_previous (anchor_list)->data)->type == PIKA_ANCHOR_ANCHOR) anchor->position = PIKA_ANCHOR (g_list_previous (anchor_list)->data)->position; if (g_list_next (anchor_list) && PIKA_ANCHOR (g_list_next (anchor_list)->data)->type == PIKA_ANCHOR_ANCHOR) anchor->position = PIKA_ANCHOR (g_list_next (anchor_list)->data)->position; } break; default: g_warning ("pika_bezier_stroke_anchor_convert: " "unimplemented anchor conversion %d\n", feature); } } static PikaBezierDesc * pika_bezier_stroke_make_bezier (PikaStroke *stroke) { GArray *points; GArray *cmd_array; PikaBezierDesc *bezdesc; cairo_path_data_t pathdata; gint num_cmds, i; points = pika_stroke_control_points_get (stroke, NULL); g_return_val_if_fail (points && points->len % 3 == 0, NULL); if (points->len < 3) return NULL; /* Moveto + (n-1) * curveto + (if closed) curveto + closepath */ num_cmds = 2 + (points->len / 3 - 1) * 4; if (stroke->closed) num_cmds += 1 + 4; cmd_array = g_array_sized_new (FALSE, FALSE, sizeof (cairo_path_data_t), num_cmds); pathdata.header.type = CAIRO_PATH_MOVE_TO; pathdata.header.length = 2; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, 1).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, 1).position.y; g_array_append_val (cmd_array, pathdata); for (i = 2; i+2 < points->len; i += 3) { pathdata.header.type = CAIRO_PATH_CURVE_TO; pathdata.header.length = 4; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, i).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, i).position.y; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, i+1).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, i+1).position.y; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, i+2).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, i+2).position.y; g_array_append_val (cmd_array, pathdata); } if (stroke->closed) { pathdata.header.type = CAIRO_PATH_CURVE_TO; pathdata.header.length = 4; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, i).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, i).position.y; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, 0).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, 0).position.y; g_array_append_val (cmd_array, pathdata); pathdata.point.x = g_array_index (points, PikaAnchor, 1).position.x; pathdata.point.y = g_array_index (points, PikaAnchor, 1).position.y; g_array_append_val (cmd_array, pathdata); pathdata.header.type = CAIRO_PATH_CLOSE_PATH; pathdata.header.length = 1; g_array_append_val (cmd_array, pathdata); } if (cmd_array->len != num_cmds) g_printerr ("miscalculated path cmd length! (%d vs. %d)\n", cmd_array->len, num_cmds); bezdesc = pika_bezier_desc_new ((cairo_path_data_t *) cmd_array->data, cmd_array->len); g_array_free (points, TRUE); g_array_free (cmd_array, FALSE); return bezdesc; } static GArray * pika_bezier_stroke_interpolate (PikaStroke *stroke, gdouble precision, gboolean *ret_closed) { GArray *ret_coords; PikaAnchor *anchor; GList *anchorlist; PikaCoords segmentcoords[4]; gint count; gboolean need_endpoint = FALSE; if (g_queue_is_empty (stroke->anchors)) { if (ret_closed) *ret_closed = FALSE; return NULL; } ret_coords = g_array_new (FALSE, FALSE, sizeof (PikaCoords)); count = 0; for (anchorlist = stroke->anchors->head; anchorlist && PIKA_ANCHOR (anchorlist->data)->type != PIKA_ANCHOR_ANCHOR; anchorlist = g_list_next (anchorlist)); for ( ; anchorlist; anchorlist = g_list_next (anchorlist)) { anchor = anchorlist->data; segmentcoords[count] = anchor->position; count++; if (count == 4) { pika_coords_interpolate_bezier (segmentcoords, precision, ret_coords, NULL); segmentcoords[0] = segmentcoords[3]; count = 1; need_endpoint = TRUE; } } if (stroke->closed && ! g_queue_is_empty (stroke->anchors)) { anchorlist = stroke->anchors->head; while (count < 3) { segmentcoords[count] = PIKA_ANCHOR (anchorlist->data)->position; count++; } anchorlist = g_list_next (anchorlist); if (anchorlist) segmentcoords[3] = PIKA_ANCHOR (anchorlist->data)->position; pika_coords_interpolate_bezier (segmentcoords, precision, ret_coords, NULL); need_endpoint = TRUE; } if (need_endpoint) ret_coords = g_array_append_val (ret_coords, segmentcoords[3]); if (ret_closed) *ret_closed = stroke->closed; if (ret_coords->len == 0) { g_array_free (ret_coords, TRUE); ret_coords = NULL; } return ret_coords; } static void pika_bezier_stroke_transform (PikaStroke *stroke, const PikaMatrix3 *matrix, GQueue *ret_strokes) { PikaStroke *first_stroke = NULL; PikaStroke *last_stroke = NULL; GList *anchorlist; PikaAnchor *anchor; PikaCoords segmentcoords[4]; GQueue *transformed[2]; gint n_transformed; gint count; gboolean first; gboolean last; /* if there's no need for clipping, use the default implementation */ if (! ret_strokes || pika_matrix3_is_affine (matrix) || g_queue_is_empty (stroke->anchors)) { PIKA_STROKE_CLASS (parent_class)->transform (stroke, matrix, ret_strokes); return; } /* transform the individual segments */ count = 0; first = TRUE; last = FALSE; /* find the first non-control anchor */ for (anchorlist = stroke->anchors->head; anchorlist && PIKA_ANCHOR (anchorlist->data)->type != PIKA_ANCHOR_ANCHOR; anchorlist = g_list_next (anchorlist)); for ( ; anchorlist || stroke->closed; anchorlist = g_list_next (anchorlist)) { /* wrap around if 'stroke' is closed, so that we transform the final * segment */ if (! anchorlist) { anchorlist = stroke->anchors->head; last = TRUE; } anchor = anchorlist->data; segmentcoords[count] = anchor->position; count++; if (count == 4) { gboolean start_in; gboolean end_in; gint i; pika_transform_bezier_coords (matrix, segmentcoords, transformed, &n_transformed, &start_in, &end_in); for (i = 0; i < n_transformed; i++) { PikaStroke *s = NULL; GList *list; gint j; if (i == 0 && start_in) { /* current stroke is connected to last stroke */ s = last_stroke; } else if (last_stroke) { /* current stroke is not connected to last stroke. finalize * last stroke. */ anchor = g_queue_peek_tail (last_stroke->anchors); g_queue_push_tail (last_stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, &anchor->position)); } for (list = transformed[i]->head; list; list = g_list_next (list)) { PikaCoords *transformedcoords = list->data; if (! s) { /* start a new stroke */ s = pika_bezier_stroke_new (); g_queue_push_tail (s->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, &transformedcoords[0])); g_queue_push_tail (ret_strokes, s); j = 0; } else { /* continue an existing stroke, skipping the first anchor, * which is the same as the last anchor of the last stroke */ j = 1; } for (; j < 4; j++) { PikaAnchorType type; if (j == 0 || j == 3) type = PIKA_ANCHOR_ANCHOR; else type = PIKA_ANCHOR_CONTROL; g_queue_push_tail (s->anchors, pika_anchor_new (type, &transformedcoords[j])); } g_free (transformedcoords); } g_queue_free (transformed[i]); /* if the current stroke is an initial segment of 'stroke', * remember it, so that we can possibly connect it to the last * stroke later. */ if (i == 0 && start_in && first) first_stroke = s; last_stroke = s; first = FALSE; } if (! end_in && last_stroke) { /* the next stroke is not connected to the last stroke. finalize * the last stroke. */ anchor = g_queue_peek_tail (last_stroke->anchors); g_queue_push_tail (last_stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, &anchor->position)); last_stroke = NULL; } if (last) break; segmentcoords[0] = segmentcoords[3]; count = 1; } } /* if the last stroke is a final segment of 'stroke'... */ if (last_stroke) { /* ... and the first stroke is an initial segment of 'stroke', and * 'stroke' is closed ... */ if (first_stroke && stroke->closed) { /* connect the first and last strokes */ /* remove the first anchor, which is a synthetic control point */ pika_anchor_free (g_queue_pop_head (first_stroke->anchors)); /* remove the last anchor, which is the same anchor point as the * first anchor */ pika_anchor_free (g_queue_pop_tail (last_stroke->anchors)); if (first_stroke == last_stroke) { /* the result is a single stroke. move the last anchor, which is * an orphan control point, to the front, to fill in the removed * control point of the first anchor, and close the stroke. */ g_queue_push_head (first_stroke->anchors, g_queue_pop_tail (first_stroke->anchors)); first_stroke->closed = TRUE; } else { /* the result is multiple strokes. prepend the last stroke to * the first stroke, and discard it. */ while ((anchor = g_queue_pop_tail (last_stroke->anchors))) g_queue_push_head (first_stroke->anchors, anchor); g_object_unref (g_queue_pop_tail (ret_strokes)); } } else { /* otherwise, the first and last strokes are not connected. finalize * the last stroke. */ anchor = g_queue_peek_tail (last_stroke->anchors); g_queue_push_tail (last_stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, &anchor->position)); } } } PikaStroke * pika_bezier_stroke_new_moveto (const PikaCoords *start) { PikaStroke *stroke = pika_bezier_stroke_new (); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, start)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_ANCHOR, start)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, start)); return stroke; } void pika_bezier_stroke_lineto (PikaStroke *stroke, const PikaCoords *end) { g_return_if_fail (PIKA_IS_BEZIER_STROKE (stroke)); g_return_if_fail (stroke->closed == FALSE); g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, end)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_ANCHOR, end)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, end)); } void pika_bezier_stroke_conicto (PikaStroke *stroke, const PikaCoords *control, const PikaCoords *end) { PikaCoords start, coords; g_return_if_fail (PIKA_IS_BEZIER_STROKE (stroke)); g_return_if_fail (stroke->closed == FALSE); g_return_if_fail (g_queue_get_length (stroke->anchors) > 1); start = PIKA_ANCHOR (stroke->anchors->tail->prev->data)->position; pika_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, &start, &coords); PIKA_ANCHOR (stroke->anchors->tail->data)->position = coords; pika_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, end, &coords); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, &coords)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_ANCHOR, end)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, end)); } void pika_bezier_stroke_cubicto (PikaStroke *stroke, const PikaCoords *control1, const PikaCoords *control2, const PikaCoords *end) { g_return_if_fail (PIKA_IS_BEZIER_STROKE (stroke)); g_return_if_fail (stroke->closed == FALSE); g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE); PIKA_ANCHOR (stroke->anchors->tail->data)->position = *control1; g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, control2)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_ANCHOR, end)); g_queue_push_tail (stroke->anchors, pika_anchor_new (PIKA_ANCHOR_CONTROL, end)); } static gdouble arcto_circleparam (gdouble h, gdouble *y) { gdouble t0 = 0.5; gdouble dt = 0.25; gdouble pt0; gdouble y01, y12, y23, y012, y123, y0123; /* subdividing y[] */ while (dt >= 0.00001) { pt0 = ( y[0] * (1-t0) * (1-t0) * (1-t0) + 3 * y[1] * (1-t0) * (1-t0) * t0 + 3 * y[2] * (1-t0) * t0 * t0 + y[3] * t0 * t0 * t0 ); if (pt0 > h) t0 = t0 - dt; else if (pt0 < h) t0 = t0 + dt; else break; dt = dt/2; } y01 = y[0] * (1-t0) + y[1] * t0; y12 = y[1] * (1-t0) + y[2] * t0; y23 = y[2] * (1-t0) + y[3] * t0; y012 = y01 * (1-t0) + y12 * t0; y123 = y12 * (1-t0) + y23 * t0; y0123 = y012 * (1-t0) + y123 * t0; y[0] = y0123; y[1] = y123; y[2] = y23; /* y[3] unchanged */ return t0; } static void arcto_subdivide (gdouble t, gint part, PikaCoords *p) { PikaCoords p01, p12, p23, p012, p123, p0123; pika_coords_mix (1-t, &(p[0]), t, &(p[1]), &p01 ); pika_coords_mix (1-t, &(p[1]), t, &(p[2]), &p12 ); pika_coords_mix (1-t, &(p[2]), t, &(p[3]), &p23 ); pika_coords_mix (1-t, &p01 , t, &p12 , &p012 ); pika_coords_mix (1-t, &p12 , t, &p23 , &p123 ); pika_coords_mix (1-t, &p012 , t, &p123 , &p0123); if (part == 0) { /* p[0] unchanged */ p[1] = p01; p[2] = p012; p[3] = p0123; } else { p[0] = p0123; p[1] = p123; p[2] = p23; /* p[3] unchanged */ } } static void arcto_ellipsesegment (gdouble radius_x, gdouble radius_y, gdouble phi0, gdouble phi1, PikaCoords *ellips) { const PikaCoords template = PIKA_COORDS_DEFAULT_VALUES; const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0; gdouble phi_s, phi_e; gdouble y[4]; gdouble h0, h1; gdouble t0, t1; g_return_if_fail (ellips != NULL); y[0] = 0.0; y[1] = circlemagic; y[2] = 1.0; y[3] = 1.0; ellips[0] = template; ellips[1] = template; ellips[2] = template; ellips[3] = template; if (phi0 < phi1) { phi_s = floor (phi0 / G_PI_2) * G_PI_2; while (phi_s < 0) phi_s += 2 * G_PI; phi_e = phi_s + G_PI_2; } else { phi_e = floor (phi1 / G_PI_2) * G_PI_2; while (phi_e < 0) phi_e += 2 * G_PI; phi_s = phi_e + G_PI_2; } h0 = sin (fabs (phi0-phi_s)); h1 = sin (fabs (phi1-phi_s)); ellips[0].x = cos (phi_s); ellips[0].y = sin (phi_s); ellips[3].x = cos (phi_e); ellips[3].y = sin (phi_e); pika_coords_mix (1, &(ellips[0]), circlemagic, &(ellips[3]), &(ellips[1])); pika_coords_mix (circlemagic, &(ellips[0]), 1, &(ellips[3]), &(ellips[2])); if (h0 > y[0]) { t0 = arcto_circleparam (h0, y); /* also subdivides y[] at t0 */ arcto_subdivide (t0, 1, ellips); } if (h1 < y[3]) { t1 = arcto_circleparam (h1, y); arcto_subdivide (t1, 0, ellips); } ellips[0].x *= radius_x ; ellips[0].y *= radius_y; ellips[1].x *= radius_x ; ellips[1].y *= radius_y; ellips[2].x *= radius_x ; ellips[2].y *= radius_y; ellips[3].x *= radius_x ; ellips[3].y *= radius_y; } void pika_bezier_stroke_arcto (PikaStroke *bez_stroke, gdouble radius_x, gdouble radius_y, gdouble angle_rad, gboolean large_arc, gboolean sweep, const PikaCoords *end) { PikaCoords start; PikaCoords middle; /* between start and end */ PikaCoords trans_delta; PikaCoords trans_center; PikaCoords tmp_center; PikaCoords center; PikaCoords ellips[4]; /* control points of untransformed ellipse segment */ PikaCoords ctrl[4]; /* control points of next bezier segment */ PikaMatrix3 anglerot; gdouble lambda; gdouble phi0, phi1, phi2; gdouble tmpx, tmpy; g_return_if_fail (PIKA_IS_BEZIER_STROKE (bez_stroke)); g_return_if_fail (bez_stroke->closed == FALSE); g_return_if_fail (g_queue_get_length (bez_stroke->anchors) > 1); if (radius_x == 0 || radius_y == 0) { pika_bezier_stroke_lineto (bez_stroke, end); return; } start = PIKA_ANCHOR (bez_stroke->anchors->tail->prev->data)->position; pika_matrix3_identity (&anglerot); pika_matrix3_rotate (&anglerot, -angle_rad); pika_coords_mix (0.5, &start, -0.5, end, &trans_delta); pika_matrix3_transform_point (&anglerot, trans_delta.x, trans_delta.y, &tmpx, &tmpy); trans_delta.x = tmpx; trans_delta.y = tmpy; lambda = (SQR (trans_delta.x) / SQR (radius_x) + SQR (trans_delta.y) / SQR (radius_y)); if (lambda < 0.00001) { /* don't bother with it - endpoint is too close to startpoint */ return; } trans_center = trans_delta; if (lambda > 1.0) { /* The radii are too small for a matching ellipse. We expand them * so that they fit exactly (center of the ellipse between the * start- and endpoint */ radius_x *= sqrt (lambda); radius_y *= sqrt (lambda); trans_center.x = 0.0; trans_center.y = 0.0; } else { gdouble factor = sqrt ((1.0 - lambda) / lambda); trans_center.x = trans_delta.y * radius_x / radius_y * factor; trans_center.y = - trans_delta.x * radius_y / radius_x * factor; } if ((large_arc && sweep) || (!large_arc && !sweep)) { trans_center.x *= -1; trans_center.y *= -1; } pika_matrix3_identity (&anglerot); pika_matrix3_rotate (&anglerot, angle_rad); tmp_center = trans_center; pika_matrix3_transform_point (&anglerot, tmp_center.x, tmp_center.y, &tmpx, &tmpy); tmp_center.x = tmpx; tmp_center.y = tmpy; pika_coords_mix (0.5, &start, 0.5, end, &middle); pika_coords_add (&tmp_center, &middle, ¢er); phi1 = atan2 ((trans_delta.y - trans_center.y) / radius_y, (trans_delta.x - trans_center.x) / radius_x); phi2 = atan2 ((- trans_delta.y - trans_center.y) / radius_y, (- trans_delta.x - trans_center.x) / radius_x); if (phi1 < 0) phi1 += 2 * G_PI; if (phi2 < 0) phi2 += 2 * G_PI; if (sweep) { while (phi2 < phi1) phi2 += 2 * G_PI; phi0 = floor (phi1 / G_PI_2) * G_PI_2; while (phi0 < phi2) { arcto_ellipsesegment (radius_x, radius_y, MAX (phi0, phi1), MIN (phi0 + G_PI_2, phi2), ellips); pika_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y, &tmpx, &tmpy); ellips[0].x = tmpx; ellips[0].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y, &tmpx, &tmpy); ellips[1].x = tmpx; ellips[1].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y, &tmpx, &tmpy); ellips[2].x = tmpx; ellips[2].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y, &tmpx, &tmpy); ellips[3].x = tmpx; ellips[3].y = tmpy; pika_coords_add (¢er, &(ellips[1]), &(ctrl[1])); pika_coords_add (¢er, &(ellips[2]), &(ctrl[2])); pika_coords_add (¢er, &(ellips[3]), &(ctrl[3])); pika_bezier_stroke_cubicto (bez_stroke, &(ctrl[1]), &(ctrl[2]), &(ctrl[3])); phi0 += G_PI_2; } } else { while (phi1 < phi2) phi1 += 2 * G_PI; phi0 = ceil (phi1 / G_PI_2) * G_PI_2; while (phi0 > phi2) { arcto_ellipsesegment (radius_x, radius_y, MIN (phi0, phi1), MAX (phi0 - G_PI_2, phi2), ellips); pika_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y, &tmpx, &tmpy); ellips[0].x = tmpx; ellips[0].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y, &tmpx, &tmpy); ellips[1].x = tmpx; ellips[1].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y, &tmpx, &tmpy); ellips[2].x = tmpx; ellips[2].y = tmpy; pika_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y, &tmpx, &tmpy); ellips[3].x = tmpx; ellips[3].y = tmpy; pika_coords_add (¢er, &(ellips[1]), &(ctrl[1])); pika_coords_add (¢er, &(ellips[2]), &(ctrl[2])); pika_coords_add (¢er, &(ellips[3]), &(ctrl[3])); pika_bezier_stroke_cubicto (bez_stroke, &(ctrl[1]), &(ctrl[2]), &(ctrl[3])); phi0 -= G_PI_2; } } } PikaStroke * pika_bezier_stroke_new_ellipse (const PikaCoords *center, gdouble radius_x, gdouble radius_y, gdouble angle) { PikaStroke *stroke; PikaCoords p1 = *center; PikaCoords p2 = *center; PikaCoords p3 = *center; PikaCoords dx = { 0, }; PikaCoords dy = { 0, }; const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0; PikaAnchor *handle; dx.x = radius_x * cos (angle); dx.y = - radius_x * sin (angle); dy.x = radius_y * sin (angle); dy.y = radius_y * cos (angle); pika_coords_mix (1.0, center, 1.0, &dx, &p1); stroke = pika_bezier_stroke_new_moveto (&p1); handle = g_queue_peek_head (stroke->anchors); pika_coords_mix (1.0, &p1, -circlemagic, &dy, &handle->position); pika_coords_mix (1.0, &p1, circlemagic, &dy, &p1); pika_coords_mix (1.0, center, 1.0, &dy, &p3); pika_coords_mix (1.0, &p3, circlemagic, &dx, &p2); pika_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); pika_coords_mix (1.0, &p3, -circlemagic, &dx, &p1); pika_coords_mix (1.0, center, -1.0, &dx, &p3); pika_coords_mix (1.0, &p3, circlemagic, &dy, &p2); pika_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); pika_coords_mix (1.0, &p3, -circlemagic, &dy, &p1); pika_coords_mix (1.0, center, -1.0, &dy, &p3); pika_coords_mix (1.0, &p3, -circlemagic, &dx, &p2); pika_bezier_stroke_cubicto (stroke, &p1, &p2, &p3); handle = g_queue_peek_tail (stroke->anchors); pika_coords_mix (1.0, &p3, circlemagic, &dx, &handle->position); pika_stroke_close (stroke); return stroke; } /* helper function to get the associated anchor of a listitem */ static GList * pika_bezier_stroke_get_anchor_listitem (GList *list) { if (!list) return NULL; if (PIKA_ANCHOR (list->data)->type == PIKA_ANCHOR_ANCHOR) return list; if (list->prev && PIKA_ANCHOR (list->prev->data)->type == PIKA_ANCHOR_ANCHOR) return list->prev; if (list->next && PIKA_ANCHOR (list->next->data)->type == PIKA_ANCHOR_ANCHOR) return list->next; g_return_val_if_fail (/* bezier stroke inconsistent! */ FALSE, NULL); return NULL; }