diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index 4d75698a83..dc43bc2fa4 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -26,6 +26,7 @@ #include "gskpathprivate.h" #include "gsksplineprivate.h" #include "gskstrokeprivate.h" +#include "gskconvexityprivate.h" typedef struct _GskContourClass GskContourClass; @@ -86,6 +87,7 @@ struct _GskContourClass const GskStroke *stroke, graphene_rect_t *bounds); GskContour * (* reverse) (const GskContour *contour); + gboolean (* is_convex) (const GskContour *contour); }; static gsize @@ -497,6 +499,12 @@ gsk_rect_contour_reverse (const GskContour *contour) self->height)); } +static gboolean +gsk_rect_contour_is_convex (const GskContour *contour) +{ + return TRUE; +} + static const GskContourClass GSK_RECT_CONTOUR_CLASS = { sizeof (GskRectContour), @@ -516,6 +524,7 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS = gsk_rect_contour_get_winding, gsk_rect_contour_get_stroke_bounds, gsk_rect_contour_reverse, + gsk_rect_contour_is_convex, }; GskContour * @@ -878,6 +887,12 @@ gsk_circle_contour_reverse (const GskContour *contour) self->start_angle); } +static gboolean +gsk_circle_contour_is_convex (const GskContour *contour) +{ + return TRUE; +} + static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = { sizeof (GskCircleContour), @@ -897,6 +912,7 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = gsk_circle_contour_get_winding, gsk_circle_contour_get_stroke_bounds, gsk_circle_contour_reverse, + gsk_circle_contour_is_convex, }; GskContour * @@ -930,6 +946,7 @@ struct _GskStandardContour GskContour contour; GskPathFlags flags; + GskConvexity convexity; gsize n_ops; gsize n_points; @@ -1702,6 +1719,17 @@ gsk_standard_contour_reverse (const GskContour *contour) return res; } +static gboolean +gsk_standard_contour_is_convex (const GskContour *contour) +{ + GskStandardContour *self = (GskStandardContour *) contour; + + if (self->convexity == GSK_CONVEXITY_UNKNOWN) + self->convexity = gsk_contour_compute_convexity (contour); + + return self->convexity == GSK_CONVEXITY_CONVEX; +} + static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = { sizeof (GskStandardContour), @@ -1721,6 +1749,7 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = gsk_standard_contour_get_winding, gsk_standard_contour_get_stroke_bounds, gsk_standard_contour_reverse, + gsk_standard_contour_is_convex, }; /* You must ensure the contour has enough size allocated, @@ -1742,6 +1771,7 @@ gsk_standard_contour_init (GskContour *contour, self->contour.klass = &GSK_STANDARD_CONTOUR_CLASS; self->flags = flags; + self->convexity = GSK_CONVEXITY_UNKNOWN; self->n_ops = n_ops; self->n_points = n_points; self->points = (graphene_point_t *) &self->ops[n_ops]; @@ -1914,3 +1944,9 @@ gsk_contour_reverse (const GskContour *src) { return src->klass->reverse (src); } + +gboolean +gsk_contour_is_convex (const GskContour *contour) +{ + return contour->klass->is_convex (contour); +} diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h index 6d2a98f8e1..413bf39022 100644 --- a/gsk/gskcontourprivate.h +++ b/gsk/gskcontourprivate.h @@ -97,6 +97,8 @@ gboolean gsk_contour_get_stroke_bounds (const GskContou const GskStroke *stroke, graphene_rect_t *bounds); +gboolean gsk_contour_is_convex (const GskContour *contour); + G_END_DECLS #endif /* __GSK_CONTOUR_PRIVATE_H__ */ diff --git a/gsk/gskconvexity.c b/gsk/gskconvexity.c new file mode 100644 index 0000000000..2228f8a74b --- /dev/null +++ b/gsk/gskconvexity.c @@ -0,0 +1,293 @@ +#include "config.h" + +#include + +#include "gskconvexityprivate.h" +#include "gskcontourprivate.h" + +typedef enum +{ + GSK_DIR_CHANGE_UNKNOWN, + GSK_DIR_CHANGE_LEFT, + GSK_DIR_CHANGE_RIGHT, + GSK_DIR_CHANGE_STRAIGHT, + GSK_DIR_CHANGE_REVERSE, + GSK_DIR_CHANGE_INVALID, +} GskDirChange; + +typedef enum +{ + GSK_PATH_DIRECTION_UNKNOWN, + GSK_PATH_DIRECTION_CLOCKWISE, + GSK_PATH_DIRECTION_COUNTERCLOCKWISE, +} GskPathDirection; + +typedef struct _GskConvexityChecker GskConvexityChecker; +struct _GskConvexityChecker +{ + graphene_point_t first_point; + graphene_vec2_t first_vec; + graphene_point_t last_point; + graphene_vec2_t last_vec; + GskDirChange expected_direction; + GskPathDirection first_direction; + int reversals; + gboolean finite; + GskConvexity convexity; + int xsign; + int ysign; + int dx; + int dy; +}; + +static float +cross_product (graphene_vec2_t *a, + graphene_vec2_t *b) +{ + float fa[2]; + float fb[2]; + graphene_vec2_to_float (a, fa); + graphene_vec2_to_float (b, fb); + return fa[0] * fb[1] - fa[1] * fb[0]; +} + +static GskDirChange +direction_change (GskConvexityChecker *c, + graphene_vec2_t *v) +{ + float cross = cross_product (&(c->last_vec), v); + if (!isfinite (cross)) + return GSK_DIR_CHANGE_UNKNOWN; + + if (cross == 0) + return graphene_vec2_dot (&(c->last_vec), v) < 0 + ? GSK_DIR_CHANGE_REVERSE + : GSK_DIR_CHANGE_STRAIGHT; + + return cross > 0 + ? GSK_DIR_CHANGE_RIGHT + : GSK_DIR_CHANGE_LEFT; +} + +static gboolean +add_vec (GskConvexityChecker *c, + graphene_vec2_t *v) +{ + GskDirChange dir; + int sign; + + dir = direction_change (c, v); + switch (dir) + { + case GSK_DIR_CHANGE_LEFT: + case GSK_DIR_CHANGE_RIGHT: + if (c->expected_direction == GSK_DIR_CHANGE_INVALID) + { + c->expected_direction = dir; + c->first_direction = (dir == GSK_DIR_CHANGE_RIGHT) + ? GSK_PATH_DIRECTION_CLOCKWISE + : GSK_PATH_DIRECTION_COUNTERCLOCKWISE; + } + else if (c->expected_direction != dir) + { + c->first_direction = GSK_PATH_DIRECTION_UNKNOWN; + c->convexity = GSK_CONVEXITY_CONCAVE; + return FALSE; + } + graphene_vec2_init_from_vec2 (&c->last_vec, v); + break; + + case GSK_DIR_CHANGE_STRAIGHT: + break; + + case GSK_DIR_CHANGE_REVERSE: + graphene_vec2_init_from_vec2 (&c->last_vec, v); + c->reversals++; + if (c->reversals > 2) + c->convexity = GSK_CONVEXITY_CONCAVE; + return c->reversals < 3; + + case GSK_DIR_CHANGE_UNKNOWN: + c->finite = FALSE; + return FALSE; + + case GSK_DIR_CHANGE_INVALID: + default: + g_assert_not_reached (); + } + + if (graphene_vec2_get_x (v) > 0) + sign = 1; + else if (graphene_vec2_get_x (v) < 0) + sign = -1; + else + sign = 0; + + if (sign != 0) + { + if (c->xsign != 42) + { + if (c->xsign != sign) + c->dx++; + + if (c->dx > 2) + { + c->convexity = GSK_CONVEXITY_CONCAVE; + return FALSE; + } + } + c->xsign = sign; + } + + if (graphene_vec2_get_y (v) > 0) + sign = 1; + else if (graphene_vec2_get_y (v) < 0) + sign = -1; + else + sign = 0; + + if (sign != 0) + { + if (c->ysign != 42) + { + if (c->ysign != sign) + c->dy++; + + if (c->dy > 2) + { + c->convexity = GSK_CONVEXITY_CONCAVE; + return FALSE; + } + } + c->ysign = sign; + } + + return TRUE; +} + +static void +gsk_convexity_checker_init (GskConvexityChecker *c) +{ + c->first_point = GRAPHENE_POINT_INIT(0,0); + c->last_point = GRAPHENE_POINT_INIT(0,0); + graphene_vec2_init (&c->first_vec, 0, 0); + graphene_vec2_init (&c->last_vec, 0, 0); + c->expected_direction = GSK_DIR_CHANGE_INVALID; + c->first_direction = GSK_PATH_DIRECTION_UNKNOWN; + c->reversals = 0; + c->finite = TRUE; + c->convexity = GSK_CONVEXITY_UNKNOWN; + c->xsign = 42; + c->ysign = 42; + c->dx = 0; + c->dy = 0; +} + +static void +gsk_convexity_checker_move (GskConvexityChecker *c, + const graphene_point_t *p) +{ + c->first_point = c->last_point = *p; + c->expected_direction = GSK_DIR_CHANGE_INVALID; + c->convexity = GSK_CONVEXITY_CONVEX; +} + +static gboolean +gsk_convexity_checker_add_point (GskConvexityChecker *c, + const graphene_point_t *p) +{ + graphene_vec2_t v; + + if (graphene_point_equal (&c->last_point, p)) + return TRUE; + + graphene_vec2_init (&v, + p->x - c->last_point.x, + p->y - c->last_point.y); + + if (graphene_point_equal (&c->first_point, &c->last_point) && + c->expected_direction == GSK_DIR_CHANGE_INVALID) + { + graphene_vec2_init_from_vec2 (&c->last_vec, &v); + graphene_vec2_init_from_vec2 (&c->first_vec, &v); + } + else if (!add_vec (c, &v)) + { + c->convexity = GSK_CONVEXITY_CONCAVE; + return FALSE; + } + + c->last_point = *p; + return TRUE; +} + +static gboolean +gsk_convexity_checker_close (GskConvexityChecker *c) +{ + if (!(gsk_convexity_checker_add_point (c, &c->first_point) && + add_vec (c, &c->first_vec))) + { + c->convexity = GSK_CONVEXITY_CONCAVE; + return FALSE; + } + + return TRUE; +} + +static gboolean +convex_cb (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + GskConvexityChecker *c = user_data; + + switch (op) + { + case GSK_PATH_MOVE: + gsk_convexity_checker_move (c, &pts[0]); + break; + case GSK_PATH_CLOSE: + if (!gsk_convexity_checker_close (c)) + return FALSE; + break; + case GSK_PATH_LINE: + if (!gsk_convexity_checker_add_point (c, &pts[1])) + return FALSE; + break; + case GSK_PATH_QUAD: + if (!gsk_convexity_checker_add_point (c, &pts[1]) || + !gsk_convexity_checker_add_point (c, &pts[3])) + return FALSE; + break; + case GSK_PATH_CUBIC: + if (!gsk_convexity_checker_add_point (c, &pts[1]) || + !gsk_convexity_checker_add_point (c, &pts[2]) || + !gsk_convexity_checker_add_point (c, &pts[3])) + return FALSE; + break; + case GSK_PATH_CONIC: + if (!gsk_convexity_checker_add_point (c, &pts[1]) || + !gsk_convexity_checker_add_point (c, &pts[3])) + return FALSE; + break; + default: + g_assert_not_reached (); + } + + return TRUE; +} + +GskConvexity +gsk_contour_compute_convexity (const GskContour *contour) +{ + GskConvexityChecker c; + + gsk_convexity_checker_init (&c); + gsk_contour_foreach (contour, 0.001, convex_cb, &c); + + g_assert (c.convexity != GSK_CONVEXITY_UNKNOWN); + + return c.convexity; +} diff --git a/gsk/gskconvexityprivate.h b/gsk/gskconvexityprivate.h new file mode 100644 index 0000000000..d7953c037e --- /dev/null +++ b/gsk/gskconvexityprivate.h @@ -0,0 +1,14 @@ +#pragma once + +#include "gskcontourprivate.h" + +typedef enum +{ + GSK_CONVEXITY_UNKNOWN, + GSK_CONVEXITY_CONVEX, + GSK_CONVEXITY_CONCAVE, +} GskConvexity; + +GskConvexity gsk_contour_compute_convexity (const GskContour *contour); + + diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 28b2770529..e6972287eb 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -1351,3 +1351,25 @@ gsk_path_transform (GskPath *self, return gsk_path_builder_free_to_path (data.builder); } + +/** + * gsk_path_is_convex: + * @self: a `GskPath` + * + * Returns information about whether this path is convex. + * + * A path with more than one contour is never convex. + * + * Returns: `TRUE` if @self is convex + */ +gboolean +gsk_path_is_convex (GskPath *self) +{ + if (self->n_contours == 0) + return TRUE; + + if (self->n_contours > 1) + return FALSE; + + return gsk_contour_is_convex (gsk_path_get_contour (self, 0)); +} diff --git a/gsk/gskpath.h b/gsk/gskpath.h index 230786de0a..cfd818beeb 100644 --- a/gsk/gskpath.h +++ b/gsk/gskpath.h @@ -102,6 +102,9 @@ gboolean gsk_path_get_stroke_bounds (GskPath const GskStroke *stroke, graphene_rect_t *bounds); +GDK_AVAILABLE_IN_ALL +gboolean gsk_path_is_convex (GskPath *self); + GDK_AVAILABLE_IN_ALL gboolean gsk_path_foreach (GskPath *self, GskPathForeachFlags flags, diff --git a/gsk/meson.build b/gsk/meson.build index 2b3fd095d2..86a22468ac 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -43,6 +43,7 @@ gsk_public_sources = files([ gsk_private_sources = files([ 'gskcairoblur.c', 'gskcontour.c', + 'gskconvexity.c', 'gskcurve.c', 'gskdebug.c', 'gskprivate.c',