The Canonical Spline

The Graphics class includes a second type of spline called the canonical spline, meaning a standard or normal spline. You draw a canonical spline by using one of the DrawCurve methods. DrawCurve comes in seven different versions, but you'll probably use the following four methods most frequently:

Graphics DrawCurve Methods (selection)

DrawCurve(Pen

pen,

Point []

apt)

DrawCurve(Pen

pen,

PointF[]

aptf)

DrawCurve(Pen

pen,

Point []

apt, float fTension)

DrawCurve(Pen

pen,

PointF[]

aptf, float fTension)

At least two points are required. If the array contains only two points, the DrawCurve method draws a straight line from the first point to the second. For three points or more, the method draws a curved line that connects all the points.

The big difference between the Bezier spline and the canonical spline is that the canonical spline passes through every point in the array. The curve between each adjacent pair of points is sometimes called a segment of the total curve. The shape of each segment of the curve is governed by the two points at the beginning and the end of the segment (of course) but also the other two adjacent points. For example, for an array of Point structures named apt, the shape of the segment between apt[3] and apt[4] is also affected by the points apt[2] and apt[5].

The spline is also affected by the tension, which is an explicit argument in some of the DrawCurve overloads. If you think of traditional wooden or metal splines, the tension is equivalent to the stiffness of the spline. The default is 0.5. A tension of 0 results in straight lines: DrawCurve becomes DrawLines. With tensions greater than 0.5, the curve gets curvier. You can set tensions less than 0, but they often result in loops. Tensions much higher than 1 can also create loops.

Let's experiment. The following program is much like the Bezier program except that it also includes a scroll bar for setting the tension and it gives you more flexibility in moving the points around.

CanonicalSpline.cs

// CanonicalSpline.cs ® 2001 by Charles Petzold //----------------------------------------------

using System;

using System.Drawing;

using System.Windows.Forms;

class CanonicalSpline: Forra {

protected float fTension = 0.5f;

public static void Main() {

Application.Run(new CanonicalSpline());

public CanonicalSpline() {

Text = "Canonical Spline"; BackColor = SysteraColors.Window; ForeColor = SysteraColors.WindowText; ResizeRedraw = true;

ScrollBar scroll = new VScrollBar(); scroll.Parent = this; scroll.Dock = DockStyle.Right; scroll.Miniraura = -100; scroll.Maxiraura = 10 9; scroll.SraallChange = 1; scroll.LargeChange = 10; scroll.Value = (int) (10 * fTension);

scroll.ValueChanged += new EventHandler(ScrollOnValueChanged); OnResize(EventArgs.Erapty);

void ScrollOnValueChanged(object obj, EventArgs ea) {

ScrollBar scroll = (ScrollBar) obj; fTension = scroll.Value / 10f; Invalidate(false);

protected override void OnResize(EventArgs ea) {

base.OnResize(ea);

int cx = ClientSize.Width; int cy = ClientSize.Height;

apt[2] = new Point( cx / 2, 3 * cy / 4); apt[3] = new Point(3 * cx / 4, cy / 2);

protected override void OnMouseDown(MouseEventArgs mea) {

Point pt;

if (mea.Button == MouseButtons.Left) {

if (ModifierKeys == Keys.Shift)

pt = apt[0]; else if (ModifierKeys == Keys.None) pt = apt[l];

else return;

else if (mea.Button == MouseButtons.Right) {

if (ModifierKeys == Keys.None)

pt = apt[2]; else if (ModifierKeys == Keys.Shift) pt = apt[3];

else return;

else return;

Cursor.Position = PointToScreen(pt);

protected override void OnMouseMove(MouseEventArgs mea) {

if (mea.Button == MouseButtons.Left) {

if (ModifierKeys == Keys.Shift)

apt [0] = pt; else if (ModifierKeys == Keys.None) apt [l] = pt;

else return;

else if (mea.Button == MouseButtons.Right) {

if (ModifierKeys == Keys.None)

apt [2]= pt; else if (ModifierKeys == Keys.Shift) apt [3] = pt;

else return;

else return;

Invalidate();

protected override void OnPaint(PaintEventArgs pea) {

Graphics grfx = pea.Graphics;

Brush brush = new SolidBrush(ForeColor);

grfx.DrawCurve(new Pen(ForeColor), apt, fTension);

grfx.DrawString("Tension = " + fTension, Font, brush, 0, 0);

grfx.FillEllipse(brush, apt[i].X - 3, apt[i].Y - 3, 7, 7);

As with the Bezier program, you use the left mouse button and the right mouse button to change the locations of p1 and p2. In addition, the CanonicalSpline program lets you change the locations of p0 and p3 by using the left and right mouse buttons in conjunction with the Shift key. Here's a typical display:

You adjust the tension with the scroll bar; the value is displayed in the upper left corner of the window. I've allowed the tension to range between -10 and 10, just so you can see for yourself how extreme values make the curve go crazy. Here's one of my favorites using the program's default setting of the Point array:

It's also possible to use a subset of the point array in the following DrawCurve methods: Graphics DrawCurve Methods (selection)

DrawCurve(Pen pen, PointF[] aptf, int ioffset, int iSegments) DrawCurve(Pen pen, Point[] apt, int ioffset, int iSegments, float fTension)

DrawCurve(Pen pen, PointF[] aptf, int ioffset, int iSegments, float fTension)

Think of the iOffset argument as an index into the Point or PointF array. That's where the curve begins. The iSegments argument indicates the number of segments drawn and also the number of additional Point or PointF structures the method will use. For example, suppose aptf is an array of Po ntF structures. The call grfx.DrawCurve(pen, aptf, 2, 3);

draws three segments, from aptf[2] to aptf[3], from aptf[3] to aptf[4], and from aptf[4] to aptf[5]. The visual results aren't the same as calling the simpler version of DrawCurve with just these four points. The versions with Offset and iSegments use the aptf[1] point in determining the shape of the curve from aptf[2] to apf[3], and the aptf[6] point for the curve between aptf[4] and aptf[5].

The DrawClosedCurve methods connect the last point in the array to the first point in the array with an additional curve:

Graphics DrawClosedCurve Methods

DrawClosedCurve(Pen

pen,

Point[]

apt)

DrawClosedCurve(Pen

pen,

PointF[]

aptf)

DrawClosedCurve(Pen

pen,

Point[]

apt, float fTension, FillMode fm)

DrawClosedCurve(Pen

pen,

PointF[]

aptf, float fTension, FillMode fm)

DrawClosedCurve does more than simply draw an additional segment. The first segment drawn by DrawClosedCurve is a little different than the segment drawn by DrawCurve because it is influenced by the last point in the array; similarly, the penultimate curve is influenced by the first point in the array.

Two of the DrawClosedCurve overloads have a FillMode argument. Of course you remember FillMode, an enumeration defined in the namespace System.Drawing.Drawing2D that is used in the DrawPolygon method to govern which enclosed areas are filled:

FillMode Enumeration

Member

Value

Comments

Alternate

0

Default; alternates filled and unfilled areas

Winding

1

Most interior areas are filled

But why—you ask—is a fill mode required in a method that simply draws lines and doesn't fill? It's a mystery, and the methods seem to work the same regardless of the FillMode setting.

The FillMode argument makes a lot more sense in the FillClosedCurve methods:

Graphics FillClosedCurve Methods

FillClosedCurve(Brush

brush,

Point[]

apt)

FillClosedCurve(Brush

brush,

PointF[]

aptf)

FillClosedCurve(Brush

brush,

Point[]

apt, FillMode fm)

FillClosedCurve(Brush

brush,

PointF[]

aptf, FillMode fm)

FillClosedCurve(Brush

brush,

Point[]

apt, FillMode fm, float fTension)

FillClosedCurve(Brush

brush,

PointF[]

aptf, FillMode fm, float fTension)

The ClosedCurveFillModes program shown next is almost identical to the FillModesClassical program from Chapter 5. The program draws two five-pointed stars to illustrate the difference between FillMode.Alternate and FillMode.Winding.

ClosedCurveFillModes.es

//---------------------------------------------------

// ClosedCurveFillModes.es ® 2001 by Charles Petzold //---------------------------------------------------

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Windows.Forms;

The ClosedCurveFillModes program shown next is almost identical to the FillModesClassical program from Chapter 5. The program draws two five-pointed stars to illustrate the difference between FillMode.Alternate and FillMode.Winding.

ClosedCurveFillModes.es

//---------------------------------------------------

// ClosedCurveFillModes.es ® 2001 by Charles Petzold //---------------------------------------------------

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Windows.Forms;

elass ClosedCurveFillModes: PrintableForm {

publie new statie void Main() {

Applieation.Run(new ClosedCurveFillModes());

ClosedCurveFillModes() {

Text = "FillClosedCurve Fill Modes";

ClientSize = new Size(2 * ClientSize.Height, ClientSize.Height);

protected override void DoPage(Graphics grfx, Color clr, int cx, int {

Brush brush = new SolidBrush(clr); Point[] apt = new Point[5];

double dAngle = (i * 0.8 - 0.5) * Math.PI; apt[i] = new Point(

(int) (cx *(0.25 + 0.24 * Math.Cos(dAngle) (int)(cy *(0.50 + 0.48 *

Math.Sin(dAngle) }

grfx.FillClosedCurve(brush, apt, FillMode.Alternate)

for (int i = 0; i < apt.Length; i++) apt[i].X += cx / 2;

grfx.FillClosedCurve(brush, apt, FillMode.Winding);

While still recognizable as stars, these figures have a softer look:

Net Fillclosedcurve

They look more like star-shaped cookies that came out of the cookie cutter with straight sides but then plumped up a little when baking.

0 0

Post a comment

  • Receive news updates via email from this site