Draw a smooth closed curve in WPF and C#

[smooth closed curve]

The post Draw a smooth curve in WPF and C# explains how you can create a series of Bézier curves to smoothly connect a group of points. This example shows how to modify that one to create a smooth closed curve.

The previous example almost creates a smooth closed curve. You might try repeating the first point at the end of the list of points to make the curve closed. If you do that, you do get a closed curve, but the way the code handles the start and end points means the result is not smooth.

Fortunately, it’s not too hard to modify the previous code to create a smooth closed curve. To do that, you need to make two main changes.

The first changes is to make the loop that creates Bézier curves run for one more iteration so it creates a final curve to close the loop.

The second change is to not treat the first and last points as special cases. Instead of making those points use themselves as their “neighbors,” the code should wrap around to the other end of the point array to find neighbors. For example, the neighbors of the first point should be the last point and the second point. Similarly the neighbors of the last point should be the second-to-last point and the first point.

The following code shows the new method that creates the curves’ control points. The changes to the previous version are highlighted in blue.

// Make an array containing Bezier curve points and control points.
private Point[] MakeClosedCurvePoints(Point[] points, double tension)
    if (points.Length < 2) return null;
    double control_scale = tension / 0.5 * 0.175;

    // Make a list containing the points and
    // appropriate control points.
    List<Point> result_points = new List<Point>();

    int num_points = points.Length;
    for (int i = 0; i < num_points; i++)
        // Get the point and its neighbors.
        Point pt_before = points[(i - 1 + num_points) % num_points];

        Point pt = points[i];
        Point pt_after = points[(i + 1) % num_points];

        Point pt_after2 = points[(i + 2) % num_points];

        double dx1 = pt_after.X - pt_before.X;
        double dy1 = pt_after.Y - pt_before.Y;

        Point p1 = points[i];
        Point p4 = pt_after;

        double dx = pt_after.X - pt_before.X;
        double dy = pt_after.Y - pt_before.Y;
        Point p2 = new Point(
            pt.X + control_scale * dx,
            pt.Y + control_scale * dy);

        dx = pt_after2.X - pt.X;
        dy = pt_after2.Y - pt.Y;
        Point p3 = new Point(
            pt_after.X - control_scale * dx,
            pt_after.Y - control_scale * dy);

        // Save points p2, p3, and p4.

    // Return the points.
    return result_points.ToArray();

This method’s for loop now loops over all of the points in the points array. The previous version stopped before the final point so it did not create a Bézier curve following the last point in the array. The new version does create that curve so the result is closed.

The blue code inside the loop adds or subtracts from the index of the current point much as before. This time, however, it uses the modulus operator % to make the new points’ indices wrap the ends of the points array if necessary. That lets it treat the first and last points just as it treats the others without making them special cases. The code finds each point’s neighboring points, so it can use them to find the locations of the corresponding control points.

The rest of the example is the same as the previous version with a few name changes. See the previous example to see additional details. Download the example to experiment with it.

Download Example   Follow me on Twitter   RSS feed   Donate

About RodStephens

Rod Stephens is a software consultant and author who has written more than 30 books and 250 magazine articles covering C#, Visual Basic, Visual Basic for Applications, Delphi, and Java.
This entry was posted in drawing, graphics, wpf and tagged , , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.