Commit 136c9d60 authored by Raph Levien's avatar Raph Levien

The vector path stroking and set operations now work.

parent 525981a0
Wed Dec 9 23:36:35 1998 Raph Levien <raph@gimp.org>
* art_vpath.c (art_vpath_perturb): Made it deal correctly
with closed paths (the MOVETO and closing LINETO have to
agree).
* art_svp_wind.c: Made the bbox calculations for the resulting
svp's correct.
* art_svp.h:
* art_svp.c: The art_svp_seg_compare function moved here, because
it's required in art_svp_ops.
* art_svp.c (art_svp_add_segment): It now does bbox calculations.
* art_svp_ops.h:
* art_svp_ops.c: Added. Populated with basic union, intersection,
and diff functions.
* art_vpath_svp.h:
* art_vpath_svp.c: Added. Populated with a function to convert
from sorted to unsorted vector paths
* Makefile.am: added the new files
* testuta.c: exercise the stroke outline and vector path
operations.
1998-12-08 Herbert Valerio Riedel <hvr@hvrlab.ml.org>
* art_svp_wind.c: added #include <string.h> for memcpy()
......
......@@ -17,8 +17,10 @@ libart_lgpl_la_SOURCES = \
art_rgb.c \
art_rgb_svp.c \
art_svp.c \
art_svp_ops.c \
art_svp_render_aa.c \
art_svp_vpath.c \
art_svp_vpath_stroke.c \
art_svp_wind.c \
art_svp_wind.h \
art_uta.c \
......@@ -26,7 +28,8 @@ libart_lgpl_la_SOURCES = \
art_uta_rect.c \
art_uta_vpath.c \
art_vpath.c \
art_vpath_bpath.c
art_vpath_bpath.c \
art_vpath_svp.c
libart_lgpl_la_LDFLAGS = -version-info @LIBART_VERSION_INFO@
......@@ -42,14 +45,17 @@ libart_lgplinc_HEADERS = \
art_rgb.h \
art_rgb_svp.h \
art_svp.h \
art_svp_ops.h \
art_svp_render_aa.h \
art_svp_vpath.h \
art_svp_vpath_stroke.h \
art_uta.h \
art_uta_ops.h \
art_uta_rect.h \
art_uta_vpath.h \
art_vpath.h \
art_vpath_bpath.h
art_vpath_bpath.h \
art_vpath_svp.h
INCLUDES = -I$(top_srcdir)/..
......
......@@ -33,22 +33,49 @@
int
art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max,
int **pn_points_max,
int n_points, int dir, ArtPoint *points)
int n_points, int dir, ArtPoint *points,
ArtDRect *bbox)
{
int seg_num;
ArtSVP *svp;
ArtSVPSeg *seg;
seg_num = (*p_vp)->n_segs++;
svp = *p_vp;
seg_num = svp->n_segs++;
if (*pn_segs_max == seg_num)
{
*pn_segs_max <<= 1;
*p_vp = (ArtSVP *)art_realloc (*p_vp, sizeof(ArtSVP) +
(*pn_segs_max - 1) * sizeof(ArtSVPSeg));
svp = (ArtSVP *)art_realloc (svp, sizeof(ArtSVP) +
(*pn_segs_max - 1) * sizeof(ArtSVPSeg));
*p_vp = svp;
if (pn_points_max != NULL)
*pn_points_max = art_renew (*pn_points_max, int, *pn_segs_max);
}
(*p_vp)->segs[seg_num].n_points = n_points;
(*p_vp)->segs[seg_num].dir = dir;
(*p_vp)->segs[seg_num].points = points;
seg = &svp->segs[seg_num];
seg->n_points = n_points;
seg->dir = dir;
seg->points = points;
if (bbox)
seg->bbox = *bbox;
else if (points)
{
double x_min, x_max;
int i;
x_min = x_max = points[0].x;
for (i = 1; i < n_points; i++)
{
if (x_min > points[i].x)
x_min = points[i].x;
if (x_max < points[i].x)
x_max = points[i].x;
}
seg->bbox.x0 = x_min;
seg->bbox.y0 = points[0].y;
seg->bbox.x1 = x_max;
seg->bbox.y1 = points[n_points - 1].y;
}
return seg_num;
}
......@@ -63,3 +90,22 @@ art_svp_free (ArtSVP *svp)
art_free (svp);
}
#define EPSILON 1e-6
int
art_svp_seg_compare (const void *s1, const void *s2)
{
const ArtSVPSeg *seg1 = s1;
const ArtSVPSeg *seg2 = s2;
if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1;
else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1;
else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1;
else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1;
else if ((seg1->points[1].x - seg1->points[0].x) *
(seg2->points[1].y - seg2->points[0].y) -
(seg1->points[1].y - seg1->points[0].y) *
(seg2->points[1].x - seg2->points[0].x) > 0) return 1;
else return -1;
}
......@@ -47,11 +47,15 @@ struct _ArtSVP {
int
art_svp_add_segment (ArtSVP **p_vp, int *pn_segs_max,
int **pn_points_max,
int n_points, int dir, ArtPoint *points);
int n_points, int dir, ArtPoint *points,
ArtDRect *bbox);
void
art_svp_free (ArtSVP *svp);
int
art_svp_seg_compare (const void *s1, const void *s2);
#ifdef __cplusplus
}
#endif /* __cplusplus */
......
/* Libart_LGPL - library of basic graphic primitives
* Copyright (C) 1998 Raph Levien
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#define noVERBOSE
/* Vector path set operations, over sorted vpaths. */
#include "art_misc.h"
#include "art_svp.h"
#include "art_vpath.h"
#include "art_svp_vpath.h"
#include "art_svp_wind.h"
#include "art_svp_ops.h"
#include "art_vpath_svp.h"
/* Merge the segments of the two svp's. The resulting svp will share
segments with args passed in, so be super-careful with the
allocation. */
static ArtSVP *
art_svp_merge (const ArtSVP *svp1, const ArtSVP *svp2)
{
ArtSVP *svp_new;
int ix;
int ix1, ix2;
svp_new = (ArtSVP *)art_alloc (sizeof(ArtSVP) +
(svp1->n_segs + svp2->n_segs - 1) *
sizeof(ArtSVPSeg));
ix1 = 0;
ix2 = 0;
for (ix = 0; ix < svp1->n_segs + svp2->n_segs; ix++)
{
if (ix1 < svp1->n_segs &&
(ix2 == svp2->n_segs ||
art_svp_seg_compare (&svp1->segs[ix1], &svp2->segs[ix2]) < 1))
svp_new->segs[ix] = svp1->segs[ix1++];
else
svp_new->segs[ix] = svp2->segs[ix2++];
}
svp_new->n_segs = ix;
return svp_new;
}
#ifdef VERBOSE
#define XOFF 50
#define YOFF 700
static void
print_ps_vpath (ArtVpath *vpath)
{
int i;
for (i = 0; vpath[i].code != ART_END; i++)
{
switch (vpath[i].code)
{
case ART_MOVETO:
printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
break;
case ART_LINETO:
printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
break;
default:
break;
}
}
printf ("stroke showpage\n");
}
#define DELT 4
static void
print_ps_svp (ArtSVP *vpath)
{
int i, j;
printf ("%% begin\n");
for (i = 0; i < vpath->n_segs; i++)
{
printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0);
for (j = 0; j < vpath->segs[i].n_points; j++)
{
printf ("%g %g %s\n",
XOFF + vpath->segs[i].points[j].x,
YOFF - vpath->segs[i].points[j].y,
j ? "lineto" : "moveto");
}
printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n",
XOFF + vpath->segs[i].points[0].x - DELT,
YOFF - DELT - vpath->segs[i].points[0].y,
XOFF + vpath->segs[i].points[0].x - DELT,
YOFF - vpath->segs[i].points[0].y,
XOFF + vpath->segs[i].points[0].x + DELT,
YOFF - vpath->segs[i].points[0].y,
XOFF + vpath->segs[i].points[0].x + DELT,
YOFF - DELT - vpath->segs[i].points[0].y);
printf ("%g %g moveto %g %g lineto %g %g lineto %g %g lineto stroke\n",
XOFF + vpath->segs[i].points[j - 1].x - DELT,
YOFF + DELT - vpath->segs[i].points[j - 1].y,
XOFF + vpath->segs[i].points[j - 1].x - DELT,
YOFF - vpath->segs[i].points[j - 1].y,
XOFF + vpath->segs[i].points[j - 1].x + DELT,
YOFF - vpath->segs[i].points[j - 1].y,
XOFF + vpath->segs[i].points[j - 1].x + DELT,
YOFF + DELT - vpath->segs[i].points[j - 1].y);
printf ("stroke\n");
}
printf ("showpage\n");
}
#endif
static ArtSVP *
art_svp_merge_perturbed (const ArtSVP *svp1, const ArtSVP *svp2)
{
ArtVpath *vpath1, *vpath2;
ArtVpath *vpath1_p, *vpath2_p;
ArtSVP *svp1_p, *svp2_p;
ArtSVP *svp_new;
vpath1 = art_vpath_from_svp (svp1);
vpath1_p = art_vpath_perturb (vpath1);
art_free (vpath1);
svp1_p = art_svp_from_vpath (vpath1_p);
art_free (vpath1_p);
vpath2 = art_vpath_from_svp (svp2);
vpath2_p = art_vpath_perturb (vpath2);
art_free (vpath2);
svp2_p = art_svp_from_vpath (vpath2_p);
art_free (vpath2_p);
svp_new = art_svp_merge (svp1_p, svp2_p);
#ifdef VERBOSE
print_ps_svp (svp1_p);
print_ps_svp (svp2_p);
print_ps_svp (svp_new);
#endif
art_free (svp1_p);
art_free (svp2_p);
return svp_new;
}
/* Compute the union of two vector paths.
Status of this routine:
Basic correctness: Seems to work.
Numerical stability: We cheat (adding random perturbation). Thus,
it seems very likely that no numerical stability problems will be
seen in practice.
Speed: Would be better if we didn't go to unsorted vector path
and back to add the perturbation.
Precision: The perturbation fuzzes the coordinates slightly. In
cases of butting segments, razor thin long holes may appear.
*/
ArtSVP *
art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2)
{
ArtSVP *svp3, *svp4, *svp_new;
svp3 = art_svp_merge_perturbed (svp1, svp2);
svp4 = art_svp_uncross (svp3);
art_svp_free (svp3);
svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_POSITIVE);
#ifdef VERBOSE
print_ps_svp (svp4);
print_ps_svp (svp_new);
#endif
art_svp_free (svp4);
return svp_new;
}
/* Compute the intersection of two vector paths.
Status of this routine:
Basic correctness: Seems to work.
Numerical stability: We cheat (adding random perturbation). Thus,
it seems very likely that no numerical stability problems will be
seen in practice.
Speed: Would be better if we didn't go to unsorted vector path
and back to add the perturbation.
Precision: The perturbation fuzzes the coordinates slightly. In
cases of butting segments, razor thin long isolated segments may
appear.
*/
ArtSVP *
art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2)
{
ArtSVP *svp3, *svp4, *svp_new;
svp3 = art_svp_merge_perturbed (svp1, svp2);
svp4 = art_svp_uncross (svp3);
art_svp_free (svp3);
svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_INTERSECT);
art_svp_free (svp4);
return svp_new;
}
/* Compute the symmetric difference of two vector paths.
Status of this routine:
Basic correctness: Seems to work.
Numerical stability: We cheat (adding random perturbation). Thus,
it seems very likely that no numerical stability problems will be
seen in practice.
Speed: We could do a lot better by scanning through the svp
representations and culling out any segments that are exactly
identical. It would also be better if we didn't go to unsorted
vector path and back to add the perturbation.
Precision: Awful. In the case of inputs which are similar (the
common case for canvas display), the entire outline is "hairy." In
addition, the perturbation fuzzes the coordinates slightly. It can
be used as a conservative approximation.
*/
ArtSVP *
art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2)
{
ArtSVP *svp3, *svp4, *svp_new;
svp3 = art_svp_merge_perturbed (svp1, svp2);
svp4 = art_svp_uncross (svp3);
art_svp_free (svp3);
svp_new = art_svp_rewind_uncrossed (svp4, ART_WIND_RULE_ODDEVEN);
art_svp_free (svp4);
return svp_new;
}
/* Libart_LGPL - library of basic graphic primitives
* Copyright (C) 1998 Raph Levien
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef __ART_SVP_OPS_H__
#define __ART_SVP_OPS_H__
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/* Vector path set operations, over sorted vpaths. */
ArtSVP *art_svp_union (const ArtSVP *svp1, const ArtSVP *svp2);
ArtSVP *art_svp_intersect (const ArtSVP *svp1, const ArtSVP *svp2);
ArtSVP *art_svp_diff (const ArtSVP *svp1, const ArtSVP *svp2);
ArtSVP *art_svp_minus (const ArtSVP *svp1, const ArtSVP *svp2);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __ART_SVP_OPS_H__ */
......@@ -197,24 +197,3 @@ art_svp_from_vpath (ArtVpath *vpath)
return svp;
}
#define EPSILON 1e-6
/* It may be that this one should move out into art_svp or somesuch. */
static int
art_svp_seg_compare (const void *s1, const void *s2)
{
const ArtSVPSeg *seg1 = s1;
const ArtSVPSeg *seg2 = s2;
if (seg1->points[0].y - EPSILON > seg2->points[0].y) return 1;
else if (seg1->points[0].y + EPSILON < seg2->points[0].y) return -1;
else if (seg1->points[0].x - EPSILON > seg2->points[0].x) return 1;
else if (seg1->points[0].x + EPSILON < seg2->points[0].x) return -1;
else if ((seg1->points[1].x - seg1->points[0].x) *
(seg2->points[1].y - seg2->points[0].y) -
(seg1->points[1].y - seg1->points[0].y) *
(seg2->points[1].x - seg2->points[0].x) > 0) return 1;
else return -1;
}
This diff is collapsed.
......@@ -17,8 +17,8 @@
* Boston, MA 02111-1307, USA.
*/
#ifndef __ART_SVP_VPATH_H__
#define __ART_SVP_VPATH_H__
#ifndef __ART_SVP_VPATH_STROKE_H__
#define __ART_SVP_VPATH_STROKE_H__
/* Sort vector paths into sorted vector paths. */
......@@ -59,4 +59,4 @@ art_svp_vpath_stroke_raw (ArtVpath *vpath,
}
#endif /* __cplusplus */
#endif /* __ART_SVP_VPATH_H__ */
#endif /* __ART_SVP_VPATH_STROKE_H__ */
This diff is collapsed.
......@@ -132,7 +132,7 @@ art_vpath_bbox_irect (const ArtVpath *vec, ArtIRect *irect)
art_drect_to_irect (irect, &drect);
}
#define EPSILON 1e-6
#define PERTURBATION 1e-6
/* Perturb each of the points by a small random amount. This is helpful
for cheating in cases when algorithms haven't attained numerical
......@@ -144,19 +144,42 @@ art_vpath_perturb (ArtVpath *src)
int i;
int size;
ArtVpath *new;
double x, y;
double x_start, y_start;
int open;
for (i = 0; src[i].code != ART_END; i++);
size = i;
new = art_new (ArtVpath, size + 1);
x_start = 0;
y_start = 0;
open = 0;
for (i = 0; i < size; i++)
{
new[i].code = src[i].code;
new[i].x = src[i].x + (EPSILON * rand ()) / RAND_MAX - EPSILON * 0.5;
new[i].y = src[i].y + (EPSILON * rand ()) / RAND_MAX - EPSILON * 0.5;
x = src[i].x + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
y = src[i].y + (PERTURBATION * rand ()) / RAND_MAX - PERTURBATION * 0.5;
if (src[i].code == ART_MOVETO)
{
x_start = x;
y_start = y;
open = 0;
}
else if (src[i].code == ART_MOVETO_OPEN)
open = 1;
if (!open && (i + 1 == size || src[i + 1].code != ART_LINETO))
{
x = x_start;
y = y_start;
}
new[i].x = x;
new[i].y = y;
}
new[i].code = ART_END;
return new;
}
/* todo: implement minus */
/* Libart_LGPL - library of basic graphic primitives
* Copyright (C) 1998 Raph Levien
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* "Unsort" a sorted vector path into an ordinary vector path. */
#include <stdio.h> /* for printf - debugging */
#include "art_misc.h"
#include "art_vpath.h"
#include "art_svp.h"
#include "art_vpath_svp.h"
typedef struct _ArtVpathSVPEnd ArtVpathSVPEnd;
struct _ArtVpathSVPEnd {
int seg_num;
int which; /* 0 = top, 1 = bottom */
double x, y;
};
#define EPSILON 1e-6
static int
art_vpath_svp_point_compare (double x1, double y1, double x2, double y2)
{
if (y1 - EPSILON > y2) return 1;
if (y1 + EPSILON < y2) return -1;
if (x1 - EPSILON > x2) return 1;
if (x1 + EPSILON < x2) return -1;
return 0;
}
static int
art_vpath_svp_compare (const void *s1, const void *s2)
{
const ArtVpathSVPEnd *e1 = s1;
const ArtVpathSVPEnd *e2 = s2;
return art_vpath_svp_point_compare (e1->x, e1->y, e2->x, e2->y);
}
/* Convert from sorted vector path representation into regular
vector path representation.
Status of this routine:
Basic correctness: Only works with closed paths.
Numerical stability: Not known to work when more than two segments
meet at a point.
Speed: Should be pretty good.
Precision: Does not degrade precision.
*/
ArtVpath *
art_vpath_from_svp (const ArtSVP *svp)
{
int n_segs = svp->n_segs;
ArtVpathSVPEnd *ends;
ArtVpath *new;
int *visited;
int n_new, n_new_max;
int i, j, k;
int seg_num;
int first;
double last_x, last_y;
int n_points;
int pt_num;
last_x = 0; /* to eliminate "uninitialized" warning */
last_y = 0;
ends = art_new (ArtVpathSVPEnd, n_segs * 2);
for (i = 0; i < svp->n_segs; i++)
{
int lastpt;
ends[i * 2].seg_num = i;
ends[i * 2].which = 0;
ends[i * 2].x = svp->segs[i].points[0].x;
ends[i * 2].y = svp->segs[i].points[0].y;
lastpt = svp->segs[i].n_points - 1;
ends[i * 2 + 1].seg_num = i;
ends[i * 2 + 1].which = 1;
ends[i * 2 + 1].x = svp->segs[i].points[lastpt].x;
ends[i * 2 + 1].y = svp->segs[i].points[lastpt].y;
}
qsort (ends, n_segs * 2, sizeof (ArtVpathSVPEnd), art_vpath_svp_compare);
n_new = 0;
n_new_max = 16; /* I suppose we _could_ estimate this from traversing
the svp, so we don't have to reallocate */
new = art_new (ArtVpath, n_new_max);
visited = art_new (int, n_segs);
for (i = 0; i < n_segs; i++)
visited[i] = 0;
first = 1;
for (i = 0; i < n_segs; i++)
{
if (!first)
{
/* search for the continuation of the existing subpath */
/* This could be a binary search (which is why we sorted, above) */
for (j = 0; j < n_segs * 2; j++)
{
if (!visited[ends[j].seg_num] &&
art_vpath_svp_point_compare (last_x, last_y,
ends[j].x, ends[j].y) == 0)
break;
}
if (j == n_segs * 2)
first = 1;
}
if (first)
{
/* start a new subpath */
for (j = 0; j < n_segs * 2; j++)
if (!visited[ends[j].seg_num])
break;
}
if (j == n_segs * 2)
{
printf ("failure\n");
}
seg_num = ends[j].seg_num;
n_points = svp->segs[seg_num].n_points;
for (k = 0; k < n_points; k++)
{
pt_num = svp->segs[seg_num].dir ? k : n_points - (1 + k);
if (k == 0)
{
if (first)
{
art_vpath_add_point (&new, &n_new, &n_new_max,
ART_MOVETO,
svp->segs[seg_num].points[pt_num].x,
svp->segs[seg_num].points[pt_num].y);
}
}
else
{
art_vpath_add_point (&new, &n_new, &n_new_max,
ART_LINETO,
svp->segs[seg_num].points[pt_num].x,
svp->segs[seg_num].points[pt_num].y);
if (k == n_points - 1)
{
last_x = svp->segs[seg_num].points[pt_num].x;
last_y = svp->segs[seg_num].points[pt_num].y;
/* to make more robust, check for meeting first_[xy],
set first