“Rubber Sheeting” with QWarpTracker

GDI+, the new Windows graphics API, makes it easy to transform graphic objects in a number of ways. By means of matrix manipulations, you can not only move and scale and object, but also rotate and shear a graphic. Earlier, I presented the MFC class QTransformTracker. With this “CRectTracker on Steroids,” you can apply such linear transformations interactively.

Apart from these, GDI+ also offers some support for non-linear transformations, called warps. Warping modifies the forms of graphic objects in even more drastic ways. It is more or less like they are on a flexible rubber sheet. With warping, you basically move the four corners of a box independently to another location, stretching and folding everything inside the box on the way.

Note: In the world of computer graphics, “warp” sometimes is also used as a broader term, denoting transformations in general, including the linear ones.

Warping is the realm of my class QWarpTracker. A demonstration application, called QWTDemo, shows it in action. It displays a number of screen objects. Click on any object to load it in the tracker. Warp it by dragging one of the blue-colored Handles. See how you not only drag the Handle and two lines, but also an outline representation of the object. This is one of the main features of the class.

There are two warp modes: Bilinear and Perspective. Use the Ctrl key when dragging to toggle between them and see the difference. Any transformation can be canceled by pressing the Esc key or clicking the secondary (right) mouse button.

The Options menu of the demo app lets you change the behaviour and appearance of QWarpTracker in several ways.

Coding with the QWarpTracker class

Using QWarpTracker is straightforward, and very much like using QTransformTracker. First, create a QWarpTracker object. Its constructor has a pointer to the associated CWnd as a parameter.

Setting the tracker to work basically involves these steps:

  • Load a screen object.
  • In the application’s OnLButtonDown() handler, call QWarpTracker’s member function Track().
  • If Track() returns a positive value, warp the object with one of the ApplyWarp() member functions.

A screen object is one of two things to QTransformTracker:

  • A RectF object, the GDI+ rectangle with REAL coordinates
  • A GDI+ GraphicsPath, by far the most versatile object

For each of these screen objects, QTransformTracker has its own Load() member function. There is a Clear() function to unload:

void Load(GraphicsPath& path, CDC * pDC = NULL);
void Load(RectF& rect, CDC * pDC = NULL);
void Clear(CDC * pDC = NULL);

If pDC is set to the screen dc, QWarpTracker draws and erases itself.

The Track() member function has the following signature:

int Track(CDC * pDC, UINT nFlags, CPoint point,
          bool bClipCursor = false);

The pDC parameter points to the (prepared) screen device context. nFlags and point are the parameters of the window’s OnLButtonDown() handler. If bClipCursor is true, the cursor movement is clipped to the client area of the window. If a GraphicsPath is loaded, an outline representation of it is shown.

Track(), which is a method of the base class QTracker, has a number of possible return values, the interesting ones being (the QTracker.h file has them all):

TrackSucceeded We have a valid transformation
TrackFailed The user clicked outside the object, perhaps on another object
TrackCanceled The user pressed Esc, or clicked the secondary mouse button

If Track() returned TrackSucceeded, the warp can be applied to a GraphicsPath, or to an array of PointF’s. Use one of the following methods:

Status ApplyWarp(GraphicsPath& path);
Status ApplyWarp(PointF * points, INT count);

The warping information can also be retrieved with the following member function:

bool GetWarpData(QWarpData * pWarpData);

It writes the information to a QWarpData struct, which is pointed to by the pWarpData. parameter This has the following definition:

struct QWarpData
{
   RectF rectSrc;
   PointF pntDest[4];
   WarpMode mode;
};

The data members of QWarpData are compatible with the parameters to the GraphicsPath::Warp() method.

To implement user feedback by changing cursors, the associated CWnd should delegate calls to its OnSetCursor() message handler to QWarpTracker’s method:

BOOL OnSetCursor(CDC * pDC);

If this returns TRUE, QWarpTracker did handle the message. Otherwise, the window should handle it, possibly by delegating it to its base class, as usual. See the source code of the CQWTDemoView class for an example.

QWarpTracker has a static function, LoadCursor(), to set the displayed cursor. It may be called before an instance of the class exists. By default, the tracker uses a standard MFC cursor.

When tracking, a constantly changing indicator string can be retrieved by calling the member function:

LPCTSTR GetIndicatorString() const;

This may be used to update a pane on the application’s status bar. The demonstration project QWTDemo shows how to integrate this with MFC’s update-UI scheme.

Options

QWarpTracker has quite a few options in behaviour and appearance. The public member variable m_Options can be set to a combination of the following flags:

OptionPerspective Allows warping in perspective mode (default)
OptionBilinear Allows warping in bilinear mode (default)
OptionAllowMirrorPerspective Allows “mirroring” of object in perspective mode
OptionAllowMirrorBilinear Allows “mirroring” of object in bilinear mode (default)
OptionMarkDotted Draws the Mark Lines with a dotted line
OptionTrackDotted Draws the Track Lines with a dotted line (default)
OptionPathDotted Draws the GraphicsPath with a dotted line

In default mode, mirroring is off for Perspective warping, because it hardly produces acceptable results. To see why I made that decision, switch on the “Allow Mirror in Perspective mode” option in the demo application.

The dotted line options will only have effect on Windows 2000 and later.

QWarpTracker also has public COLORREF variables to set the colors of:

  • The Mark Lines
  • The Track Lines
  • The GraphicsPath
  • The Handles

There are functions to set and retrieve some sizes:

void SetMetrics(UINT handleSize, UINT margin, CDC * pDC = NULL);
void GetMetrics(UINT& handleSize, UINT& margin) const;

Both sizes are in logical coordinates (QWarpTracker works in all mapping modes). The handleSize parameter is the size of the Handles; margin is the margin between the loaded screen object and the Mark Lines.

If you would prefer another format for the indicator string, you may derive a class from QWarpTracker and override the function:

virtual void SetIndicatorString(Mode mode, REAL x, REAL y);

The header file QWarpTracker.h has more information.

Implementation

As with QTransformTracker, QWarpTracker is derived from QTracker, a universal tracking/dragging/rubber banding class, which may be useful in itself. QWarpTracker shares some implementation tricks with QTransformTracker, such as the mixing of GDI+ graphics and “old fashioned” Windows GDI code. For smooth screen drawing, double buffering is used. It is implemented with the QBufferDC class. One of the more complicated implementation details is the handling of the AllowMirror options. Some math is needed to ensure that the destination points are always kept in a convex configuration.

The actual warping is done with my QWarper class. I didn’t use the Warp() method of the GDI+ class GraphicsPath because, in my humble opinion, it is seriously flawed, especially in its Bilinear mode. Look at my “Weird Warps” article for more information on this. Since then, I rewrote QWarper to make it even better. Although version 1.0 was already considerably faster than the GraphicsPath::Warp() method, version 2.0 is more so. Also, it is more versatile because you are not restricted to a rectangle to define the source points. I didn’t use the latter feature in QWarpTracker, though.

Using QWarpTracker in your own project

QWarpTracker, or more specifically QWarper, makes use of the matrix class in the Matrix TCL Lite 1.13 software of Techsoft Pvt. Ltd. This product is freeware for non-commercial and educational purposes only. Consequently, you can’t use QWarpTracker or QWarper in their present forms for any commercial product. I enclosed Techsoft’s Readme and License files into the demo project.

To use QWarpTracker, the following files of the demo project should be inserted in your MFC project:

  • Geometry.h and Geometry.cpp
  • Int.h
  • matrix.h
  • QBufferDC.h and QBufferDC.cpp
  • QGdiPlus.h
  • QTracker.h and QTracker.cpp
  • QWarper.h and QWarper.cpp
  • QWarpTracker.h and QWarpTracker.cpp

Only QWarpTracker.h should be included.

Note: 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.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read