PETZOLD BOOK BLOG

Charles Petzold on writing books, reading books, and exercising the internal UTM


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

Canonical Splines in WPF and Silverlight

January 22, 2009
New York, N.Y.

Windows Forms has two methods named DrawCurve and DrawClosedCurve that draw canonical (aka cardinal) splines. Like the more familiar Bézier spline, the canonical spline is a cubic polynomial; however unlike the Bézier, the canonical spline passes through each of its control points, and the amount of curvature is governed by a tension parameter.

A complete derivation of the cubic polynomials for the canonical spline can be found in my Windows Forms books Programming Microsoft Windows with C#, pages 645-646, and Programming Microsoft Windows with Microsoft Visual Basic .NET, pages 638-639, but here's the simple version:

If four consective control points are pt0, pt1, pt2, and pt3, then the canonical spline curve between pt1 and pt2 has a slope at pt1 equal to the product of the tension and the slope of the straight line between pt0 and pt2. (If pt0 is not available, which happens in the first segment of an unclosed curve, then pt1 is used instead for that slope calculation.) The slope of the curve at pt2 is equal to the tension times the slope of the straight line between pt1 and pt3, or between pt1 and pt2 if pt3 is not available. This information, together with two vital pieces of information in the first paragraph of this blog entry, is actually all you need to derive the cubic parametric formulas for the canonical spline.

What's most peculiar about the canonical spline is that each segment is based not only on the start point and end point of the segment (in my example, pt1 and pt2) but also on the neighboring points (pt0 and pt2). I don't know why the canonical spline didn't become part of the Windows Presentation Foundation or Silverlight, but I have a hunch that the problem is related to this peculiarity: Proper integration of the canonical spline in WPF and Silverlight would require PathSegment derivatives named something like CanonicalSplineSegment and PolyCanonicalSplineSegment. But these classes would have a very different behavior from the other PathSegment derivatives because they would require access to neighboring points outside the actual segment. Perhaps there was just no good way to do this.

At any rate, if you just need to use a canonical spline by itself, I've created WPF and Silverlight classes named CanonicalSpline that help you out. (The WPF version is derived from Shape; the Silverlight version is derived from — I'll give you one guess — UserControl.) Here's a demo of the Silverlight version:

CanonicalSplineDemo.html

I've provided a scrollbar that lets you set the tension to a value between –10 and 10. The default value is 0.5; if you set it to 0, the curve degenerates into straight lines. At values greater than 1, the curve begins exhibiting excessive curviness; at negative values, the curve takes roundabout paths between each pair of points. At a value of –9, you'll get the display shown on page 642 of the C# WinForms book (634 of the VB version):

You can also drag the actual control points around, and add new control points or remove points. A checkbox lets you close the spline. Notice that closing the spline results in a new segment being drawn from the last point to the first point, but it also affects the previous first and last segments, because these are now influenced by the last and first control points, respectively.

You can also fill the interior (notice how filling doesn't require closing the spline) and choose between the EvenOdd and Nonzero fill rules. The easiest way to see the difference is to arrange five points in a star:

The EvenOdd fill rule leaves the center "pentagon" uncolored. The Nonzero rule colors it.

I like to set up several points, close the curve, fill it with the EvenOdd rule, and then move the scrollbar back and forth. It keeps me entertained for minutes.

This demo program doesn't show one of the features of the canonical spline code I've created here: You can actually specify different tension values for each control point. This alters the parametric equations for the canonical spline somewhat: For each segment of the spline, two tension values are required, one associated with pt1 and the other with pt2. The first tension value is multiplied by the straight-line slope from pt0 to pt2 and the second is multiplied by the straight-line slope from pt1 to pt3.

This feature is obviously helpful for fine-tuning the image. For example, suppose you had a collection of 5 points like so:

You use the CanonicalSpline class to draw these points setting the Tension property to 1:

Well, this is not quite what you want. You want the top parts to be rounded, but you want the center point unrounded, and you want the curve at the end-points to be a little straighter. The solution is to set the Tensions property (notice the plural) to a DoubleCollection that consists of the values 0, 1, 0, 1, 0. The result looks like this:

All three of those images were produced by the following WPF XAML file:

Let's look at the Visual Studio CanonicalSplineDemo solution containing both the WPF and Silverlight code. I tried to share as much code as possible. There are five projects:

In Visual Studio, you can select the "Set as StartupProject" menu option on either the CanonicalSplineDemo.Web or CanonicalSplineDemo.Wpf project.

For convenience in sharing code, both DLL's have the same namespace name and assembly name, CanonicalSplineLib. The two DLLs share a file (and static internal class) named CanonicalSplineHelper. This class contains the code to generate a PathGeometry from a set of control points and other information.

The WPF DLL also contains a public class named CanonicalSpline that derives from Shape and defines the additional seven properties (all backed with dependency properties):

The Silverlight DLL also has a public class named CanonicalSpline but which derives from UserControl. This class defines all the same properties defined by the WPF version of CanonicalSpline plus all the properties defined by Shape (Fill, Stretch, Stroke, StrokeDashArray, StrokeDashCap, StrokeDashOffset, StrokeEndLineCap, StrokeLineJoin, StrokeMiterLimit, StrokeStartLineCap, and StrokeThickness).

For the Silverlight application (which you can run from the link above) I created a small UserControl derivative named Dot to represent the points, and another UserControl derivative named MainPage that displays the spline and all the controls that let you fiddle around with it.

The WPF application has a Window class derivative named MainWindow but the content is just the MainPage class that it shares with the Silverlight version.


Comments:

Wow, great! Thanks so much for this! I was wondering why cardinal splines were omitted from WPF; I used them a lot in GDI+. Cardinal splines certainly have their uses, especially when determining the smooth path of a moving object that needs to intersect certain keyframes.

Can your canonical spline class be used for path animations?

Thanks again, sir. You're a legend.

Burton, Thu, 29 Jan 2009 18:03:40 -0500 (EST)

> Can your canonical spline class be used for path animations?

That would be a nice feature, wouldn't it? The way I'd add that feature is to replace the pathGeometry field in CanonicalSpline.cs with a readonly dependency property named PathGeometry. Here's a revised CanonicalSpline.cs with that change. Then, the canonical spline path geometry can be accessed with a binding. Here's a demo program using it in conjunction with a MatrixAnimationUsingPath:

    <Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
          xmlns:spline="clr-namespace:CanonicalSplineLib;assembly=CanonicalSplineLib">
        <Canvas>
            <spline:CanonicalSpline 
                Name="spline"
                Stroke="Black"
                Points="200 400, 400 200, 400 600, 600 400"
                IsClosed="True"
                Tension="-3.3" />
            <Button Content="Button">
                <Button.RenderTransform>
                    <MatrixTransform x:Name="xform" />
                </Button.RenderTransform>
            </Button>
        </Canvas>
        <Page.Triggers>
            <EventTrigger RoutedEvent="Page.Loaded">
                <BeginStoryboard>
                    <Storyboard TargetName="xform"
                                TargetProperty="Matrix">
                        <MatrixAnimationUsingPath 
                            PathGeometry=
                                "{Binding ElementName=spline, 
                                          Path=PathGeometry}"
                            DoesRotateWithTangent="True"
                            Duration="0:0:10"
                            RepeatBehavior="Forever" />
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Page.Triggers>
    </Page>

Of course, if you don't want the actual spline to be displayed, just don't set Stroke. — Charles

Cool, thanks again! I'll take a look at this. I can't remember having as much fun programming as I have with WPF/XAML.

Burton, Fri, 30 Jan 2009 13:05:24 -0500 (EST)

Thanks for the helpful insights.

Art Scott, Fri, 30 Jan 2009 19:42:30 -0500 (EST)

Sorry to be so effusive, but this has been like borrowing a cup of sugar from Julia Child! Thanks again!

Burton, Mon, 2 Feb 2009 04:39:10 -0500 (EST)

A couple of things that spline controls seem to always leave out but that seem very valuable to me are hit testing and a routine to provide some sort of data on individual points on the spline. The latter would preferably take the form of a function which would take a distance and return the spline point at that distance along the curve. Wouldn't have to be exact, though that would be a bonus. Just something so somebody could animate something along the spline and it look reasonable. Just a couple of thoughts for potential future additions.

Also, I believe that there is an algorithm for deriving the knots for a Bezier spline from those of a cardinal spline so that might be another way to handle this.

Darrell Plank, Fri, 6 Mar 2009 10:20:35 -0500 (EST)

Can your canonical spline class be used for path animations in silverlight?

— Imthi, Fri, 17 Apr 2009 20:17:02 -0400 (EDT)

This is an excellent stuff especially looking for smoothline chart/curves in Silverlight.

— PG Krish, Fri, 18 Sep 2009 20:14:30 -0400 (EDT)


Recent Entries
< PreviousBrowse the ArchivesNext >
Subscribe to the RSS Feed

(c) Copyright Charles Petzold
www.charlespetzold.com