TweetFollow Us on Twitter

Color Animation, Part II
Volume Number:10
Issue Number:5
Column Tag:Getting Started

Related Info: Color Manager

Color Animation, Part II

GWorlds, Bitmaps, PixMaps, and CopyBits - getting the bits flying

By Dave Mark, MacTech Magazine Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

Last month’s column introduced PixMapper, a colorized version of last September’s BitMapper program. BitMapper caused a black and white BitMap to float over a patterned background with absolutely no flicker (Figure 1). BitMapper based its magic on a series of three offscreen BitMaps, BitMaps created in memory but not visible on the screen. The first offscreen contains the foreground image (the left-pointing hand). The second offscreen contains the background image (the grey, framed rectangle). The third offscreen is used to combine the foreground and background before drawing the combined image into the BitMapper window.

Figure 1. Last September’s BitMapper program.

Why does this work? Rather than erasing the old image and repainting it in a new position in the window (in BitMapper’s case, the hand tracks the position of the mouse, sort of a giant cursor), this algorithm repeatedly repaints the entire window, based on the contents of the combining offscreen BitMap. Just as a movie consisting of a series of still frames appears to be a single animated image, the constant repainting of the BitMapper window gives the appearance of an animated image.

Adding Color to the Algorithm

As you’d expect, PixMapper achieves the same affect in color by using PixMaps instead of BitMaps. Just as a BitMap describes a black and white bit pattern, a PixMap describes a color bit pattern. Unfortunately, animation with PixMaps is not quite as simple as its black and white counterpart. Why? A PixMap is just more complex. While a BitMap’s pixels are all either black or white, a PixMap’s pixels can be any one of a number of colors, depending on the depth of the PixMap.

The BitMap data structure is relatively simple:

struct BitMap
{
 Ptr baseAddr;
 short rowBytes;
 Rect bounds;
};

Now compare the BitMap to the PixMap data structure:

struct PixMap
{
 Ptr baseAddr;   /*pointer to pixels*/
 short rowBytes; /*offset to next line*/
 Rect bounds;    /*encloses bitmap*/
 short pmVersion;/*pixMap version number*/
 short packType; /*defines packing format*/
 long packSize;  /*length of pixel data*/
 Fixed hRes;/*horiz. resolution (ppi)*/
 Fixed vRes;/*vert. resolution (ppi)*/
 short pixelType;/*defines pixel type*/
 short pixelSize;/*# bits in pixel*/
 short cmpCount; /*# components in pixel*/
 short cmpSize;  /*# bits per component*/
 long planeBytes;/*offset to next plane*/
 CTabHandle pmTable; /*color map for this pixMap*/
 long pmReserved;/*for future use. MUST BE 0*/
};

As you can see, the PixMap data structure is far more complicated. In addition, while BitMapper used a GrafPort, PixMapper must use a CGrafPort. As you might suspect, setting up a CGrafPort is more complicated than setting up its black and white counterpart.

The GWorld

Fortunately, rather than forcing you to go through the arduous process of creating and maintaining these far more complex structures, the Toolbox offers a terrific shortcut, namely, the GWorld.

The GWorld is a full-color, offscreen drawing environment. Just as you’d use an offscreen GrafPort and BitMap to prepare a black and white image for blitting to the screen (blitting comes from an old term BLT, which stood for BLock Transfer, meaning to copy a block of memory from one area to another, all at once), you’ll use a GWorld to do the same for a color image. PixMapper demonstrates all this and a little bit more.

Before we get into the code, let’s take a quick look back at PixMapper in action.

Running PixMapper

The PixMapper menu bar features four menus: Apple, File, Edit, and Colors. PixMapper features a single window filling the entire main screen (see GetMainDevice()). PixMapper fills this window with randomly colored squares. Once the window is completely drawn, PixMapper loads a PICT resource, and uses a series of offscreen GWorlds to animate the PICT across the colored background. The speed of your PICT will depend on the speed of your machine and the size of the PICT. The important thing to notice is that the PICT animates smoothly with absolutely no flicker. If there is any hesitation, it is most likely due to the system taking time to do some housekeeping chore. Notice that PixMapper works, even if the PICT is non-rectangular or has a hole in it (see Figure 2).

Figure 2. PixMapper in action.

The PICT starts in the upper-left corner of the window and moves towards the lower right corner. Every time the PICT hits the edge of the window, the PICT will bounce off and continue in the opposite direction.

When you select Redraw from the File menu, PixMapper redraws the window and starts the animation over again.

Go to the Colors menu and play with all the different menu settings. PixMapper lets you play with both the RGB and HSV color models. RGB lets you set a color’s red, green, and blue values. If you uncheck Random Reds, for example, the screen will be redrawn with a red value of 0 (all greens and blues). If Random Reds is checked, the red component of the color is a random value from 0 to 65535.

If you select HSV Colors, you’ll be able to work with hue, saturation, and brightness rather than red, green, and blue. Unlike the RGB menu items, if you uncheck Random Hues, a color’s hues component is set to 65535 instead of 0. Play around with these settings. Do you think you’ll get different results when you choose random RGB versus when you choose random hues?

To learn more about color and the RGB and HSV color models, read “About the Color Manager” in Inside Macintosh, Volume V, THINK Reference or in the upcoming volume Inside Macintosh: Imaging.

The PixMapper Source Code

PixMapper starts off with three critical #includes. <QDOffscreen.h> has all the GWorld defines, <Picker.h> has the definitions needed to work with the RGB and HSV data structures and routines, and <GestaltEqu.h> has the definitions you’ll need to work with Gestalt(). Take a few minutes to look through these three include files.


/* 1 */
#include <QDOffscreen.h>
#include <Picker.h>
#include <GestaltEqu.h>

There are a lot of #defines used in this program. As usual, they’ll all be described as they occur in the code.


/* 2 */
#define kMBARResID 128
#define kSleep   0L
#define kMoveToFront (WindowPtr)-1L
#define kEmptyString "\p"
#define kEmptyTitlekEmptyString
#define kVisible true
#define kNoGoAwayfalse
#define kNilRefCon (long)nil
#define kErrorAlertID128
#define kNilFilterProc  nil

#define kSquareSize16

#define kForegroundPICT 128
#define kIgnored nil
#define kUseMaxDepth 0
#define kNoFlags (GWorldFlags)0

#define mApple   128
#define iAbout   1

#define mFile    129
#define iRedraw  1
#define iQuit    3

#define mColors  131
#define iUseRGB  1
#define iUseHSV  2
#define iRed4
#define iGreen   5
#define iBlue    6
#define iHue4
#define iSaturation5
#define iBrightness6

PixMapper uses a bunch of globals. gDone starts life as false and is set to true when Quit is selected from the File menu. gIsRGB is true when the RGB Colors item is selected from the Colors menu, false when HSV Colors is selected. gRandomReds, gRandomGreens, and gRandomBlues are true when their respective items are selected from the Colors menu. The same holds true for gRandomHue, gRandomSaturation, and gRandomBrightness.


/* 3 */
Boolean gDone;
Boolean gIsRGB = true, gRandomReds = true,
 gRandomGreens = true, gRandomBlues = true;
Boolean gRandomHue = true, gRandomSaturation = true,
 gRandomBrightness = true;

gMainWindow points to the window with all the colors and the animated PICT. gXBump and gYBump specify the number of pixels the PICT moves each new animation cycle. One way to speed up the animation is to raise the bump values, though the code was written do work with single pixel movements. To learn more about the program, try modifying the code to handle any size bump values. You might try using the mouse position to dynamically vary the rate of movement.


/* 4 */
WindowPtr gMainWindow;
short   gXBump = 1, gYBump = 1; // <--Try changing these numbers

We’ll use three GWorlds. gPictWorld holds the PICT image. gSaveWorld holds the background of the window for later restoration. gSaveMixWorld is used to combine the PICT and the background. The three PixMapHandles are handles to the PixMaps tied to their respective GWorld.


/* 5 */
GWorldPtr gPictWorld, gSaveWorld, gSaveMixWorld;
PixMapHandlegPixMapSave, gPixMapSaveMix, gPixMapPict;

gPictWorldRect is the exact size of the PICT and is the bounding rectangle for gPictWorld. gWorldRect is the bounding Rect for the mixing GWorld. The mixing GWorld is one pixel bigger in all directions than the PICT. This was done so that when we save a region the size of gWorldRect from the PixMapper window, we’ll have a one pixel border around the PICT. This way, when the PICT moves one pixel in any direction, we’ll have the right pixels saved for later restoration. You’ll see how this works later in the code. gSavedFloaterRect contains the current position of the PICT in the PixMapper window.


/* 6 */
Rect    gSavedFloaterRect, gPictWorldRect, gWorldRect;

gGlobalHue holds a hue value that we use if HSV Colors is selected from the Colors menu and Random Hues is not selected.


/* 7 */
short   gGlobalHue;

Here are all the function prototypes. Have I mentioned that I always use function prototypes?


/* 8 */
void    ToolboxInit( void );
void    MenuBarInit( void );
Boolean HasGWorlds( void );
void    WindowInit( void );
void    PaintWindow( void );
void    RandomForeColor( void );
void    GWorldInit( void );
GWorldPtr MakeGWorld( Rect *boundsPtr );
void    EventLoop( void );
void    DoEvent( EventRecord *eventPtr );
void    HandleMouseDown( EventRecord *eventPtr );
void    HandleMenuChoice( long menuChoice );
void    HandleAppleChoice( short item );
void    HandleFileChoice( short item );
void    HandleColorsChoice( short item );
void    MainLoop( void );
void    DrawFirstFloater( void );
void    MoveFloater( void );
void    CalcNewFloaterPosition( void );
void    DoError( Str255 errorString );

main() initializes the Toolbox and the menu bar.

/****************** main ***************************/

void  main( void )
{
 ToolboxInit();
 MenuBarInit();

See if GWorlds are available on this machine.


/* 9 */
 if ( ! HasGWorlds() )
 DoError("\pDeep GWorlds not supported by this machine!");

If so, create the main window and the three GWorlds.


/* 10 */
 WindowInit();
 GWorldInit();

Next, plot the initial position of the PICT and enter the main animation loop. The word “floater” refers to the foreground PICT, which appears to float over the background.


/* 11 */
 DrawFirstFloater();
 EventLoop();
}
ToolboxInit() and MenuBarInit() are the same as always.

/****************** ToolboxInit *********************/

void  ToolboxInit( void )
{
 InitGraf( &thePort );
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs( NULL );
 InitCursor();
}

/****************** MenuBarInit ***********************/

void  MenuBarInit( void )
{
 Handle menuBar;
 MenuHandle menu;
 
 menuBar = GetNewMBar( kMBARResID );
 SetMenuBar( menuBar );

 menu = GetMHandle( mApple );
 AddResMenu( menu, 'DRVR' );
 DrawMenuBar();
}

HasGWorlds() calls Gestalt() using the selector gestaltQuickdrawFeatures. If Gestalt() returns an error we’ll display an appropriate error message.


/* 12 */
/****************** HasGWorlds *****************/

Boolean HasGWorlds( void )
{
 long   feature, mask;
 OSErr  err;
 
 err = Gestalt( gestaltQuickdrawFeatures, &feature );
 
 if ( err != noErr )
 DoError( "\pError calling Gestalt!" );

Next, we’ll set up a comparison mask so we can look at the appropriate bit in feature. Since gestaltHasDeepGWorlds has a value of 1, we’ll want to look at bit number 1, which is the second bit from the right. We’ll use the << operator to set bit number 1 in mask, leaving mask with a value of 2.


/* 13 */
 mask = 1 << gestaltHasDeepGWorlds;

Finally, we’ll use mask to see if bit number 1 is set in feature. If so, deep GWorlds are available and we’ll return true. Otherwise, we’ll return false. You could also write the following statement as return (feature&mask);


/* 14 */
 if ( feature & mask )
 return true;
 else
 return false;
}

WindowInit() creates a new color window a little shorter than the main screen. The top of the window starts just below the menu bar. You could also use GetMainDevice() to get the rectangle of the main screen rather than screenBits.bounds.


/* 15 */
/****************** WindowInit ***********************/

void  WindowInit( void )
{
 Rect   r;
 
 r = screenBits.bounds;
 r.top += GetMBarHeight();
 
 gMainWindow = NewCWindow( nil, &r, kEmptyTitle,
 kVisible, plainDBox, kMoveToFront,
 kNoGoAway, kNilRefCon );

 SetPort( gMainWindow );

Now call PaintWindow() to fill the window with colored rectangles.


/* 16 */
 PaintWindow();
}

PaintWindow() starts by calculating the number of columns and rows in the PixMapper window.


/* 17 */
/****************** PaintWindow ***********************/

void  PaintWindow( void )
{
 Rect   r;
 short  row, col, numRows, numCols;
 
 SetPort( gMainWindow );
 
 r = gMainWindow->portRect;

Both numCols and numRows are based on kSquareSize. Each square on the window will be kSquareSize pixels on a side. If either numCols or numRows is not evenly divisible by kSquareSize, we’ll add another row or column just so we don’t leave any white space at the edge of the window.


/* 18 */
 numCols = (r.right - r.left) / kSquareSize;
 if ( numCols != numCols/kSquareSize*kSquareSize )
 numCols++;
 
 numRows = (r.bottom - r.top) / kSquareSize;
 if ( numRows != numRows/kSquareSize*kSquareSize )
 numRows++;

Next, we initialize the random number seed and set gGlobalHue to a random value. We’ll get to gGlobalHue in the next routine.


/* 19 */
 GetDateTime( (unsigned long *)(&randSeed) );
 
 gGlobalHue = Random();

Next, we’ll step through all the squares, drawing each in the color set by RandomForeColor(). When we’re done, we’ll set the foreground and background colors to their normal values.


/* 20 */
 for ( row=0; row<numRows; row++ )
 for ( col=0; col<numCols; col++ )
 {
 r.top = row * kSquareSize;
 r.bottom = r.top + kSquareSize;
 r.left = col * kSquareSize;
 r.right = r.left + kSquareSize;
 RandomForeColor();
 PaintRect( &r );
 }

 ForeColor( blackColor );
 BackColor( whiteColor );
}

RandomForeColor() sees which color model is in use.


/* 21 */
/****************** RandomForeColor ***********************/

void  RandomForeColor( void )
{
 RGBColor color;
 HSVColor hsvColor;

If we’re using RGB colors, we’ll need to specify values for the red, green, and blue fields of the RGBColor. If random reds is turned on, we’ll use a random red, otherwise we’ll set red to 0 (no red at all). The same holds true for green and blue.


/* 22 */
 if ( gIsRGB )
 {
 if ( gRandomReds )
 color.red = Random();
 else
 color.red = 0;
 
 if ( gRandomGreens )
 color.green = Random();
 else
 color.green = 0;
 
 if ( gRandomBlues )
 color.blue = Random();
 else
 color.blue = 0;

Once we set up the color, we’ll make it the new foreground color using RGBForeColor().


/* 23 */
 RGBForeColor( &color );
 }

If we’re using HSV colors, we’ll set values for the hue, saturation, and value fields. Every time you see the value field, think brightness. If gRandomHue is true, we’ll use a random hue, otherwise we’ll use the value stored in gGlobalHue.


/* 24 */
 else
 {
 if ( gRandomHue )
 hsvColor.hue = Random();
 else
 hsvColor.hue = gGlobalHue;

The same holds true for saturation and brightness, except that if random saturation or brightness is turned off, we’ll use a value of 65535 for the saturation or value fields.


/* 25 */
 if ( gRandomSaturation )
 hsvColor.saturation = Random();
 else
 hsvColor.saturation = 65535;
 
 if ( gRandomBrightness )
 hsvColor.value = Random();
 else
 hsvColor.value = 65535;

Next, we’ll convert the HSV color to an RGB color by calling HSV2RGB() and then pass the RGB color to RGBForeColor().


/* 26 */
 HSV2RGB( &hsvColor, &color );
 RGBForeColor( &color );
 }
}

GWorldInit() starts by loading the PICT resource.


/* 27 */
/****************** GWorldInit ***********************/

void  GWorldInit( void )
{
 PicHandlepic;
 
 pic = GetPicture( kForegroundPICT );

 if ( pic == nil )
 DoError( "\pError loading PICT..." );

// Call HNoPurge() if your PICT is purgeable

We’ll normalize the PICT’s frame (offset it to (0,0)).


/* 28 */
 gPictWorldRect = (**pic).picFrame;
 OffsetRect( &gPictWorldRect, - gPictWorldRect.left,
  - gPictWorldRect.top );

gWorldRect is set to be 2 pixels taller and 2 pixels wider than the PICT. That gives us room for a one pixel border all the way around.


/* 29 */
 gWorldRect = gPictWorldRect;
 gWorldRect.bottom += 2;
 gWorldRect.right += 2;

Next, we call our own MakeGWorld() routine to build one GWorld the size of the PICT and two the size of gWorldRect, storing the pointers in our three globals.


/* 30 */
 gPictWorld = MakeGWorld( &gPictWorldRect );
 gSaveWorld = MakeGWorld( &gWorldRect );
 gSaveMixWorld = MakeGWorld( &gWorldRect );

When we create a GWorld, a PixMap is created for us. We’ll call GetGWorldPixMap() to store the handle to each PixMap in its respective global.


/* 31 */
 gPixMapPict = GetGWorldPixMap( gPictWorld );
 gPixMapSave = GetGWorldPixMap( gSaveWorld );
 gPixMapSaveMix = GetGWorldPixMap( gSaveMixWorld );

Next, we’ll lock all three PixMaps in memory. Why? Just as you’d lock a handle before you singly dereferenced it to access its pointer, you lock your PixMap before you draw into it. Normally, you’d lock the pixels just before you draw, then unlock the pixels after the call to the drawing routine returns to prevent heap fragmentation. To keep things simple, we’re just going to lock all three PixMaps for the duration of the program.


/* 32 */
 if ( ! LockPixels( gPixMapPict ) )
 DoError( "\pLockPixels failed..." );

 if ( ! LockPixels( gPixMapSave ) )
 DoError( "\pLockPixels failed..." );

 if ( ! LockPixels( gPixMapSaveMix ) )
 DoError( "\pLockPixels failed..." );

Finally, we’ll make the gPictWorld the current GWorld and draw the PICT in it. SetGWorld() makes the specified GWorld the current port and the current gWorld, just as a call to SetPort() might make a window the current port.


/* 33 */
 SetGWorld( gPictWorld, kIgnored );
 
 DrawPicture( pic, &gPictWorldRect );
}

MakeGWorld() calls NewGWorld() to create a new GWorld, returning a pointer to the new GWorld. Look up NewGWorld() in Inside Macintosh, Volume VI or in THINK Reference for more detail on the parameters. The first parameter is the address of the GWorldPtr that will eventually point to the new GWorld. The second parameter specifies the pixel depth of the new GWorld. By passing in a value of 0, we’re asking NewGWorld() to use the deepest device that intersects boundsPtr, the third parameter. The fourth and fifth parameters specify a color table and a GDevice, in case you want to roll your own. We’ll pass nil in for each, asking NewGWorld() to take care of these parameters for us. The final parameter lets us set special GWorld flags. We’ll pass in 0, ignoring the flags. You can read about these flags in the description of NewGWorld().


/* 34 */
/*********************************** MakeGWorld */

GWorldPtr MakeGWorld( Rect *boundsPtr )
{
 QDErr  err;
 GWorldPtrnewGWorld;

 err = NewGWorld( &newGWorld, kUseMaxDepth,
 boundsPtr, kIgnored, kIgnored, noNewDevice );

// In the real world, call DisposeGWorld() when you are done with the 
GWorld...
 
 if ( err != noErr )
 DoError( "\pMy call to NewGWorld died!  Bye..." );
 
 return( newGWorld );
}

EventLoop() does its normal thing with one exception. Each time through the loop, the position of the floater is updated via a call to MoveFloater().


/* 35 */
/************************************* EventLoop */

void  EventLoop( void )
{
 EventRecordevent;
 
 gDone = false;
 while ( gDone == false )
 {
 if ( WaitNextEvent( everyEvent, &event, kSleep, NULL ) )
 DoEvent( &event );
 MoveFloater();
 }
}

DoEvent() does its normal thing. Notice that we do the absolute minimum when it comes to handling update events. You might want to handle update events in your own code...


codeexamplestart

/* 36 */

/************************************* DoEvent */

void  DoEvent( EventRecord *eventPtr )
{
 char theChar;
 
 switch ( eventPtr->what )
 {
 case mouseDown: 
 HandleMouseDown( eventPtr );
 break;
 case keyDown:
 case autoKey:
 theChar = eventPtr->message & charCodeMask;
 if ( (eventPtr->modifiers & cmdKey) != 0 ) 
 HandleMenuChoice( MenuKey( theChar ) );
 break;
 case updateEvt:
 BeginUpdate( (WindowPtr)(eventPtr->message) );
 EndUpdate( (WindowPtr)(eventPtr->message) );
 break;
 }
}


The next three routines are the same as ever


/* 37 */
/************************************* HandleMouseDown */

void  HandleMouseDown( EventRecord *eventPtr )
{
 WindowPtrwindow;
 short  thePart;
 long   menuChoice;
 
 thePart = FindWindow( eventPtr->where, &window );
 
 switch ( thePart )
 {
 case inMenuBar:
 menuChoice = MenuSelect( eventPtr->where );
 HandleMenuChoice( menuChoice );
 break;
 case inSysWindow : 
 SystemClick( eventPtr, window );
 break;
 }
}

/************************************* HandleMenuChoice */

void  HandleMenuChoice( long menuChoice )
{
 short  menu;
 short  item;
 
 if ( menuChoice != 0 )
 {
 menu = HiWord( menuChoice );
 item = LoWord( menuChoice );
 
 switch ( menu )
 {
 case mApple:
 HandleAppleChoice( item );
 break;
 case mFile:
 HandleFileChoice( item );
 break;
 case mColors:
 HandleColorsChoice( item );
 break;
 }
 HiliteMenu( 0 );
 }
}


/************************************* HandleAppleChoice */

void  HandleAppleChoice( short item )
{
 MenuHandle appleMenu;
 Str255 accName;
 short  accNumber;
 
 switch ( item )
 {
 case iAbout:
 SysBeep( 20 );
 break;
 default:
 appleMenu = GetMHandle( mApple );
 GetItem( appleMenu, item, accName );
 accNumber = OpenDeskAcc( accName );
 break;
 }
}

When Redraw is selected from the File menu, HandleFileChoice() calls PaintWindow() to redraw the PixMapper window, then calls DrawFirstFloater() to redraw the floater in its initial position.


/* 38 */
/************************************* HandleFileChoice */

void  HandleFileChoice( short item )
{
 switch ( item )
 {
 case iRedraw:
 PaintWindow();
 DrawFirstFloater();
 break;
 case iQuit:
 gDone = true;
 break;
 }
}

HandleColorsChoice() handles all the logic of the Colors menu, changing items and modifying globals as needed. CheckItem() places or removes the check mark from a menu’s item. Once the change is made, we call PaintWindow() and DrawFirstFloater() to restart the process with the modified color scheme.


/* 39 */
/************************************* HandleColorsChoice */

void  HandleColorsChoice( short item )
{
 MenuHandle menu;
 
 menu = GetMenu( mColors );
 
 if ( item == iUseRGB )
 {
 gIsRGB = true;
 
 SetItem( menu, iRed, "\pRandom Reds" );
 SetItem( menu, iGreen, "\pRandom Greens" );
 SetItem( menu, iBlue, "\pRandom Blues" );
 
 CheckItem( menu, iUseRGB, true );
 CheckItem( menu, iUseHSV, false );
 CheckItem( menu, iRed, gRandomReds );
 CheckItem( menu, iGreen, gRandomGreens );
 CheckItem( menu, iBlue, gRandomBlues );
 }
 else if ( item == iUseHSV )
 {
 gIsRGB = false;
 
 SetItem( menu, iHue, "\pRandom Hue" );
 SetItem( menu, iSaturation, "\pRandom Saturation" );
 SetItem( menu, iBrightness, "\pRandom Brightness" );
 
 CheckItem( menu, iUseRGB, false );
 CheckItem( menu, iUseHSV, true );
 CheckItem( menu, iHue, gRandomHue );
 CheckItem( menu, iSaturation, gRandomSaturation );
 CheckItem( menu, iBrightness, gRandomBrightness );
 }
 else if ( gIsRGB )
 {
 switch ( item )
 {
 case iRed:
 gRandomReds = !gRandomReds;
 CheckItem( menu, iRed, gRandomReds );
 break;
 case iGreen:
 gRandomGreens = ! gRandomGreens;
 CheckItem( menu, iGreen, gRandomGreens );
 break;
 case iBlue:
 gRandomBlues = ! gRandomBlues;
 CheckItem( menu, iBlue, gRandomBlues );
 break;
 }
 }
 else
 {
 switch ( item )
 {
 case iHue:
 gRandomHue = !gRandomHue;
 CheckItem( menu, iHue, gRandomHue );
 break;
 case iSaturation:
 gRandomSaturation = ! gRandomSaturation;
 CheckItem( menu, iSaturation, gRandomSaturation );
 break;
 case iBrightness:
 gRandomBrightness = ! gRandomBrightness;
 CheckItem( menu, iBrightness, gRandomBrightness );
 break;
 }
 }
 PaintWindow();
 DrawFirstFloater();
}

Here comes the really important stuff. Just as it did in BitMapper, CopyBits() is used to copy a block of pixels from one offscreen to another. Though CopyBits() expects to work with pointers to BitMaps, it can handle either BitMaps or PixMaps. Since the compiler isn’t aware that the called function can handle two different types (BitMap or PixMap), a bit of typecasting is necessary.


/* 40 */
/****************** DrawFirstFloater *********************/

void  DrawFirstFloater( void )
{

Each call to CopyBits() copies from the first parameter (the source BitMap or PixMap) to the second (destination BitMap or PixMap), using the Rects in the third and fourth parameters. If the rectangles are the same size, no scaling happens, and that’s what we do here. If they are different sizes, CopyBits() stretches or squashes the source to fit the destination. The srcCopy mode tells CopyBits() to replace all the destination bits with the appropriate source bits. The transparent mode, on the other hand, tells the compiler not to copy the white pixels. This comes in handy when we copy a non-rectangular or non-solid image from one GWorld to another. The last parameter to CopyBits() specifies an optional mask parameter which we won’t use. Passing nil tells CopyBits() to ignore this parameter.

The first call to CopyBits() copies the background of the PixMapper window into the gPixMapSave PixMap. We’re saving away the pixels we’re about to obliterate with the PICT, with an extra one pixel border we’ll need when the floater moves in one direction or the other.


/* 41 */
 CopyBits( &(gMainWindow->portBits), 
 (BitMap *)(*gPixMapSave),
 &gWorldRect, &gWorldRect, srcCopy, nil );

Next, we’ll set up a Rect the size of the PICT that is 1 pixel down and 1 pixel to the right of the upper left corner of the window. This is where we’ll plot the PICT.


/* 42 */
 gSavedFloaterRect = gPictWorldRect;
 OffsetRect( &gSavedFloaterRect, 1, 1 );

This draws the PICT in the PixMapper window.


/* 43 */
 CopyBits( (BitMap *)(*gPixMapPict), 
 &(gMainWindow->portBits),
 &gPictWorldRect, &gSavedFloaterRect, transparent, nil );
}

MoveFloater() calls CalcNewFloaterPosition() to update the values of gXBump and gYBump in case the floater hits the edge of the window.


/* 44 */
/****************** MoveFloater *********************/

void  MoveFloater( void )
{
 Rect   r;
 RgnHandlenewRgn, savedRgn, oldClip;

 CalcNewFloaterPosition();

This copies the saved pixels to the mixer GWorld.


/* 45 */
 CopyBits( (BitMap *)(*gPixMapSave), 
 (BitMap *)(*gPixMapSaveMix),
 &gWorldRect, &gWorldRect, srcCopy, nil );

Next, we’ll position a Rect the size of the PICT in the mix GWorld using gXBump and gYBump. This Rect is the new position for the floater in the mix GWorld. Then we CopyBits() the floater on top of the saved pixels in the mixing GWorld. Remember, we used transparent mode so we’d only draw the non-background pixels. You would handle this differently if you needed to draw white pixels.


/* 46 */
 r = gPictWorldRect;
 OffsetRect( &r, gXBump + 1, gYBump + 1 );
 
 CopyBits( (BitMap *)(*gPixMapPict), 
 (BitMap *)(*gPixMapSaveMix),
 &gPictWorldRect, &r, transparent, nil );

Next, we construct a Rect at the floater’s last position in the PixMapper window, then make it one pixel bigger in all directions (the size of the mixing GWorld). We’re use this Rect to copy the contents of the mixing GWorld into the window.


/* 47 */
 r = gSavedFloaterRect;
 InsetRect( &r, -1, -1 );

 CopyBits( (BitMap *)(*gPixMapSaveMix), 
 &(gMainWindow->portBits),
 &gWorldRect, &r, srcCopy, nil );

Next, we update the saved floater position stored in gSavedFloaterRect to reflect the new position.


/* 48 */
 OffsetRect( &gSavedFloaterRect, gXBump, gYBump );

Following that, we’ll create our one pixel bigger Rect again, this time at the floater’s new position, and copy the floater, with a one pixel border, into the mixing GWorld.


/* 49 */
 r = gSavedFloaterRect;
 InsetRect( &r, -1, -1 );
 
 CopyBits( &(gMainWindow->portBits), 
 (BitMap *)(*gPixMapSaveMix),
 &r, &gWorldRect, srcCopy, nil );

Next, we’ll copy the saved pixels into the appropriate position in the mixing GWorld. The idea here is that we are reconstructing the pixels that should be behind the floater.


/* 50 */
 r = gWorldRect;
 OffsetRect( &r, -gXBump, -gYBump );
 
 CopyBits( (BitMap *)(*gPixMapSave), 
 (BitMap *)(*gPixMapSaveMix),
 &gWorldRect, &r, srcCopy, nil );

Finally, we’ll copy the reconstructed “behind the floater” pixels from the mix GWorld into the save GWorld. We are now ready to move the floater all over again.


/* 51 */
 CopyBits( (BitMap *)(*gPixMapSaveMix),
 (BitMap *)(*gPixMapSave),
 &gWorldRect, &gWorldRect, srcCopy, nil );
}

This routine figures out if bumping the floater will move it off the edge of the window in any direction. If so, we change the direction of floater movement so the it moves away from that edge, rather than towards it.


/* 52 */
/*********************************** CalcNewFloaterPosition */

void  CalcNewFloaterPosition( void )
{
 Rect r;
 
 r = gSavedFloaterRect;
 
 OffsetRect( &r, gXBump, gYBump );
 
 if ( (r.left < gMainWindow->portRect.left) ||
 ( r.right > gMainWindow->portRect.right ) )
 gXBump *= -1;
 
 if ( (r.top < gMainWindow->portRect.top) ||
 ( r.bottom > gMainWindow->portRect.bottom ) )
 gYBump *= -1;
}

You’ve seen this one before...


/* 53 */
/***************** DoError ********************/

void  DoError( Str255 errorString )
{
 ParamText( errorString, kEmptyString, 
 kEmptyString, kEmptyString );
 
 StopAlert( kErrorAlertID, kNilFilterProc );
 
 ExitToShell();
}

Till Next Month...

Don’t worry too much about the specifics of the PixMapper algorithm. The important things to understand are how to construct a GWorld, how to use CopyBits() to copy PixMaps between GWorlds and windows, and the basics of working with color. Try your hand at adding some new color schemes to the program. Try going back to Color Mondrian and playing with those colors. Try working with the HSV model, varying only the hue, leaving the saturation and brightness constant. Sometimes RGB does just fine, but sometimes the HSV model really comes in handy.

[If you have a need for even more speed, then you need to think about helping QuickDraw go as fast as it can. There are a number of clues that you can give QuickDraw about what you are doing. CopyBits often gets blamed for poor performance, and it’s true that it can take a good bit of time if you ask it to do something extremely complex (it is capable of a wide range of transforms). If you get to know the call well (by reading Inside Macintosh and the wide variety of sample code available), you can get some blazing speed if you use one of the optimized cases. Many game writers have started out with the assumption that they should avoid CopyBits and write their own blitting routine for performance, only to surprise themselves with the speed CopyBits can offer if used well. Not only can you get terrific performance on 68K Macs, you should try this out on Power Macs where QuickDraw has been rewritten to take advantage of all that PowerPC horsepower. - Ed stb]

I am very interested in animation. I’d love to see your own animation based programs. Once you’ve got something you think is cool, please send the application (no source code, please) to me on Compuserve. My CompuServe address is [70007,2530]. Good luck!

If you can hold out till summer, there’s a new book that you might be interested in. It’s called Ultimate Mac Programming, and (among other things) it will cover both animation and sound. If you are interested in game programming, you might want to check it out. I’ll tell you more about the book as it progresses.

- DM

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Adobe Acrobat Reader 20.012.20041 - View...
Adobe Acrobat Reader allows users to view PDF documents. You may not know what a PDF file is, but you've probably come across one at some point. PDF files are used by companies and even the IRS to... Read more
Adobe Acrobat DC 20.012.20041 - Powerful...
Acrobat DC is available only as a part of Adobe Creative Cloud, and can only be installed and/or updated through Adobe's Creative Cloud app. Adobe Acrobat DC with Adobe Document Cloud services is... Read more
Sketch 68 - Design app for UX/UI for iOS...
Sketch is an innovative and fresh look at vector drawing. Its intentionally minimalist design is based upon a drawing space of unlimited size and layers, free of palettes, panels, menus, windows, and... Read more
Bean 3.3.1 - Fast and uncluttered word p...
Bean is no longer being actively developed, but will be updated as necessary to patch bugs and maintain OS X compatibility Bean is lean, fast, and uncluttered. If you get depressed at the thought... Read more
RetroArch 1.9.0 - Game emulator.
RetroArch is most popularly known for being a program with which you can play many emulators and games, which have all been customized and tailor-ported to the libretro API. It is designed to be fast... Read more
NetNewsWire 5.0.4 - RSS and Atom news re...
NetNewsWire is the best way to keep up with the sites and authors you read most regularly. Let NetNewsWire pull down the latest articles, and read them in a distraction-free and Mac-like way. Native... Read more
EarthDesk 7.4.5 - $24.99
EarthDesk replaces your static desktop picture with a rendered image of Earth showing correct sun, moon, and city illumination. With an Internet connection, EarthDesk displays near-real-time global... Read more
BetterTouchTool 3.401 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom)... Read more
Vienna 3.5.6 :e12c952d: - RSS and Atom n...
Vienna is a freeware and Open-Source RSS/Atom newsreader with article storage and management via a SQLite database, written in Objective-C and Cocoa, for the OS X operating system. It provides... Read more
WhatsApp 2.2031.5 - Desktop client for W...
WhatsApp is the desktop client for WhatsApp Messenger, a cross-platform mobile messaging app which allows you to exchange messages without having to pay for SMS. WhatsApp Messenger is available for... Read more

Latest Forum Discussions

See All

Scrappers receives a major update that a...
Q-Games' Scrappers has received a fairly sizeable new update that adds fresh gameplay features and a host of quality-of-life tweaks. [Read more] | Read more »
Motorball is a car football game from No...
A few years back Noodlecake Studios announced that they would be dipping in the multiplayer gaming realm with two different games. The first of those, Golf Blitz, released a while back and has proven to be very popular. Now, the second has arrived... | Read more »
SINoALICE's latest update introduce...
SINoALICE's latest update has now arrived, adding several fan-favourite characters from popular RPG series NieR. Young Nier, Kaine, and Young Emil are available in-game as part of a limited-time crossover event set to run until August 20th. [Read... | Read more »
Rocat Jumpurr is an intense roguelite pl...
Rocat Jumpurr is a roguelite platformer from developer Mousetrap Games. You might already be familiar with it if you follow the Big Indie Pitch, where it won first place during this year's Pocket Gamer Connects London competition. Following its... | Read more »
PUBG Mobile's Play As One campaign...
Back in mid-July, we reported that PUGB Mobile had teamed up with Direct Relief to help raise money for the charity's COVID-19 response project. It focused on an in-game running challenge for players, which lead to the PUBG Mobile donating $2... | Read more »
Marvel Contest of Champions' latest...
Marvel Contest of Champions' latest motion comic has arrived, and it shows off new fighters Air-Walker and Dragon Man. Both characters are set to arrive in-game this month. [Read more] | Read more »
Clash Royale: The Road to Legendary Aren...
Supercell recently celebrated its 10th anniversary and their best title, Clash Royale, is as good as it's ever been. Even for lapsed players, returning to the game is as easy as can be. If you want to join us in picking the game back up, we've put... | Read more »
Global Spy is an intriguing 2D spy sim f...
Developer Yuyosoft Innovations' Global Spy launched last month for iOS and Android, though if you missed it at the time, we're here to tell you why it's well worth a go. This one's all about international espionage, tracking down elusive spies,... | Read more »
Distract Yourself With These Great Mobil...
There’s a lot going on right now, and I don’t really feel like trying to write some kind of pithy intro for it. All I’ll say is lots of people have been coming together and helping each other in small ways, and I’m choosing to focus on that as I... | Read more »
Hyena Squad is sci-fi turn-based strateg...
Wave Light Games has just revealed its latest release, Hyena Squad, a turn-based RPG set in a space station infested by gross aliens and the living dead. The announcement was first reported on by Touch Arcade. [Read more] | Read more »

Price Scanner via MacPrices.net

Apple drops prices on clearance 27″ 5K iMacs,...
Apple has dropped prices on Certified Refurbished 2019 27″ iMacs to a new low of $1439 and up to $520 off their original MSRP. Apple’s one-year warranty is standard and shipping is free. The... Read more
Price drop: Clearance 8-core iMac Pro for $38...
Apple has dropped their price on Certified Refurbished 27″ 3.2GHz 8-Core iMac Pros to $3819 including free shipping. Their price is $1180 off the original MSRP of new models. A standard Apple one-... Read more
Monday sale: New 13″ 2.0GHz MacBook Pros for...
Amazon has new 2020 13″ 2.0GHz/512GB MacBook Pros back in stock on sale today for $200 off Apple’s MSRP. Shipping is free. Be sure to purchase the MacBook Pro from Amazon, rather than a third-party... Read more
Sale! Apple’s 16″ MacBook Pros for up to $349...
Apple Authorized Reseller Adorama has new 2019 16″ MacBook Pros in stock on sale today for $100-$349 off Apple’s MSRP, each including free shipping. Their prices for 8-core models ($349 off) are the... Read more
Save hundreds of dollars on a custom-configur...
Save up to $920 on a custom-configured 16″ MacBook Pro with these Certified Refurbished models that Apple has restocked today. Each MacBook Pro features a new outer case, free shipping, and includes... Read more
New 2020 12.9″ iPad Pros on sale for up to $8...
Apple reseller Expercom has new 2020 Apple 12.9″ iPad Pros on sale today for $60-$85 off MSRP, with prices starting at $939. These are the same iPad Pros sold by Apple in their retail and online... Read more
Woot offers numerous 2018-2020 MacBook Pros a...
Amazon-owned Woot has many open-box return MacBook Airs and MacBook Pros available today at prices starting at $879. Shipping is free for Prime members. Here’s what they have as of this post, and... Read more
Apple restocks refurbished 2020 13″ MacBook A...
Apple has restocked Certified Refurbished 2020 13″ MacBook Airs starting at only $849 and up to $200 off the cost of new Airs. Each MacBook features a new outer case, comes with a standard Apple one-... Read more
Apple restocks clearance 2019 13″ 2.4GHz MacB...
Apple has restocked Certified Refurbished 2019 13″ 2.4GHz 4-Core Touch Bar MacBook Pros starting at $1359 and up to $560 off original MSRP. Apple’s one-year warranty is included, shipping is free,... Read more
Apple restocks refurbished iPhone XR models s...
Apple has restocked Certified Refurbished, unlocked, iPhone XR models in the refurbished section of their online store starting at $539. Each iPhone comes with Apple’s standard one-year warranty,... Read more

Jobs Board

Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 6 days ago Requisition # 122305 Sign Up Read more
Director of *Apple* Enterprise Operations -...
## Description JOB SUMMARY: The Director of Apple Enterprise Operations is responsible for developing and managing the overall strategy, operation and management of Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 5 days ago Requisition # 122305 Sign Up Read more
Blue *Apple* Cafe Student Worker - Fall - P...
…to enhance your work experience. Student positions are available at the Blue Apple Cafe. Employee meal discount during working hours is provided. Duties include food Read more
Cub Foods - *Apple* Valley - Now Hiring Par...
Cub Foods - Apple Valley - Now Hiring Part Time! United States of America, Minnesota, Apple Valley New Retail Post Date 4 days ago Requisition # 122305 Sign Up Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.