Drawing Curved Objects

Anyone who has ever worked or played with graphic applications like Adobe Illustrator or CorelDRAW will know how to use the mouse to draw curved objects.

It is done by creating and manipulating Bézier curves. These are very versatile graphic building blocks, defined by only a few points. By dragging these points, the shape of the curved object can be altered drastically. It takes some time and exercise to grasp the concept, but once you get used to it, Bézier curves form a great tool to draw almost any shape you can think of.

QBezierTracker

For the programmer wanting to implement support for curve drawing, there isn't much help around. You basically have to code it from the ground up. This, at least, was the situation up till now. For here is QBezierTracker, a complete class implementing interactive curve drawing. It can be dropped into any MFC application. The code gluing between the app and my class takes some care, but after that, your project will have sophisticated Bézier drawing.

Probably the best way to appreciate QBezierTracker is to play a bit with QBezierDemo, the demonstration applet accompanying this article. The user interface closely resembles that of many other graphic applications. You can start drawing with the mouse on the empty view, immediately after the demo started up.

One closed curve and two open curves

The following rules apply:

  • Drawing a figure boils down to drawing the corner points. I call these anchors.
  • After the first anchor is drawn, you'll see a 'rubber band,' indicating the growing shape.
  • To draw a sharp-angled anchor, or corner, just click the mouse at a certain point.
  • To draw a smooth, curved anchor, drag the mouse.
  • To complete a figure, re-click its starting anchor. This will yield a closed curve.
  • You also can finalize a figure by re-clicking the last anchor. This gets you an open curve.
  • Each anchor consists of three points, called the main point, the in point, and the out point. The in point and the out point are the control points. Generally, they won't be a part of the curve itself. Instead, they act like a kind of magnet, pulling the curve and affecting the curvature.
  • The anchors of a completed figure can be moved by dragging them.
  • The control points also can be moved, altering the curvature, by dragging the control handles. Often, the control handles move in concordance with each other.
  • A selected anchor can be removed from the figure by pressing the Del key.
  • At any point on the outline of a figure, an anchor can be added, simply by clicking with the mouse. This way, a simple figure can be gradually refined.

The demo is far from a full-blown graphics application, but it does have a few extras. You can draw multiple figures. A previously drawn shape can be re-loaded in QBezierTracker by clicking on it. There are some menu options to choose colors, line width, and transparency.

Anchors

Corner anchor, smooth anchor, and semi-smooth anchor

The anchors are the points where the Bézier curve fragments join. With QBezierTracker, they come in three types. Under the Edit menu, the demo has commands to set the type of the currently selected anchor. Each type is displayed in its own color. The types are:

  • Corner. The two control handles can be moved independently. Generally, a corner anchor will appear as a sharp angle in the figure.
  • Smooth. If you move one of the control handles, the other one travels in a mirror-like fashion. The control vectors stay anti-aligned, and of equal length. The anchor appears as a smooth part of the figure. Technically, the figure at the anchor is called 'parametrically continuous in the first degree' at the anchor point.
  • Semi-smooth. This is a kind of in-between type. The control vectors stay anti-aligned, but their lengths may differ. The anchor appears as a smooth part, but to a lesser degree. The figure at the anchor is 'parametrically continuous to the zeroth degree'.

Using QBezierTracker

To see how QBezierTracker may be used in your own code, refer to the example in QBezierDemo, specifically to the CChildView class. QBezierTracker can be used in any CWnd-derived class, but some kind of CView probably would be the best habitat.

To sum it up:

  • Define a QBezierTracker variable in your CWnd-derived class, and initialize it with a pointer to the class (in other words, with this). See the CChildView constructor.
  • Optionally, load a few handsome cursors in QBezierTracker with its LoadCursor() method. Again, see CChildView's constructor.
  • In the handler for WM_SETCURSOR (typically OnSetCursor()), call QBezierTracker's OnSetCursor() method.
  • In the main drawing function (OnDraw() or OnPaint()), call QBezierTracker's Draw() function.
  • If you want to start a tracking operation, which almost certainly will be in the handler for WM_LBUTTONDOWN (OnLButtonDown()), call QBezierTracker's Track() method. You'll have to provide a (possibly 'prepared') device context (CDC, or derivative). If QBezierTracker is empty, a new figure will be drawn. Otherwise, an attempt will be made to modify the loaded figure.
  • If the call to Track() returns, check the return value. If it's greater than zero, a new figure will be completed, or the loaded figure will have been modified. You can retrieve the figure as a GraphicsPath, and (optionally) the anchor types, by calling QBezierTracker's GetGraphicsPath() method.
  • QBezierTracker() knows which part of the screen is affected by its operations. You can retrieve this 'dirty rectangle' by calling GetDirtyRect(). It can be used to invalidate the screen with InvalidateRect(). The demo's CChildView has the member function InvalidateDirty() to do just that.

QBezierTracker has quite a few other useful functions. There are also a number of options. You may try them out with the Options menu of the demo. Details are in the header file, QBezierTracker.h.

Drawing Curved Objects

About Bézier Curves

Bézier curves are named after the French engineer Pierre Bézier (1910-1999) who invented them in 1962, while working for the Renault automobile factory. They were first applied in the design of car bodies. The Bézier curves we are talking about are a form of 'parametric cubic curves'. Quite a few others exist, like Hermite curves, NURBS, and several splines, but Béziers have a lot of nice properties, especially in the 2D domain. They form the building blocks of TrueType fonts.

First of all, almost any graphics library supports them. Windows GDI is no exception. It can draw them directly by using the PolyBezier() function. The GraphicsPath class of the extended Windows graphics API GDI+, is in fact a string of Bézier curves.

The fact that Béziers are more widely used than other parametric cubic curves is primarily due to two features:

  • The four points (two end points and two control points) defining the curve also define the bounding box. In other words, under no circumstance will there ever be a part of a Bézier curve extending the smallest rectangle enclosing the four points (in fact, they won't even extend the 'closest convex hull' of the four points).
  • It is relatively, or even amazingly, easy to divide a Bézier curve into two smaller ones. The splitting can be done at any point. The two newly formed curves together follow exactly the same path as the original curve; they are not an approximation. The technique most commonly used is called 'De Casteljau's geometric method'.

Inside QBezierTracker

QBezierTracker is a close relative of my QTransformTracker and QWarpTracker classes. They all derive from QTracker, my general mouse-tracking class.

Like the other classes, QBezierTracker uses a mix of GDI and GDI+ techniques. The interactive part is done with GDI calls, in part for efficiency, but primarely because, for some mysterious reason, GDI+ doesn't support binary raster-operation drawing modes (ROP2).

QBezierTracker is a rather extensive class. To keep it more or less manageable, I distributed the source code among four files QBezierTracker1.cpp ... QBezierTracker4.cpp. There is one header, QBezierTracker.h.

In fact, most work is done in the QBezierTrackerBase class, of which QBezierTracker is derived. I did this to separate the GDI+-functionality from the rest. QBezierTrackerBase might be employed without GDI+, using only 'classic' GDI.

QBezierTracker needs several other classes to work. One of them is the embedded Anchor class, which stores the anchor information and also does quite a lot of the hard work. Source file QBezierTracker2.cpp is entirely devoted to it.

Bézier Hit Testing

De Casteljau's method to divide a Bézier curve into two smaller ones

Some other classes are needed for the most demanding task QBezierTracker has to perform, namely the hit test against a curve fragment. In other words: The test whether a given point is on, or very near to a given Bézier curve. This is needed because you want to be able to add an extra anchor to an existing figure.

The hit test problem is tackled with a divide-and-conquer strategy:

  • Determine the bounding box of the curve segment. This is easy: Just determine the bounding box of the four defining points.
  • Is the point inside the bounding box? If not, exit.
  • Is the bounding box sufficiently small? If so, you can conclude that the point really is on, or near, the curve.
  • If not, divide the curve in two parts, and test the parts separately. Dividing the curve is done with De Casteljau's method.

The whole thing is in QBezierTracker3.cpp. The clever part is in the FindBezierHit() method. First, I had this function recursively call itself, but later I removed this and used a built-in stack structure to store intermediate results. While less elegant at first sight, this is to be preferred because of efficiency.

Note: For this application to run, your system must support GDI+, which currently only XP does natively. However, other Windows versions can be upgraded. Also, VC++ 6.0 comes without the GDI+ headers. You may obtain them by downloading the Windows Platform SDK. The GDI+ headers are included with VC++ 7.0 and later.

References

There are loads of pages about Bézier curves and related subjects on the Internet. To name just a few:

Book:

  • Chapter 11.2 of Computer Graphics, Principles and Practice, 2nd Edition, by James D. Foley, Andries van Dam, Steven K. Feiner, and John F. Hughes. Addison-Wesley, 1995. ISBN: 0201848406.


Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Event Date: April 15, 2014 The ability to effectively set sales goals, assign quotas and territories, bring new people on board and quickly make adjustments to the sales force is often crucial to success--and to the field experience! But for sales operations leaders, managing the administrative processes, systems, data and various departments to get it all right can often be difficult, inefficient and manually intensive. Register for this webinar and learn how you can: Align sales goals, quotas and …

  • Live Event Date: August 14, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Data protection has long been considered "overhead" by many organizations in the past, many chalking it up to an insurance policy or an extended warranty you may never use. The realities of today makes data protection a must-have, as we live in a data-driven society -- the digital assets we create, share, and collaborate with others on must be managed and protected for many purposes. Check out this upcoming eSeminar and join Seagate Cloud …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds