In this article

Path Information and Enumeration

In this article

The SKPath class defines several properties and methods that allow you to obtain information about the path. The Bounds and TightBounds properties (and related methods) obtain the metrical dimensions of a path. The Contains method lets you determine if a particular point is within a path.

It is sometimes useful to determine the total length of all the lines and curves that make up a path. Calculating this length is not an algorithmically simple task, so an entire class named PathMeasure is devoted to it.

It is also sometimes useful to obtain all the drawing operations and points that make up a path. At first, this facility might seem unnecessary: If your program has created the path, the program already knows the contents. However, you've seen that paths can also be created by path effects and by converting text strings into paths. You can also obtain all the drawing operations and points that make up these paths. One possibility is to apply an algorithmic transform to all the points, for example, to wrap text around a hemisphere:

Getting the Path Length

In the article Paths and Text you saw how to use the DrawTextOnPath method to draw a text string whose baseline follows the course of a path. But what if you want to size the text so that it fits the path precisely? Drawing text around a circle is easy because the circumference of a circle is simple to calculate. But the circumference of an ellipse or the length of a Bézier curve is not so simple.

The PathLengthPage.xaml.cs code-behind file allows you to move four touch points to define the end points and control points of a cubic Bézier curve. Three fields define a text string, an SKPaint object, and a calculated width of the text:

The Length property of the newly created SKPathMeasure object obtains the length of the path. The path length is divided by the baseTextWidth value (which is the width of the text based on a text size of 10) and then multiplied by the base text size of 10. The result is a new text size for displaying the text along that path:

As the Bézier curve gets longer or shorter, you can see the text size change.

Traversing the Path

SKPathMeasure can do more than just measure the length of the path. For any value between zero and the path length, an SKPathMeasure object can obtain the position on the path, and the tangent to the path curve at that point. The tangent is available as a vector in the form of an SKPoint object, or as a rotation encapsulated in an SKMatrix object. Here are the methods of SKPathMeasure that obtain this information in varied and flexible ways:

The class contains the standard overrides of the OnAppearing and OnDisappearing methods for animation. The PaintSurface handler creates the path for the half-pipe and then draws it. An SKPathMeasure object is then created based on this path:

The PaintSurface handler calculates a value of t that goes from 0 to 1 every five seconds. It then uses the Math.Cos function to convert that to a value of t that ranges from 0 to 1 and back to 0, where 0 corresponds to the unicycle at the beginning on the top left, while 1 corresponds to the unicycle at the top right. The cosine function causes the speed to be slowest at the top of the pipe and fastest at the bottom.

Notice that this value of t must be multiplied by the path length for the first argument to GetMatrix. The matrix is then applied to the SKCanvas object for drawing the unicycle path.

Enumerating the Path

Two embedded classes of SKPath allow you to enumerate the contents of path. These classes are SKPath.Iterator and SKPath.RawIterator. The two classes are very similar, but SKPath.Iterator can eliminate elements in the path with a zero length, or close to a zero length. The RawIterator is used in the example below.

You can obtain an object of type SKPath.RawIterator by calling the CreateRawIterator method of SKPath. Enumerating through the path is accomplished by repeatedly calling the Next method. Pass to it an array of four SKPoint values:

The Next method returns a member of the SKPathVerb enumeration type. These values indicate the particular drawing command in the path. The number of valid points inserted in the array depends on this verb:

Move with a single point

Line with two points

Cubic with four points

Quad with three points

Conic with three points (and also call the ConicWeight method for the weight)

Close with one point

Done

The Done verb indicates that the path enumeration is complete.

Notice that there are no Arc verbs. This indicates that all arcs are converted into Bézier curves when added to the path.

Some of the information in the SKPoint array is redundant. For example, if a Move verb is followed by a Line verb, then the first of the two points that accompany the Line is the same as the Move point. In practice, this redundancy is very helpful. When you get a Cubic verb, it is accompanied by all four points that define the cubic Bézier curve. You don't need to retain the current position established by the previous verb.

The problematic verb, however, is Close. This command draws a straight line from the current position to the beginning of the contour established earlier by the Move command. Ideally, the Close verb should provide these two points rather than just one point. What's worse is that the point accompanying the Close verb is always (0, 0). When you enumerate through a path, you'll probably need to retain the Move point and the current position.

Enumerating, Flattening, and Malforming

It is sometimes desirable to apply an algorithmic transform to a path to malform it in some way:

Most of these letters consist of straight lines, yet these straight lines have apparently been twisted into curves. How is this possible?

The key is that the original straight lines are broken into a series of smaller straight lines. These individual smaller straight lines can then be manipulated in different ways to form a curve.

To help with this process, the SkiaSharpFormsDemos sample contains a static PathExtensions class with an Interpolate method that breaks down a straight line into numerous short lines that are only one unit in length. In addition, the class contains several methods that convert the three types of Bézier curves into a series of tiny straight lines that approximate the curve. (The parametric formulas were presented in the article Three Types of Bézier Curves.) This process is called flattening the curve:

All these methods are referenced from the extension method CloneWithTransform also included in this class and shown below. This method clones a path by enumerating the path commands and constructing a new path based on the data. However, the new path consists only of MoveTo and LineTo calls. All the curves and straight lines are reduced to a series of tiny lines.

When calling CloneWithTransform, you pass to the method a Func<SKPoint, SKPoint>, which is a function with an SKPaint parameter that returns an SKPoint value. This function is called for every point to apply a custom algorithmic transform:

Because the cloned path is reduced to tiny straight lines, the transform function has the capability of converting straight lines to curves.

Notice that the method retains the first point of each contour in the variable called firstPoint and the current position after each drawing command in the variable lastPoint. These variables are necessary to construct the final closing line when a Close verb is encountered.

The GlobularText sample uses this extension method to seemingly wrap text around a hemisphere in a 3D effect:

The GlobularTextPage class constructor performs this transform. It creates an SKPaint object for the text, and then obtains an SKPath object from the GetTextPath method. This is the path passed to the CloneWithTransform extension method along with a transform function:

The transform function first calculates two values named longitude and latitude that range from –π/2 at the top and left of the text, to π/2 at the right and bottom of the text. The range of these values isn't visually satisfactory, so they are reduced by multiplying by 0.75. (Try the code without those adjustments. The text becomes too obscure at the north and south poles, and too thin at the sides.) These three-dimensional spherical coordinates are converted to two-dimensional x and y coordinates by standard formulas.

The new path is stored as a field. The PaintSurface handler then merely needs to center and scale the path to display it on the screen: