From a88dbb820b0e0ecfcd37f4a8d8d780ea8ce9cc53 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Apr 2022 22:13:31 -0400 Subject: [PATCH] Add GskRoundedRectContour The main advantage here is that we can avoid going through the stroker for the common case of rounded rectangles. --- gsk/gskcontour.c | 546 +++++++++++++++++++++++++++++++++++++--- gsk/gskcontourprivate.h | 1 + gsk/gskpathbuilder.c | 53 +--- 3 files changed, 515 insertions(+), 85 deletions(-) diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index 359a070153..945a1f4515 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -109,6 +109,8 @@ gsk_contour_get_size_default (const GskContour *contour) return contour->klass->struct_size; } +/* {{{ Utilities */ + static void gsk_find_point_on_line (const graphene_point_t *a, const graphene_point_t *b, @@ -142,25 +144,6 @@ gsk_find_point_on_line (const graphene_point_t *a, } } -/* {{{ Rectangle contour */ - -typedef struct _GskRectContour GskRectContour; -struct _GskRectContour -{ - GskContour contour; - - float x; - float y; - float width; - float height; -}; - -static GskPathFlags -gsk_rect_contour_get_flags (const GskContour *contour) -{ - return GSK_PATH_FLAT | GSK_PATH_CLOSED; -} - static void _g_string_append_double (GString *string, double d) @@ -180,6 +163,42 @@ _g_string_append_point (GString *string, _g_string_append_double (string, pt->y); } +static gboolean +stroke_is_simple (GskStroke *stroke) +{ + if (stroke->line_join == GSK_LINE_JOIN_ROUND || + stroke->line_join == GSK_LINE_JOIN_BEVEL) + return FALSE; + + if (stroke->miter_limit < 1.5) + return FALSE; + + if (stroke->dash_length != 0) + return FALSE; + + return TRUE; +} + +/* }}} */ +/* {{{ Rectangle contour */ + +typedef struct _GskRectContour GskRectContour; +struct _GskRectContour +{ + GskContour contour; + + float x; + float y; + float width; + float height; +}; + +static GskPathFlags +gsk_rect_contour_get_flags (const GskContour *contour) +{ + return GSK_PATH_FLAT | GSK_PATH_CLOSED; +} + static void gsk_rect_contour_print (const GskContour *contour, GString *string) @@ -516,22 +535,6 @@ gsk_rect_contour_get_stroke_bounds (const GskContour *contour, return TRUE; } -static gboolean -stroke_is_simple (GskStroke *stroke) -{ - if (stroke->line_join == GSK_LINE_JOIN_ROUND || - stroke->line_join == GSK_LINE_JOIN_BEVEL) - return FALSE; - - if (stroke->miter_limit < 1.5) - return FALSE; - - if (stroke->dash_length != 0) - return FALSE; - - return TRUE; -} - static void gsk_rect_contour_add_stroke (const GskContour *contour, GskPathBuilder *builder, @@ -1923,6 +1926,479 @@ gsk_standard_contour_new (GskPathFlags flags, return contour; } +/* }}} */ +/* {{{ Rounded rectangle contour */ + +typedef struct _GskRoundedRectContour GskRoundedRectContour; +struct _GskRoundedRectContour +{ + GskContour contour; + + GskRoundedRect rect; + gboolean ccw; +}; + +static GskPathFlags +gsk_rounded_rect_contour_get_flags (const GskContour *contour) +{ + return GSK_PATH_CLOSED; +} + +static inline void +get_rounded_rect_points (const GskRoundedRect *rect, + graphene_point_t *pts) +{ + pts[0] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, rect->bounds.origin.y); + pts[1] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->bounds.origin.y); + pts[2] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y); + pts[3] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height); + pts[4] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height); + pts[5] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->bounds.size.height); + pts[6] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->bounds.origin.y + rect->bounds.size.height); + pts[7] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width, rect->bounds.origin.y + rect->bounds.size.height); + pts[8] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->bounds.size.height); + pts[9] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height); + pts[10] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height); + pts[11] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y); + pts[12] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, rect->bounds.origin.y); +} + +static void +gsk_rounded_rect_contour_print (const GskContour *contour, + GString *string) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + graphene_point_t pts[13]; + + get_rounded_rect_points (&self->rect, pts); + +#define APPEND_MOVE(p) \ + g_string_append (string, "M "); \ + _g_string_append_point (string, p); +#define APPEND_LINE(p) \ + g_string_append (string, " L "); \ + _g_string_append_point (string, p); +#define APPEND_CONIC(p1, p2) \ + g_string_append (string, " O "); \ + _g_string_append_point (string, p1); \ + g_string_append (string, ", "); \ + _g_string_append_point (string, p2); \ + g_string_append (string, ", "); \ + _g_string_append_double (string, M_SQRT1_2); +#define APPEND_CLOSE g_string_append (string, " z"); + if (self->ccw) + { + graphene_point_t p; +#define SWAP(a,b,c) a = b; b = c; c = a; + SWAP (p, pts[1], pts[11]); + SWAP (p, pts[2], pts[10]); + SWAP (p, pts[3], pts[9]); + SWAP (p, pts[4], pts[8]); + SWAP (p, pts[5], pts[7]); +#undef SWAP + + APPEND_MOVE (&pts[0]); + APPEND_CONIC (&pts[1], &pts[2]); + APPEND_LINE (&pts[3]); + APPEND_CONIC (&pts[4], &pts[5]); + APPEND_LINE (&pts[6]); + APPEND_CONIC (&pts[7], &pts[8]); + APPEND_LINE (&pts[9]); + APPEND_CONIC (&pts[10], &pts[11]); + APPEND_LINE (&pts[12]); + APPEND_CLOSE + } + else + { + APPEND_MOVE (&pts[0]); + APPEND_LINE (&pts[1]); + APPEND_CONIC (&pts[2], &pts[3]); + APPEND_LINE (&pts[4]); + APPEND_CONIC (&pts[5], &pts[6]); + APPEND_LINE (&pts[7]); + APPEND_CONIC (&pts[8], &pts[9]); + APPEND_LINE (&pts[10]); + APPEND_CONIC (&pts[11], &pts[12]); + APPEND_CLOSE; + } +#undef APPEND_MOVE +#undef APPEND_LINE +#undef APPEND_CONIC +#undef APPEND_CLOSE +} + +static gboolean +gsk_rounded_rect_contour_get_bounds (const GskContour *contour, + graphene_rect_t *rect) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + graphene_rect_init_from_rect (rect, &self->rect.bounds); + + return TRUE; +} + +static void +gsk_rounded_rect_contour_get_start_end (const GskContour *contour, + graphene_point_t *start, + graphene_point_t *end) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (start) + *start = GRAPHENE_POINT_INIT (self->rect.bounds.origin.x + self->rect.corner[GSK_CORNER_TOP_LEFT].width, + self->rect.bounds.origin.y); + + if (end) + *end = GRAPHENE_POINT_INIT (self->rect.bounds.origin.x + self->rect.corner[GSK_CORNER_TOP_LEFT].width, + self->rect.bounds.origin.y); +} + +static gboolean +gsk_rounded_rect_contour_foreach (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + graphene_point_t pts[13]; + + get_rounded_rect_points (&self->rect, pts); + if (self->ccw) + { + graphene_point_t p; +#define SWAP(a,b,c) a = b; b = c; c = a; + SWAP (p, pts[1], pts[11]); + SWAP (p, pts[2], pts[10]); + SWAP (p, pts[3], pts[9]); + SWAP (p, pts[4], pts[8]); + SWAP (p, pts[5], pts[7]); +#undef SWAP + + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) && + func (GSK_PATH_CONIC, &pts[0], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[2], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[3], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[5], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[6], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[8], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[9], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[11], 2, 0, user_data) && + func (GSK_PATH_CLOSE, &pts[12], 2, 0, user_data); + } + else + { + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) && + func (GSK_PATH_LINE, &pts[0], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[1], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[3], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[4], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[6], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[7], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[9], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[10], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_CLOSE, &pts[12], 2, 0, user_data); + } +} + +typedef struct +{ + GskPath *path; + const GskContour *contour; + gpointer measure_data; +} RoundedRectMeasureData; + +static gboolean +add_cb (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + GskPathBuilder *builder = user_data; + switch (op) + { + case GSK_PATH_MOVE: + gsk_path_builder_move_to (builder, pts[0].x, pts[0].y); + break; + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, pts[1].x, pts[1].y); + break; + case GSK_PATH_CURVE: + gsk_path_builder_curve_to (builder, + pts[1].x, pts[1].y, + pts[2].x, pts[2].y, + pts[3].x, pts[3].y); + break; + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, + pts[1].x, pts[1].y, + pts[3].x, pts[3].y, + weight); + break; + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + default: + g_assert_not_reached (); + } + + return TRUE; +} + +static gpointer +gsk_rounded_rect_contour_init_measure (const GskContour *contour, + float tolerance, + float *out_length) +{ + GskPathBuilder *builder; + RoundedRectMeasureData *data; + + builder = gsk_path_builder_new (); + + gsk_contour_foreach (contour, tolerance, add_cb, builder); + + data = g_new (RoundedRectMeasureData, 1); + data->path = gsk_path_builder_free_to_path (builder); + data->contour = gsk_path_get_contour (data->path, 0); + data->measure_data = gsk_standard_contour_init_measure (data->contour, tolerance, out_length); + + return data; +} + +static void +gsk_rounded_rect_contour_free_measure (const GskContour *contour, + gpointer measure_data) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_free_measure (data->contour, data->measure_data); + gsk_path_unref (data->path); + g_free (data); +} + +static void +gsk_rounded_rect_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_get_point (data->contour, + data->measure_data, + distance, + pos, + tangent); +} + +static float +gsk_rounded_rect_contour_get_curvature (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + RoundedRectMeasureData *data = measure_data; + + return gsk_standard_contour_get_curvature (data->contour, + data->measure_data, + distance, + center); +} + +static gboolean +gsk_rounded_rect_contour_get_closest_point (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + RoundedRectMeasureData *data = measure_data; + + return gsk_standard_contour_get_closest_point (data->contour, + data->measure_data, + tolerance, + point, + threshold, + out_distance, + out_pos, + out_offset, + out_tangent); +} + +static void +gsk_rounded_rect_contour_copy (const GskContour *contour, + GskContour *dest) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + GskRoundedRectContour *target = (GskRoundedRectContour *) dest; + + *target = *self; +} + +static void +gsk_rounded_rect_contour_add_segment (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_add_segment (data->contour, + builder, + data->measure_data, + emit_move_to, + start, + end); +} + +static int +gsk_rounded_rect_contour_get_winding (const GskContour *contour, + gpointer measure_data, + const graphene_point_t *point, + gboolean *on_edge) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (gsk_rounded_rect_contains_point (&self->rect, point)) + return self->ccw ? 1 : -1; + + return 0; +} + +static gboolean +gsk_rounded_rect_contour_get_stroke_bounds (const GskContour *contour, + const GskStroke *stroke, + graphene_rect_t *bounds) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + graphene_rect_init_from_rect (bounds, &self->rect.bounds); + graphene_rect_inset (bounds, - stroke->line_width / 2, - stroke->line_width / 2); + + return TRUE; +} + +static void +gsk_rounded_rect_contour_add_stroke (const GskContour *contour, + GskPathBuilder *builder, + GskStroke *stroke) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (stroke_is_simple (stroke)) + { + GskRoundedRect rect; + GskContour *c; + + gsk_rounded_rect_init_copy (&rect, &self->rect); + + gsk_rounded_rect_shrink (&rect, + stroke->line_width / 2, + stroke->line_width / 2, + stroke->line_width / 2, + stroke->line_width / 2); + c = gsk_rounded_rect_contour_new (&rect); + gsk_path_builder_add_contour (builder, c); + + gsk_rounded_rect_init_copy (&rect, &self->rect); + gsk_rounded_rect_shrink (&rect, + - stroke->line_width / 2, + - stroke->line_width / 2, + - stroke->line_width / 2, + - stroke->line_width / 2); + c = gsk_rounded_rect_contour_new (&rect); + ((GskRoundedRectContour *)c)->ccw = TRUE; + gsk_path_builder_add_contour (builder, c); + } + else + gsk_contour_default_add_stroke (contour, builder, stroke); +} + +static void +gsk_rounded_rect_contour_offset (const GskContour *contour, + GskPathBuilder *builder, + float distance, + GskLineJoin line_join, + float miter_limit) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + GskRoundedRect rect; + GskContour *c; + + gsk_rounded_rect_init_copy (&rect, &self->rect); + if (self->ccw) + distance = - distance; + gsk_rounded_rect_shrink (&rect, - distance, - distance, - distance, - distance); + c = gsk_rounded_rect_contour_new (&rect); + gsk_path_builder_add_contour (builder, c); +} + +static GskContour * +gsk_rounded_rect_contour_reverse (const GskContour *contour) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + GskRoundedRectContour *copy; + + copy = g_new0 (GskRoundedRectContour, 1); + gsk_rounded_rect_contour_copy (contour, (GskContour *)copy); + copy->ccw = !self->ccw; + + return (GskContour *)copy; +} + +static gboolean +gsk_rounded_rect_contour_is_convex (const GskContour *contour) +{ + return TRUE; +} + +static const GskContourClass GSK_ROUNDED_RECT_CONTOUR_CLASS = +{ + sizeof (GskRoundedRectContour), + "GskRoundedRectContour", + gsk_contour_get_size_default, + gsk_rounded_rect_contour_get_flags, + gsk_rounded_rect_contour_print, + gsk_rounded_rect_contour_get_bounds, + gsk_rounded_rect_contour_get_start_end, + gsk_rounded_rect_contour_foreach, + gsk_rounded_rect_contour_init_measure, + gsk_rounded_rect_contour_free_measure, + gsk_rounded_rect_contour_get_point, + gsk_rounded_rect_contour_get_curvature, + gsk_rounded_rect_contour_get_closest_point, + gsk_rounded_rect_contour_copy, + gsk_rounded_rect_contour_add_segment, + gsk_rounded_rect_contour_get_winding, + gsk_rounded_rect_contour_get_stroke_bounds, + gsk_rounded_rect_contour_add_stroke, + gsk_rounded_rect_contour_offset, + gsk_rounded_rect_contour_reverse, + gsk_rounded_rect_contour_is_convex, +}; + +GskContour * +gsk_rounded_rect_contour_new (const GskRoundedRect *rect) +{ + GskRoundedRectContour *self; + + self = g_new0 (GskRoundedRectContour, 1); + + self->contour.klass = &GSK_ROUNDED_RECT_CONTOUR_CLASS; + + self->rect = *rect; + + return (GskContour *) self; +} + /* }}} */ /* {{{ API */ diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h index cf5bf5493c..3a2be372d9 100644 --- a/gsk/gskcontourprivate.h +++ b/gsk/gskcontourprivate.h @@ -40,6 +40,7 @@ typedef enum typedef struct _GskContour GskContour; GskContour * gsk_rect_contour_new (const graphene_rect_t *rect); +GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect); GskContour * gsk_circle_contour_new (const graphene_point_t *center, float radius, float start_angle, diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c index ff4c024398..d63a9ffdf6 100644 --- a/gsk/gskpathbuilder.c +++ b/gsk/gskpathbuilder.c @@ -367,60 +367,13 @@ void gsk_path_builder_add_rounded_rect (GskPathBuilder *builder, const GskRoundedRect *rect) { - const float weight = M_SQRT1_2; + GskContour *contour; g_return_if_fail (builder != NULL); g_return_if_fail (rect != NULL); - gsk_path_builder_move_to (builder, - rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, - rect->bounds.origin.y); - /* top */ - gsk_path_builder_line_to (builder, - rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width, - rect->bounds.origin.y); - /* topright corner */ - gsk_path_builder_conic_to (builder, - rect->bounds.origin.x + rect->bounds.size.width, - rect->bounds.origin.y, - rect->bounds.origin.x + rect->bounds.size.width, - rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height, - weight); - /* right */ - gsk_path_builder_line_to (builder, - rect->bounds.origin.x + rect->bounds.size.width, - rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height); - /* bottomright corner */ - gsk_path_builder_conic_to (builder, - rect->bounds.origin.x + rect->bounds.size.width, - rect->bounds.origin.y + rect->bounds.size.height, - rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width, - rect->bounds.origin.y + rect->bounds.size.height, - weight); - /* bottom */ - gsk_path_builder_line_to (builder, - rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width, - rect->bounds.origin.y + rect->bounds.size.height); - /* bottomleft corner */ - gsk_path_builder_conic_to (builder, - rect->bounds.origin.x, - rect->bounds.origin.y + rect->bounds.size.height, - rect->bounds.origin.x, - rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height, - weight); - /* left */ - gsk_path_builder_line_to (builder, - rect->bounds.origin.x, - rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height); - /* topleft corner */ - gsk_path_builder_conic_to (builder, - rect->bounds.origin.x, - rect->bounds.origin.y, - rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, - rect->bounds.origin.y, - weight); - /* done */ - gsk_path_builder_close (builder); + contour = gsk_rounded_rect_contour_new (rect); + gsk_path_builder_add_contour (builder, contour); } /**