diff --git a/docs/reference/gsk/gsk4.toml.in b/docs/reference/gsk/gsk4.toml.in index be0d478958..cbc6f79b44 100644 --- a/docs/reference/gsk/gsk4.toml.in +++ b/docs/reference/gsk/gsk4.toml.in @@ -43,6 +43,8 @@ content_images = [ "images/caps-light.png", "images/cubic-dark.png", "images/cubic-light.png", + "images/curvature-dark.png", + "images/curvature-light.png", "images/directions-dark.png", "images/directions-light.png", "images/fill-even-odd.png", diff --git a/docs/reference/gsk/images/curvature-dark.png b/docs/reference/gsk/images/curvature-dark.png new file mode 100644 index 0000000000..2a3a985751 Binary files /dev/null and b/docs/reference/gsk/images/curvature-dark.png differ diff --git a/docs/reference/gsk/images/curvature-light.png b/docs/reference/gsk/images/curvature-light.png new file mode 100644 index 0000000000..1055f324e0 Binary files /dev/null and b/docs/reference/gsk/images/curvature-light.png differ diff --git a/docs/reference/gsk/images/curvature.svg b/docs/reference/gsk/images/curvature.svg new file mode 100644 index 0000000000..3f744a8b27 --- /dev/null +++ b/docs/reference/gsk/images/curvature.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + diff --git a/docs/reference/gsk/paths.md b/docs/reference/gsk/paths.md index 9a921313b7..dd672f888b 100644 --- a/docs/reference/gsk/paths.md +++ b/docs/reference/gsk/paths.md @@ -80,8 +80,8 @@ to the path, which can be selected with a [enum@Gsk.FillRule] value.
The same path, filled with GSK_FILL_RULE_EVEN_ODD
-To fill a path, use [gtk_snapshot_append_fill](../gtk4/method.Snapshot.append_fill.html) -or the more general [gtk_snapshot_push_fill](../gtk4/method.Snapshot.push_fill.html). +To fill a path, use [gtk_snapshot_append_fill()](../gtk4/method.Snapshot.append_fill.html) +or the more general [gtk_snapshot_push_fill()](../gtk4/method.Snapshot.push_fill.html). Alternatively, a path can be **_stroked_**, which means to emulate drawing with an idealized pen along the path. The result of stroking a path is another @@ -106,8 +106,8 @@ of line joins and line caps, and a dash pattern. To stroke a path, use -[gtk_snapshot_append_stroke](../gtk4/method.Snapshot.append_stroke.html) -or [gtk_snapshot_push_stroke](../gtk4/method.Snapshot.push_stroke.html). +[gtk_snapshot_append_stroke()](../gtk4/method.Snapshot.append_stroke.html) +or [gtk_snapshot_push_stroke()](../gtk4/method.Snapshot.push_stroke.html). ## Hit testing @@ -132,3 +132,19 @@ To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point], [method@Gsk To query properties of the path at a point, use [method@Gsk.PathPoint.get_position], [method@Gsk.PathPoint.get_tangent], [method@Gsk.PathPoint.get_rotation] and [method@Gsk.PathPoint.get_curvature]. + +## Going beyond GskPath + +Lots of powerful functionality can be implemented for paths: + +- Finding intersections +- Offsetting curves +- Molding curves (making them pass through a given point) + +GSK does not provide API for all of these, but it does offer a way to get at +the underlying Bézier curves, so you can implement such functionality yourself. +You can use [method@Gsk.Path.foreach] to iterate over the operations of the +path, and get the points needed to reconstruct or modify the path piece by piece. + +See e.g. the [Primer on Bézier curves](https://pomax.github.io/bezierinfo/) +for inspiration of useful things to explore. diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index a8ec1f60d3..0fb6ad32ba 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -76,6 +76,7 @@ struct _GskContourClass graphene_vec2_t *tangent); float (* get_curvature) (const GskContour *contour, GskRealPathPoint *point, + GskPathDirection direction, graphene_point_t *center); void (* add_segment) (const GskContour *contour, GskPathBuilder *builder, @@ -530,16 +531,35 @@ gsk_standard_contour_get_tangent (const GskContour *contour, static float gsk_standard_contour_get_curvature (const GskContour *contour, GskRealPathPoint *point, + GskPathDirection direction, graphene_point_t *center) { GskStandardContour *self = (GskStandardContour *) contour; GskCurve curve; + gsize idx; + float t; if (G_UNLIKELY (point->idx == 0)) return 0; - gsk_curve_init (&curve, self->ops[point->idx]); - return gsk_curve_get_curvature (&curve, point->t, center); + idx = point->idx; + t = point->t; + + if (t == 0 && idx > 1 && + (direction == GSK_PATH_FROM_START || direction == GSK_PATH_TO_START)) + { + idx--; + t = 1; + } + else if (t == 1 && idx + 1 < self->n_ops && + (direction == GSK_PATH_FROM_END || direction == GSK_PATH_TO_END)) + { + idx++; + t = 0; + } + + gsk_curve_init (&curve, self->ops[idx]); + return gsk_curve_get_curvature (&curve, t, center); } static void @@ -802,9 +822,10 @@ gsk_contour_get_tangent (const GskContour *self, float gsk_contour_get_curvature (const GskContour *self, GskRealPathPoint *point, + GskPathDirection direction, graphene_point_t *center) { - return self->klass->get_curvature (self, point, center); + return self->klass->get_curvature (self, point, direction, center); } void diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h index 2e5bf7ee5d..7193e4bf1f 100644 --- a/gsk/gskcontourprivate.h +++ b/gsk/gskcontourprivate.h @@ -72,6 +72,7 @@ void gsk_contour_get_tangent (const GskContou graphene_vec2_t *tangent); float gsk_contour_get_curvature (const GskContour *self, GskRealPathPoint *point, + GskPathDirection direction, graphene_point_t *center); void gsk_contour_add_segment (const GskContour *self, GskPathBuilder *builder, diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 1f8d5059d7..8d4c5994f5 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -367,7 +367,7 @@ gsk_path_is_closed (GskPath *self) * If the path is empty, `FALSE` is returned and @bounds are set to * graphene_rect_zero(). This is different from the case where the path * is a single point at the origin, where the @bounds will also be set to - * the zero rectangle but 0 will be returned. + * the zero rectangle but `TRUE` will be returned. * * Returns: `TRUE` if the path has bounds, `FALSE` if the path is known * to be empty and have no bounds. @@ -616,10 +616,17 @@ gsk_path_get_closest_point (GskPath *self, * * Calls @func for every operation of the path. * - * Note that this only approximates @self, because paths can contain - * optimizations for various specialized contours, and depending on - * the @flags, the path may be decomposed into simpler curves than - * the ones that it contained originally. + * Note that this may only approximate @self, because paths can contain + * optimizations for various specialized contours, and depending on the + * @flags, the path may be decomposed into simpler curves than the ones + * that it contained originally. + * + * This function serves two purposes: + * + * - When the @flags allow everything, it provides access to the raw, + * unmodified data of the path. + * - When the @flags disallow certain operations, it provides + * an approximation of the path using just the allowed operations. * * Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise. * diff --git a/gsk/gskpath.h b/gsk/gskpath.h index 5ea38c29e2..b386984e96 100644 --- a/gsk/gskpath.h +++ b/gsk/gskpath.h @@ -35,12 +35,13 @@ G_BEGIN_DECLS * @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations. * @GSK_PATH_FOREACH_ALLOW_ARC: Allow emission of `GSK_PATH_ARC` operations. * - * Flags that can be passed to gsk_path_foreach() to enable additional - * features. + * Flags that can be passed to gsk_path_foreach() to influence what + * kinds of operations the path is decomposed into. * - * By default, [method@Gsk.Path.foreach] will only emit a path with all operations - * flattened to straight lines to allow for maximum compatibility. The only - * operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`. + * By default, [method@Gsk.Path.foreach] will only emit a path with all + * operations flattened to straight lines to allow for maximum compatibility. + * The only operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and + * `GSK_PATH_CLOSE`. * * Since: 4.14 */ @@ -54,15 +55,23 @@ typedef enum /** * GskPathForeachFunc: - * @op: The operation to perform + * @op: The operation * @pts: The points of the operation * @n_pts: The number of points * @user_data: The user data provided with the function * - * Prototype of the callback to iterate throught the operations of + * Prototype of the callback to iterate through the operations of * a path. * - * Returns: %TRUE to continue evaluating the path, %FALSE to + * For each operation, the callback is given the @op itself, + * and the points that the operation is applied to in @pts. The + * @n_pts argument is somewhat redundant, since the number of points + * can be inferred from the operation. + * + * Each contour of the path starts with a @GSK_PATH_MOVE operation. + * Closed contours end with a @GSK_PATH_CLOSE operation. + * + * Returns: %TRUE to continue iterating the path, %FALSE to * immediately abort and not call the function again. */ typedef gboolean (* GskPathForeachFunc) (GskPathOperation op, diff --git a/gsk/gskpathpoint.c b/gsk/gskpathpoint.c index d1835743a1..28f481ae09 100644 --- a/gsk/gskpathpoint.c +++ b/gsk/gskpathpoint.c @@ -219,7 +219,8 @@ gsk_path_point_get_tangent (const GskPathPoint *point, * * This is a convenience variant of [method@Gsk.PathPoint.get_tangent] * that returns the angle between the tangent and the X axis. The angle - * can e.g. be used in [method@Gtk.Snapshot.rotate]. + * can e.g. be used in + * [gtk_snapshot_rotate()](../gtk4/method.Snapshot.rotate.html). * * Returns: the angle between the tangent and the X axis, in degrees * @@ -247,16 +248,29 @@ gsk_path_point_get_rotation (const GskPathPoint *point, * gsk_path_point_get_curvature: * @point: a `GskPathPoint` * @path: the path that @point is on + * @direction: the direction for which to return the curvature * @center: (out caller-allocates) (nullable): Return location for * the center of the osculating circle * * Calculates the curvature of the path at the point. * * Optionally, returns the center of the osculating circle as well. + * The curvature is the inverse of the radius of the osculating circle. * - * If the curvature is infinite (at line segments), zero is returned, - * and @center is not modified. + * Lines have a curvature of zero (indicating an osculating circle of + * infinite radius. In this case, the @center is not modified. * + * Note that certain points on a path may not have a single curvature, + * such as sharp turns. At such points, there are two curvatures -- + * the (limit of) the curvature of the path going into the point, + * and the (limit of) the curvature of the path coming out of it. + * The @direction argument lets you choose which one to get. + * + * + * + * Osculating circle + * + * Returns: The curvature of the path at the given point * * Since: 4.14 @@ -264,6 +278,7 @@ gsk_path_point_get_rotation (const GskPathPoint *point, float gsk_path_point_get_curvature (const GskPathPoint *point, GskPath *path, + GskPathDirection direction, graphene_point_t *center) { GskRealPathPoint *self = (GskRealPathPoint *) point; @@ -274,5 +289,5 @@ gsk_path_point_get_curvature (const GskPathPoint *point, g_return_val_if_fail (self->contour < gsk_path_get_n_contours (path), 0); contour = gsk_path_get_contour (path, self->contour); - return gsk_contour_get_curvature (contour, self, center); + return gsk_contour_get_curvature (contour, self, direction, center); } diff --git a/gsk/gskpathpoint.h b/gsk/gskpathpoint.h index a2164645d8..13711b768a 100644 --- a/gsk/gskpathpoint.h +++ b/gsk/gskpathpoint.h @@ -75,6 +75,7 @@ float gsk_path_point_get_rotation (const GskPathPoint *poin GDK_AVAILABLE_IN_4_14 float gsk_path_point_get_curvature (const GskPathPoint *point, GskPath *path, + GskPathDirection direction, graphene_point_t *center); diff --git a/testsuite/gsk/path-special-cases.c b/testsuite/gsk/path-special-cases.c index 30230ba6b9..8627dca036 100644 --- a/testsuite/gsk/path-special-cases.c +++ b/testsuite/gsk/path-special-cases.c @@ -473,7 +473,7 @@ test_path_point (void) gsk_path_point_get_position (&point, path, &pos); gsk_path_point_get_tangent (&point, path, GSK_PATH_FROM_START, &t1); gsk_path_point_get_tangent (&point, path, GSK_PATH_TO_END, &t2); - curvature = gsk_path_point_get_curvature (&point, path, ¢er); + curvature = gsk_path_point_get_curvature (&point, path, GSK_PATH_FROM_START, ¢er); g_assert_true (graphene_point_equal (&pos, &GRAPHENE_POINT_INIT (100, 100))); g_assert_true (graphene_vec2_equal (&t1, graphene_vec2_y_axis ()));