path builder: Handle degenerate cubics

Replace cubics by lines or quadratics
when possible, and split at cusps.
This commit is contained in:
Matthias Clasen 2023-09-16 21:56:54 -04:00
parent 68b4d9c35e
commit 7e13cfea91

View File

@ -764,6 +764,38 @@ gsk_path_builder_rel_quad_to (GskPathBuilder *self,
self->current_point.y + y2);
}
static gboolean
point_is_between (const graphene_point_t *q,
const graphene_point_t *p0,
const graphene_point_t *p1)
{
return collinear (p0, p1, q) &&
fabsf (graphene_point_distance (p0, q, NULL, NULL) + graphene_point_distance (p1, q, NULL, NULL) - graphene_point_distance (p0, p1, NULL, NULL)) < 0.001;
}
static gboolean
bounding_box_corner_between (const GskBoundingBox *bb,
const graphene_point_t *p0,
const graphene_point_t *p1,
graphene_point_t *p)
{
for (int i = 0; i < 4; i++)
{
graphene_point_t q;
gsk_bounding_box_get_corner (bb, i, &q);
if (point_is_between (&q, p0, p1))
{
*p = q;
return TRUE;
}
}
return FALSE;
}
/**
* gsk_path_builder_cubic_to:
* @self: a `GskPathBuilder`
@ -796,25 +828,136 @@ gsk_path_builder_cubic_to (GskPathBuilder *self,
float x3,
float y3)
{
graphene_point_t p0 = self->current_point;
graphene_point_t p1 = GRAPHENE_POINT_INIT (x1, y1);
graphene_point_t p2 = GRAPHENE_POINT_INIT (x2, y2);
graphene_point_t p3 = GRAPHENE_POINT_INIT (x3, y3);
graphene_point_t p, q;
gboolean p01, p12, p23;
g_return_if_fail (self != NULL);
/* skip the cubic if it collapses to a point */
if (graphene_point_equal (&self->current_point,
&GRAPHENE_POINT_INIT (x1, y1)) &&
graphene_point_equal (&GRAPHENE_POINT_INIT (x1, y1),
&GRAPHENE_POINT_INIT (x2, y2)) &&
graphene_point_equal (&GRAPHENE_POINT_INIT (x2, y2),
&GRAPHENE_POINT_INIT (x3, y3)))
p01 = graphene_point_equal (&p0, &p1);
p12 = graphene_point_equal (&p1, &p2);
p23 = graphene_point_equal (&p2, &p3);
if (p01 && p12 && p23)
return;
if ((p01 && p23) || (p12 && (p01 || p23)))
{
gsk_path_builder_line_to (self, x3, y3);
return;
}
if (collinear (&p0, &p1, &p2) &&
collinear (&p1, &p2, &p3) &&
(!p12 || collinear (&p0, &p1, &p3)))
{
GskBoundingBox bb;
gboolean p1in, p2in;
gsk_bounding_box_init (&bb, &p0, &p3);
p1in = gsk_bounding_box_contains_point (&bb, &p1);
p2in = gsk_bounding_box_contains_point (&bb, &p2);
if (p1in && p2in)
{
gsk_path_builder_line_to (self, x3, y3);
}
else
{
GskCurve c;
gsk_curve_init_foreach (&c,
GSK_PATH_CUBIC,
(const graphene_point_t[]) { p0, p1, p2, p3 },
4,
0.f);
gsk_curve_get_tight_bounds (&c, &bb);
if (!p1in)
{
/* Find the intersection of bb with p0 - p1.
* It must be a corner
*/
bounding_box_corner_between (&bb, &p0, &p1, &p);
gsk_path_builder_line_to (self, p.x, p.y);
}
if (!p2in)
{
/* Find the intersection of bb with p2 - p3. */
bounding_box_corner_between (&bb, &p3, &p2, &p);
gsk_path_builder_line_to (self, p.x, p.y);
}
gsk_path_builder_line_to (self, x3, y3);
}
return;
}
/* reduce to a quadratic if possible */
graphene_point_interpolate (&p0, &p1, 1.5, &p);
graphene_point_interpolate (&p3, &p2, 1.5, &q);
if (graphene_point_near (&p, &q, 0.001))
{
gsk_path_builder_quad_to (self, p.x, p.y, x3, y3);
return;
}
self->flags &= ~GSK_PATH_FLAT;
/* At this point, we are dealing with a cubic that can't be reduced to
* lines or quadratics. Check for cusps.
*/
{
GskCurve c, c1, c2, c3, c4;
float t[2];
int n;
gsk_curve_init_foreach (&c,
GSK_PATH_CUBIC,
(const graphene_point_t[]) { p0, p1, p2, p3 },
4,
0.f);
n = gsk_curve_get_cusps (&c, t);
if (n == 1)
{
gsk_curve_split (&c, t[0], &c1, &c2);
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, &c1.cubic.points[1]);
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, &c2.cubic.points[1]);
return;
}
else if (n == 2)
{
if (t[1] < t[0])
{
float s = t[0];
t[0] = t[1];
t[1] = s;
}
gsk_curve_split (&c, t[0], &c1, &c2);
gsk_curve_split (&c2, (t[1] - t[0]) / (1 - t[0]), &c3, &c4);
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, &c1.cubic.points[1]);
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, &c3.cubic.points[1]);
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, &c4.cubic.points[1]);
return;
}
}
gsk_path_builder_append_current (self,
GSK_PATH_CUBIC,
3, (graphene_point_t[3]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (x2, y2),
GRAPHENE_POINT_INIT (x3, y3)
});
3, (graphene_point_t[3]) { p1, p2, p3 });
}
/**