Gimp/app/vectors/gimpbezierstroke.c
Michael Natterer 28e1a379e6 app: remove const qualifiers from all object parameters
They are unreliable because every type checking cast discards them,
they are useless anyway, visual clutter, added inconsistently, and
generally suck. Wanted to do this a long time ago, it was a bad idea
in the first place.
2016-05-19 23:54:14 +02:00

2103 lines
67 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* gimpbezierstroke.c
* Copyright (C) 2002 Simon Budig <simon@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <glib-object.h>
#include <cairo.h>
#include "libgimpmath/gimpmath.h"
#include "vectors-types.h"
#include "core/gimpbezierdesc.h"
#include "core/gimpcoords.h"
#include "core/gimpcoords-interpolate.h"
#include "gimpanchor.h"
#include "gimpbezierstroke.h"
/* local prototypes */
static gdouble
gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
const GimpCoords *coord,
gdouble precision,
GimpCoords *ret_point,
GimpAnchor **ret_segment_start,
GimpAnchor **ret_segment_end,
gdouble *ret_pos);
static gdouble
gimp_bezier_stroke_segment_nearest_point_get
(const GimpCoords *beziercoords,
const GimpCoords *coord,
gdouble precision,
GimpCoords *ret_point,
gdouble *ret_pos,
gint depth);
static gdouble
gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
const GimpCoords *coord1,
const GimpCoords *coord2,
gdouble precision,
GimpCoords *nearest,
GimpAnchor **ret_segment_start,
GimpAnchor **ret_segment_end,
gdouble *ret_pos);
static gdouble
gimp_bezier_stroke_segment_nearest_tangent_get
(const GimpCoords *beziercoords,
const GimpCoords *coord1,
const GimpCoords *coord2,
gdouble precision,
GimpCoords *ret_point,
gdouble *ret_pos);
static void
gimp_bezier_stroke_anchor_move_relative
(GimpStroke *stroke,
GimpAnchor *anchor,
const GimpCoords *deltacoord,
GimpAnchorFeatureType feature);
static void
gimp_bezier_stroke_anchor_move_absolute
(GimpStroke *stroke,
GimpAnchor *anchor,
const GimpCoords *coord,
GimpAnchorFeatureType feature);
static void
gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
GimpAnchor *anchor,
GimpAnchorFeatureType feature);
static void
gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
GimpAnchor *anchor);
static gboolean
gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position);
static void
gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position,
const GimpCoords *deltacoord,
GimpAnchorFeatureType feature);
static void
gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position,
const GimpCoords *coord,
GimpAnchorFeatureType feature);
static void gimp_bezier_stroke_close (GimpStroke *stroke);
static GimpStroke *
gimp_bezier_stroke_open (GimpStroke *stroke,
GimpAnchor *end_anchor);
static gboolean
gimp_bezier_stroke_anchor_is_insertable
(GimpStroke *stroke,
GimpAnchor *predec,
gdouble position);
static GimpAnchor *
gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position);
static gboolean
gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
GimpAnchor *neighbor);
static gboolean
gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
GimpAnchor *anchor,
GimpStroke *extension,
GimpAnchor *neighbor);
static GArray *
gimp_bezier_stroke_interpolate (GimpStroke *stroke,
gdouble precision,
gboolean *closed);
static GimpBezierDesc *
gimp_bezier_stroke_make_bezier (GimpStroke *stroke);
static void gimp_bezier_stroke_finalize (GObject *object);
static GList * gimp_bezier_stroke_get_anchor_listitem
(GList *list);
G_DEFINE_TYPE (GimpBezierStroke, gimp_bezier_stroke, GIMP_TYPE_STROKE)
#define parent_class gimp_bezier_stroke_parent_class
static void
gimp_bezier_stroke_class_init (GimpBezierStrokeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpStrokeClass *stroke_class = GIMP_STROKE_CLASS (klass);
object_class->finalize = gimp_bezier_stroke_finalize;
stroke_class->nearest_point_get = gimp_bezier_stroke_nearest_point_get;
stroke_class->nearest_tangent_get = gimp_bezier_stroke_nearest_tangent_get;
stroke_class->nearest_intersection_get = NULL;
stroke_class->anchor_move_relative = gimp_bezier_stroke_anchor_move_relative;
stroke_class->anchor_move_absolute = gimp_bezier_stroke_anchor_move_absolute;
stroke_class->anchor_convert = gimp_bezier_stroke_anchor_convert;
stroke_class->anchor_delete = gimp_bezier_stroke_anchor_delete;
stroke_class->point_is_movable = gimp_bezier_stroke_point_is_movable;
stroke_class->point_move_relative = gimp_bezier_stroke_point_move_relative;
stroke_class->point_move_absolute = gimp_bezier_stroke_point_move_absolute;
stroke_class->close = gimp_bezier_stroke_close;
stroke_class->open = gimp_bezier_stroke_open;
stroke_class->anchor_is_insertable = gimp_bezier_stroke_anchor_is_insertable;
stroke_class->anchor_insert = gimp_bezier_stroke_anchor_insert;
stroke_class->is_extendable = gimp_bezier_stroke_is_extendable;
stroke_class->extend = gimp_bezier_stroke_extend;
stroke_class->connect_stroke = gimp_bezier_stroke_connect_stroke;
stroke_class->interpolate = gimp_bezier_stroke_interpolate;
stroke_class->make_bezier = gimp_bezier_stroke_make_bezier;
}
static void
gimp_bezier_stroke_init (GimpBezierStroke *stroke)
{
}
static void
gimp_bezier_stroke_finalize (GObject *object)
{
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/* Bezier specific functions */
GimpStroke *
gimp_bezier_stroke_new (void)
{
return g_object_new (GIMP_TYPE_BEZIER_STROKE, NULL);
}
GimpStroke *
gimp_bezier_stroke_new_from_coords (const GimpCoords *coords,
gint n_coords,
gboolean closed)
{
GimpStroke *stroke;
GimpAnchor *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 = gimp_bezier_stroke_new ();
last_anchor = NULL;
for (count = 0; count < n_coords; count++)
last_anchor = gimp_bezier_stroke_extend (stroke,
&coords[count],
last_anchor,
EXTEND_SIMPLE);
if (closed)
gimp_stroke_close (stroke);
return stroke;
}
static void
gimp_bezier_stroke_anchor_delete (GimpStroke *stroke,
GimpAnchor *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);
gimp_anchor_free (list->data);
g_queue_delete_link (stroke->anchors, list);
list = list2;
}
}
static GimpStroke *
gimp_bezier_stroke_open (GimpStroke *stroke,
GimpAnchor *end_anchor)
{
GList *list;
GList *list2;
GimpStroke *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 = gimp_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
gimp_bezier_stroke_anchor_is_insertable (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position)
{
return (g_queue_find (stroke->anchors, predec) != NULL);
}
static GimpAnchor *
gimp_bezier_stroke_anchor_insert (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position)
{
GList *segment_start;
GList *list;
GList *list2;
GimpCoords subdivided[8];
GimpCoords 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] = GIMP_ANCHOR (list->data)->position;
list = g_list_next (list);
if (! list)
list = stroke->anchors->head;
}
subdivided[0] = beziercoords[0];
subdivided[6] = beziercoords[3];
gimp_coords_mix (1-position, &(beziercoords[0]),
position, &(beziercoords[1]),
&(subdivided[1]));
gimp_coords_mix (1-position, &(beziercoords[1]),
position, &(beziercoords[2]),
&(subdivided[7]));
gimp_coords_mix (1-position, &(beziercoords[2]),
position, &(beziercoords[3]),
&(subdivided[5]));
gimp_coords_mix (1-position, &(subdivided[1]),
position, &(subdivided[7]),
&(subdivided[2]));
gimp_coords_mix (1-position, &(subdivided[7]),
position, &(subdivided[5]),
&(subdivided[4]));
gimp_coords_mix (1-position, &(subdivided[2]),
position, &(subdivided[4]),
&(subdivided[3]));
/* subdivided 0-6 contains the bezier segement subdivided at <position> */
list = segment_start;
for (i = 0; i <= 6; i++)
{
if (i >= 2 && i <= 4)
{
list2 = g_list_append (NULL,
gimp_anchor_new ((i == 3 ?
GIMP_ANCHOR_ANCHOR:
GIMP_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
{
GIMP_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 GIMP_ANCHOR (segment_start->data);
}
static gboolean
gimp_bezier_stroke_point_is_movable (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position)
{
return (g_queue_find (stroke->anchors, predec) != NULL);
}
static void
gimp_bezier_stroke_point_move_relative (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position,
const GimpCoords *deltacoord,
GimpAnchorFeatureType feature)
{
GimpCoords 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/gimpvectortool.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;
gimp_coords_scale ((1-feel_good)/(3*position*
(1-position)*(1-position)),
deltacoord,
&(offsetcoords[0]));
gimp_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++)
{
gimp_stroke_anchor_move_relative (stroke, GIMP_ANCHOR (list->data),
&(offsetcoords[i]), feature);
list = g_list_next (list);
if (! list)
list = stroke->anchors->head;
}
}
static void
gimp_bezier_stroke_point_move_absolute (GimpStroke *stroke,
GimpAnchor *predec,
gdouble position,
const GimpCoords *coord,
GimpAnchorFeatureType feature)
{
GimpCoords deltacoord;
GimpCoords tmp1, tmp2, abs_pos;
GimpCoords 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] = GIMP_ANCHOR (list->data)->position;
list = g_list_next (list);
if (! list)
list = stroke->anchors->head;
}
gimp_coords_mix ((1-position)*(1-position)*(1-position), &(beziercoords[0]),
3*(1-position)*(1-position)*position, &(beziercoords[1]),
&tmp1);
gimp_coords_mix (3*(1-position)*position*position, &(beziercoords[2]),
position*position*position, &(beziercoords[3]),
&tmp2);
gimp_coords_add (&tmp1, &tmp2, &abs_pos);
gimp_coords_difference (coord, &abs_pos, &deltacoord);
gimp_bezier_stroke_point_move_relative (stroke, predec, position,
&deltacoord, feature);
}
static void
gimp_bezier_stroke_close (GimpStroke *stroke)
{
GList *start;
GList *end;
GimpAnchor *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 (gimp_coords_equal (&(GIMP_ANCHOR (start->next->data)->position),
&(GIMP_ANCHOR (start->data)->position)) &&
gimp_coords_equal (&(GIMP_ANCHOR (start->data)->position),
&(GIMP_ANCHOR (end->data)->position)) &&
gimp_coords_equal (&(GIMP_ANCHOR (end->data)->position),
&(GIMP_ANCHOR (end->prev->data)->position)))
{
/* redundant segment */
gimp_anchor_free (stroke->anchors->tail->data);
g_queue_delete_link (stroke->anchors, stroke->anchors->tail);
gimp_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);
gimp_anchor_free (stroke->anchors->head->data);
stroke->anchors->head->data = anchor;
}
}
GIMP_STROKE_CLASS (parent_class)->close (stroke);
}
static gdouble
gimp_bezier_stroke_nearest_point_get (GimpStroke *stroke,
const GimpCoords *coord,
gdouble precision,
GimpCoords *ret_point,
GimpAnchor **ret_segment_start,
GimpAnchor **ret_segment_end,
gdouble *ret_pos)
{
gdouble min_dist, dist, pos;
GimpCoords point = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
GimpCoords segmentcoords[4];
GList *anchorlist;
GimpAnchor *segment_start;
GimpAnchor *segment_end = NULL;
GimpAnchor *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;
GIMP_ANCHOR (anchorlist->data)->type != GIMP_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 = gimp_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] = GIMP_ANCHOR (anchorlist->data)->position;
count++;
}
anchorlist = g_list_next (anchorlist);
if (anchorlist)
{
segment_end = GIMP_ANCHOR (anchorlist->data);
segmentcoords[3] = segment_end->position;
}
dist = gimp_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
gimp_bezier_stroke_segment_nearest_point_get (const GimpCoords *beziercoords,
const GimpCoords *coord,
gdouble precision,
GimpCoords *ret_point,
gdouble *ret_pos,
gint depth)
{
/*
* beziercoords has to contain four GimpCoords with the four control points
* of the bezier segment. We subdivide it at the parameter 0.5.
*/
GimpCoords subdivided[8];
gdouble dist1, dist2;
GimpCoords point1, point2;
gdouble pos1, pos2;
gimp_coords_difference (&beziercoords[1], &beziercoords[0], &point1);
gimp_coords_difference (&beziercoords[3], &beziercoords[2], &point2);
if (!depth || (gimp_coords_bezier_is_straight (beziercoords[0],
beziercoords[1],
beziercoords[2],
beziercoords[3],
precision)
&& gimp_coords_length_squared (&point1) < precision
&& gimp_coords_length_squared (&point2) < precision))
{
GimpCoords line, dcoord;
gdouble length2, scalar;
gint i;
gimp_coords_difference (&(beziercoords[3]),
&(beziercoords[0]),
&line);
gimp_coords_difference (coord,
&(beziercoords[0]),
&dcoord);
length2 = gimp_coords_scalarprod (&line, &line);
scalar = gimp_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;
gimp_coords_mix (1.0, &(beziercoords[0]),
scalar, &line,
ret_point);
gimp_coords_difference (coord, ret_point, &dcoord);
return gimp_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"); */
gimp_coords_average (&(beziercoords[0]), &(beziercoords[1]),
&(subdivided[1]));
gimp_coords_average (&(beziercoords[1]), &(beziercoords[2]),
&(subdivided[7]));
gimp_coords_average (&(beziercoords[2]), &(beziercoords[3]),
&(subdivided[5]));
gimp_coords_average (&(subdivided[1]), &(subdivided[7]),
&(subdivided[2]));
gimp_coords_average (&(subdivided[7]), &(subdivided[5]),
&(subdivided[4]));
gimp_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 = gimp_bezier_stroke_segment_nearest_point_get (&(subdivided[0]),
coord, precision,
&point1, &pos1,
depth - 1);
dist2 = gimp_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
gimp_bezier_stroke_nearest_tangent_get (GimpStroke *stroke,
const GimpCoords *coord1,
const GimpCoords *coord2,
gdouble precision,
GimpCoords *nearest,
GimpAnchor **ret_segment_start,
GimpAnchor **ret_segment_end,
gdouble *ret_pos)
{
gdouble min_dist, dist, pos;
GimpCoords point;
GimpCoords segmentcoords[4];
GList *anchorlist;
GimpAnchor *segment_start;
GimpAnchor *segment_end = NULL;
GimpAnchor *anchor;
gint count;
if (g_queue_is_empty (stroke->anchors))
return -1.0;
count = 0;
min_dist = -1;
for (anchorlist = stroke->anchors->head;
GIMP_ANCHOR (anchorlist->data)->type != GIMP_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 = gimp_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] = GIMP_ANCHOR (anchorlist->data)->position;
count++;
}
anchorlist = g_list_next (anchorlist);
if (anchorlist)
{
segment_end = GIMP_ANCHOR (anchorlist->data);
segmentcoords[3] = segment_end->position;
}
dist = gimp_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
gimp_bezier_stroke_segment_nearest_tangent_get (const GimpCoords *beziercoords,
const GimpCoords *coord1,
const GimpCoords *coord2,
gdouble precision,
GimpCoords *ret_point,
gdouble *ret_pos)
{
GArray *ret_coords;
GArray *ret_params;
GimpCoords dir, line, dcoord, min_point;
gdouble min_dist = -1;
gdouble dist, length2, scalar, ori, ori2;
gint i;
gimp_coords_difference (coord2, coord1, &line);
ret_coords = g_array_new (FALSE, FALSE, sizeof (GimpCoords));
ret_params = g_array_new (FALSE, FALSE, sizeof (gdouble));
g_printerr ("(%.2f, %.2f)-(%.2f,%.2f): ", coord1->x, coord1->y,
coord2->x, coord2->y);
gimp_coords_interpolate_bezier (beziercoords[0],
beziercoords[1],
beziercoords[2],
beziercoords[3],
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;
gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, 1),
&g_array_index (ret_coords, GimpCoords, 0),
&dir);
ori = dir.x * line.y - dir.y * line.x;
for (i = 2; i < ret_coords->len; i++)
{
gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
&g_array_index (ret_coords, GimpCoords, i-1),
&dir);
ori2 = dir.x * line.y - dir.y * line.x;
if (ori * ori2 <= 0)
{
gimp_coords_difference (&g_array_index (ret_coords, GimpCoords, i),
coord1,
&dcoord);
length2 = gimp_coords_scalarprod (&line, &line);
scalar = gimp_coords_scalarprod (&line, &dcoord) / length2;
if (scalar >= 0 && scalar <= 1)
{
gimp_coords_mix (1.0, coord1,
scalar, &line,
&min_point);
gimp_coords_difference (&min_point,
&g_array_index (ret_coords, GimpCoords, i),
&dcoord);
dist = gimp_coords_length (&dcoord);
if (dist < min_dist || min_dist < 0)
{
min_dist = dist;
*ret_point = g_array_index (ret_coords, GimpCoords, 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
gimp_bezier_stroke_is_extendable (GimpStroke *stroke,
GimpAnchor *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 == GIMP_ANCHOR_CONTROL)
{
if (listneighbor->prev &&
GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
{
listneighbor = listneighbor->prev;
}
else if (listneighbor->next &&
GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_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);
}
GimpAnchor *
gimp_bezier_stroke_extend (GimpStroke *stroke,
const GimpCoords *coords,
GimpAnchor *neighbor,
GimpVectorExtendMode extend_mode)
{
GimpAnchor *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 = gimp_anchor_new (GIMP_ANCHOR_CONTROL, coords);
g_queue_push_tail (stroke->anchors, anchor);
switch (extend_mode)
{
case EXTEND_SIMPLE:
break;
case EXTEND_EDITABLE:
anchor = gimp_bezier_stroke_extend (stroke,
coords, anchor,
EXTEND_SIMPLE);
/* we return the GIMP_ANCHOR_ANCHOR */
gimp_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 == GIMP_ANCHOR_CONTROL)
{
if (listneighbor->prev &&
GIMP_ANCHOR (listneighbor->prev->data)->type == GIMP_ANCHOR_ANCHOR)
{
listneighbor = listneighbor->prev;
}
else if (listneighbor->next &&
GIMP_ANCHOR (listneighbor->next->data)->type == GIMP_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)
{
GimpAnchorType type;
/* We have to detect the type of the point to add... */
control_count = 0;
if (loose_end == 1)
{
while (listneighbor &&
GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
{
control_count++;
listneighbor = listneighbor->prev;
}
}
else
{
while (listneighbor &&
GIMP_ANCHOR (listneighbor->data)->type == GIMP_ANCHOR_CONTROL)
{
control_count++;
listneighbor = listneighbor->next;
}
}
switch (extend_mode)
{
case EXTEND_SIMPLE:
switch (control_count)
{
case 0:
type = GIMP_ANCHOR_CONTROL;
break;
case 1:
if (listneighbor) /* only one handle in the path? */
type = GIMP_ANCHOR_CONTROL;
else
type = GIMP_ANCHOR_ANCHOR;
break;
case 2:
type = GIMP_ANCHOR_ANCHOR;
break;
default:
g_warning ("inconsistent bezier curve: "
"%d successive control handles", control_count);
type = GIMP_ANCHOR_ANCHOR;
}
anchor = gimp_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 = gimp_bezier_stroke_extend (stroke,
&(neighbor->position),
neighbor,
EXTEND_SIMPLE);
case 1:
neighbor = gimp_bezier_stroke_extend (stroke,
coords,
neighbor,
EXTEND_SIMPLE);
case 2:
anchor = gimp_bezier_stroke_extend (stroke,
coords,
neighbor,
EXTEND_SIMPLE);
neighbor = gimp_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
gimp_bezier_stroke_connect_stroke (GimpStroke *stroke,
GimpAnchor *anchor,
GimpStroke *extension,
GimpAnchor *neighbor)
{
GList *list1;
GList *list2;
list1 = g_queue_find (stroke->anchors, anchor);
list1 = gimp_bezier_stroke_get_anchor_listitem (list1);
list2 = g_queue_find (extension->anchors, neighbor);
list2 = gimp_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);
gimp_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 void
gimp_bezier_stroke_anchor_move_relative (GimpStroke *stroke,
GimpAnchor *anchor,
const GimpCoords *deltacoord,
GimpAnchorFeatureType feature)
{
GimpCoords delta, coord1, coord2;
GList *anchor_list;
delta = *deltacoord;
delta.pressure = 0;
delta.xtilt = 0;
delta.ytilt = 0;
delta.wheel = 0;
gimp_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 == GIMP_ANCHOR_ANCHOR)
{
if (g_list_previous (anchor_list))
{
coord2 = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
gimp_coords_add (&coord2, &delta, &coord1);
GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position = coord1;
}
if (g_list_next (anchor_list))
{
coord2 = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
gimp_coords_add (&coord2, &delta, &coord1);
GIMP_ANCHOR (g_list_next (anchor_list)->data)->position = coord1;
}
}
else
{
if (feature == GIMP_ANCHOR_FEATURE_SYMMETRIC)
{
GList *neighbour = NULL, *opposite = NULL;
/* search for opposite control point. Sigh. */
neighbour = g_list_previous (anchor_list);
if (neighbour &&
GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
{
opposite = g_list_previous (neighbour);
}
else
{
neighbour = g_list_next (anchor_list);
if (neighbour &&
GIMP_ANCHOR (neighbour->data)->type == GIMP_ANCHOR_ANCHOR)
{
opposite = g_list_next (neighbour);
}
}
if (opposite &&
GIMP_ANCHOR (opposite->data)->type == GIMP_ANCHOR_CONTROL)
{
gimp_coords_difference (&(GIMP_ANCHOR (neighbour->data)->position),
&(anchor->position), &delta);
gimp_coords_add (&(GIMP_ANCHOR (neighbour->data)->position),
&delta, &coord1);
GIMP_ANCHOR (opposite->data)->position = coord1;
}
}
}
}
static void
gimp_bezier_stroke_anchor_move_absolute (GimpStroke *stroke,
GimpAnchor *anchor,
const GimpCoords *coord,
GimpAnchorFeatureType feature)
{
GimpCoords deltacoord;
gimp_coords_difference (coord, &anchor->position, &deltacoord);
gimp_bezier_stroke_anchor_move_relative (stroke, anchor,
&deltacoord, feature);
}
static void
gimp_bezier_stroke_anchor_convert (GimpStroke *stroke,
GimpAnchor *anchor,
GimpAnchorFeatureType feature)
{
GList *anchor_list;
anchor_list = g_queue_find (stroke->anchors, anchor);
g_return_if_fail (anchor_list != NULL);
switch (feature)
{
case GIMP_ANCHOR_FEATURE_EDGE:
if (anchor->type == GIMP_ANCHOR_ANCHOR)
{
if (g_list_previous (anchor_list))
GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position =
anchor->position;
if (g_list_next (anchor_list))
GIMP_ANCHOR (g_list_next (anchor_list)->data)->position =
anchor->position;
}
else
{
if (g_list_previous (anchor_list) &&
GIMP_ANCHOR (g_list_previous (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
anchor->position = GIMP_ANCHOR (g_list_previous (anchor_list)->data)->position;
if (g_list_next (anchor_list) &&
GIMP_ANCHOR (g_list_next (anchor_list)->data)->type == GIMP_ANCHOR_ANCHOR)
anchor->position = GIMP_ANCHOR (g_list_next (anchor_list)->data)->position;
}
break;
default:
g_warning ("gimp_bezier_stroke_anchor_convert: "
"unimplemented anchor conversion %d\n", feature);
}
}
static GimpBezierDesc *
gimp_bezier_stroke_make_bezier (GimpStroke *stroke)
{
GArray *points;
GArray *cmd_array;
GimpBezierDesc *bezdesc;
cairo_path_data_t pathdata;
gint num_cmds, i;
points = gimp_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, GimpAnchor, 1).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, 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, GimpAnchor, i).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
g_array_append_val (cmd_array, pathdata);
pathdata.point.x = g_array_index (points, GimpAnchor, i+1).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, i+1).position.y;
g_array_append_val (cmd_array, pathdata);
pathdata.point.x = g_array_index (points, GimpAnchor, i+2).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, 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, GimpAnchor, i).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, i).position.y;
g_array_append_val (cmd_array, pathdata);
pathdata.point.x = g_array_index (points, GimpAnchor, 0).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, 0).position.y;
g_array_append_val (cmd_array, pathdata);
pathdata.point.x = g_array_index (points, GimpAnchor, 1).position.x;
pathdata.point.y = g_array_index (points, GimpAnchor, 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 = gimp_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 *
gimp_bezier_stroke_interpolate (GimpStroke *stroke,
gdouble precision,
gboolean *ret_closed)
{
GArray *ret_coords;
GimpAnchor *anchor;
GList *anchorlist;
GimpCoords 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 (GimpCoords));
count = 0;
for (anchorlist = stroke->anchors->head;
anchorlist && GIMP_ANCHOR (anchorlist->data)->type != GIMP_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)
{
gimp_coords_interpolate_bezier (segmentcoords[0],
segmentcoords[1],
segmentcoords[2],
segmentcoords[3],
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] = GIMP_ANCHOR (anchorlist->data)->position;
count++;
}
anchorlist = g_list_next (anchorlist);
if (anchorlist)
segmentcoords[3] = GIMP_ANCHOR (anchorlist->data)->position;
gimp_coords_interpolate_bezier (segmentcoords[0],
segmentcoords[1],
segmentcoords[2],
segmentcoords[3],
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;
}
GimpStroke *
gimp_bezier_stroke_new_moveto (const GimpCoords *start)
{
GimpStroke *stroke = gimp_bezier_stroke_new ();
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
start));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
start));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
start));
return stroke;
}
void
gimp_bezier_stroke_lineto (GimpStroke *stroke,
const GimpCoords *end)
{
g_return_if_fail (GIMP_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,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
end));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
end));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
end));
}
void
gimp_bezier_stroke_conicto (GimpStroke *stroke,
const GimpCoords *control,
const GimpCoords *end)
{
GimpCoords start, coords;
g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
g_return_if_fail (stroke->closed == FALSE);
g_return_if_fail (g_queue_get_length (stroke->anchors) > 1);
start = GIMP_ANCHOR (stroke->anchors->tail->prev->data)->position;
gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, &start, &coords);
GIMP_ANCHOR (stroke->anchors->tail->data)->position = coords;
gimp_coords_mix (2.0 / 3.0, control, 1.0 / 3.0, end, &coords);
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
&coords));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
end));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
end));
}
void
gimp_bezier_stroke_cubicto (GimpStroke *stroke,
const GimpCoords *control1,
const GimpCoords *control2,
const GimpCoords *end)
{
g_return_if_fail (GIMP_IS_BEZIER_STROKE (stroke));
g_return_if_fail (stroke->closed == FALSE);
g_return_if_fail (g_queue_is_empty (stroke->anchors) == FALSE);
GIMP_ANCHOR (stroke->anchors->tail->data)->position = *control1;
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
control2));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_ANCHOR_ANCHOR,
end));
g_queue_push_tail (stroke->anchors,
gimp_anchor_new (GIMP_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,
GimpCoords *p)
{
GimpCoords p01, p12, p23, p012, p123, p0123;
gimp_coords_mix (1-t, &(p[0]), t, &(p[1]), &p01 );
gimp_coords_mix (1-t, &(p[1]), t, &(p[2]), &p12 );
gimp_coords_mix (1-t, &(p[2]), t, &(p[3]), &p23 );
gimp_coords_mix (1-t, &p01 , t, &p12 , &p012 );
gimp_coords_mix (1-t, &p12 , t, &p23 , &p123 );
gimp_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,
GimpCoords *ellips)
{
const GimpCoords template = GIMP_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);
gimp_coords_mix (1, &(ellips[0]), circlemagic, &(ellips[3]), &(ellips[1]));
gimp_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
gimp_bezier_stroke_arcto (GimpStroke *bez_stroke,
gdouble radius_x,
gdouble radius_y,
gdouble angle_rad,
gboolean large_arc,
gboolean sweep,
const GimpCoords *end)
{
GimpCoords start;
GimpCoords middle; /* between start and end */
GimpCoords trans_delta;
GimpCoords trans_center;
GimpCoords tmp_center;
GimpCoords center;
GimpCoords ellips[4]; /* control points of untransformed ellipse segment */
GimpCoords ctrl[4]; /* control points of next bezier segment */
GimpMatrix3 anglerot;
gdouble lambda;
gdouble phi0, phi1, phi2;
gdouble tmpx, tmpy;
g_return_if_fail (GIMP_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)
{
gimp_bezier_stroke_lineto (bez_stroke, end);
return;
}
start = GIMP_ANCHOR (bez_stroke->anchors->tail->prev->data)->position;
gimp_matrix3_identity (&anglerot);
gimp_matrix3_rotate (&anglerot, -angle_rad);
gimp_coords_mix (0.5, &start, -0.5, end, &trans_delta);
gimp_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)
{
/* dont 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;
}
gimp_matrix3_identity (&anglerot);
gimp_matrix3_rotate (&anglerot, angle_rad);
tmp_center = trans_center;
gimp_matrix3_transform_point (&anglerot,
tmp_center.x, tmp_center.y,
&tmpx, &tmpy);
tmp_center.x = tmpx;
tmp_center.y = tmpy;
gimp_coords_mix (0.5, &start, 0.5, end, &middle);
gimp_coords_add (&tmp_center, &middle, &center);
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);
gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
&tmpx, &tmpy);
ellips[0].x = tmpx; ellips[0].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
&tmpx, &tmpy);
ellips[1].x = tmpx; ellips[1].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
&tmpx, &tmpy);
ellips[2].x = tmpx; ellips[2].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
&tmpx, &tmpy);
ellips[3].x = tmpx; ellips[3].y = tmpy;
gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
gimp_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);
gimp_matrix3_transform_point (&anglerot, ellips[0].x, ellips[0].y,
&tmpx, &tmpy);
ellips[0].x = tmpx; ellips[0].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[1].x, ellips[1].y,
&tmpx, &tmpy);
ellips[1].x = tmpx; ellips[1].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[2].x, ellips[2].y,
&tmpx, &tmpy);
ellips[2].x = tmpx; ellips[2].y = tmpy;
gimp_matrix3_transform_point (&anglerot, ellips[3].x, ellips[3].y,
&tmpx, &tmpy);
ellips[3].x = tmpx; ellips[3].y = tmpy;
gimp_coords_add (&center, &(ellips[1]), &(ctrl[1]));
gimp_coords_add (&center, &(ellips[2]), &(ctrl[2]));
gimp_coords_add (&center, &(ellips[3]), &(ctrl[3]));
gimp_bezier_stroke_cubicto (bez_stroke,
&(ctrl[1]), &(ctrl[2]), &(ctrl[3]));
phi0 -= G_PI_2;
}
}
}
GimpStroke *
gimp_bezier_stroke_new_ellipse (const GimpCoords *center,
gdouble radius_x,
gdouble radius_y,
gdouble angle)
{
GimpStroke *stroke;
GimpCoords p1 = *center;
GimpCoords p2 = *center;
GimpCoords p3 = *center;
GimpCoords dx = { 0, };
GimpCoords dy = { 0, };
const gdouble circlemagic = 4.0 * (G_SQRT2 - 1.0) / 3.0;
GimpAnchor *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);
gimp_coords_mix (1.0, center, 1.0, &dx, &p1);
stroke = gimp_bezier_stroke_new_moveto (&p1);
handle = g_queue_peek_tail (stroke->anchors);
gimp_coords_mix (1.0, &p1, -circlemagic, &dy, &handle->position);
gimp_coords_mix (1.0, &p1, circlemagic, &dy, &p1);
gimp_coords_mix (1.0, center, 1.0, &dy, &p3);
gimp_coords_mix (1.0, &p3, circlemagic, &dx, &p2);
gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p1);
gimp_coords_mix (1.0, center, -1.0, &dx, &p3);
gimp_coords_mix (1.0, &p3, circlemagic, &dy, &p2);
gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
gimp_coords_mix (1.0, &p3, -circlemagic, &dy, &p1);
gimp_coords_mix (1.0, center, -1.0, &dy, &p3);
gimp_coords_mix (1.0, &p3, -circlemagic, &dx, &p2);
gimp_bezier_stroke_cubicto (stroke, &p1, &p2, &p3);
handle = g_queue_peek_tail (stroke->anchors);
gimp_coords_mix (1.0, &p3, circlemagic, &dx, &handle->position);
gimp_stroke_close (stroke);
return stroke;
}
/* helper function to get the associated anchor of a listitem */
static GList *
gimp_bezier_stroke_get_anchor_listitem (GList *list)
{
if (!list)
return NULL;
if (GIMP_ANCHOR (list->data)->type == GIMP_ANCHOR_ANCHOR)
return list;
if (list->prev && GIMP_ANCHOR (list->prev->data)->type == GIMP_ANCHOR_ANCHOR)
return list->prev;
if (list->next && GIMP_ANCHOR (list->next->data)->type == GIMP_ANCHOR_ANCHOR)
return list->next;
g_return_val_if_fail (/* bezier stroke inconsistent! */ FALSE, NULL);
return NULL;
}