Color
Volume Number: 10
Issue Number: 10
Column Tag: Getting Started
Related Info: Color Quickdraw
Working With Color
By Dave Mark, MacTech Magazine Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
This month’s column combines two of my favorite activities: working with color
and rewriting Primer, Volume II code (bringing it from the Pliocene era to full
PowerPC squishiness). This month’s program is a floor to ceiling rewrite of
ColorTutor. ColorTutor is a hands-on color blending environment. You specify the
foreground and background colors and patterns, then select a Color Quickdraw drawing
mode. ColorTutor uses CopyBits() to mix the foreground and background colors. Figure
1 shows a sample.
Figure 1. The ColorTutor window.
ColorTutor first copies the Background image to the lower-right rectangle, then
copies the Source image on top of the Background using the current Mode and OpColor.
Since this program is so large, we’ll get into the details in next month’s column. For
now, we’ll focus on putting the project together and getting ColorTutor up and running.
The ColorTutor Resources
ColorTutor uses six different resource types: an ALRT, a CNTL, a DITL, an MBAR,
a MENU, and a WIND. Start by creating a folder named ColorTutor in your Projects
folder. Next, fire up ResEdit or Resorcerer and create a new file named
ColorTutor.π.rsrc in the ColorTutor folder.
Create an ALRT resource with an ID of 128, a top of 40, left of 40, bottom of 156
and right of 332. Make sure the DITL ID is set to 128.
Next, create a DITL with an ID of 128. Figure 2 shows the specifications for item
1, the OK button, and Figure 3 shows the specs for item 2, the static text field. The
alert you just created is used to display an error message.
Figure 2. Specifications for the OK button.
Figure 3. Specifications for item 2, the static text field.
Next, you’ll create a CNTL resource with an ID of 128. The CNTL will be used to
implement the OpColor button in the lower-left corner of the ColorTutor window. The
ProcID of 0 specifies a pushButtonProc control.
Figure 4. Specifications for the CNTL resource.
Now create an MBAR resource with an ID of 128. Add the menu IDs 128, 129,
and 130 (the , File, and Edit menus) to the MBAR. Though we’ll be creating 5 menus,
don’t be fooled. Only the first three will be added to the menu bar.
Next, you’ll create five MENU resources. The first four are shown in Figure 5,
and the fifth in Figure 6. MENUs 128, 129, and 130 will be used to create the menu
bar. The last two implement the ColorTutor popup menus. Note that the popup menus
don’t have titles. Note also that MENU 132 has 17 items including the separator line
(the 9th item).
Figure 5. Specifications for the first four MENUs.
Figure 6. Specifications for the two popup MENUs.
The last resource is a WIND with a resource ID of 128. Figure 7 shows the
ResEdit WIND editing screen for my WIND. This WIND implements the main
ColorTutor window.
Figure 7. Specifications for the WIND resource.
Finally, save your changes and quit your resource editor.
The ColorTutor Project
Next, pick your development environment and create a new project. From now
on, I’ll test all my source code to make sure it compiles in both THINK C and
CodeWarrior, so it shouldn’t matter which environment you pick. Create your new
project with the name ColorTutor.π inside the ColorTutor folder.
Next, add MacTraps to the project if you are using THINK C, or MacOS.lib if you
are using CodeWarrior.
Finally, create a new source code file, save it as ColorTutor.c, and add it to the
project. Here’s the source code:
/* 1 */
#include
#include
#define kBaseResID 128
#define kErrorALRTid 128
#define kNullFilterProc NULL
#define kMoveToFront (WindowPtr)-1L
#define kNotNormalMenu -1
#define kSleep 60L
#define mApple kBaseResID
#define iAbout 1
#define mFile kBaseResID+1
#define iQuit 1
#define mColorsPopup kBaseResID+3
#define iBlackPattern 1
#define iGrayPattern 2
#define iColorRamp 4
#define iGrayRamp 5
#define iSingleColor 6
#define mModePopup kBaseResID+4
Boolean gDone;
Rect gSrcRect, gBackRect, gDestRect, gSrcMenuRect,
gBackMenuRect, gModeMenuRect, gOpColorRect;
int gSrcPattern, gBackPattern, gCopyMode, gSrcType,
gBackType;
RGBColor gSrcColor, gBackColor, gOpColor;
MenuHandle gSrcMenu, gBackMenu, gModeMenu;
void ToolboxInit( void );
void MenuBarInit( void );
void CreateWindow( void );
void SetUpGlobals( void );
void EventLoop( void );
void DoEvent( EventRecord * eventPtr );
void HandleMouseDown( EventRecord * eventPtr );
void HandleMenuChoice( long menuChoice );
void HandleAppleChoice( short item );
void HandleFileChoice( short item );
void DoUpdate( WindowPtr window );
void DrawContents( WindowPtr window );
void DrawColorRamp( Rect *rPtr );
void DrawGrayRamp( Rect *rPtr );
void DrawLabel( Rect *boundsPtr, Str255 s );
void DoContent( WindowPtr window, Point globalPoint );
void UpdateSrcMenu( void );
void UpdateBackMenu( void );
void UpdateModeMenu( void );
void DoSrcChoice( short item );
void DoBackChoice( short item );
void DoModeChoice( short item );
short DoPopup( MenuHandle menu, Rect *boundsPtr );
Boolean PickColor( RGBColor *colorPtr );
Boolean HasColorQD( void );
void DoError( Str255 errorString );
main
void main( void )
{
ToolboxInit();
MenuBarInit();
if ( ! HasColorQD() )
DoError( "\pThis machine does not support Color QuickDraw!" );
CreateWindow();
SetUpGlobals();
EventLoop();
}
ToolboxInit
void ToolboxInit( void )
{
InitGraf( &qd.thePort );
InitFonts();
InitWindows();
InitMenus();
TEInit();
InitDialogs( 0L );
InitCursor();
}
MenuBarInit
void MenuBarInit( void )
{
Handle menuBar;
MenuHandle menu;
menuBar = GetNewMBar( kBaseResID );
if ( menuBar == NULL )
DoError( "\pCouldn't load the MBAR resource..." );
SetMenuBar( menuBar );
menu = GetMHandle( mApple );
AddResMenu( menu, 'DRVR' );
DrawMenuBar();
}
CreateWindow
void CreateWindow( void )
{
WindowPtr window;
window = GetNewCWindow( kBaseResID, NULL, kMoveToFront );
GetNewControl( kBaseResID, window );
SetPort( window );
TextFont( systemFont );
}
SetUpGlobals
void SetUpGlobals( void )
{
SetRect( &gSrcRect, 15, 6, 95, 86 );
SetRect( &gBackRect, 125, 6, 205, 86 );
SetRect( &gDestRect, 125, 122, 205, 202 );
SetRect( &gOpColorRect, 15, 122, 95, 202 );
SetRect( &gSrcMenuRect, 7, 90, 103, 108 );
SetRect( &gBackMenuRect, 117, 90, 213, 108 );
SetRect( &gModeMenuRect, 117, 206, 213, 224 );
gSrcPattern = iBlackPattern;
gBackPattern = iBlackPattern;
gCopyMode = srcCopy;
gSrcColor.red = 65535;
gSrcColor.green = gSrcColor.blue = 0;
gSrcType = iSingleColor;
gBackColor.blue = 65535;
gBackColor.red = gBackColor.green = 0;
gBackType = iSingleColor;
gOpColor.green = 32767;
gOpColor.red = 32767;
gOpColor.blue = 32767;
OpColor( &gOpColor );
gSrcMenu = GetMenu( mColorsPopup );
InsertMenu( gSrcMenu, kNotNormalMenu );
gBackMenu = GetMenu( mColorsPopup );
InsertMenu( gBackMenu, kNotNormalMenu );
gModeMenu = GetMenu( mModePopup );
InsertMenu( gModeMenu, kNotNormalMenu );
}
EventLoop
void EventLoop( void )
{
EventRecord event;
gDone = false;
while ( gDone == false )
{
if ( WaitNextEvent( everyEvent, & event, kSleep, NULL ) )
DoEvent( &event );
}
}
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:
DoUpdate( (WindowPtr) eventPtr->message );
break;
}
}
HandleMouseDown
void HandleMouseDown( EventRecord * eventPtr )
{
WindowPtr window;
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;
case inContent:
if ( window != FrontWindow() )
SelectWindow( window );
else
DoContent( window, eventPtr->where );
break;
case inDrag :
DragWindow( window, eventPtr->where, &qd.screenBits.bounds );
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;
}
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;
}
}
HandleFileChoice
void HandleFileChoice( short item )
{
switch ( item )
{
case iQuit:
gDone = true;
break;
}
}
DoUpdate
void DoUpdate( WindowPtr window )
{
BeginUpdate( window );
DrawContents( window );
DrawControls( window );
EndUpdate( window );
}
DrawContents
void DrawContents( WindowPtr window )
{
RGBColor rgbBlack;
Rect source, dest;
rgbBlack.red = rgbBlack.green = rgbBlack.blue = 0;
if ( gSrcPattern == iBlackPattern )
PenPat( &qd.black );
else
PenPat( &qd.gray );
if ( gSrcType == iColorRamp )
DrawColorRamp( &gSrcRect );
else if ( gSrcType == iGrayRamp )
DrawGrayRamp( &gSrcRect );
else
{
RGBForeColor( &gSrcColor );
PaintRect( &gSrcRect );
}
if ( gBackPattern == iBlackPattern )
PenPat( &qd.black );
else
PenPat( &qd.gray );
if ( gBackType == iColorRamp )
DrawColorRamp( &gBackRect );
else if ( gBackType == iGrayRamp )
DrawGrayRamp( &gBackRect );
else
{
RGBForeColor( &gBackColor );
PaintRect( &gBackRect );
}
PenPat( &qd.black );
RGBForeColor( &gOpColor );
PaintRect( &gOpColorRect );
RGBForeColor( &rgbBlack );
DrawLabel( &gSrcMenuRect, "\pSource" );
DrawLabel( &gBackMenuRect, "\pBackground" );
DrawLabel( &gModeMenuRect, "\pMode" );
PenSize( 2, 2 );
FrameRect( &gSrcRect );
FrameRect( &gBackRect );
FrameRect( &gDestRect );
FrameRect( &gOpColorRect );
PenNormal();
source = gBackRect;
InsetRect( &source, 2, 2 );
dest = gDestRect;
InsetRect( &dest, 2, 2 );
CopyBits( (BitMap *)&(((CGrafPtr) window)->portPixMap),
(BitMap *)&(((CGrafPtr) window)->portPixMap),
&source, &dest, srcCopy, NULL );
source = gSrcRect;
InsetRect( &source, 2, 2 );
CopyBits( (BitMap *)&(((CGrafPtr) window)->portPixMap),
(BitMap *)&(((CGrafPtr) window)->portPixMap),
&source, &dest, gCopyMode, NULL );
}
DrawColorRamp
void DrawColorRamp( Rect *rPtr )
{
long numColors, i;
HSVColor hsvColor;
RGBColor rgbColor;
Rect r;
r = *rPtr;
InsetRect( &r, 2, 2 );
numColors = ( rPtr->right - rPtr->left - 2 ) / 2;
hsvColor.value = hsvColor.saturation = 65535;
for ( i = 0; i < numColors; i++ )
{
hsvColor.hue = i * 65535 / numColors;
HSV2RGB( &hsvColor, &rgbColor );
RGBForeColor( &rgbColor );
FrameRect( &r );
InsetRect( &r, 1, 1 );
}
}
DrawGrayRamp
void DrawGrayRamp( Rect *rPtr )
{
long numColors, i;
RGBColor rgbColor;
Rect r;
r = *rPtr;
InsetRect( &r, 2, 2 );
numColors = ( rPtr->right - rPtr->left - 2 ) / 2;
for ( i = 0; i < numColors; i++ )
{
rgbColor.red = i * 65535 / numColors;
rgbColor.green = rgbColor.red;
rgbColor.blue = rgbColor.red;
RGBForeColor( &rgbColor );
FrameRect( &r );
InsetRect( &r, 1, 1 );
}
}
DrawLabel
void DrawLabel( Rect *boundsPtr, Str255 s )
{
Rect r;
int size;
r = *boundsPtr;
r.bottom -= 1;
r.right -= 1;
FrameRect( &r );
MoveTo( r.left + 1, r.bottom );
LineTo( r.right, r.bottom );
LineTo( r.right, r.top + 1 );
size = boundsPtr->right - boundsPtr->left - StringWidth(s);
MoveTo( boundsPtr->left + size / 2, boundsPtr->bottom - 6);
DrawString( s );
}
DoContent
void DoContent( WindowPtr window, Point globalPoint )
{
int choice;
ControlHandle control;
RGBColor rgbColor;
Point p;
p = globalPoint;
GlobalToLocal( &p );
if ( FindControl( p, window, &control ) )
{
if ( TrackControl( control, p, NULL ) )
{
rgbColor = gOpColor;
if ( PickColor( &rgbColor ) )
{
gOpColor = rgbColor;
InvalRect( &gOpColorRect );
InvalRect( &gDestRect );
OpColor( &gOpColor );
}
}
}
else if ( PtInRect( p, &gSrcMenuRect ) )
{
UpdateSrcMenu();
choice = DoPopup( gSrcMenu, &gSrcMenuRect );
if ( choice > 0 )
{
DoSrcChoice( choice );
InvalRect( &gSrcRect );
InvalRect( &gDestRect );
}
}
else if ( PtInRect( p, &gBackMenuRect ) )
{
UpdateBackMenu();
choice = DoPopup( gBackMenu, &gBackMenuRect );
if ( choice > 0 )
{
DoBackChoice( choice );
InvalRect( &gBackRect );
InvalRect( &gDestRect );
}
}
else if ( PtInRect( p, &gModeMenuRect ) )
{
UpdateModeMenu();
choice = DoPopup( gModeMenu, &gModeMenuRect );
if ( choice > 0 )
{
DoModeChoice( choice );
InvalRect( &gDestRect );
}
}
}
UpdateSrcMenu
void UpdateSrcMenu( void )
{
int i;
for ( i = 1; i <= 6; i++ )
CheckItem( gSrcMenu, i, false );
if ( gSrcPattern == iBlackPattern )
CheckItem( gSrcMenu, iBlackPattern, true );
else
CheckItem( gSrcMenu, iGrayPattern, true );
if ( gSrcType == iColorRamp )
CheckItem( gSrcMenu, iColorRamp, true );
else if ( gSrcType == iGrayRamp )
CheckItem( gSrcMenu, iGrayRamp, true );
else if ( gSrcType == iSingleColor )
CheckItem( gSrcMenu, iSingleColor, true );
}
UpdateBackMenu
void UpdateBackMenu( void )
{
int i;
for ( i = 1; i <= 6; i++ )
CheckItem( gBackMenu, i, false );
if ( gBackPattern == iBlackPattern )
CheckItem( gBackMenu, iBlackPattern, true );
else
CheckItem( gBackMenu, iGrayPattern, true );
if ( gBackType == iColorRamp )
CheckItem( gBackMenu, iColorRamp, true );
else if ( gBackType == iGrayRamp )
CheckItem( gBackMenu, iGrayRamp, true );
else if ( gBackType == iSingleColor )
CheckItem( gBackMenu, iSingleColor, true );
}
UpdateModeMenu
void UpdateModeMenu( void )
{
int i;
for ( i = 1; i <= 17; i++ )
CheckItem( gModeMenu, i, false );
if ( ( gCopyMode >= 0 ) && ( gCopyMode <= 7 ) )
CheckItem( gModeMenu, gCopyMode + 1, true );
else
CheckItem( gModeMenu, gCopyMode - 22, true );
}
DoSrcChoice
void DoSrcChoice( short item )
{
RGBColor rgbColor;
switch ( item )
{
case iBlackPattern:
case iGrayPattern:
gSrcPattern = item;
break;
case iColorRamp:
case iGrayRamp:
gSrcType = item;
break;
case iSingleColor:
gSrcType = iSingleColor;
rgbColor = gSrcColor;
if ( PickColor( &rgbColor ) )
gSrcColor = rgbColor;
break;
}
}
DoBackChoice
void DoBackChoice( short item )
{
RGBColor rgbColor;
switch ( item )
{
case iBlackPattern:
case iGrayPattern:
gBackPattern = item;
break;
case iColorRamp:
case iGrayRamp:
gBackType = item;
break;
case iSingleColor:
gBackType = iSingleColor;
rgbColor = gBackColor;
if ( PickColor( &rgbColor ) )
gBackColor = rgbColor;
break;
}
}
DoModeChoice
void DoModeChoice( short item )
{
if ( ( item >= 1 ) && ( item <= 8 ) )
gCopyMode = item - 1;
else
gCopyMode = item + 22;
}
DoPopup
short DoPopup( MenuHandle menu, Rect *boundsPtr )
{
Point corner;
long theChoice = 0L;
corner.h = boundsPtr->left;
corner.v = boundsPtr->bottom;
LocalToGlobal( &corner );
InvertRect( boundsPtr );
theChoice = PopUpMenuSelect(menu,corner.v-1,corner.h+1,0);
InvertRect( boundsPtr );
return( LoWord( theChoice ) );
}
PickColor
Boolean PickColor( RGBColor *colorPtr )
{
Point where;
where.h = -1;
where.v = -1;
return( GetColor( where, "\pChoose a color...", colorPtr,
colorPtr ) );
}
HasColorQD
Boolean HasColorQD( void )
{
unsigned char version[ 4 ];
OSErr err;
err = Gestalt( gestaltQuickdrawVersion, (long *)version );
if ( version[ 2 ] > 0 )
return( true );
else
return( false );
}
void DoError( Str255 errorString )
{
ParamText( errorString, "\p", "\p", "\p" );
StopAlert( kErrorALRTid, kNullFilterProc );
ExitToShell();
}
Running ColorTutor
Save your code, and run ColorTutor. The ColorTutor window will appear, as
shown in Figure 8.
Figure 8. The ColorTutor WIndow.
The Source and Background menus are identical, as shown in Figure 9. Play with
these selections till you get the source and background that you want.
Figure 9. The Source and Background menus.
The real fun comes when you play with the Mode popup (Figure 10). Basically,
the mode is passed as the fifth parameter to the CopyBits() call that copies the source
rectangles over the destination rectangle which had been previously copied to the lower
right corner of the ColorTutor window. Some of the modes take an OpColor, which you
can set using the OpColor button.
Figure 10. The Mode popup menu.
Till Next Month
Confused? Experiment! We’ll get into all the hows and whys next month. Till
then, read up on the Color Quickdraw transfer modes in THINK Reference and Inside
Macintosh.