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


Downloads

Comments

  • GDI+ in container window?

    Posted by marcony on 01/25/2006 12:31pm

    Excelent article about GDI+ transformations! I noticed it looking for some help using GDI+
    
    I am using various transformations on GDI+ objects in my application. Everything works fine. But when I tried to make the same application as full-server (VS C++ 6.0, no .NET!!!), I encountered problems. I have no idea how to pass the pointer to correct CDC object to COleServerItem object of my application, so there is no way to display anything inside container application. In short, all that I can draw inside my application is not possible to draw inside container application.
    
    Passing pDC from COleServerItem::OnDraw(), seems to be incorrect, like:
    
    BOOL CGDocSrvrItem::OnDraw(CDC* pDC, CSize& rSize)
    {
    	Graphics gr(pDC->m_hDC); // this line returns wrong status!!!!!
    
    	Graphics gr(pDoc->m_pView->GetDC()->m_hDC); // this one returns correct status, but does not help in drawing line inside container!
    	Status st = gr.GetLastStatus();
    	if(Ok != st) {
    		CString oStr;
    		oStr.Format("Error: status: %d", st);
    		AfxMessageBox(oStr);
    		return FALSE;
    	}
    	Pen docPen(Color(255, 0, 255, 0), 2);
    	gr.DrawLine(&docPen, 10.0f, 10.0f, 50.0f, 50.0f);
    
    }
    
    
    So, seems to be very big problem to draw a single line, not talking about all my drawing that happens inside application.
    
    Any help?

    Reply
  • Full Visio 2003 like MFC/Visual C++ Source Code Kit!

    Posted by andytim on 08/23/2005 10:43pm

    If you need full features of CoreDraw or full feature of Visio 2003 like MFC/Visual C++ Source Code Kit can be found here: http://www.ucancode.net

    Reply
  • Zhefu Zhang

    Posted by Legacy on 09/11/2003 12:00am

    Originally posted by: Just too cool and great article

    plus, good article text format

    Reply
  • Very Nice

    Posted by Legacy on 09/10/2003 12:00am

    Originally posted by: Oktronic

    This is pretty nice. I don't have alot of use for a warping class, but if I do find a need, its nice to know this one is around.

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

Top White Papers and Webcasts

  • 10 Rules that Make or Break Enterprise App Development Projects In today's app-driven world, application development is a top priority. Even so, 68% of enterprise application delivery projects fail. Designing and building applications that pay for themselves and adapt to future needs is incredibly difficult. Executing one successful project is lucky, but making it a repeatable process and strategic advantage? That's where the money is. With help from our most experienced project leads and software engineers, …

  • Live Event Date: August 19, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT You deployed your app with the Bluemix PaaS and it's gaining some serious traction, so it's time to make some tweaks. Did you design your application in a way that it can scale in the cloud? Were you even thinking about the cloud when you built the app? If not, chances are your app is going to break. Check out this upcoming webcast to learn various techniques for designing applications that will scale successfully in Bluemix, for the …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds