Animation

I mentioned at the beginning of Chapter 11 that GDI supports only static pictures. Although it's true that Windows has no traditional animation support (such as the ability to flip video pages, check for vertical retrace of the video signal, or construct rotatable sprites), that doesn't mean that we can't move images around on the display. Yes, it's time for the bouncing ball program. The BOUNCE program, shown in Figure 13-2 beginning on the following page, constructs a ball that bounces around in the window's client area. The program uses the timer to pace the ball; it draws the ball with a simple ”bit-block transfer“ from a memory device context.

BOUNCE.MAK

#----------------------

# BOUNCE.MAK make file

#----------------------

bounce.exe : bounce.obj bounce.def

link bounce, /align:16, NUL, /nod slibcew libw, bounce

rc bounce.exe

bounce.obj : bounce.c

cl -c -Gsw -Ow -W2 -Zp bounce.c

BOUNCE.C

/*---------------------------------------

BOUNCE.C -- Bouncing Ball Program

(c) Charles Petzold, 1990

---------------------------------------*/

#include <windows.h>

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,

LPSTR lpszCmdLine, int nCmdShow)

{

static char szAppName [] = "Bounce" ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

if (!hPrevInstance)

{

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = NULL ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

RegisterClass (&wndclass) ;

}

hwnd = CreateWindow (szAppName, "Bouncing Ball",

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

if (!SetTimer (hwnd, 1, 50, NULL))

{

MessageBox (hwnd, "Too many clocks or timers!",

szAppName, MB_ICONEXCLAMATION | MB_OK) ;

return FALSE ;

}

ShowWindow (hwnd, nCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)

{

static HANDLE hBitmap ;

static short cxClient, cyClient, xCenter, yCenter, cxTotal, cyTotal,

cxRadius, cyRadius, cxMove, cyMove, xPixel, yPixel ;

HBRUSH hBrush ;

HDC hdc, hdcMem ;

short nScale ;

switch (message)

{

case WM_CREATE :

hdc = GetDC (hwnd) ;

xPixel = GetDeviceCaps (hdc, ASPECTX) ;

yPixel = GetDeviceCaps (hdc, ASPECTY) ;

ReleaseDC (hwnd, hdc) ;

return 0 ;

case WM_SIZE :

xCenter = (cxClient = LOWORD (lParam)) / 2 ;

yCenter = (cyClient = HIWORD (lParam)) / 2 ;

nScale = min (cxClient * xPixel, cyClient * yPixel) / 16 ;

cxRadius = nScale / xPixel ;

cyRadius = nScale / yPixel ;

cxMove = max (1, cxRadius / 4) ;

cyMove = max (1, cyRadius / 4) ;

cxTotal = 2 * (cxRadius + cxMove) ;

cyTotal = 2 * (cyRadius + cyMove) ;

if (hBitmap)

DeleteObject (hBitmap) ;

hdc = GetDC (hwnd) ;

hdcMem = CreateCompatibleDC (hdc) ;

hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;

ReleaseDC (hwnd, hdc) ;

SelectObject (hdcMem, hBitmap) ;

Rectangle (hdcMem, -1, -1, cxTotal + 1, cyTotal + 1) ;

hBrush = CreateHatchBrush (HS_DIAGCROSS, 0L) ;

SelectObject (hdcMem, hBrush) ;

SetBkColor (hdcMem, RGB (255, 0, 255)) ;

Ellipse (hdcMem, cxMove, cyMove, cxTotal - cxMove,

cyTotal - cyMove) ;

DeleteDC (hdcMem) ;

DeleteObject (hBrush) ;

return 0 ;

case WM_TIMER :

if (!hBitmap)

break ;

hdc = GetDC (hwnd) ;

hdcMem = CreateCompatibleDC (hdc) ;

SelectObject (hdcMem, hBitmap) ;

BitBlt (hdc, xCenter - cxTotal / 2,

yCenter - cyTotal / 2, cxTotal, cyTotal,

hdcMem, 0, 0, SRCCOPY) ;

ReleaseDC (hwnd, hdc) ;

DeleteDC (hdcMem) ;

xCenter += cxMove ;

yCenter += cyMove ;

if ((xCenter + cxRadius >= cxClient) ||

(xCenter - cxRadius <= 0))

cxMove = -cxMove ;

if ((yCenter + cyRadius >= cyClient) ||

(yCenter - cyRadius <= 0))

cyMove = -cyMove ;

return 0 ;

case WM_DESTROY :

if (hBitmap)

DeleteObject (hBitmap) ;

KillTimer (hwnd, 1) ;

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

BOUNCE.DEF

;-----------------------------------

; BOUNCE.DEF module definition file

;-----------------------------------

NAME BOUNCE

DESCRIPTION 'Bouncing Ball Program (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

BOUNCE reconstructs the ball whenever the program gets a WM_SIZE message, the diameter of the ball being one-sixteenth of either the height or the width of the client area, whichever is shorter. However, the program constructs a bitmap that is larger than the ball—on each of its four sides, the bitmap extends beyond the ball's dimensions by one quarter of the ball's radius.

After the bitmap is selected into a memory device context, it is colored white:

Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;

A diagonally hatched brush is selected into the memory device context, and the ball is drawn in the center of the bitmap:

Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;

The margins around the edges of the ball effectively erase the previous image of the ball when the ball is moved. Redrawing the ball at another position requires only a simple BitBlt call using the ROP code of SRCCOPY:

BitBlt (hdc, xCenter - xTotal / 2,

yCenter - yTotal / 2, xTotal, yTotal,

hdcMem, 0, 0, SRCCOPY) ;

BOUNCE demonstrates the simplest way to move an image around the display, but this approach isn't satisfactory for general purposes. If you're interested in animation, you'll want to explore some of the other ROP codes (such as SRCINVERT) that perform an exclusive OR operation on the source and destination.