Add gsk_path_stroke

Implement stroking for paths.

The current implementation does not try to handle short
segments in the vicinity of sharp joins in any special
way, so there can be some artifacts in that situation.

There are special-case implementations for rectangle
and circle contours, since in many cases the outlines
of these contours just consist of two of the same
shapes.
This commit is contained in:
Matthias Clasen
2023-07-21 23:51:53 -04:00
parent c1fd77ed45
commit c460a1a002
7 changed files with 2932 additions and 0 deletions

View File

@@ -94,6 +94,13 @@ struct _GskContourClass
float (* get_distance) (const GskContour *contour,
GskPathPoint *point,
gpointer measure_data);
void (* add_stroke) (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke);
void (* offset) (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke);
};
static gsize
@@ -727,6 +734,23 @@ gsk_standard_contour_get_winding (const GskContour *contour,
return winding;
}
static void
gsk_standard_contour_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke)
{
gsk_contour_default_add_stroke (contour, builder, stroke);
}
static void
gsk_standard_contour_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke)
{
gsk_contour_default_offset (contour, builder, distance, stroke);
}
static gboolean
gsk_standard_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
@@ -922,6 +946,8 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
gsk_standard_contour_add_segment,
gsk_standard_contour_get_point,
gsk_standard_contour_get_distance,
gsk_standard_contour_add_stroke,
gsk_standard_contour_offset,
};
/* You must ensure the contour has enough size allocated,
@@ -1385,6 +1411,68 @@ gsk_rect_contour_get_winding (const GskContour *contour,
return 0;
}
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,
GskStroke *stroke)
{
const GskRectContour *self = (const GskRectContour *) contour;
if (stroke_is_simple (stroke))
{
graphene_rect_t rect;
graphene_rect_init (&rect, self->x, self->y, self->width, self->height);
graphene_rect_inset (&rect, stroke->line_width / 2, stroke->line_width / 2);
gsk_path_builder_add_rect (builder, &rect);
graphene_rect_inset (&rect, - stroke->line_width, - stroke->line_width);
rect.origin.x += rect.size.width;
rect.size.width = - rect.size.width;
gsk_path_builder_add_rect (builder, &rect);
}
else
gsk_contour_default_add_stroke (contour, builder, stroke);
}
static void
gsk_rect_contour_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke)
{
const GskRectContour *self = (const GskRectContour *) contour;
if (stroke_is_simple (stroke))
{
graphene_rect_t rect;
graphene_rect_init (&rect, self->x, self->y, self->width, self->height);
graphene_rect_inset (&rect, distance, distance);
gsk_path_builder_add_rect (builder, &rect);
}
else
gsk_contour_default_offset (contour, builder, distance, stroke);
}
static gboolean
gsk_rect_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
@@ -1475,6 +1563,8 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS =
gsk_rect_contour_add_segment,
gsk_rect_contour_get_point,
gsk_rect_contour_get_distance,
gsk_rect_contour_add_stroke,
gsk_rect_contour_offset,
};
GskContour *
@@ -1822,6 +1912,54 @@ gsk_circle_contour_get_winding (const GskContour *contour,
return 0;
}
static void
gsk_circle_contour_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (stroke->dash_length == 0 &&
fabs (self->start_angle - self->end_angle) >= 360)
{
GskContour *c;
c = gsk_circle_contour_new (&self->center, self->radius + stroke->line_width / 2,
self->start_angle,
self->end_angle);
gsk_path_builder_add_contour (builder, c);
c = gsk_circle_contour_new (&self->center, self->radius - stroke->line_width / 2,
self->end_angle,
self->start_angle);
gsk_path_builder_add_contour (builder, c);
}
else
gsk_contour_default_add_stroke (contour, builder, stroke);
}
static void
gsk_circle_contour_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (stroke->dash_length == 0 &&
fabs (self->start_angle - self->end_angle) >= 360)
{
GskContour *c;
c = gsk_circle_contour_new (&self->center, self->radius + distance,
self->start_angle,
self->end_angle);
gsk_path_builder_add_contour (builder, c);
}
else
gsk_contour_default_offset (contour, builder, distance, stroke);
}
typedef struct
{
float angle;
@@ -1931,6 +2069,8 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
gsk_circle_contour_add_segment,
gsk_circle_contour_get_point,
gsk_circle_contour_get_distance,
gsk_circle_contour_add_stroke,
gsk_circle_contour_offset,
};
GskContour *
@@ -2327,6 +2467,60 @@ gsk_rounded_rect_contour_get_winding (const GskContour *contour,
return 0;
}
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,
GskStroke *stroke)
{
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 gboolean
gsk_rounded_rect_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
@@ -2424,6 +2618,8 @@ static const GskContourClass GSK_ROUNDED_RECT_CONTOUR_CLASS =
gsk_rounded_rect_contour_add_segment,
gsk_rounded_rect_contour_get_point,
gsk_rounded_rect_contour_get_distance,
gsk_rounded_rect_contour_add_stroke,
gsk_rounded_rect_contour_offset,
};
GskContour *
@@ -2604,6 +2800,23 @@ gsk_contour_get_distance (const GskContour *self,
return self->klass->get_distance (self, point, measure_data);
}
void
gsk_contour_add_stroke (const GskContour *self,
GskPathBuilder *builder,
GskStroke *stroke)
{
self->klass->add_stroke (self, builder, stroke);
}
void
gsk_contour_offset (const GskContour *self,
GskPathBuilder *builder,
float distance,
GskStroke *stroke)
{
self->klass->offset (self, builder, distance, stroke);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@@ -112,4 +112,22 @@ gboolean gsk_contour_dash (const GskContou
GskPathForeachFunc func,
gpointer user_data);
void gsk_contour_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke);
void gsk_contour_default_add_stroke (const GskContour *contour,
GskPathBuilder *builder,
GskStroke *stroke);
void gsk_contour_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke);
void gsk_contour_default_offset (const GskContour *contour,
GskPathBuilder *builder,
float distance,
GskStroke *stroke);
G_END_DECLS

View File

@@ -39,6 +39,7 @@
* The [struct@Gsk.PathBuilder] structure is meant to help in this endeavor.
*/
struct _GskPath
{
/*< private >*/
@@ -1324,3 +1325,58 @@ error:
return NULL;
}
/**
* gsk_path_stroke:
* @self: a `GskPath`
* @stroke: stroke parameters
*
* Create a new path that follows the outline of the area
* that would be affected by stroking along @self with
* the given stroke parameters.
*
* Returns: a new `GskPath`
*/
GskPath *
gsk_path_stroke (GskPath *self,
GskStroke *stroke)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
for (int i = 0; i < self->n_contours; i++)
gsk_contour_add_stroke (gsk_path_get_contour (self, i), builder, stroke);
return gsk_path_builder_free_to_path (builder);
}
/**
* gsk_path_offset:
* @self: a `GskPath`
* @distance: the distance to offset the path by
* @stroke: stroke parameters
*
* Create a new path that is offset from @self by @distance.
*
* The offset can be positive (to the right) or negative
* (to the left). The line-join and miter-limit of the @stroke
* influence how joins between the offset path segments
* are made.
*
* Returns: a new `GskPath`
*/
GskPath *
gsk_path_offset (GskPath *self,
float distance,
GskStroke *stroke)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
for (int i = 0; i < self->n_contours; i++)
gsk_contour_offset (gsk_path_get_contour (self, i), builder, distance, stroke);
return gsk_path_builder_free_to_path (builder);
}

View File

@@ -124,6 +124,15 @@ gboolean gsk_path_dash (GskPath
GskPathForeachFunc func,
gpointer user_data);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_stroke (GskPath *self,
GskStroke *stroke);
GDK_AVAILABLE_IN_ALL
GskPath * gsk_path_offset (GskPath *self,
float distance,
GskStroke *stroke);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
G_END_DECLS

View File

@@ -20,6 +20,7 @@
#include "config.h"
#include "gskpathmeasure.h"
#include "gskpathbuilder.h"
#include "gskpathbuilder.h"
#include "gskpathpointprivate.h"

2634
gsk/gskpathstroke.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -30,6 +30,7 @@ gsk_public_sources = files([
'gskpathbuilder.c',
'gskpathmeasure.c',
'gskpathpoint.c',
'gskpathstroke.c',
'gskrenderer.c',
'gskrendernode.c',
'gskrendernodeimpl.c',