Add quadratic curves

This commit is contained in:
Matthias Clasen
2023-06-30 07:17:45 -04:00
parent 321f6a2ab4
commit d705f267c1
14 changed files with 408 additions and 16 deletions

View File

@@ -246,6 +246,10 @@ build_path (GskPathOperation op,
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;

View File

@@ -122,6 +122,14 @@ gtk_path_transform_op (GskPathOperation op,
}
break;
case GSK_PATH_QUAD:
{
graphene_point_t res[2];
gtk_path_transform_point (transform->measure, &pts[1], &transform->offset, transform->scale, &res[0]);
gtk_path_transform_point (transform->measure, &pts[2], &transform->offset, transform->scale, &res[1]);
gsk_path_builder_quad_to (transform->builder, res[0].x, res[0].y, res[1].x, res[1].y);
}
break;
case GSK_PATH_CUBIC:
{
graphene_point_t res[3];

View File

@@ -982,6 +982,13 @@ gsk_standard_contour_print (const GskContour *contour,
_g_string_append_point (string, &pt[1]);
break;
case GSK_PATH_QUAD:
g_string_append (string, " Q ");
_g_string_append_point (string, &pt[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[2]);
break;
case GSK_PATH_CUBIC:
g_string_append (string, " C ");
_g_string_append_point (string, &pt[1]);

View File

@@ -218,6 +218,225 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = {
gsk_line_curve_get_start_end_tangent
};
/** QUADRATIC **/
static void
gsk_quad_curve_init_from_points (GskQuadCurve *self,
const graphene_point_t pts[3])
{
self->op = GSK_PATH_QUAD;
self->has_coefficients = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 3);
}
static void
gsk_quad_curve_init (GskCurve *curve,
gskpathop op)
{
GskQuadCurve *self = &curve->quad;
gsk_quad_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_quad_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight)
{
GskQuadCurve *self = &curve->quad;
g_assert (n_pts == 3);
gsk_quad_curve_init_from_points (self, pts);
}
static void
gsk_quad_curve_ensure_coefficients (const GskQuadCurve *curve)
{
GskQuadCurve *self = (GskQuadCurve *) curve;
const graphene_point_t *pts = self->points;
if (self->has_coefficients)
return;
self->coeffs[2] = pts[0];
self->coeffs[1] = GRAPHENE_POINT_INIT (2 * (pts[1].x - pts[0].x),
2 * (pts[1].y - pts[0].y));
self->coeffs[0] = GRAPHENE_POINT_INIT (pts[2].x - 2 * pts[1].x + pts[0].x,
pts[2].y - 2 * pts[1].y + pts[0].y);
self->has_coefficients = TRUE;
}
static void
gsk_quad_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
GskQuadCurve *self = (GskQuadCurve *) &curve->quad;
const graphene_point_t *c = self->coeffs;
gsk_quad_curve_ensure_coefficients (self);
*pos = GRAPHENE_POINT_INIT ((c[0].x * t + c[1].x) * t + c[2].x,
(c[0].y * t + c[1].y) * t + c[2].y);
}
static void
gsk_quad_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
GskQuadCurve *self = (GskQuadCurve *) &curve->quad;
const graphene_point_t *c = self->coeffs;
gsk_quad_curve_ensure_coefficients (self);
graphene_vec2_init (tangent,
2.0f * c[0].x * t + c[1].x,
2.0f * c[0].y * t + c[1].y);
graphene_vec2_normalize (tangent, tangent);
}
static void
gsk_quad_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
GskQuadCurve *self = (GskQuadCurve *) &curve->quad;
const graphene_point_t *pts = self->points;
graphene_point_t ab, bc;
graphene_point_t final;
graphene_point_interpolate (&pts[0], &pts[1], progress, &ab);
graphene_point_interpolate (&pts[1], &pts[2], progress, &bc);
graphene_point_interpolate (&ab, &bc, progress, &final);
if (start)
gsk_quad_curve_init_from_points (&start->quad, (graphene_point_t[3]) { pts[0], ab, final });
if (end)
gsk_quad_curve_init_from_points (&end->quad, (graphene_point_t[3]) { final, bc, pts[2] });
}
static void
gsk_quad_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
GskCurve tmp;
gsk_quad_curve_split (curve, start, NULL, &tmp);
gsk_quad_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_quad_curve_too_curvy (const GskQuadCurve *self,
float tolerance)
{
const graphene_point_t *pts = self->points;
float dx, dy;
dx = fabs (pts[1].x / 2 - (pts[0].x + pts[2].x) / 4);
dy = fabs (pts[1].y / 2 - (pts[0].y + pts[2].y) / 4);
return MAX (dx, dy) > tolerance;
}
static gboolean
gsk_quad_curve_decompose_step (const GskCurve *curve,
float start_progress,
float end_progress,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskQuadCurve *self = &curve->quad;
GskCurve left, right;
float mid_progress;
if (!gsk_quad_curve_too_curvy (self, tolerance))
return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data);
if (end_progress - start_progress <= MIN_PROGRESS)
return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data);
gsk_quad_curve_split ((const GskCurve *) self, 0.5, &left, &right);
mid_progress = (start_progress + end_progress) / 2;
return gsk_quad_curve_decompose_step (&left, start_progress, mid_progress, tolerance, add_line_func, user_data)
&& gsk_quad_curve_decompose_step (&right, mid_progress, end_progress, tolerance, add_line_func, user_data);
}
static gboolean
gsk_quad_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
return gsk_quad_curve_decompose_step (curve, 0.0, 1.0, tolerance, add_line_func, user_data);
}
static gskpathop
gsk_quad_curve_pathop (const GskCurve *curve)
{
const GskQuadCurve *self = &curve->quad;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_quad_curve_get_start_point (const GskCurve *curve)
{
const GskQuadCurve *self = &curve->quad;
return &self->points[0];
}
static const graphene_point_t *
gsk_quad_curve_get_end_point (const GskCurve *curve)
{
const GskQuadCurve *self = &curve->quad;
return &self->points[2];
}
static void
gsk_quad_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskQuadCurve *self = &curve->quad;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_quad_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskQuadCurve *self = &curve->quad;
get_tangent (&self->points[1], &self->points[2], tangent);
}
static const GskCurveClass GSK_QUAD_CURVE_CLASS = {
gsk_quad_curve_init,
gsk_quad_curve_init_foreach,
gsk_quad_curve_get_point,
gsk_quad_curve_get_tangent,
gsk_quad_curve_split,
gsk_quad_curve_segment,
gsk_quad_curve_decompose,
gsk_quad_curve_pathop,
gsk_quad_curve_get_start_point,
gsk_quad_curve_get_end_point,
gsk_quad_curve_get_start_tangent,
gsk_quad_curve_get_end_tangent
};
/** CUBIC **/
static void
@@ -870,6 +1089,7 @@ get_class (GskPathOperation op)
const GskCurveClass *klasses[] = {
[GSK_PATH_CLOSE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_QUAD] = &GSK_QUAD_CURVE_CLASS,
[GSK_PATH_CUBIC] = &GSK_CUBIC_CURVE_CLASS,
[GSK_PATH_CONIC] = &GSK_CONIC_CURVE_CLASS,
};

View File

@@ -30,6 +30,7 @@ typedef gpointer gskpathop;
typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskQuadCurve GskQuadCurve;
typedef struct _GskCubicCurve GskCubicCurve;
typedef struct _GskConicCurve GskConicCurve;
@@ -42,6 +43,17 @@ struct _GskLineCurve
graphene_point_t points[2];
};
struct _GskQuadCurve
{
GskPathOperation op;
gboolean has_coefficients;
graphene_point_t points[3];
graphene_point_t coeffs[3];
};
struct _GskCubicCurve
{
GskPathOperation op;
@@ -72,6 +84,7 @@ union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskQuadCurve quad;
GskCubicCurve cubic;
GskConicCurve conic;
};

View File

@@ -257,6 +257,9 @@ typedef enum {
* @GSK_PATH_CLOSE: A close operation ending the current contour with
* a line back to the starting point. Two points describe the start
* and end of the line.
* @GSK_PATH_QUAD: A curve-to operation describing a quadratic Bézier curve
* with 3 points describing the start point, the control point
* and the end point of the curve.
* @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve
* with 4 points describing the start point, the two control points
* and the end point of the curve.
@@ -272,6 +275,7 @@ typedef enum {
GSK_PATH_MOVE,
GSK_PATH_CLOSE,
GSK_PATH_LINE,
GSK_PATH_QUAD,
GSK_PATH_CUBIC,
GSK_PATH_CONIC,
} GskPathOperation;

View File

@@ -229,6 +229,17 @@ gsk_path_to_cairo_add_op (GskPathOperation op,
cairo_line_to (cr, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
{
double x, y;
cairo_get_current_point (cr, &x, &y);
cairo_curve_to (cr,
1/3.f * x + 2/3.f * pts[1].x, 1/3.f * y + 2/3.f * pts[1].y,
2/3.f * pts[1].x + 1/3.f * pts[2].x, 2/3.f * pts[1].y + 1/3.f * pts[2].y,
pts[2].x, pts[2].y);
}
break;
case GSK_PATH_CUBIC:
cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
@@ -426,6 +437,7 @@ gsk_path_foreach_trampoline (GskPathOperation op,
case GSK_PATH_LINE:
return trampoline->func (op, pts, n_pts, weight, trampoline->user_data);
case GSK_PATH_QUAD:
case GSK_PATH_CUBIC:
{
GskCurve curve;
@@ -971,7 +983,7 @@ gsk_path_parse (const char *string)
case 'Q':
case 'q':
{
double x1, y1, x2, y2, xx1, yy1, xx2, yy2;
double x1, y1, x2, y2;
if (parse_coordinate_pair (&p, &x1, &y1) &&
parse_coordinate_pair (&p, &x2, &y2))
@@ -983,17 +995,13 @@ gsk_path_parse (const char *string)
x2 += x;
y2 += y;
}
xx1 = (x + 2.0 * x1) / 3.0;
yy1 = (y + 2.0 * y1) / 3.0;
xx2 = (x2 + 2.0 * x1) / 3.0;
yy2 = (y2 + 2.0 * y1) / 3.0;
if (_strchr ("zZ", prev_cmd))
{
gsk_path_builder_move_to (builder, x, y);
path_x = x;
path_y = y;
}
gsk_path_builder_cubic_to (builder, xx1, yy1, xx2, yy2, x2, y2);
gsk_path_builder_quad_to (builder, x1, y1, x2, y2);
prev_x1 = x1;
prev_y1 = y1;
x = x2;
@@ -1007,7 +1015,7 @@ gsk_path_parse (const char *string)
case 'T':
case 't':
{
double x1, y1, x2, y2, xx1, yy1, xx2, yy2;
double x1, y1, x2, y2;
if (parse_coordinate_pair (&p, &x2, &y2))
{
@@ -1026,17 +1034,13 @@ gsk_path_parse (const char *string)
x1 = x;
y1 = y;
}
xx1 = (x + 2.0 * x1) / 3.0;
yy1 = (y + 2.0 * y1) / 3.0;
xx2 = (x2 + 2.0 * x1) / 3.0;
yy2 = (y2 + 2.0 * y1) / 3.0;
if (_strchr ("zZ", prev_cmd))
{
gsk_path_builder_move_to (builder, x, y);
path_x = x;
path_y = y;
}
gsk_path_builder_cubic_to (builder, xx1, yy1, xx2, yy2, x2, y2);
gsk_path_builder_quad_to (builder, x1, y1, x2, y2);
prev_x1 = x1;
prev_y1 = y1;
x = x2;

View File

@@ -652,6 +652,67 @@ gsk_path_builder_rel_line_to (GskPathBuilder *self,
self->current_point.y + y);
}
/**
* gsk_path_builder_quad_to:
* @self: a #GskPathBuilder
* @x1: x coordinate of control point
* @y1: y coordinate of control point
* @x2: x coordinate of the end of the curve
* @y2: y coordinate of the end of the curve
*
* Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x2, @y2 with @x1, @y1 as the control point.
*
* After this, @x2, @y2 will be the new current point.
**/
void
gsk_path_builder_quad_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
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)
});
}
/**
* gsk_path_builder_rel_quad_to:
* @self: a #GskPathBuilder
* @x1: x offset of control point
* @y1: y offset of control point
* @x3: x offset of the end of the curve
* @y3: y offset of the end of the curve
*
* Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
* from the current point to @x2, @y2 with @x1, @y1 as the control point.
* All coordinates are given relative to the current point.
*
* This is the relative version of gsk_path_builder_quad_to().
**/
void
gsk_path_builder_rel_quad_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
gsk_path_builder_quad_to (self,
self->current_point.x + x1,
self->current_point.y + y1,
self->current_point.x + x2,
self->current_point.y + y2);
}
/**
* gsk_path_builder_cubic_to:
* @self: a #GskPathBuilder

View File

@@ -97,6 +97,18 @@ void gsk_path_builder_rel_line_to (GskPathBuilder
float x,
float y);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_quad_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_rel_quad_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2);
GDK_AVAILABLE_IN_ALL
void gsk_path_builder_cubic_to (GskPathBuilder *self,
float x1,
float y1,

View File

@@ -215,6 +215,7 @@ gsk_path_dash_foreach (GskPathOperation op,
G_GNUC_FALLTHROUGH;
case GSK_PATH_LINE:
case GSK_PATH_QUAD:
case GSK_PATH_CUBIC:
case GSK_PATH_CONIC:
gsk_curve_init_foreach (&self->curve, op, pts, n_pts, weight);

View File

@@ -89,6 +89,9 @@ gsk_pathop_foreach (gskpathop pop,
case GSK_PATH_LINE:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data);
case GSK_PATH_QUAD:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data);
case GSK_PATH_CUBIC:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data);
@@ -124,6 +127,10 @@ gsk_path_builder_pathop_to (GskPathBuilder *builder,
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;
@@ -158,6 +165,10 @@ gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;

View File

@@ -32,6 +32,17 @@ init_random_curve (GskCurve *curve)
}
break;
case GSK_PATH_QUAD:
{
graphene_point_t p[3];
init_random_point (&p[0]);
init_random_point (&p[1]);
init_random_point (&p[2]);
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_QUAD, p));
}
break;
case GSK_PATH_CUBIC:
{
graphene_point_t p[4];

View File

@@ -44,6 +44,10 @@ build_path (GskPathOperation op,
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
break;

View File

@@ -248,7 +248,7 @@ add_standard_contour (GskPathBuilder *builder)
n = g_test_rand_int_range (1, 20);
for (i = 0; i < n; i++)
{
switch (g_test_rand_int_range (0, 6))
switch (g_test_rand_int_range (0, 8))
{
case 0:
gsk_path_builder_line_to (builder,
@@ -263,6 +263,22 @@ add_standard_contour (GskPathBuilder *builder)
break;
case 2:
gsk_path_builder_quad_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 3:
gsk_path_builder_rel_quad_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 4:
gsk_path_builder_cubic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
@@ -272,7 +288,7 @@ add_standard_contour (GskPathBuilder *builder)
g_test_rand_double_range (-1000, 1000));
break;
case 3:
case 5:
gsk_path_builder_rel_cubic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
@@ -282,7 +298,7 @@ add_standard_contour (GskPathBuilder *builder)
g_test_rand_double_range (-1000, 1000));
break;
case 4:
case 6:
gsk_path_builder_conic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
@@ -291,7 +307,7 @@ add_standard_contour (GskPathBuilder *builder)
random_weight ());
break;
case 5:
case 7:
gsk_path_builder_rel_conic_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
@@ -381,6 +397,13 @@ path_operation_print (const PathOperation *p,
_g_string_append_point (string, &p->pts[1]);
break;
case GSK_PATH_QUAD:
g_string_append (string, " Q ");
_g_string_append_point (string, &p->pts[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &p->pts[2]);
break;
case GSK_PATH_CUBIC:
g_string_append (string, " C ");
_g_string_append_point (string, &p->pts[1]);
@@ -425,6 +448,10 @@ path_operation_equal (const PathOperation *p1,
case GSK_PATH_CLOSE:
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon);
case GSK_PATH_QUAD:
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon);
case GSK_PATH_CUBIC:
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
@@ -1063,6 +1090,11 @@ rotate_path_cb (GskPathOperation op,
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);