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 months 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 months 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, well get into the details in next months column. For now, well 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, youll 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 well be creating 5 menus, dont be fooled. Only the first three will be added to the menu bar.
Next, youll 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 dont 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, Ill test all my source code to make sure it compiles in both THINK C and CodeWarrior, so it shouldnt 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. Heres the source code:
/* 1 */
#include <Picker.h>
#include <GestaltEqu.h>
#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 iBlackPattern1
#define iGrayPattern 2
#define iColorRamp 4
#define iGrayRamp5
#define iSingleColor 6
#define mModePopup kBaseResID+4
Globals
Boolean gDone;
Rect gSrcRect, gBackRect, gDestRect, gSrcMenuRect,
gBackMenuRect, gModeMenuRect, gOpColorRect;
intgSrcPattern, gBackPattern, gCopyMode, gSrcType,
gBackType;
RGBColorgSrcColor, gBackColor, gOpColor;
MenuHandlegSrcMenu, gBackMenu, gModeMenu;
Functions
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 )
{
WindowPtrwindow;
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 )
{
EventRecordevent;
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 )
{
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;
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 );
}
DoError
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! Well 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.