mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
Merge branch 'path-builder-simplify' into 'main'
pathbuilder: Simplify degenerate curves See merge request GNOME/gtk!6402
This commit is contained in:
commit
5a0b65c611
@ -112,4 +112,32 @@ gsk_bounding_box_union (const GskBoundingBox *a,
|
||||
gsk_bounding_box_init (res, &min, &max);
|
||||
}
|
||||
|
||||
G_END_DECLS
|
||||
static inline void
|
||||
gsk_bounding_box_get_corner (const GskBoundingBox *b,
|
||||
GskCorner c,
|
||||
graphene_point_t *p)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case GSK_CORNER_TOP_LEFT:
|
||||
*p = b->min;
|
||||
break;
|
||||
|
||||
case GSK_CORNER_TOP_RIGHT:
|
||||
p->x = b->max.x;
|
||||
p->y = b->min.y;
|
||||
break;
|
||||
|
||||
case GSK_CORNER_BOTTOM_RIGHT:
|
||||
*p = b->max;
|
||||
break;
|
||||
|
||||
case GSK_CORNER_BOTTOM_LEFT:
|
||||
p->x = b->min.x;
|
||||
p->y = b->max.y;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
@ -2581,6 +2581,57 @@ gsk_curve_get_curvature_points (const GskCurve *curve,
|
||||
return filter_allowable (t, n);
|
||||
}
|
||||
|
||||
/* Find cusps inside the open interval from 0 to 1.
|
||||
*
|
||||
* According to Stone & deRose, A Geometric Characterization
|
||||
* of Parametric Cubic curves, a necessary and sufficient
|
||||
* condition is that the first derivative vanishes.
|
||||
*/
|
||||
int
|
||||
gsk_curve_get_cusps (const GskCurve *curve,
|
||||
float t[2])
|
||||
{
|
||||
const graphene_point_t *pts = curve->cubic.points;
|
||||
graphene_point_t p[3];
|
||||
float ax, bx, cx;
|
||||
float ay, by, cy;
|
||||
float tx[3];
|
||||
int nx;
|
||||
int n = 0;
|
||||
|
||||
if (curve->op != GSK_PATH_CUBIC)
|
||||
return 0;
|
||||
|
||||
p[0].x = 3 * (pts[1].x - pts[0].x);
|
||||
p[0].y = 3 * (pts[1].y - pts[0].y);
|
||||
p[1].x = 3 * (pts[2].x - pts[1].x);
|
||||
p[1].y = 3 * (pts[2].y - pts[1].y);
|
||||
p[2].x = 3 * (pts[3].x - pts[2].x);
|
||||
p[2].y = 3 * (pts[3].y - pts[2].y);
|
||||
|
||||
ax = p[0].x - 2 * p[1].x + p[2].x;
|
||||
bx = - 2 * p[0].x + 2 * p[1].x;
|
||||
cx = p[0].x;
|
||||
|
||||
nx = solve_quadratic (ax, bx, cx, tx);
|
||||
nx = filter_allowable (tx, nx);
|
||||
|
||||
ay = p[0].y - 2 * p[1].y + p[2].y;
|
||||
by = - 2 * p[0].y + 2 * p[1].y;
|
||||
cy = p[0].y;
|
||||
|
||||
for (int i = 0; i < nx; i++)
|
||||
{
|
||||
float ti = tx[i];
|
||||
|
||||
if (0 < ti && ti < 1 &&
|
||||
fabsf (ay * ti * ti + by * ti + cy) < 0.001)
|
||||
t[n++] = ti;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* vim:set foldmethod=marker expandtab: */
|
||||
|
@ -181,9 +181,12 @@ float gsk_curve_at_length (const GskCurve
|
||||
float distance,
|
||||
float epsilon);
|
||||
|
||||
int gsk_curve_get_curvature_points (const GskCurve * curve,
|
||||
int gsk_curve_get_curvature_points (const GskCurve *curve,
|
||||
float t[3]);
|
||||
|
||||
int gsk_curve_get_cusps (const GskCurve *curve,
|
||||
float t[2]);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "gskpathbuilder.h"
|
||||
|
||||
#include "gskpathprivate.h"
|
||||
#include "gskcurveprivate.h"
|
||||
#include "gskpathpointprivate.h"
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
@ -67,6 +68,9 @@
|
||||
*
|
||||
* This is similar to how paths are drawn in Cairo.
|
||||
*
|
||||
* Note that `GskPathBuilder` will reduce the degree of added Bézier
|
||||
* curves as much as possible, to simplify rendering.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
|
||||
@ -624,6 +628,40 @@ gsk_path_builder_rel_line_to (GskPathBuilder *self,
|
||||
self->current_point.y + y);
|
||||
}
|
||||
|
||||
static inline void
|
||||
closest_point (const graphene_point_t *p,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b,
|
||||
graphene_point_t *q)
|
||||
{
|
||||
graphene_vec2_t n;
|
||||
graphene_vec2_t ap;
|
||||
float t;
|
||||
|
||||
graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
|
||||
graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
|
||||
|
||||
t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
|
||||
|
||||
q->x = a->x + t * (b->x - a->x);
|
||||
q->y = a->y + t * (b->y - a->y);
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
collinear (const graphene_point_t *p,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b)
|
||||
{
|
||||
graphene_point_t q;
|
||||
|
||||
if (graphene_point_equal (a, b))
|
||||
return TRUE;
|
||||
|
||||
closest_point (p, a, b, &q);
|
||||
|
||||
return graphene_point_near (p, &q, 0.001);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_builder_quad_to:
|
||||
* @self: a #GskPathBuilder
|
||||
@ -651,22 +689,49 @@ gsk_path_builder_quad_to (GskPathBuilder *self,
|
||||
float x2,
|
||||
float y2)
|
||||
{
|
||||
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);
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
|
||||
/* skip the quad 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)))
|
||||
return;
|
||||
if (collinear (&p0, &p1, &p2))
|
||||
{
|
||||
GskBoundingBox bb;
|
||||
|
||||
/* We simplify degenerate quads to one or two lines */
|
||||
if (!gsk_bounding_box_contains_point (gsk_bounding_box_init (&bb, &p0, &p2), &p1))
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init_foreach (&c, GSK_PATH_QUAD,
|
||||
(const graphene_point_t []) { p0, p1, p2 },
|
||||
3, 0.f);
|
||||
gsk_curve_get_tight_bounds (&c, &bb);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
graphene_point_t q;
|
||||
|
||||
gsk_bounding_box_get_corner (&bb, i, &q);
|
||||
if (graphene_point_equal (&p0, &q) ||
|
||||
graphene_point_equal (&p2, &q))
|
||||
{
|
||||
gsk_bounding_box_get_corner (&bb, (i + 2) % 4, &q);
|
||||
gsk_path_builder_line_to (self, q.x, q.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gsk_path_builder_line_to (self, x2, y2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self->flags &= ~GSK_PATH_FLAT;
|
||||
gsk_path_builder_append_current (self,
|
||||
GSK_PATH_QUAD,
|
||||
2, (graphene_point_t[2]) {
|
||||
GRAPHENE_POINT_INIT (x1, y1),
|
||||
GRAPHENE_POINT_INIT (x2, y2)
|
||||
});
|
||||
2, (graphene_point_t[2]) { p1, p2 });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -702,6 +767,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`
|
||||
@ -734,25 +831,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 });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -831,15 +1039,53 @@ gsk_path_builder_conic_to (GskPathBuilder *self,
|
||||
float y2,
|
||||
float weight)
|
||||
{
|
||||
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);
|
||||
|
||||
g_return_if_fail (self != NULL);
|
||||
g_return_if_fail (weight > 0);
|
||||
|
||||
/* skip the conic 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)))
|
||||
return;
|
||||
if (weight == 1)
|
||||
{
|
||||
gsk_path_builder_quad_to (self, x1, y1, x2, y2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (collinear (&p0, &p1, &p2))
|
||||
{
|
||||
GskBoundingBox bb;
|
||||
|
||||
/* We simplify degenerate quads to one or two lines
|
||||
* (two lines are needed if there's a cusp).
|
||||
*/
|
||||
if (!gsk_bounding_box_contains_point (gsk_bounding_box_init (&bb, &p0, &p2), &p1))
|
||||
{
|
||||
GskCurve c;
|
||||
|
||||
gsk_curve_init_foreach (&c, GSK_PATH_CONIC,
|
||||
(const graphene_point_t []) { p0, p1, p2 },
|
||||
3, weight);
|
||||
gsk_curve_get_tight_bounds (&c, &bb);
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
graphene_point_t q;
|
||||
|
||||
gsk_bounding_box_get_corner (&bb, i, &q);
|
||||
if (graphene_point_equal (&p0, &q) ||
|
||||
graphene_point_equal (&p2, &q))
|
||||
{
|
||||
gsk_bounding_box_get_corner (&bb, (i + 2) % 4, &q);
|
||||
gsk_path_builder_line_to (self, q.x, q.y);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gsk_path_builder_line_to (self, x2, y2);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self->flags &= ~GSK_PATH_FLAT;
|
||||
gsk_path_builder_append_current (self,
|
||||
|
@ -4364,16 +4364,16 @@ gtk_window_realize (GtkWidget *widget)
|
||||
{
|
||||
gtk_window_enable_csd (window);
|
||||
|
||||
if (priv->title_box == NULL)
|
||||
{
|
||||
priv->title_box = gtk_header_bar_new ();
|
||||
gtk_widget_add_css_class (priv->title_box, "titlebar");
|
||||
gtk_widget_add_css_class (priv->title_box, "default-decoration");
|
||||
if (priv->title_box == NULL)
|
||||
{
|
||||
priv->title_box = gtk_header_bar_new ();
|
||||
gtk_widget_add_css_class (priv->title_box, "titlebar");
|
||||
gtk_widget_add_css_class (priv->title_box, "default-decoration");
|
||||
|
||||
gtk_widget_insert_before (priv->title_box, widget, NULL);
|
||||
}
|
||||
gtk_widget_insert_before (priv->title_box, widget, NULL);
|
||||
}
|
||||
|
||||
update_window_actions (window);
|
||||
update_window_actions (window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
fill {
|
||||
path: "M 0 0 O 10 10 20 20 5";
|
||||
path: "M 0 0 O 10 0 20 20 5";
|
||||
fill-rule: even-odd;
|
||||
}
|
||||
|
@ -4,6 +4,6 @@ fill {
|
||||
color: rgb(255,0,204);
|
||||
}
|
||||
path: "\
|
||||
M 0 0 O 10 10, 20 20, 5";
|
||||
M 0 0 O 10 0, 20 20, 5";
|
||||
fill-rule: even-odd;
|
||||
}
|
||||
|
@ -426,10 +426,23 @@ test_foreach (void)
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
s2 = gsk_path_to_string (path2);
|
||||
|
||||
g_assert_cmpstr (sp, ==, s2);
|
||||
/* We still end up with quads here, since GskPathBuilder aggressively reduces
|
||||
* curves degrees.
|
||||
*/
|
||||
g_assert_cmpstr (s, ==, s2);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
g_free (s2);
|
||||
|
||||
path2 = gsk_path_parse (sp);
|
||||
s2 = gsk_path_to_string (path2);
|
||||
|
||||
g_assert_cmpstr (s, ==, s2);
|
||||
|
||||
gsk_path_unref (path2);
|
||||
g_free (s2);
|
||||
|
||||
gsk_path_unref (path);
|
||||
}
|
||||
|
||||
static void
|
||||
|
Loading…
Reference in New Issue
Block a user