Animation with the Windows GDI

Joe Sam presents techniques for bitblts and screen repainting that let you create smoother, faster animations.


January 01, 1994
URL:http://www.drdobbs.com/windows/animation-with-the-windows-gdi/184409381

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 2


Copyright © 1994, Dr. Dobb's Journal

Figure 3


Copyright © 1994, Dr. Dobb's Journal

SP 94: Animation with the Windows GDI

Animation with the Windows GDI

Saving and restoring bitmapped images within a window

Joe Sam is the owner of Autumn Software, which provides software and services for the AS/400, DOS, and Windows. He has been involved in consulting and commercial software for over 15 years and currently specializes in C, C++, and RPG. He can be contacted at P.O. Box 261646, Tampa, FL 33685.


Animation with Windows presents some special challenges, as I discovered in a recent project that involved an animation sequence in a multiprogram-database application. The animation was similar to the classic bouncing ball, moving a nonrectangular bitmap over a previously painted background. A requirement of the project was that the animation should work even if the background changed, and it had be independent and reusable enough to easily integrate into other programs. I soon found that my standard library let me down, and I was off on one of those deductive journeys for which Windows so freely offers a ticket to ride.

This article describes some of the quirks_er, features in Windows that, if ignored, can cause graphical graffiti to erupt all over your beautiful displays. Some of the hurdles include performing bit-block transfers (bitblts) in a window and overcoming Windows' optimization procedures when repainting the screen. I'll also address smoothness in larger animations and present a program, MOVBMP, that shows you how to put it all together.

Animating Bitmaps

Most paint programs as well as the BitBlt function, which is our primary tool for manipulating images, require rectangular bitmaps. In general, to display nonrectangular images, a program combines a portion of the screen, a bit mask and the image itself using AND and XOR operations. This allows background graphics to appear in the areas of the rectangle outside of the actual image ("transparent bitmaps" in Microsoft lingo). For this to work, the bit mask must be a duplicate of the original, except that the image is totally black on a white background. The image bitmap must be enclosed in black. This can be done in code at run time, but it is both more convenient and faster to use predrawn images, usually as BITMAPs in the resource file. The animated image in MOVBMP (see Listing One, page 50, and Listing Two, page 51) is an iron cross with three layers of color on each arm and a mostly transparent center. Figure 1 shows the six bitmaps that are used. Background graphics will appear in the white areas of the mask. The top, right, bottom, and left bitmaps are used for rotating the animation sample. The "true image" bitmap shows the iron cross as it appears on screen.

In any graphics system, this type of animation is accomplished by saving the display area where the image will appear, ANDing the mask with the screen, and XORing the image with the ANDed result. New positions are then calculated, the saved image is restored, and the process is repeated. Depending on the bitmap size, the preceding steps may be enough. Windows is no speed demon, however, and display memory is relatively slow, so most programs require the use of memory device contexts (DCs). Memory DCs created with CreateCompatibleDC can be treated exactly like a display or other DC, depending on which type of DC is passed as an argument to this function. For larger bitmaps, additional efficiency measures are necessary.

The Trouble with BitBlt

The BitBlt function provides a means to quickly get and put predrawn screen images. Unfortunately, when copying a displayed image to memory, BitBlt is more interested in what appears on the screen, and not necessarily what appears in your window. This means that if another window overlaps your client area, or even if one of your own menus is displayed over the animation, BitBlt copies the image from the overlapping display or menu. When your window regains focus, you may suddenly have a piece of another program's screen embedded in your window, which is generally unacceptable in a professional application. Also, due to the low priority and imprecision of timer messages, which are used to determine when to move the images, this state of events occurs more frequently than you might expect. This is a known problem that has occasionally popped up in question-and-answer columns. Microsoft is surely aware of the problem, but who knows when, or if, they will fix it, and, more importantly, if your users will have the fix. Fortunately, BitBlts to the screen are confined to a window's client area.

An Optimization that Creates Work

The second feature to overcome involves a Windows 3.1 optimization. When BeginPaint is called, a structure is returned that contains, among other things, what Windows considers the invalid portion of your client area expressed as a rectangle. For the example in Figure 2, your program will get coordinates corresponding to rect A, C, G, F. However, Windows appears to clip to the region F, D, E, B, C; and the area A, B, E, D will not be repainted. This is good for Windows since less work and less time is necessary, but bad for you under certain circumstances. There's no fast, reliable method to determine if another window covers the image or if Windows will clean up the screen for you. In the end, you're pretty much on your own since most animations operate outside of the paint routine.

And there you have it: BitBlt may end up saving and restoring pieces of a menu or another program's display on your client area. The paint routine would seem to be a good place to get a clean copy of the background to avoid that situation, but part or all of the image can be inside the invalid rectangle yet outside of the clip region. In that case, if you resave the screen area, your image gets embedded in the screen in

interesting ways.

Getting a Clean Image

My solution appears in DoEraseBkgnd (see Listing Two) which is called in response to the WM_ERASEBKGND message. The bypass timer flag is set (reset in DoPaint) to temporarily stop the animation, and the window DC is obtained to avoid clipping problems. The currently saved image is then restored to the screen. At that point, the DC is released and DefWindowProc is called to perform normal processing. If the image was in the invalid region, the screen is cleaned up. If not, the proper background is restored. Either way, you're then able to get a certifiably clean background image in DoPaint. This may seem inefficient, but the available alternatives take more code and more time, and are still imprecise (rectangles vs. regions again).

To automate the animation, a timer is used to send messages at approximately correct intervals and to allow the animation to proceed even when other windows have the focus. GetNewTimer illustrates obtaining a timer and allows the user, via a message box, to retry or cancel on failure (courtesy Charles Petzold).

Now that you can ensure clean images and know when to display them, how can you do so smoothly? Figure 3 illustrates a typical situation: The image is moving to the southeast. Rectangle 1 has just been displayed. Rectangle 2 will be displayed after restoring the screen. There is a large area of overlap and, if possible, you'd prefer to paint this area only once. This is actually what happens in MOVBMP. When the animation begins, a clean image of the screen is saved in the global HBITMAP hbmOrg (see SelectBmp) and maintained throughout the animation. The Animate routine handles all save, display, and restore operations using two additional bitmaps, the window DC, and two memory DCs.

In Animate, the old positions are saved and new ones calculated. hbmOrg is copied to hbmSave. The old and new positions are passed to DiffORects, which returns the client coordinates of the incremental area to be covered by the next display (the L-shaped area marked "2" in Figure 3) as two rectangles. These clean areas are copied from the screen to the proper positions in hbmOrg, as is the overlap portion from hbmSave. hbmOrg now has a complete clean image of the area to be used by the next display. hbmOrg is then copied to hbmDest. The bit mask is ANDed with hbmDest and the selected image is XORed with hbmDest. BitBlt accomplishes these tasks using the raster operation (ROP) codes SRCAND and SRCINVERT instead of the usual SRCCOPY. At this point, the new "transparent" image is complete. DiffORects is called again to determine the area to be restored. The same two rectangles are used, but the arguments are reversed, since we are now concerned with the L-shaped area marked "1" in Figure 3. With the values for these two differential rectangles in hand, the restore area is BitBlted to the screen from hbmSave and the new image is put to the screen from hbmDest. While there is a lot going on here, the work is done with very fast memory bitmaps and DCs. No redundant data is written to the display. DiffORects is the mainstay of this differential approach. The routine was developed empirically and has been (very) thoroughly tested. Because it works with client coordinates, the returned values are immediately usable. For smaller images, routines like DiffORects can be eliminated, but after about 20x20 pixels,

flash and jerkiness become increasingly noticeable. The iron

-cross images are 66x66 pixels.

Unless the Single Image menu option has been chosen, a different image is selected every other cycle. The bitmaps were drawn and loaded as top, right, bottom, and left images. By displaying the images in order, the illusion of rotation is generated in addition to the diagonal movement of the image. These could have been different colors, sunbursts, gradual fills, and so on, and are intended to demonstrate that animation does not have to involve moving to another area of the display. MOVBMP supports zero displacements so you can see pure rotation and even what appears to be a single stationary image.

Lastly, Animate cleans up the working DCs and bitmaps. Pay close attention to the reselection of the original or old bitmaps before deleting the DCs and work bitmaps. If this is omitted or done incorrectly, Windows will experience severe technical difficulties, as may your reputation, because the resulting loss in system resources continues for the duration of the session. Animation routines may be invoked thousands of times during a program run, so any leakage can totally deplete available system resources.

Additions for Your Toolbox

MOVBMP also illustrates both unusual and commonly needed functionality for your toolbox. Probably the most obvious is the elimination of the switch statement in WndProc via an array of message id/function pointer pairs (courtesy Ray Duncan). This is efficient for programmers because, among other benefits, new messages are easy to add, each message is related to a function (which helps avoid side effects and increases modularity), size boundaries can be calculated for you at compile time, and the basic skeleton can be used in every program. An extra function call is generated, but in practice this has been a small price to pay. Priority messages or those requiring special operations can be handled outside the array loop, as are WM_TIMER and WM_DESTROY. The message-associated functions take the standard hwnd, msg, wParam, and lParam arguments, which are #defined as STDWINARGS in the header file for use in function declarations and definitions. I've defined STDWINARGS in a base header that is included in every program.

Illustrations of font common-dialog usage are available, but little has been written about nondefault font initialization, as shown in Listing Two. Probably the best way to determine appropriate values is to write a small program using the font common dialog and use your debugger to view the returned LOGFONT structure. The font common dialog is also demonstrated in MOVBMP and used as a device to allow the

background to be changed in order to verify that the animation is working properly.

Partial invalidation of the client area is used when displacement or timer values are changed. Although all of the code in DoPaint is invoked, only the affected area of the screen is redrawn. I cheated in calculating the area. You should determine the longest string using GetTextExtent for actual sizes.

Conclusion

I've tried to avoid "exercises for the reader," but any program can be improved. You might prefer to use a larger bitmap and one BitBlt to blast both the restore area and new image in one operation. I chose lesser memory usage and fewer calculations, and on my 386 20-MHz test machine, that is good enough. Modifying SelectBmp to allow loading any bitmap would be a more rewarding improvement. MOVBMP could then be used as a tool to determine optimal timer and displacement values for a given animation. The remainder of the program is not tied to specific bitmaps and may be used as is. MOVBMP is written as a single C-code module for simplicity; however, the animation functionality could easily be placed in a DLL or encapsulated in an animator class. The complete project, including resource, definition, and executable files is available electronically; see "Availability," page 2.

References

Duncan, Ray. "The Hazards of Exploring Evolving Environments." PC Magazine (April 13, 1993).

Petzold, Charles. Programming Windows. Redmond, WA: Microsoft Press, 1990.

Figure 1: Bitmaps used in the MOVBMP program. The animated image is an iron cross with three layers of color on each arm. The center is transparent.

Figure 2: The invalid portion of your client area expressed as a rectangle. Given the coordinates corresponding to rect A, C, G, F, Windows clips to the region F, D, E, B, C, and the area A, B, E, D is not repainted.

Figure 3: Typical situation where the image is moving to the southeast, rectangle 1 has just been displayed, and rectangle 2 will be displayed after restoring the screen.

ANIMATION

[LISTING ONE]


// movbmp.h

#ifndef    MOVBMP_H
#define    MOVBMP_H

#define STRICT

// INCLUDES
// --------
#include  <windows.h>
#include  <windowsx.h>
#include  <commdlg.h>

// DEFINES
// -------
// define standard windows arguments
#define STDWINARGS HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam

#define  IDM_EXIT            501

#define  IDM_FONT            505
#define  IDM_SELECTBMP       510
#define  IDM_UNSELECTBMP     511
#define  IDM_DDEC            515
#define  IDM_DINC            520
#define  IDM_TDEC            525
#define  IDM_TINC            530
#define  IDM_SINGLEIMAGE     535
// Timer ID
#define IMAGE_TIMER            1

// VARIABLE/USER-DEFINED TYPES
// ---------------------------
typedef struct
 { UINT   msgid;          // message id
 // pointer to a funtion taking std winproc parms
   LONG PASCAL (*pfnProc)( STDWINARGS );
 } WINMSGFN;


// FUNCTION PROTOTYPES
// -------------------
// Window and Exported procedures
extern "C" {
LRESULT WINAPI WndProc( STDWINARGS );
       }  // end extern statement

// message function procs
LONG PASCAL DoCmd( STDWINARGS );
LONG PASCAL DoEraseBkgnd( STDWINARGS );
LONG PASCAL DoPaint     ( STDWINARGS );
LONG PASCAL DoSize      ( STDWINARGS );

// Command message function procs
LONG PASCAL ChangeDisplacement( STDWINARGS );
LONG PASCAL ChangeTimer       ( STDWINARGS );
LONG PASCAL DoFont            ( STDWINARGS );
LONG PASCAL SelectBmp         ( STDWINARGS );
LONG PASCAL SingleImage       ( STDWINARGS );
LONG PASCAL UnSelectBmp       ( STDWINARGS );

// other functions
void Animate(HWND hwnd);
void DiffORects(RECT *rBmp, RECT *rRst, RECT *rUpper, RECT *rLower);
BOOL GetNewTimer(HWND hwnd);

#endif  /* movbmp.h */


[LISTING TWO]



// MOVBMP Main Module

#include "movbmp.h"

// pgm globals ---------------------------
WINMSGFN msgfn[] =
     {
        WM_COMMAND,       DoCmd,
        WM_ERASEBKGND,    DoEraseBkgnd,
        WM_PAINT,         DoPaint,
        WM_SIZE,          DoSize,
     };
WINMSGFN cmdmsgfn[] =
     {
            IDM_DDEC,         ChangeDisplacement,
            IDM_DINC,         ChangeDisplacement,
            IDM_TDEC,         ChangeTimer,
            IDM_TINC,         ChangeTimer,
            IDM_FONT,         DoFont,
            IDM_SELECTBMP,    SelectBmp,
            IDM_SINGLEIMAGE,  SingleImage,
            IDM_UNSELECTBMP,  UnSelectBmp,
     };

// keep number of msgfn and cmdmsgfn elements
int  imsgfndim = (sizeof(msgfn) / sizeof(msgfn[0]));
int  icmdmsgfndim = (sizeof(cmdmsgfn) / sizeof(cmdmsgfn[0]));

HINSTANCE  hInstGBL;   // this instance
COLORREF   crrgbColors = RGB(0,0,255); // blue
RECT       rWndGBL = {0, 0, 0, 0}, rWndInv = {5, 0, 255, 0};
HDC        hdcOrg;
HBITMAP    hbmOrg, hbmOldOrg;
HBITMAP    hbm[5]; // top, right, bottom, left, background

// init the font to TrueType Arial, 72 points
LOGFONT    lfFontSel = {    -96,  // Height
                              0,  // Width
                            250,  // Escapement
                              0,  // Orientation
                            400,  // Weight
                           '\0',  // Italic
                           '\0',  // Underline
                           '\0',  // StrikeOut
                           '\0',  // CharSet
                         '\x03',  // OutPrecision
                         '\x02',  // ClipPrecision
                         '\x01',  // Quality
                         '\x22',  // PitchAndFamily
                         "Arial"  // FaceName
                       };

// flag variables
BYTE    btrblFlag = 0; // top/right/bottom/left flag
BYTE    bBypassTimer   = 255;  // 255 indicates first pass
BYTE    bBmpIsSelected = 0, bSingleImage = 0;

char    szAppName[] = "Move Bitmap", szClassName[] = "MOVBMP";

int     ndxGBL;  // work index
int     imageWidth, imageHeight, nTimerValue = 50;
int     nXDsp = 1, nYDsp = 1; // displacement values
int     nbmpX = 1, nDeltaX = 1, nbmpY = 1, nDeltaY = 1;

// begin -------------------------------------
#pragma argsused
int pascal WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
           LPSTR  lpszCmdLine,  int cmdShow)
 { HWND     hWnd;
   MSG      msg;
   WNDCLASS wndclass;
   char     szString[] = "Move Bitmap is ending...";

   hInstGBL = hInstance;  // save instance info
   // handle class registration and window creation
   if( !hPrevInstance )
   { wndclass.lpszClassName = szClassName;
     wndclass.hInstance     = hInstance;
     wndclass.lpfnWndProc   = WndProc;
     wndclass.hCursor       = LoadCursor(NULL, IDC_ARROW);
     wndclass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
     wndclass.lpszMenuName  = "Main";
     wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
     wndclass.style         = NULL;
     wndclass.cbClsExtra    = 0;
     wndclass.cbWndExtra    = 0;

     if(!RegisterClass( &wndclass ))  //error
     { MessageBox(NULL, "Can't create window class.", szString,
                         MB_ICONEXCLAMATION | MB_OK);
       return 1;
     }
   }
   hWnd = CreateWindow( szClassName,
                szAppName,
            WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
            CW_USEDEFAULT,
            0,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL
              );
   if(hWnd == NULL)
   { MessageBox(NULL, "Can't create window.", szString,
                       MB_ICONEXCLAMATION | MB_OK);
     return 1;
   }
   ShowWindow (hWnd, cmdShow);
   UpdateWindow(hWnd);

   // pgm loop
   while ( GetMessage(&msg, NULL, 0, 0) )
   {
     TranslateMessage (&msg);
     DispatchMessage  (&msg);
   }
   return 0;
 } // end WNDMAIN



LRESULT WINAPI WndProc( STDWINARGS )
 { // handle Windows messages
   if( msg == WM_TIMER ) // here for faster handling
   { // animate if appropriate timer;
     if(wParam == IMAGE_TIMER && !bBypassTimer)  Animate(hwnd);
     return(0L);
   }
   for(ndxGBL = 0; ndxGBL < imsgfndim; ndxGBL++)
   { // walk messages array
     if(msg == msgfn[ndxGBL].msgid)  // if match, call Fn
        return ((*msgfn[ndxGBL].pfnProc) (hwnd, msg, wParam, lParam));
   }
   if( msg == WM_DESTROY ) // if not in array
   { // happens once; don't waste searches or fn
     if(bBmpIsSelected)  // clean up if bitmap was moving
        UnSelectBmp(hwnd, msg, wParam, lParam);
     PostQuitMessage (0);
     return(0L);
   }
   else return ( DefWindowProc(hwnd, msg, wParam, lParam) );
 }  // end WndProc



void Animate(HWND hwnd)  // Handle Bitmap Movement
 { static int  ncounter = 0; // when to inc btrblFlag
   int     nToX = 0, nToY = 0, nFromX = 0, nFromY = 0,
           nPrevX = nbmpX, nPrevY = nbmpY;
   RECT    rBmp, rRst, rUpper, rLower;
   HDC     hdcClient, hdcDest, hdcSave;
   HBITMAP hbmDest, hbmOldDest, hbmSave, hbmOldSave;

   hdcClient  = GetDC( hwnd);
   hdcDest    = CreateCompatibleDC( hdcClient);
   hdcSave    = CreateCompatibleDC( hdcClient);

   hbmSave = CreateCompatibleBitmap(hdcClient,
                                    imageWidth, imageHeight);
   hbmOldSave = (HBITMAP)SelectObject(hdcSave, hbmSave);

   // create destination bitmap
   hbmDest = CreateCompatibleBitmap(hdcClient,
                                    imageWidth, imageHeight);
   hbmOldDest = (HBITMAP)SelectObject(hdcDest, hbmDest);

   // determine border/direction changes, if any
   if( nbmpX < 1)  nDeltaX = nXDsp;
   else if( nbmpX > (rWndGBL.right-imageWidth) )
            nDeltaX = -(nXDsp);

   if( nbmpY < 1)  nDeltaY = nYDsp;
   else if( nbmpY > (rWndGBL.bottom-imageHeight) )
            nDeltaY = -(nYDsp);

   nbmpX += nDeltaX;  nbmpY += nDeltaY; // get new positions

   // copy clean image of saved screen
   BitBlt(hdcSave, 0, 0, imageWidth, imageHeight,
          hdcOrg,  0, 0, SRCCOPY);

   // set up incremental new screen image
   rBmp.left   = nbmpX;  rBmp.top = nbmpY;
   rBmp.right  = nbmpX + imageWidth  - 1;
   rBmp.bottom = nbmpY + imageHeight - 1;

   rRst.left   = nPrevX;  rRst.top = nPrevY;
   rRst.right  = nPrevX + imageWidth  - 1;
   rRst.bottom = nPrevY + imageHeight - 1;
   // get overlapping rect differentials
   DiffORects(&rRst, &rBmp, &rUpper, &rLower);
   // get clean incremental rects from screen
   BitBlt(hdcOrg,  rUpper.left - nbmpX, 0,
                  (rUpper.right - rUpper.left + 1),
                  (rUpper.bottom - rUpper.top + 1),
          hdcClient, rUpper.left, rUpper.top, SRCCOPY);

   BitBlt(hdcOrg,  rLower.left - nbmpX, rLower.top - nbmpY,
                  (rLower.right - rLower.left + 1),
                  (rLower.bottom - rLower.top + 1),
          hdcClient, rLower.left, rLower.top, SRCCOPY);

   // copy overlapping portion from previously saved screen image
   if(nbmpX < nPrevX)  nToX   = nXDsp; // if moving left
   else                nFromX = nXDsp;

   if(nbmpY < nPrevY)  nToY   = nYDsp; // if moving up
   else                nFromY = nYDsp;

   BitBlt(hdcOrg, nToX, nToY,
          imageWidth - nXDsp, imageHeight - nYDsp,
          hdcSave, nFromX, nFromY, SRCCOPY);

   // set up initial destination image
   BitBlt(hdcDest, 0, 0, imageWidth, imageHeight,
             hdcOrg, 0, 0, SRCCOPY);
   // set up mask reusing hdcSave
   SelectObject(hdcSave, hbm[4]);
   // AND the mask
   BitBlt(hdcDest, 0, 0, imageWidth, imageHeight,
          hdcSave,  0, 0, SRCAND);

   // set up to put the bitmap
   SelectObject(hdcSave, hbm[btrblFlag]);
   // XOR the bitmap
   BitBlt(hdcDest,  0, 0, imageWidth, imageHeight,
          hdcSave, 0, 0, SRCINVERT);

   // restore incremental rects from old screen image
   // use same rects with reverse order in fn call
   DiffORects(&rBmp, &rRst, &rUpper, &rLower);
   // put clean rects to screen
   SelectObject(hdcSave, hbmSave);  // reselect hbmSave
   BitBlt(hdcClient, rUpper.left, rUpper.top,
                    (rUpper.right  - rUpper.left + 1),
                    (rUpper.bottom - rUpper.top  + 1),
          hdcSave,   rUpper.left - nPrevX, 0, SRCCOPY);

   BitBlt(hdcClient, rLower.left, rLower.top,
                    (rLower.right  - rLower.left + 1),
                    (rLower.bottom - rLower.top  + 1),
          hdcSave,   rLower.left - nPrevX,
                    (rLower.top -  nPrevY), SRCCOPY);

   // PUT image to the screen
   BitBlt(hdcClient, nbmpX, nbmpY, imageWidth, imageHeight,
          hdcDest, 0, 0, SRCCOPY);

   if(!bSingleImage)
   { // change top, right, bottom, left every other display
     if(++ncounter >= 2)
     { // set bitmap index
       btrblFlag++;  ncounter = 0;
       if(btrblFlag > 3)  btrblFlag = 0; // reset flag/index
     }
   }
   // clean up memory DC's, work bitmaps
      SelectObject(hdcDest, hbmOldDest);
      DeleteDC(hdcDest);
      DeleteObject(hbmDest);

   SelectObject( hdcSave, hbmOldSave);
   DeleteDC( hdcSave);
   DeleteObject( hbmSave );

   ReleaseDC( hwnd, hdcClient);
 } // end Animate



#pragma argsused
LONG PASCAL ChangeDisplacement( STDWINARGS )
 { // inc/dec x & y displacement by 1 to min/max
   if(wParam == IDM_DDEC) // decrement rqs
   {
     if(nXDsp < 1 || nYDsp < 1) // 0 pix min
        nXDsp = nYDsp = 1;
     else  nXDsp -= 1, nYDsp -= 1;
   }
   else // must have been increment rqs
   {
     if(nXDsp > 49 || nYDsp > 49) // 50 pix max
        nXDsp = nYDsp = 50;
     else  nXDsp += 1, nYDsp += 1;
   }
   // reset delta values
   nDeltaX = (nDeltaX < 1) ? -nXDsp : nXDsp;
   nDeltaY = (nDeltaY < 1) ? -nYDsp : nYDsp;

   InvalidateRect(hwnd, &rWndInv, TRUE); // redraw new values
   return (0L);
 } // end ChangeDisplacement



#pragma argsused
LONG PASCAL ChangeTimer( STDWINARGS )
 { // inc or dec timer interval by 50
   if(bBmpIsSelected)
   {
     KillTimer(hwnd, IMAGE_TIMER);
     bBypassTimer = 1; // set bypass flag
   }
   if(wParam == IDM_TDEC) // decrement rqs
   { // 50 ms min
     if(nTimerValue < 100)  nTimerValue  = 50;
     else                   nTimerValue -= 50;
   }
   else // must have been increment rqs
   { // 1000 ms max
     if(nTimerValue > 950)  nTimerValue  = 1000;
     else                   nTimerValue += 50;
   }
   InvalidateRect(hwnd, &rWndInv, TRUE); // redraw new values
   if(bBmpIsSelected)
   {
     if( !GetNewTimer(hwnd) )  // if can't get timer, end movement
          UnSelectBmp(hwnd, msg, wParam, lParam);
     bBypassTimer = 0; // reset bypass flag
   }
   return (0L);
 } // end ChangeTimer



void DiffORects(RECT *rBmp, RECT *rRst, RECT *rUpper, RECT *rLower)
 { // finds 2 rects portion of rRST outside overlap in
   // client coords, returns rects in rUpper and rLower
   rUpper->top = rRst->top; rLower->bottom = rRst->bottom;  // always

   if(rBmp->top > rRst->top)
     rUpper->bottom = rBmp->top - 1, rLower->top = rBmp->top;
   else
     rUpper->bottom = rBmp->bottom, rLower->top = rBmp->bottom + 1;

   if(rBmp->left > rRst->left)
   {
     rUpper->left = rLower->left = rRst->left;
     if(rBmp->top > rRst->top)
       rUpper->right = rRst->right, rLower->right = rBmp->left - 1;
     else  // rBmp->top < rRst->top
       rUpper->right = rBmp->left - 1, rLower->right = rRst->right;
   }
   else  // rBmp->left is same or less than rRst->left
   {
     rUpper->right = rLower->right = rRst->right;
     if(rBmp->top > rRst->top)
       rUpper->left = rRst->left, rLower->left = rBmp->right + 1;
     else
       rUpper->left = rBmp->right + 1, rLower->left = rRst->left;
   }
   return;
 } // end DiffORects



#pragma argsused
LONG PASCAL DoCmd( STDWINARGS )
 { // Handle WM_COMMAND msg, check wParam contents
   for(ndxGBL = 0; ndxGBL < icmdmsgfndim; ndxGBL++)
   {
     if(wParam == cmdmsgfn[ndxGBL].msgid)  // if match, call Fn
        return ((*cmdmsgfn[ndxGBL].pfnProc)(hwnd, msg, wParam, lParam));
   }
   if( wParam == IDM_EXIT ) // if not in array
   { // happens once; don't waste searches or fn
     SendMessage(hwnd, WM_CLOSE, 0, 0L);
     return(0L);
   }
   else return ( DefWindowProc(hwnd, msg, wParam, lParam) );
 } // end DoCmd



#pragma argsused
LONG PASCAL   // Handle WM_ERASEBKGND message
 DoEraseBkgnd( STDWINARGS )
 { // chk bypass - SOMETIMES get this msg before AND during
   // WM_PAINT/BeginPaint. Set flag here, DoPaint clears it.
   if(bBmpIsSelected && !bBypassTimer)
   { HDC  hdc;
     // WGO-- BitBlt captures image from overlapping screen
     // when bmp is underneath. So work around...  If this
     // restore is erroneous, ERASE gets it in invalidated
     // region. If valid, the restoration remains.
     // Done here due to timing and because BeginPaint
     // CLIPS to the invalid rectangle (or smaller)
     // so our work would be for nothing in that routine.
     bBypassTimer = 1; // set bypass timer routine flag.
     hdc = GetDC( hwnd );  // restore saved screen
     BitBlt(hdc, nbmpX, nbmpY, imageWidth, imageHeight,
            hdcOrg, 0, 0, SRCCOPY);
     ReleaseDC( hwnd, hdc);
   }
   return ( DefWindowProc(hwnd, msg, wParam, lParam) );
 } // end DoEraseBkgnd



 #pragma argsused
LONG PASCAL   // Handle IDM_FONT Menu message
 DoFont( STDWINARGS )
 { CHOOSEFONT cfStruct;
   LOGFONT    lfStruct = lfFontSel;

   cfStruct.lStructSize = sizeof(CHOOSEFONT);
   cfStruct.hwndOwner   = hwnd;
   cfStruct.lpLogFont   = &lfStruct;
   cfStruct.Flags       = CF_SCREENFONTS | CF_EFFECTS |
                          CF_INITTOLOGFONTSTRUCT;
   cfStruct.rgbColors   = crrgbColors;

   if( ChooseFont(&cfStruct) )
   {
     lfFontSel = lfStruct;
     lfFontSel.lfEscapement = 250;
     crrgbColors = cfStruct.rgbColors; // set selected color
     // redraw with selected font
     InvalidateRect(hwnd, &rWndGBL, TRUE);
   }
   return (0L);
 }  // end DoFont



#pragma argsused
LONG PASCAL   // Handle WM_PAINT message
 DoPaint( STDWINARGS )
 { HDC         hdc;
   HFONT       hFont = NULL, hFontPrev = NULL;
   PAINTSTRUCT ps;
   char        szOutString[40];
   int         nlength;

   hdc = BeginPaint(hwnd, &ps);
   // draw timer and displacement values
   nlength = wsprintf(szOutString, "Timer Value is %d.", nTimerValue);
   ExtTextOut(hdc, 5, rWndGBL.bottom - 60, 0, NULL, szOutString,
              nlength, NULL);

   nlength = wsprintf(szOutString, "X Displacement is %d.", nXDsp);
   ExtTextOut(hdc, 5, rWndGBL.bottom - 40, 0, NULL,
              szOutString, nlength, NULL);

   nlength = wsprintf(szOutString, "Y Displacement is %d.", nYDsp);
   ExtTextOut(hdc, 5, rWndGBL.bottom - 20, 0, NULL,
              szOutString, nlength, NULL);

   SetBkMode(hdc, TRANSPARENT);
   // create and select the font
   hFont     = CreateFontIndirect(&lfFontSel);
   hFontPrev = (HFONT)SelectObject(hdc, hFont);

   SetTextAlign(hdc, TA_BASELINE | TA_CENTER);
   // create shadow by drawing in black first, one over
   ExtTextOut(hdc, (rWndGBL.right >> 1) + 1,
                   (rWndGBL.bottom >> 1) + 1,
                    0, NULL, "U S A",  5, NULL);
   SetTextColor(hdc, crrgbColors); // now in selected color
   ExtTextOut(hdc, rWndGBL.right >> 1,
             (rWndGBL.bottom >> 1),
              0, NULL, "U S A",  5, NULL);

   if(hFontPrev) // delete the font
   { SelectObject(hdc, hFontPrev);
     DeleteObject(hFont);
   }
   if(bBmpIsSelected) // get fresh screen image
      BitBlt(hdcOrg, 0, 0, imageWidth, imageHeight,
             hdc, nbmpX, nbmpY, SRCCOPY);

   EndPaint( hwnd, &ps);
   bBypassTimer = 0; // reset bypass flag
   return(0L);
 } // end DoPaint



#pragma argsused
LONG PASCAL DoSize( STDWINARGS )
 { // Handle Wm_SIZE message
   if( rWndGBL.right  == LOWORD(lParam) &&
       rWndGBL.bottom == HIWORD(lParam) )
     return 0L;  // if same size, exit

   // otherwise get new size
   rWndGBL.right  = LOWORD(lParam);
   rWndGBL.bottom = HIWORD(lParam);
   // update invalid text rect
   rWndInv.top    = rWndGBL.bottom - 60;
   rWndInv.bottom = rWndGBL.bottom;

   if(bBypassTimer == 255)
   { // if first pass, exit after setting size
     bBypassTimer = 0;
     return 0L;
   }
   if(wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED)
   { // redraw on new size
     if(bBmpIsSelected != 2)
        InvalidateRect(hwnd, &rWndGBL, TRUE);
     else // restart if was icon and movement sel
        SelectBmp(hwnd, msg, wParam, lParam);

     return 0L;
   }
   if(wParam == SIZE_MINIMIZED && bBmpIsSelected == 1)
   { // If going to icon, stop movement, set sel to 2 for restore.
     UnSelectBmp(hwnd, msg, wParam, lParam);
     bBmpIsSelected = 2;  // set Flag for size return
   }
   return (0L);
 }  // end DoSize



BOOL GetNewTimer(HWND hwnd)
 { // set up timer
   while( !SetTimer(hwnd, IMAGE_TIMER, nTimerValue, NULL) )
   {
     if( IDCANCEL == MessageBox( hwnd,
                     "Can't get timer for animation.",
                     "Move Bitmap Information",
                     MB_ICONEXCLAMATION | MB_RETRYCANCEL) )
         return (FALSE);
   }
   return (TRUE); // got timer
 } // end GetNewTimer



#pragma argsused
LONG PASCAL   // Handle IDM_SELECTBMP Menu message
 SelectBmp( STDWINARGS )
 { HDC    hdcClient;
   BITMAP bm;  // Bitmap info
   HMENU  hmenuMain = GetMenu(hwnd);

   if( !GetNewTimer(hwnd) )
         return 0L;  // return if can't obtain timer

   if(wParam == IDM_SELECTBMP) // chg menu select to stop
      ModifyMenu(hmenuMain, IDM_SELECTBMP, MF_BYCOMMAND,
                            IDM_UNSELECTBMP, "Stop Movement");
   // load bitmaps from resource file
   hbm[0] = LoadBitmap(hInstGBL, "image1"); // top
   hbm[1] = LoadBitmap(hInstGBL, "image2"); // right
   hbm[2] = LoadBitmap(hInstGBL, "image3"); // bottom
   hbm[3] = LoadBitmap(hInstGBL, "image4"); // left
   hbm[4] = LoadBitmap(hInstGBL, "imagebkg"); // mask

   GetObject(hbm[0], sizeof(bm), (LPSTR)&bm);
   imageWidth  = bm.bmWidth;  imageHeight = bm.bmHeight;
   btrblFlag   = 1;  // set top as first image

   hdcClient = GetDC( hwnd);
   hdcOrg  = CreateCompatibleDC( hdcClient);
   hbmOrg  = CreateCompatibleBitmap(hdcClient,
                                    imageWidth, imageHeight);
   hbmOldOrg = (HBITMAP)SelectObject(hdcOrg, hbmOrg);
   // save initial screen image
   BitBlt(hdcOrg, 0, 0, imageWidth, imageHeight,
          hdcClient, nbmpX, nbmpY, SRCCOPY);

   // clean up
   ReleaseDC(hwnd, hdcClient);
   bBmpIsSelected = 1;
   return (0L);
 } // end SelectBmp



#pragma argsused
LONG PASCAL   // Handle IDM_SINGLEIMAGE command
 SingleImage( STDWINARGS )
 { // Toggle Single Image/Multiple image switch
   int    nChkType;
   HMENU  hmenu = GetMenu(hwnd);

   bSingleImage = !bSingleImage;
   nChkType = (bSingleImage) ? MF_CHECKED : MF_UNCHECKED;
   CheckMenuItem(hmenu, IDM_SINGLEIMAGE, MF_BYCOMMAND | nChkType);
   return(0L);
 } // end SingleImage



#pragma argsused
LONG PASCAL   // Handle IDM_UNSELECTBMP
 UnSelectBmp( STDWINARGS )
 { HMENU hmenu = GetMenu(hwnd);
   HDC   hdcClient;

   KillTimer(hwnd, IMAGE_TIMER);
   bBmpIsSelected = 0;
   // restore the screen
   hdcClient = GetDC( hwnd);
   BitBlt(hdcClient, nbmpX, nbmpY, imageWidth, imageHeight,
          hdcOrg, 0, 0, SRCCOPY);
   ReleaseDC( hwnd, hdcClient);

   if(wParam == IDM_UNSELECTBMP)
   {  // if menu call, redraw: image may be under menu
     InvalidateRect(hwnd, &rWndGBL, TRUE);
     // chg menu select to start
     ModifyMenu(hmenu, IDM_UNSELECTBMP, MF_BYCOMMAND,
                       IDM_SELECTBMP, "Start Movement");
   }
   for(ndxGBL = 0; ndxGBL < 5; ndxGBL++) // delete the bitmaps
       DeleteObject( hbm[ndxGBL] );

   SelectObject(hdcOrg, hbmOldOrg);  // deselects hbmOrg
   DeleteDC(hdcOrg);
   DeleteObject(hbmOrg);

   return (0L);
 } // end UnSelectBmp

Copyright © 1994, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.