Introduction

From time to time, I am faced with the question: how to draw a smooth curve through a set of 2D points? It seems strange, but we don't have out of the box primitives to do so. Yes, we can draw a polyline, Bezier polyline, or a piece-wise cardinal spline, but they are all not what is desired: polyline isn't smooth; with Bezier and cardinal spline, we have a headache with additional parameters like Bezier control points or tension. Very often, we don't have any data to evaluate these additional parameters. And, very often, all we need is to draw the curve which passes through the points given and is smooth enough. Let's try to find a solution.

We have interpolation methods at hand. The cubic spline variations, for example, give satisfactory results in most cases. We could use it and draw the result of the interpolation, but there are some nasty drawbacks:

Cubic spline is a cubic polynomial, but Win32, .NET Forms, or WPF do not provide methods to draw a piecewise cubic polynomial curve. So, to draw that spline, we have to invent an effective algorithm to approximate a cubic polynomial with polyline, which isn't trivial (word “effective” is a point!).

Cubic spline algorithms are usually designed to deal with functions in the Cartesian coordinates, y=f(x). This implies that the function is single valued; it isn't always appropriate.

On the other hand, the platform provides us with a suite of Bezier curve drawing methods. Bezier curve is a special representation of a cubic polynomial expressed in the parametric form (so it isn't the subject of single valued function restriction). Bezier curves start and end with two points often named “knots”; the form of the curve is controlled by two more points known as “control points”.

Bezier spline is a sequence of individual Bezier curves joined to form a whole curve. The trick to making it a spline is to calculate control points in such a way that the whole spline curve has two continuous derivatives.

I spent some time Googling for a code in any C-like language for a Bezier spline, but couldn't found any cool, ready-to-use code.

Here, we'll deal with open-ended curves, but the same approach could be applied to the closed curves.

Bezier Curve Representation

A Bezier curve on a single interval is expressed as:

$B(t)=(1-t)^3 P_0 +3(1-t)^2t P_1 +3(1-t)t^2 P_2 +t^3 P_3 \ldots (1)$

where t is in [0,1], and

P0 – first knot point

P1 – first control point (close to P0)

P2 – second control point (close to P3)

P3 – second knot point

The first derivative of (1) is:

$B'(t)=-3(1-t)^2 P_0 +3(3t^2 - 4t+1) P_1 + 3(2t-3t^2)P_2 +3t^2 P_3$

The second derivative of (1) is:

$B''(t)=6(1 - t)P_0 +3(6t-4)P_1+3(2 - 6t)P_2 +6tP_3$

Single Segment

If we have just two knot points, our "smooth" Bezier curve should be a straight line, i.e. in (1) the coefficients in the members with the power 2 and 3 should be zero. It's easy to deduce that its control points should be calculated as:

$3P_1 = 2P_0 + P_3 P_2 = 2P_1 - P_0$

Multiple Segments

This is the case where we have more than two points. One more time: to make a sequence of individual Bezier curves to be a spline, we should calculate Bezier control points so that the spline curve has two continuous derivatives at knot points.

Considering a set of piecewise Bezier curves with n+1 points and n subintervals, the (i-1)-th curve should connect to the i-th one. Now we will denote the points as follows:

Although I compiled this code in C# 3.0, I don’t see why it can’t be used without any modification in C# 2.0, and even in C# 1.0 if you remove the keyword “static” from the class declaration.

Note that the code uses the System.Windows.Point structure, so it is intended for use with Windows Presentation Foundation, and the using System.Windows; directive is required. But all you should do to use it with Windows Forms is to replace the System.Windows.Point type with System.Drawing.PointF. Equally, it is straightforward to convert this code to C/C++, if desired.

The Sample

The sample supplied with this article is a Visual Studio 2008 solution targeted at .NET 3.5. It contains a WPF Windows Application project designed to demonstrate some curves drawn with the Bezier spline above. You can select one of the curves from the combo box at the top of the window, experiment with the point counts, and set appropriate XY scales. You can even add your own curve, but this requires coding as follows:

Add your curve name to the CurveNames enum.

Add your curve implementation to the Curves region.

Add a call to your curve in the OnRender override.

In the sample, I use Path elements on the custom Canvas to render the curve, but in a real application, you would probably use a more effective approach like visual layer rendering.

History

18th December, 2008: Initial post

23rd March, 2009: Second article revision with the following corrections and additions:

The most important is the bug fix. This bug as well as its correction was found by Peter Lee. A lot of thanks to him! This bug produced visually distinguishable behavior in the case of small knot points count.

GetCurveControlPoints now throw exceptions if invalid parameter is passed.

Added special handling of the case where knots array has just two points

<pre lang="text"></pre>Good but I was confused about the numbering of control points and knot points.
Reading the code it seems that the schema is as follows
1.Pi – ith knot point (i=0,..,n)
2.P1i – first control point for spline i (close to Pi) (i=0,..,n-1)
3.P2i – second control point for spline i (close to P_i+1) (i=0,..,n-1)

HTH
To answer the WOW question, the code solves a system of equations with a strong diagonal matrix, as follows for n=5 (using Q_i instead of P1_i)

(so for example the first line means 2*Q_0 + 1*Q_1 = 1*P_0 + 2*P_1)
In the code the final line is actually divided by 2 to give
.. 1 3.5 .... 4 0.5
The routine is given the right hand side calculations as rhs[]
but it "knows" the coefficients on the left hand side, and uses b
as a device to allow for the change of pattern in the first and final lines
This explains the "4 ? 3.5" since the final line has been divided by 2 in the
rhs calculation which makes the coefficient 3.5 instead of the usual 4.

I was annoyed that this wasn't set up for 3D splines, but then I noticed that the pattern used for calculating the X values is the same as the one used for calculating the Y values. So I added some code that used the same pattern for the Z values and it worked like a charm. This is a very handy piece of code, thank you! =)

Hi,
Using the above draw function, when my knots array has more than 3 elements, I am getting blocks of points in the curve. I have uploaded the screen shot here:
https://sites.google.com/site/gpdeepti/upload
This what I am doing -
1) Compute the control Points using your logic
2) Interpolating line strips between ptvCtrlPts[i],firstCntrlPts[i], secondCntrlPts[i], ptvCtrlPts[i+1] as done here : http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/bezier-curves-and-surfaces-r1808
Kindly let me know what I am missing here.
Thanks!
Deepti

I am wondering about the use of this curve fitting algorithm in graph sketching. Consider, for example, sketching the graph of y=sin(x) in the interval [0;2pi] by picking the zeros and extreme points of that graph. The curve resulting from the above algorithm is a nice approximation of the true curve y=sin(x).
This is because the assumption of zero curvature at the endpoints is exactly right in this case.
However, consider y=cos(x) over the same interval, again picking zeros and extreme points: the resulting curve is not what you want.
And there is no surprise about this because, in this case, the assumption of zero curvature at the endpoints is quite wrong.

Maybe one could let the user select n+2 points and use the first and last point only to influence the direction of the curve at the endpoints? - Just a thought, and perhaps not a particularly clever one...

Hi i am facing the problem like this,
i am drawinng around two hundred curves in silverlight application by using pathdata,
For Each curve i have two end points and n control points(n<4 && n>12)
so while drawing i am using "S" for spline drawing in path data while i will take only one control point
that is centeral control point but i am not getting the smooth curve as i am using single control point.
if i use all control points i am not getting like curve.
Please do suggest

Many thanks for this great article.
I'm using this code for some experimentation and I get, in most cases, very good results. But sometimes the generated curve give me unexpected behaviours like drawing a curl between 2 knots.
Unfortunately I didn't find any way to fix this issue and even didn't find any singularity in my set of points.

Hello Jean Luc,
Sorry, but I need additional explanations:
1. We are talked on two-knots Bezier curves, isn't it? But your samples contains from 5 to 6 points. What these points are?
2. What are the measurement units in your samples. Obviously they aren't the pixels.
Regards,
Oleg V. Polikarpotchkin

1. Yes you're right, the curl I'm talking about appears between two knots but I thought you'll need the coordinates of previous and next points. These points are part of a closed polygon, this polygon is the resulting Alpha-Shape of a set of points. I know your other article on Closed Smooth Curve with Bezier Spline but I had more curls on the resulting curve.

2. Unit here is meter. The points are geographical points in a particular map projection. So I can convert coordinates from meter to pixel according to a resolution in m/px.

Hello Jean-Luc,
1. To calculate Bezier spline of either open-ended curve or closed polygon you MUST use all the points available, not just some prev and next.
2. I'm not sure but I can suppose that it's better to normalize the points coordinates before the Bezier spline calculation (e.g. convert them to pixels) to mitigate possible rounding errors.
Regards,
Oleg V. Polikarpotchkin

I provided you only 6 points because I had same "curl effect" between the same 2 knots with 6 points than with all the points of the polygon.
For second point, indeed I normalized the points coordinates after the Bezier spline calculation, thus I made a test converting coordinates before the calculation but I get the same behaviour.

I just used your sample to introduce these coordinates points in a custom curve as you explain in your article, and I get the same result :
Below you'll find the same samples than my previous message but in px this time, and working with your interface