CS-360
Fall, 2000
Class 4
R. Eckert

BITMAPS, ANIMATION, AND TIMERS

Windows graphics--up to now we have used simple GDI drawing functions 
(Ellipse(), etc.) We want to be able to work with more complex images and
animation.

Bitmap: An Off-screen Canvas--

  -A rectangular image that can be created with painting programs.
  -A data structure that stores a matrix of pixel values in memory.
  -The pixel value stored determines the color of a pixel in the image.
  -Windows supports 4-bit, 8-bit (indirect), & 16/24-bit (direct) pixel values.
  -Bitmaps can be stored as .bmp files (static resource data).
  -They can be edited with paint programs.
  -But a bitmap takes up lots of space.
  -It is a GDI object that must be selected into a Device Context to be used.
  -It can be thought of as the canvas of a DC upon which drawing takes place.
  -A bitmap must be compatible with a video display or printer.
  -It can be manipulated invisibly and apart from a physical display device.
  -Can be transfered to/from physical a device ==> flicker-free animation.
  -A bitmap doesn't store information on drawing commands (metafiles do that).
  -Icons and cursors are small bitmaps.

Using Bitmaps--

  -Create and save a bitmap using a paint editor --> image.bmp
  -Add it to the program's resource script file, e.g.:     

      Img   BITMAP   image.bmp

  -To display it, we must first load it from the program's resources:

      HBITMAP    hBitmap;
      HINSTANCE  hInstance;
      hInstance = (HINSTANCE)GetWindowLong (hWnd, GWL_HINSTANCE);
      hBitmap = LoadBitmap (hInstance, Img);

  -Note the use of GetWindowLong() to get the program's instance handle which
   identifies the program's resource data. This is one possible way of getting
   hInstance. (The type cast is necessary to avoid a compiler warning since
   GetWindowLong() is typed as a pointer to a void--which is not the same as
   HINSTANCE.) Once we have the instance handle, we can call any of the
   Windows functions that load in program resources. Here LoadBitmap() is
   used to get our bitmap.

Steps in displaying the bitmap--

   0. Get a DC (with GetDC() or BeginPaint() as usual).
   1. Create a memory device context with CreateCompatibleDC().
   2. Load the bitmap with LoadBitmap() [or create it with CreateCompatibleBitmap()].
   3. Select bitmap into the memory DC with SelectObject().
   4. Copy bitmap from the memory DC to the device DC with BitBlt() or StretchBit().


We can say:       Bitmap          Window Client Area
                ----------   =   --------------------
                Memory DC                DC

Memory DC-- Like a DC for a physical device, but it is not tied to the device. 
A memory DC is used to access a bitmap rather than a device. A bitmap must be 
selected into a memory DC before it can be displayed on the physical device.

hMemDC=CreateCompatibleDC(hDC)-- Creates a memory DC with the same physical 
attributes as the DC of the given device. A subsequent call to SelectObject() 
to select a bitmap into this DC sets up the bitmap data with the exact sequence 
of bytes needed to display it on the physical device, so copying from a Memory 
DC to a device DC is fast

BitBlt (hDestinationDC, x, y, w, h, hSrcDC, xsrc, ysrc, dwRop) --
   Copies pixels from bitmap selected into the source DC to the destination DC.
   x,y -- specify the upper lefthand corner of the destination rectangle.
   w,h -- specify the width, height in pixels of the rectangle to be copied.
   xsrc, ysrc -- specify the upper lefthand corner of the source bitmap.
   dwRop -- specify the raster operation for the copy.

Raster Ops--

How to combine source pixel colors with current pixel colors on the
destination screen--Boolean logic combinations (AND, NOT, OR, XOR, etc.)
The currently-selected brush pattern also can be combined==> 256 different
possible combintations, 15 of which are named. (In the following table: 
S=source bitmap, D=destination bitmap, P=currently-selected brush, i.e., 
the current Pattern.)

BLACKNESS       0 (all black)
DSTINVERT       ~D
MERGECOPY       P & S
MERGEPAINT      ~S | D
NOTSRCCOPY      ~S
NOTSRCERASE     ~(S | D)
PATCOPY         P
PATINVERT       P ^ D
PATPAINT        (~S | P) | D
SRCAND          S & D
SRCCOPY         S
SRCERASE        S & ~D
SRCINVERT       S ^ D
SRCPAINT        S | D
WHITENESS       1 (all white)

StretchBlt()-- Same as BitBlt() except the size of the copied bitmap can be 
changed. Both the source and destination width and height are specified.

 StretchBlt(hDestinationDC,x,y,w,h,hSrcDC, xsrc,ysrc,wsrc,hsrc,RasterOp)

BITMAP1: An Example program that uses BitBlt() and StretchBlt().

PatBlt(hDC, x, y, w, h, dwRop) -- Paints a bit pattern on the specified
DC. The pattern is a combination of the currently-selected brush and the
pattern already on the destination device. x,y,w,h determine the rectangular
area and dwRop (raster op) specifies how the pattern will be combined with
the destination pixels. Possible dwRops are: BLACKNESS (0), DSTINVERT (~D),
PATCOPY (P), PATINVERT (P^D), and WHITENESS (1). The pattern is tiled across
the specified area.

BITMAP3 Example program-- Experimenting with different raster ops:
  Source--COLORS.BMP (white, red, yellow, blue, green bands: see SRCCOPY image).
  Destination--a pattern of black and white tiles: BRIKBRSH.BMP (look at
    DSTINVERT image for reversed destination pattern).
  Brush pattern--a blue diagonal cross pattern (look at PATCOPY image).
Note that the "INVERT" raster ops disappear and appear (for animation).

The program uses BitBlt() to copy the bitmap described in COLORS.BMP to
different regions of the application window using each of the named raster ops.
PatBlt() is used to paint the client area with the pattern described in
BRIKBRSH.BMP; a brush is created from this bitmap with CreatePatternBrush().

The names of the various raster ops are displayed (using ANSI_VAR_FONT)
below each bitmap.

A string table is used to store the raster op names with the program's
resource data. This is an alternative to defining the strings as static
data in the program. Storing strings in a string table with the resource
data means it's easy to make changes without searching through the program
for the strings.

Defining a string table resource--

STRINGTABLE [load option] [memory options]
BEGIN
  idNumber   "Text String 1"
  idNumber   "Text String 2"
  ...
END

Developer Studio also has a string table editor that facilitates the
preparation of a string table with the other program resources.

Loading a string from the string table--like loading other resources from
the program's resources:

  LoadString(hInstance, idNumber, lpszBuf, cbSizeBuf);

where lpszBuf is the address of a buffer to receive the string.
ANIMATED GRAPHICS--

To create a moving picture--
  We want to give the illusion of motion by continual draw/erase/redraw.
  But how do we do it without taking over Windows?
  We shouldn't allow disruption of other applications.

In a DOS application, you could do the following:

while (TRUE)
{
  /* exit loop if a key is pressed */
  /* erase old object image */
  /* compute new location of object */
  /* draw object at new location */
}

In Windows, other programs won't be able to run while this loop is
executing. We need to keep giving control back to Windows so other programs
can operate. This is especially true under the cooperative multitasking
used in Windows 3.xx.

One method: Use a PeekMessage() loop instead of a GetMessage() loop.

GetMessage() only returns control if a message is waiting for the calling
program.

PeekMessage() returns (with 0 value) if no active messages are in the system.
   -i.e., when no other programs are doing anything and there's no message for
    our program.

When this occurs, our application can use the opportunity to redraw the image:
  -i.e., take action if PeekMessage() doesn't find a pending message.
  -This is different from the GetMessage() loop structure.

Detail--PeekMessage() doesn't return zero for WM_QUIT message (like Getmessage())
  So application must explicitly check for a WM_QUIT message to exit the program.

PeekMessage(lpMsg, hWnd, uFilterFirst, uFilterLast, wRemove);
   -The first 4 parameters are same as GetMessage.
   -Last one: specifies whether or not the message should be removed from the Queue.

Our PeekMessage() message loop will look like:

while (TRUE)
{
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
     {                           /* non-zero means we must handle the message */
     if (msg.message == WM_QUIT)
        return msg.wParam;       /* return to Windows if msg is a WM_QUIT */
     else
        {
        TranslateMessage (&msg);
        DispatchMessage (&msg);  /* dispatch the msg to our WndProc */
        }
     }
  else                   /* zero means there's dead time that can be used */
     {
     do some other stuff like drawing the next animation frame
     }
 }

THE "BALL" ANIMATION APPLICATION (Ball bouncing off walls)--

BALL.H--

   Define menu item constants.
   Define ball constants: (VELOCITY, BALLRAD, MINRAD--how close to wall).

BALL.CPP--

global variables--
   _dDrawOn: on/off switch to toggle animation on or off.
   _nXSize,_nYSize: window width, height--to determine if ball is inside
       (if window is resized, we need to change these).

WM_CREATE message--Set initial animation toggle switch to off.

WM_SIZE message--sent by Windows anytime window is resized by the user.
       -vertical/horizontal size of client area is encoded in lParam.
       -least significant two bytes = horizontal size in pixels.
       -most significant two bytes = vertical size.
       -macros HIWORD() and LOWORD() get these values.
       -store in globals _nXSize, _nYSize (used by WndProc(), WinMain() &
        our function DrawBall()).

Drawing the ball with helper function DrawBall()--
   Use a broad white pen to outline the ball-- 
      -has the effect of erasing the visible part of the "old" ball.
   Use a solid red brush for the ball's interior.
   Draw the ball in its new position with Ellipse().
   Use Sleep() to slow down the animation (for fast machines).
   Get rid of pen, brush, and DC.

Motion of the ball in DrawBall()--
   Keep track of current position in nX,nY.
   Each time function is called, add nVelX, nVelY velocity values to previous x,y.
   When ball is within MINRAD pixels of a wall...
     -reverse the velocity in the direction of the collision;
     -this is an elastic collision in physics.

If part of the animation window is obscured by another program's wndow, the ball
continues to bounce underneath the hidden parts of the window--it will show up
again when exposed. We don't have to worry about whether or not the ball is
hidden by another window; we just send our output to the window's device
context; Windows takes care of figuring out which portions of our window are 
visible and which are not.

DRAWING ON A MEMORY BITMAP--IMPROVING AN ANIMATION--

The BALL application is fairly flicker free, but if we were drawing many
objects during each frame of an animation, we would notice an annoying
flicker. This is because of the multiple accesses to the frame buffer
during each new frame. The best way of getting around this problem is to
just make ONE ACCESS to the frame buffer during each new frame. Under
Windows this can be done by using offscreen memory bitmaps.

We can use GDI graphics functions to "draw" on a bitmap selected into a
memory DC just as with a "real" DC. So we can do many drawing operations,
and, when done, BitBlt() the result to the real DC-- which is very fast, so 
there will be no flicker in animation applications.

Getting a Bitmap to draw on--

  Up to now we've used LoadBitmap() to get a bitmap defined in a resource
  description (.rc) file and then selected it into a memory DC.

  An alternative: create a blank bitmap in memory with:

   hBitmap = CreateCompatibleBitmap (hDC, w, h);

     hDC--a handle to the device (hDC) the Bitmap is to be compatible with.
     w,h--the width and height of the bitmap.

When this bitmap is selected into a memory DC, we can use all the GDI
graphics functions to draw on it without affecting the real device screen.
(All the GDI drawing operations are now invisible to the user.)

When drawing is all done, BitBlt() it to the real device==>just one screen
access, as opposed to drawing directly to the screen device context. If the
image is fairly complex, many accesses would be made to the screen during
each animation frame, which could cause flicker.

Animation of a moving object over a stationary background--

   Set up an offscreen bitmap and select it into a memory DC.

   For Each Frame (each time PeekMessage() returns):
     Calculate the new position of the object(s).
     Erase entire off-screen bitmap (or BitBlt() the background bitmap to it).
     Redraw the object(s) (in new position) on the off-screen bitmap.
     BitBlt() the entire off-screen bitmap to the screen.

For a large image field, this BitBlt() covers a large area, so it could be slow.
A better method would be to calculate the affected area (the rectangle
encompassing the old and new object position) and BitBlt() to that area only.

See the BALLBLT example program--

Sprite--a little bitmap that moves around on the screen. We could restore
the background and just BitBlt() the sprite over it.

But there's a problem--The sprite consists of the object we want enclosed
in a rectangle. So when the blitting is done, the background color inside
the enclosing rectangle will wipe out the background area on the
destination bitmap. So our moving object will have a "halo" around it. It
will also always have a rectangular shape.

Solution (Sprite Animation)--

1. Set up a "mask bitmap" in which the sprite pixels are black and the rest
of the enclosing rectangle is white.

2. BitBlt() this over the background using the SRCAND (AND) raster op.

3. Set up an "image bitmap" in which the sprite pixels are set to the color
they should be (whatever colors are in the sprite object) and the rest of
the enclosing rectange pixels are black.

4. BitBlt() this to the result of step 2 using the SRCINVERT (XOR) raster op.

The result will make the sprite move to its new location with the background
around it intact.

DIB (Device Independent Bitmaps)--

The bitmaps we've looked at until now are Device Dependent Bitmaps(DDB).
These have the same color organization as the real graphics output device.
What's stored are the dimensions of the display and the pixel values--no
information showing how the pixel values are mapped to actual RGB colors.
So DDBs can't be used without loss of color fidelity on a device with
different color organization (e.g., a different video card).

Windows 3.0 initiated the Device Independent Bitmap (DIB)--includes its own
color table showing how pixel values correspond to RGB colors. It can be
displayed on any raster output device; the color mapping info in the DIB can be
used to convert to the nearest colors the device can render.

    DDB storage format                DIB storage format
   -------------------------------------------------------
    Bitmap Info Header                Bitmap Info Header
    Pixel values                      Color Table
                                      Pixel Values

Using a DIB--

 It Can't be selected into a DC ==> no BitBlt(), so how do we display it?

 1. Convert it to a DDB using CreateDIBitmap();
      -creates a DDB and returns a handle to it.
      -can be used as any other DDB (BitBlt(), etc.).
      -memory intensive since it requires twice the memory.

 or

 2. Use StretchDIBits();
      -copies bits directly from a DIB to the target DC.
      -used like StretchBlt(), but with differences (search "StretchDIBits").

Check out the online help and the course references for more information on
DIBs.

The Direct-X Graphics Library--

Although BitBlt() and StretchBlt() are relatively fast, they are no match
to direct accesses to the computer's frame buffer (video RAM memory).
Until fairly recently, that meant that the best fast-action game programs
for PCs had to be written as DOS applications. In 1995 Microsoft created a
library of routines that permit direct access to the frame buffer and to
any acceleration hardware that might be built into the system's video card.
This library is called DirectX, and comes with the Windows operating system.
DirectX will be introduced in the next set of notes. It is also covered in
detail in several of the references.


THE WINDOWS TIMER--

An input device that periodically notifies an application when a specified
time interval has elapsed. The program tells Windows the interval; Windows
sends WM_TIMER message to signal the interval has elapsed.

SOME TIMER APPLICATIONS--

Keeping time--In clock programs, timer messages tell a program when to update
the time. But this is not precise.

Waking up--a timer message is used to trigger a preset alarm.

Multitasking--Since Windows 3.xx was a nonpre-emptive multitasking system,
programs must return control to Windows quickly. But what if the program
has to do a lot of processing? We can divide job up into smaller pieces and
process each piece on receipt of timer message. Even in pre-emptive
multitasking (Windows 95/NT), it's more efficient to return control to
Windows as soon as possible.

Maintaining an updated status report--realtime updates of continuously-
changing information.

Autosave feature--a timer message can prompt a program to periodically save
the user's work.

Pacing movement--If game objects must move at a certain rate, timer
messages can trigger that movement ==> no inconsistencies from variations
in processor speed.

Activation of a screen saver after a certain period of time.

Terminating demonstration versions of programs--a timer message signals when
the time is up.

Multimedia--Programs that play CD audio, sound, music, often let audio data
play in the background. A program can use a timer to periodically determine how
much of the audio has played and to coordinate on-screen visual information.

USING A TIMER--

Allocate and set a timer with:

  SetTimer(hWnd, timerID, Timeout, f_address);

Parameters:
  -hWnd of the window to receive the timer messages (if the last parameter is 
   NULL, this window's WndProc() will receive the messages).
  -the timer id (UINT);
  -the timeout duration (UINT); interval in milliseconds between WM_TIMER messages.
  -the TimerProc--the address of the procedure that will receive/process the
   timer messages--a "callback" function.

Possible Return Values (UINT):
 -the identifier of the new timer if 2nd argument was NULL & call was OK.
 -nonzero if 1st argument was a valid window handle and the call was successful.
 -zero if unsuccessful.

From that point on, the timer will repeatedly generate WM_TIMER messages
and reset itself each time it times out.

WM_TIMER message:  wParam = Timer ID; lParam = 0.

When an application is done using a timer, stop timer messages and remove the 
timer from the system with--

    KillTimer(hWnd, timerID);

Make sure all timers have been killed in response to WM_DESTROY message
prior to program termination.

There can only be a finite number of timers running at once. If all have
been allocated, SetTimer() returns NULL, so be sure to check if the timer was
allocated successfully.

If no timer is available, a program could put up a Message Box advising the 
user to close another application that may be using one or more timers:

while (!SetTimer(hWnd, 1, 1000, NULL))
   if (IDCANCEL == MessageBox (hwnd, "Too many timers!", "Pgm Name",
                               MB_ICONEXCLAMATION | MB_RETRYCANCEL))
      return FALSE; /* return to Windows if user hits Cancel Button */

How does a timer work?--

It uses the 8253 hardware timer interrupt (INT 8)--every 54.925 msec. (18.2
times a second). The SYSTEM.DRV program intercepts INT 8 and sets a new
vector. This points to a routine within the USER module of Windows that
decrements counters for each timer set by any Windows application. When any
of the counters reaches 0, USER puts a WM_TIMER message in that application's
message queue and resets the counter to its original value. ==> the resolution is
that of the PC timer--18.2 times per second. So WM_TIMER messages can't be
generated any faster than that. Also the time interval specified is rounded
down to an integral number of clock ticks.

WM_TIMER messages are handled like WM_PAINT messages--low priority; i.e. if 
a program has only WM_TIMER and WM_PAINT messages on its queue, and other
programs have other messages on theirs, Windows will pass control to them.

If other applications can be busy, your program may not get WM_TIMER
messages at the specified interval. They are put on your program's queue,
but until your program regains control, it won't receive them.

This means that you should not use timers for precision timekeeping.

BEEPER1 Program (See Petzold)--Beeps and changes colors once every second.

Moving/resizing window --> causes a program to enter a "modal message loop"--
i.e., Windows prevents interference with resize/move operations by trapping
all messages with a message loop inside Windows. They are disgarded and
don't make it to the program's message loop ==> the program stops beeping!

Using a Timer as an alternative to the PeekMessage() loop in animation--
   A timer message can be used to generate the next frame in the animation
   ==> the animation will run at the same speed on all machines.

BALLTIME Program (like BALL, but uses a Timer instead of PeekMessage())--

AN ALTERNATIVE WAY OF USING TIMER MESSAGES (use of callbacks)--

A callback function--

A function in your program called by Windows. You give Windows information 
on the location of the callback function, and then Windows calls it when,
for example, a timer message comes along. It's sort of like the WndProc().

A callback function must be type CALLBACK, just as WndProc(), since it's
called from outside our program (by Windows).

The parameters and value returned by a callback function depend on the 
purpose of the callback function. For the call-back function associated with 
the timer, the parameters are the same as for the WndProc().

VOID CALLBACK TimerProc(HWND hWnd, UINT message,
                        WPARAM wParam, LPARAM lParam);

  -hWnd is the handle to the window specified when you call SetTimer().
  -message is always a WM_TIMER message.
  -wParam is the timer ID.
  -lParam is the current system time.

This function should do the processing of the WM_TIMER message.

But you must tell Windows where this function is--in the 4th parameter to
SetTimer(). Under Windows 95 this is just the address of the timer callback
function. (Under Windows 3.1 it was much more complicated--you had to
specify the "instance address" of a chunk of code--a thunk--that would
generate the correct segment/offset address of the timer procedure.)

Then when you set the timer, you do it with:

   SetTimer (hWnd, ID_TIMER, interval, (TIMERPROC) TimerProc);
     /* setting a timer with callback function TimerProc() under Windows 95 */

BEEPER2 (Petzold)--An example of using a timer callback function.
ANOTHER TIMER FUNCTION--

GetCurrentTime()--returns the number of milliseconds from Windows startup; not
very precise.

PERFORMANCE MONITOR COUNTERS--

High-resolution timers that provide the most accurate measurement of time
possible in the system. Can be used in applications for which precise time
measurement is important--e.g., scientific measurements, musical interval
timings, game interactions, and performance monitoring of code execution.

QueryPerformanceCounter(*pmilliseconds)--the current value of the high-
resolution performance counter is returned in *pmilliseconds with 64-bit
precision. The function returns FALSE if there is a high-resolution timer in
the system, TRUE if not. [See Visual C++ 5.0 online help for details]

GETTING ABSOLUTE TIME INFORMATION--use C library functions time() and
localtime(). (See on-line help for details.)