Introduction
It's easy to draw a BezierSpline with GDI+ (call Graphics.DrawCurve(Points)
), but how to get any arbitrary Point on that drawn curve?
What I'm Talking About
Please take a look at what the screenshot shows:
A Bezier-Splines-Curve (brown), constructed by 7 support-points, which subdivide the BezierSplines into 6 Bezier-Segments.
The 19 construction-points, which model the curve (orange).
The "Curve-Pointer" (red). It can be moved along the BezierSplines and its location is displayed, computed by interpolating the BezierSplines.
How BezierSplines is Constructed
Each segment between two supportpoints is constructed as BezierCurve with 4 construction-points:
The two support-points themselves and two additional, which take care, that the grade of the bezier arriving at the support-point is the same as the grade of the Bezier, which leaves the support-point.
Now Interpolate It
To get the Y-value of an X-position, I interpolate the BezierSplines in two steps:
First, I search the Bezier-Segment, which contains the X-position. This is quickly done by a binary search:
Dim Indx = _Points.BinarySearch(New PointF(X, 0), Function(P1, P2) P1.X.CompareTo(P2.X))
(The comparison of this search only compares the Point.X-Values
.)
Indx
will be the bit-complement of the index of the first point with point.X > X
.
This data I pass to the second step, to InterpolateSegment()
:
Protected Overrides Function InterpolateSegment( _
ByVal X As Single, ByVal Indx As Integer) As PointF
Indx = Indx * 3
Dim Pts = Me.DrawPath.PathPoints
Dim BezierSegment = New PointF() { _
Pts(Indx - 3), Pts(Indx - 2), Pts(Indx - 1), Pts(Indx)}
Dim Range = New Single() {0, 0, 1}
Dim Dlt = -2.0F
Dim Pt As PointF
Do
Range(If(Dlt < 0, 0, 2)) = Range(1)
Range(1) = (Range(0) + Range(2)) / 2
Pt = PointOfFraction(Range(1), BezierSegment)
Dlt = Pt.X - X
Loop Until Math.Abs(Dlt) < 0.5
Return Pt
End Function
Ups! Didn't I mention DrawPath
? Its a GraphicsPath
, which stores all the construction-points of the BezierSplines, I'd like to get drawn. Very useful! The construction-points are stored in the ".PathPoints
" - property.
Now we come to the Casteljau-algorithm, which computes a point on a Bezier.
I'm lucky: The commentation is sufficient, so I can do without to make still more words.
Private Shared Function PointOfFraction( _
ByVal Fraction As Single, ByVal ptBeziers() As PointF) As PointF
Dim Pts(ptBeziers.Length - 2) As PointF
For I = 0 To ptBeziers.Length - 2
Pts(I) = PointBetween(ptBeziers(I), ptBeziers(I + 1), Fraction)
Next
For UBord = Pts.Length - 2 To 0 Step -1
For I = 0 To UBord
Pts(I) = PointBetween(Pts(I), Pts(I + 1), Fraction)
Next
Next
Return Pts(0)
End Function
Private Shared Function PointBetween( _
ByVal Pt1 As PointF, ByVal Pt2 As PointF, ByVal Fraction As Single) As PointF
Return Pt1.Add(Pt2.Subtract(Pt1).Mult(Fraction))
End Function
Reduction
To interpolate Bezier-Spline-Segments as an Y = f(X)
- function is mathematically incorrect.
Although I keep the support-points ordered "from left to right", a Segment can take forms, where it shows more than one Y-Value on certain X-positions.
My "interpolation" ignores such, and simply returns the first found Y-Value.
The failure lets itself be seen by some parts of the curve, which cannot be reached by interpolation.
Mathematically correct is to interpolate CubicSplines.
They are mathematically undefined only if two support-points set on the same X-position (as vertical line).
Polygons, Cubic Splines
While interpolating Polygons is trivial, the same on CubicSplines is - ah - untrivial. There's no GDI+ - Function which draws it for you, so you need to deal with linear algebra to solve linear equation-systems - brrr! - I've done that without real understanding. For that:
Credits
- Marco Roello as my exercises on CubicSplines are based on his article
Implementation-Details
The project deals with several ownerdraw-requirements:
move- and highlight-able points, polygons, bezier-splines, cubic-splines, and a caption
I collected them into a little hierarchy of classes:
ControlCaption
/
DrawObjectBase --DrawPoint
\
\ BezierSpline
\ /
Polygon
\
CubicSpline
You see: programmatically, I consider a spline as a polygon with different ways to draw and interpolate the line between two support-points. And if there are only two support-points - they actually display the same straight line.
History
- 30/12/2007: Submitted article, sample-solution in VB2008 prof, beta
- 1/5/2008: VB2005-version to zip-file added