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.
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.
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'.
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.