Resolves #13096 In5a43a464, some debug output was added to help diagnosis a problem when Snap to Active Path was turned on. The issue seemed mostly resolved ine57ad0f8, but the print statements were left in. This leads to slowdown when moving layers while Snap is enabled (due to the continuous print statements). As it's been 19 years since the original commit, it should be fine to remove these debug statements to boost performance.
2433 lines
78 KiB
C
2433 lines
78 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <glib-object.h>
|
|
#include <cairo.h>
|
|
|
|
#include "libgimpmath/gimpmath.h"
|
|
|
|
#include "vectors-types.h"
|
|
|
|
#include "core/gimp-transform-utils.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 gboolean
|
|
gimp_bezier_stroke_reverse (GimpStroke *stroke);
|
|
static gboolean
|
|
gimp_bezier_stroke_shift_start (GimpStroke *stroke,
|
|
GimpAnchor *anchor);
|
|
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_transform (GimpStroke *stroke,
|
|
const GimpMatrix3 *matrix,
|
|
GQueue *ret_strokes);
|
|
|
|
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->reverse = gimp_bezier_stroke_reverse;
|
|
stroke_class->shift_start = gimp_bezier_stroke_shift_start;
|
|
stroke_class->interpolate = gimp_bezier_stroke_interpolate;
|
|
stroke_class->make_bezier = gimp_bezier_stroke_make_bezier;
|
|
stroke_class->transform = gimp_bezier_stroke_transform;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* Returns the EndPoint (ANCHOR) that a given Handle (CONTROL) controls.
|
|
*
|
|
* GimpAnchors are in a list.
|
|
* GimpAnchors of type ANCHOR are surrounded by two GimpAnchors
|
|
* of type CONTROL (Handle) in the pattern HEH HEH....
|
|
* E denotes a GimpAnchor of type ANCHOR, aka EndPoint.
|
|
* H denotes a GimpAnchor of type CONTROL, aka Handle.
|
|
*/
|
|
static GimpAnchor*
|
|
gimp_bezier_stroke_find_endpoint_for_handle (GimpStroke *stroke,
|
|
GimpAnchor *anchor)
|
|
{
|
|
GList *anchor_element;
|
|
GList *prior_anchor_element;
|
|
GList *result_element;
|
|
|
|
/* Require given anchor is CONTROL i.e. Handle. */
|
|
g_return_val_if_fail (anchor->type == GIMP_ANCHOR_CONTROL, NULL);
|
|
|
|
anchor_element = g_queue_find (stroke->anchors, anchor);
|
|
prior_anchor_element = g_list_previous (anchor_element);
|
|
|
|
if (prior_anchor_element == NULL)
|
|
{
|
|
/* No prior element. Given handle is very first GimpAnchor in the list: thisH E...
|
|
* The desired ANCHOR endpoint trails it.
|
|
*/
|
|
result_element = g_list_next (anchor_element);
|
|
}
|
|
else if (((GimpAnchor *)prior_anchor_element->data)->type == GIMP_ANCHOR_ANCHOR)
|
|
{
|
|
/* Prior element is-a ANCHOR i.e. EndPoint.
|
|
* Given handle is the trailing handle for the desired endpoint: ...E thisH ...
|
|
*/
|
|
result_element = prior_anchor_element;
|
|
}
|
|
else
|
|
{
|
|
/* Prior element is another CONTROL i.e. handle.
|
|
* Given handle is leading the desired endpoint ...H thisH E...
|
|
*/
|
|
result_element = g_list_next (anchor_element);
|
|
}
|
|
|
|
/* Ensure the list of anchors is not corrupt by missing next anchors. */
|
|
g_return_val_if_fail (result_element != NULL, NULL);
|
|
/* Ensure result_element type is ANCHOR i.e. EndPoint. */
|
|
g_return_val_if_fail (((GimpAnchor*)result_element->data)->type == GIMP_ANCHOR_ANCHOR, NULL);
|
|
|
|
return result_element->data;
|
|
}
|
|
|
|
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 segment 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, 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));
|
|
|
|
gimp_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;
|
|
|
|
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;
|
|
}
|
|
|
|
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 gboolean
|
|
gimp_bezier_stroke_reverse (GimpStroke *stroke)
|
|
{
|
|
g_return_val_if_fail (GIMP_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
|
|
gimp_bezier_stroke_shift_start (GimpStroke *stroke,
|
|
GimpAnchor *new_start)
|
|
{
|
|
GList *link;
|
|
GimpAnchor *real_new_start = NULL;
|
|
|
|
g_return_val_if_fail (GIMP_IS_BEZIER_STROKE (stroke), FALSE);
|
|
g_return_val_if_fail (new_start != NULL, FALSE);
|
|
|
|
/* Action "shift-start" is enabled for either Handle (CONTROL) or EndPoint (ANCHOR)
|
|
* but operates only on an EndPoint.
|
|
* When user chose a Handle, get the Handle's EndPoint to operate on.
|
|
*/
|
|
if (new_start->type == GIMP_ANCHOR_CONTROL)
|
|
{
|
|
real_new_start = gimp_bezier_stroke_find_endpoint_for_handle (stroke, new_start);
|
|
}
|
|
else if (new_start->type == GIMP_ANCHOR_ANCHOR)
|
|
{
|
|
real_new_start = new_start;
|
|
}
|
|
/* Else type is corrupt and real_new_start is NULL. */
|
|
|
|
g_return_val_if_fail (real_new_start != NULL, FALSE);
|
|
g_return_val_if_fail (real_new_start->type == GIMP_ANCHOR_ANCHOR, FALSE);
|
|
|
|
link = g_queue_find (stroke->anchors, real_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
|
|
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, 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, 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
|
|
gimp_bezier_stroke_transform (GimpStroke *stroke,
|
|
const GimpMatrix3 *matrix,
|
|
GQueue *ret_strokes)
|
|
{
|
|
GimpStroke *first_stroke = NULL;
|
|
GimpStroke *last_stroke = NULL;
|
|
GList *anchorlist;
|
|
GimpAnchor *anchor;
|
|
GimpCoords 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 ||
|
|
gimp_matrix3_is_affine (matrix) ||
|
|
g_queue_is_empty (stroke->anchors))
|
|
{
|
|
GIMP_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 && GIMP_ANCHOR (anchorlist->data)->type != GIMP_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;
|
|
|
|
gimp_transform_bezier_coords (matrix, segmentcoords,
|
|
transformed, &n_transformed,
|
|
&start_in, &end_in);
|
|
|
|
for (i = 0; i < n_transformed; i++)
|
|
{
|
|
GimpStroke *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,
|
|
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
|
|
&anchor->position));
|
|
}
|
|
|
|
for (list = transformed[i]->head; list; list = g_list_next (list))
|
|
{
|
|
GimpCoords *transformedcoords = list->data;
|
|
|
|
if (! s)
|
|
{
|
|
/* start a new stroke */
|
|
s = gimp_bezier_stroke_new ();
|
|
|
|
g_queue_push_tail (s->anchors,
|
|
gimp_anchor_new (GIMP_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++)
|
|
{
|
|
GimpAnchorType type;
|
|
|
|
if (j == 0 || j == 3)
|
|
type = GIMP_ANCHOR_ANCHOR;
|
|
else
|
|
type = GIMP_ANCHOR_CONTROL;
|
|
|
|
g_queue_push_tail (s->anchors,
|
|
gimp_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,
|
|
gimp_anchor_new (GIMP_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 */
|
|
gimp_anchor_free (g_queue_pop_head (first_stroke->anchors));
|
|
/* remove the last anchor, which is the same anchor point as the
|
|
* first anchor
|
|
*/
|
|
gimp_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,
|
|
gimp_anchor_new (GIMP_ANCHOR_CONTROL,
|
|
&anchor->position));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
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, ¢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);
|
|
|
|
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 (¢er, &(ellips[1]), &(ctrl[1]));
|
|
gimp_coords_add (¢er, &(ellips[2]), &(ctrl[2]));
|
|
gimp_coords_add (¢er, &(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 (¢er, &(ellips[1]), &(ctrl[1]));
|
|
gimp_coords_add (¢er, &(ellips[2]), &(ctrl[2]));
|
|
gimp_coords_add (¢er, &(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_head (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;
|
|
}
|