TweetFollow Us on Twitter

Color 2
Volume Number:10
Issue Number:11
Column Tag:Getting Started

Related Info: Color Quickdraw Menu Manager Gestalt Manager

Working With Color, Part 2

By Dave Mark, MacTech Magazine Regular Contributing Author

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

Before we get into this month’s column, I’d like to take a second to talk about some changes that are coming down the ’pike. Every month, I’m faced with a perplexing challenge. How can I present a decent sized program in my column, and still have room to walk through the source code? The current solution is to split the column over two issues. The first month I present the program’s resources and source code and the second month I actually walk through the source code, essentially giving you the source code twice.

Unfortunately, there’s no easy way to simplify this process. If I just present the source code as a single block, there’s no room for commentary and if I just present the source code with comments mixed in it makes it extremely difficult to type in the code. Not a good situation!

Fortunately, the powers-that-be came up with a pretty cool idea (which I’m sure you’ll read about elsewhere in the magazine over the next few months). They are putting together a small framework, called Sprocket, designed to showcase new Mac technologies. Sprocket will start off with same pretty basic capabilities, then grow as time goes on. It will be written entirely in C++ and will be made available to every MacTech subscriber.

This makes life infinitely easier for us column writers! Instead of presenting a single, monolithic application every two months, I’ll be able to focus on a few new classes, which can be folded into the framework and, more importantly, which can be presented and completely discussed, all in a single column.

The biggest implication this has for you is a change from C to C++. If this is a bad thing, now’s the time to get it off your chest. Send all bitter, sarcastic complaints to Scott Boyd, at one of the myriad addresses found on page 2 of this magazine.

I’m really looking forward to getting into this new framework and polishing my C++ skills. I think this is a great idea and hope you do too...

Back to ColorTutor

All that said and done, let’s get back to last month’s program, ColorTutor. To recap last month’s description, ColorTutor is a rewrite of the Mac Primer, Volume II program of the same name. 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. As we dig through the code, we’ll look at the parts of Color Quickdraw that make ColorTutor possible.

Exploring the ColorTutor Source Code

ColorTutor starts off with a pair of #includes. <Picker.h> contains the definitions we’ll need to use the Mac’s built-in color picker, while <GestaltEqu.h> contains what we’ll need to call Gestalt().


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

Here’s the usual cast of constants...

#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

We’ve sure got a lot of globals. Short of adding pass-through parameters to a bunch of routines, I couldn’t think of any way to get rid of them. Any ideas?

gDone is the flag that tells us when to drop out of the main event loop. gSrcRect, gBackRect, gDestRect, and gOpColorRect mark the boundaries of the four color rectangles in the ColorTutor window. gSrcMenuRect, gBackMenuRect, and gModeMenuRect mark the outline of the three popup menus. As you read through the code, remember that ‘Src’ refers to the source color, ‘Back’ refers to the background color, and ‘Op’ refers to the op-color (you’ll find out what the opcolor is about later in the column).

‘Dest’ and ‘Mode’ both refer to the lower-right corner of the ColorTutor window. ‘Dest’ refers to the lower-right color rectangle which is used to mix the source and background colors. ‘Mode’ refers to the popup menu below the ‘Dest’ rectangle and specifies the color drawing mode used to mix the source and background colors.


/* 2 */
Boolean gDone;

Rect    gSrcRect, gBackRect, gDestRect, gSrcMenuRect, 
 gBackMenuRect, gModeMenuRect, gOpColorRect;

The next set of globals mirror the current settings of the three popup menus. gSrcPattern is set to either iBlackPattern or iGrayPattern, and gSrcType is one of iColorRamp, iGrayRamp, or iSingleColor. These choices correspond directly to the menu in Figure 2. gBackPattern and gBackType follow the exact same rules, since the Background menu was built from the same MENU resource.

Figure 2. The popup menu that goes along with the Source and Background menus.

gCopyMode is one of the Color Quickdraw copy modes and mirrors the Mode menu shown in Figure 3. gCopyMode is set in the routine UpdateModeMenu().

Figure 3. The Mode popup menu.


/* 3 */
short gSrcPattern, gBackPattern, gCopyMode, gSrcType, gBackType;

gSrcColor, gBackColor, and gOpColor are the colors displayed in the source, background, and op-color rectangles in the ColorTutor window. The source and background colors only come into play when the Single Color... item is checked in their respective popup menus. The OpColor is always a single color and comes into play when you use certain Color Quickdraw copying modes.


/* 4 */
RGBColorgSrcColor, gBackColor, gOpColor;

Finally, gSrcMenu, gBackMenu, and gModeMenu are handles to their respective menus.


/* 5 */
MenuHandlegSrcMenu, gBackMenu, gModeMenu;

Here are all the function prototypes...


/* 6 */
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() initializes the Toolbox, sets up the menu bar, then checks to see if this machine supports Color Quickdraw. It’s important to at least initialize the Toolbox before you check for Color Quickdraw, since we use the Toolbox to report an error if Color Quickdraw is not present.


/* 7 */
main
void  main( void )
{
 ToolboxInit();
 MenuBarInit();
 
 if ( ! HasColorQD() )
 DoError( "\pThis machine does not support Color QuickDraw!" );

Next, we’ll create the ColorTutor window, assign initial values to our globals, then drop into the main event loop.


/* 8 */
 CreateWindow();
 SetUpGlobals();
 
 EventLoop();
}

ToolboxInit() is pretty much the same as always. Notice the use of qd.thePort instead of just plain thePort. THINK C and C++ assume that when you refer to a quickdraw global (like thePort) you are referring to the corresponding quickdraw global that gets allocated as part of each application’s memory zone. MPW and CodeWarrior both require the “qd.” syntax, turning “thePort” into “qd.thePort”. Since THINK C can handle either form, I’ve gone back to the “qd.” form so all my code compiles in either environment.


/* 9 */
ToolboxInit
void  ToolboxInit( void )
{
 InitGraf( &qd.thePort );
 InitFonts();
 InitWindows();
 InitMenus();
 TEInit();
 InitDialogs( 0L );
 InitCursor();
}

MenuBarInit() sets up the menu bar. Notice that it doesn’t set up the popup menus, which are not displayed in the menubar.


/* 10 */
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() creates a new, Color Quickdraw window based on a WIND resource.


/* 11 */
CreateWindow
void  CreateWindow( void )
{
 WindowPtrwindow;

 window = GetNewCWindow( kBaseResID, NULL, kMoveToFront );

The call to GetNewControl() uses the CNTL resource template to build the OpColor push-button control and add it to the window.


/* 12 */
 GetNewControl( kBaseResID, window );

Finally, the window is made the current port and the port’s font is set to Chicago, which is the system font. This is to make sure that the popup menu label is drawn in 12-point Chicago.


/* 13 */
 SetPort( window );
 TextFont( systemFont );
}

SetUpGlobals() starts off by setting up the four color rectangles and the label rectangles for the three popup menus.


/* 14 */
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 );

One way to get rid of these globals is to create a set of rectangle resources, each of which describes a different rectangle. You might create a ‘rect’ resource type, 8 bytes in length (4 shorts). Then, instead of using a global, you’d read and write the corresponding ‘rect’ resource. Even though the globals have a higher overhead, we’re talking about a pretty small amount of stack space and I find the resource approach a little cumbersome.

Next, we set default values for our various globals.


/* 15 */
 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;

Here, we create a grey RGBColor (all values are halfway between 0 and 65535) and pass it to OpColor(). OpColor() first checks to make sure the current port is a CGrafPort. If so (and in our case it is), OpColor() sets the rgbOpColor field of the grafvars struct (in the CGrafPort) to the specified color. Take a minute to look at these structures. In Inside Macintosh: Imaging, Inside Macintosh: Volume V, or in THINK Reference, take a look at the CGrafPort structure. The fourth field is a Handle called grafVars. This is actually a handle to a GrafVars structure.

Now look up GrafVars. The first field, rgbOpColor, contains an RGBColor used with the addPin, subPin, and blend color transfer modes. addPin replaces the destination pixel with the sum of the source and destination pixel colors, up to a maximum of the colors specified by the opColor. With the opcolor as specified below, the red, green, and blue values in the lower-right rectangle of the ColorTutor window will never exceed 32767. With addPin, the maximum color is always white (65535,65535,65535).


/* 16 */
 gOpColor.green = 32767;
 gOpColor.red = 32767;
 gOpColor.blue = 32767;
 OpColor( &gOpColor );

subPin replaces the destination pixel with the difference between the source and destination, where the opColor provides a minimum value for the ultimate red, green, and blue fields. With subPin, the minimum color is always black (0,0,0).

Finally, the blend mode uses a weighting formula to blend the source and destination colors:


/* 17 */
dest = (source * weight / 65535) + (dest * (1-(weight/65535)));

This calculation is made once each for red, green, and blue, using the respective opColor field as the weight.

None of the other transfer modes take advantage of a port’s opColor.

Next, we’ll set up MenuInfo structures for the three popup menus. Each menu is added to the application’s menu list by passing its handle to InsertMenu(). The constant kNotNormalMenu (which is -1L) tells the Menu Manager not to add these menus to the menu bar.


/* 18 */
 gSrcMenu = GetMenu( mColorsPopup );
 InsertMenu( gSrcMenu, kNotNormalMenu );
 
 gBackMenu = GetMenu( mColorsPopup );
 InsertMenu( gBackMenu, kNotNormalMenu );
 
 gModeMenu = GetMenu( mModePopup );
 InsertMenu( gModeMenu, kNotNormalMenu );
}

Blah, blah, blah... EventLoop()... blah, blah.


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

DoEvent() does its normal thing, passing mouseDowns to HandleMouseDown() and updates to DoUpdate().


/* 20 */
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;
 }
}

Nothing unusual here. Clicks in the ColorTutor window are handled by DoContent(). Since we only have one window, the call to SelectWindow() will never be made. Oh, well, force of habit...


/* 21 */
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(), HandleAppleChoice(), and HandleFileChoice() deserve a column all to themselves, eh?


/* 22 */
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() handles any update events with calls to DrawContents() to draw everything but the OpColor... push button, which is drawn by calling DrawControls().


/* 23 */
DoUpdate
void  DoUpdate( WindowPtr window )
{
 BeginUpdate( window );
 
 DrawContents( window );
 DrawControls( window );
 
 EndUpdate( window );
}

DrawContents() starts by setting up a true black RGBColor.


/* 24 */
DrawContents
void  DrawContents( WindowPtr window )
{
 RGBColor rgbBlack;
 Rect   source, dest;
 
 rgbBlack.red = rgbBlack.green = rgbBlack.blue = 0;

Next, the CGrafPort’s pattern is set to black or gray, depending on the value of gSrcPattern, which mirrors the setting of the Source popup menu. Here’s another place I could have gotten rid of some globals. Instead of checking the value of a global, I could have checked the appropriate menu item to see if it was checked. Again, I find the global-based method to be less cumbersome, though there are some who, even as we speak, are reaching for their direct lines to the Thought Police in their desparate quest for interface cleanliness. Sigh.


/* 25 */
 if ( gSrcPattern == iBlackPattern )
 PenPat( &qd.black );
 else
 PenPat( &qd.gray );

Next, we’ll draw either a color ramp, a gray ramp, or a solid color in the source rectangle. If you don’t know what a ramp is, check out the source rectangle in Figure 1.


/* 26 */
 if ( gSrcType == iColorRamp )
 DrawColorRamp( &gSrcRect );
 else if ( gSrcType == iGrayRamp )
 DrawGrayRamp( &gSrcRect );
 else
 {
 RGBForeColor( &gSrcColor );
 PaintRect( &gSrcRect );
 }

Now we’ll repeat this process for the background rectangle.


/* 27 */
 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 );
 }

Next, we’ll restore the pen pattern to solid black, paint the opColor rectangle, then set the ForeColor() back to black.


/* 28 */
 PenPat( &qd.black );
 
 RGBForeColor( &gOpColor );
 PaintRect( &gOpColorRect );
 
 RGBForeColor( &rgbBlack );

Next, we’ll draw the three popup menu labels, then the frames around the four color rectangles, then return the pen to normal.


/* 29 */
 DrawLabel( &gSrcMenuRect, "\pSource" );
 DrawLabel( &gBackMenuRect, "\pBackground" );
 DrawLabel( &gModeMenuRect, "\pMode" );
 
 PenSize( 2, 2 );
 FrameRect( &gSrcRect );
 FrameRect( &gBackRect );
 FrameRect( &gDestRect );
 FrameRect( &gOpColorRect );
 
 PenNormal();

Now we’ll set up the source and destination rectangles. We inset them to avoid copying the frames. Notice that we first copy the Background rectangle to the Dest (lower-right) rectangle. Once we do that, we’ll copy the Source rectangle on top of that.


/* 30 */
 source = gBackRect;
 InsetRect( &source, 2, 2 );
 
 dest = gDestRect;
 InsetRect( &dest, 2, 2 );

Here’s our call to CopyBits(). Since this first call is just intended to get the background pixels down to the lower-right rectangle, we’ll use the srcCopy transfer mode.


/* 31 */
 CopyBits( (BitMap *)&(((CGrafPtr)window)->portPixMap),
   (BitMap *)&(((CGrafPtr)window)->portPixMap),
   &source, &dest, srcCopy, NULL );

Now we’ll copy the Source rectangle down to the lower-right rectangle, overwriting what we just copied with the previous call to CopyBits(). The results will change, depending on the transfer mode in gCopyMode. The transfer modes are described in Inside Macintosh: Volume V, Inside Macintosh: Imaging, and in THINK Reference, but I think the best description by far is in IM: Imaging. If you plan on working with color to any great extent, pick up IM: Imaging and check out IM: Advanced Imaging (not for most folks).


/* 32 */
 source = gSrcRect;
 InsetRect( &source, 2, 2 );
 
 CopyBits( (BitMap *)&(((CGrafPtr)window)->portPixMap),
 (BitMap *)&(((CGrafPtr)window)->portPixMap),
 &source, &dest, gCopyMode, NULL );
}

The color ramp stuff is kind of interesting. Instead of using the red, green, blue color model, we depend on the hue, saturation, and brightness model. We vary the hue from 0 to 65535 with the resolution determined by width, in pixels, of the specified rectangle.


/* 33 */
DrawColorRamp
void  DrawColorRamp( Rect *rPtr )
{
 long   numColors, i;
 HSVColor hsvColor;
 RGBColor rgbColor;
 Rect   r;
 
 r = *rPtr;

We inset the rectangle by 2 pixels so we don’t draw on the rectangle’s frame. Interestingly, hue is hue, saturation is saturation, but for some reason, brightness is represented by the field named value. Hmmm... Oh, well. As you can see, our color ramp will consist of bright, saturated colors. Try rewriting the code to ramp on saturation or brightness instead.


/* 34 */
 InsetRect( &r, 2, 2 );
 numColors = ( rPtr->right - rPtr->left - 2 ) / 2;
 hsvColor.value = hsvColor.saturation = 65535;

Here’s the ramping loop. HSV2RGB() converts an HSV color to an RGB color, which produces something we can pass to RGBForeColor(). Why isn’t there an HSVForeColor() routine? I don’t know, but I wish there were.


/* 35 */
 for ( i = 0; i < numColors; i++ )
 {
 hsvColor.hue = i * 65535 / numColors;
 HSV2RGB( &hsvColor, &rgbColor );
 RGBForeColor( &rgbColor );

Each time through the loop, we draw a 1 pixel wide rectangle, then shrink the rectangle in preparation for the next pass through the loop.


/* 36 */
 FrameRect( &r );
 InsetRect( &r, 1, 1 );
 }
}

DrawGrayRamp() does essentially the same thing, but stays within the RGB model.


/* 37 */
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;

In this case, we run red from 0 to 65535, with a step resolution based on the width 
of the rectangle. We then copy the red value into both green and blue, producing progressively 
lighter shades of gray.

 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 );
 }
}

In real life, this routine shouldn’t be necessary. We really should be using the popup menu CDEF that’s been around for several years. The problem is, ResEdit has a bug in it that makes the CDEF very difficult to set up properly. I’m not sure if the problem lies with ResEdit or with the CDEF, but just to avoid the problem, here’s the old way of doing popups. What this really needs is that nifty down-pointing triangles that you usually see in popups, but I ran out of time. Aside from spending the money for Resorcerer (a good investment, IMHO), does anyone have a workaround that brings the popup CDEF under control (sorry!!) in ResEdit?


/* 38 */
DrawLabel
void  DrawLabel( Rect *boundsPtr, Str255 s )
{
 Rect r;
 int    size;
!co1deexampleend

This code basically draws a poor imitation of a popup menu label, using StringWidth() 
to calculate the proper centering position in the label.

/* 39 */
 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 );
}

When we get a click in the content region of the window, DoContent() takes a global mouse position and converts it to the local coordinate system. I probably should have added a SetPort() call to make sure that window was the current window before I called GlobalToLocal(). Since there’s only one window, this won’t cause any harm here, but be sure to add the SetPort() if you reuse this code.


/* 40 */
DoContent
void  DoContent( WindowPtr window, Point globalPoint )
{
 int    choice;
 ControlHandle control;
 RGBColor rgbColor;
 Point  p;
 
 p = globalPoint;
 GlobalToLocal( &p );

If the click was in a control, we’ll call TrackControl() to track the mouse till the button is released.


/* 41 */
 if ( FindControl( p, window, &control ) )
 {
 if ( TrackControl( control, p, NULL ) )
 {

If the mouse was released inside the control, we know it was in the OpColor... push-button. We’ll call PickColor() to put up the standard Mac color picker.


/* 42 */
 rgbColor = gOpColor;
 
 if ( PickColor( &rgbColor ) )
 {

If the user clicked the OK button to pick a new opColor, we’ll force an update and set the new opColor.


/* 43 */
 gOpColor = rgbColor;
 
 InvalRect( &gOpColorRect );
 InvalRect( &gDestRect );
 
 OpColor( &gOpColor );
 }
 }
 }

If the click was in the source menu, we’ll update the check marks, then call DoPopup() to popup the menu. If an item is chosen from the menu, we’ll pass the item to DoSrcChoice(), then force an update.


/* 44 */
 else if ( PtInRect( p, &gSrcMenuRect ) )
 {
 UpdateSrcMenu();
 
 choice = DoPopup( gSrcMenu, &gSrcMenuRect );
 
 if ( choice > 0 )
 {
 DoSrcChoice( choice );
 
 InvalRect( &gSrcRect );
 InvalRect( &gDestRect );
 }
 }

If the click was in the background or mode menus, we’ll follow the same plan, with calls to DoBackChoice() or DoModeChoice() as a result.


/* 45 */
 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() starts by unchecking all its items.


/* 46 */
UpdateSrcMenu
void  UpdateSrcMenu( void )
{
 int    i;
 
 for ( i = 1; i <= 6; i++ )
 CheckItem( gSrcMenu, i, false );

Next, it uses globals to decide which items should be checked.


/* 47 */
 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() does the same thing as UpdateSrcMenu().


/* 48 */
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() starts off the same way, by unchecking the mode menu.


/* 49 */
UpdateModeMenu
void  UpdateModeMenu( void )
{
 int    i;
 
 for ( i = 1; i <= 17; i++ )
 CheckItem( gModeMenu, i, false );

To understand this next chunk of code, take a look at the definition of the transfer modes in <Quickdraw.h>. The first 8 transfer modes are defined by an enum as 0 through 7 and correspond to items 1 through 8 in the Mode menu. The next 8 transfer modes are enum’ed as 32 through 39.


/* 50 */
 if ( ( gCopyMode >= 0 ) && ( gCopyMode <= 7 ) )
 CheckItem( gModeMenu, gCopyMode + 1, true );
 else
 CheckItem( gModeMenu, gCopyMode - 22, true );
}

DoSrcChoice() and DoBackChoice() use their choices to update their globals. If Single Color... is selected, PickColor() is called to select a new color.


/* 51 */
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() uses its selection to update gCopyMode.


/* 52 */
DoModeChoice
void  DoModeChoice( short item )
{
 if ( ( item >= 1 ) && ( item <= 8 ) )
 gCopyMode = item - 1;
 else
 gCopyMode = item + 22;
}

DoPopup() calls PopUpMenuSelect() to implement a popup menu.


/* 53 */
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() is just a wrapper for GetColor(). It sets up a Point with coordinates (-1, -1), which tells the Color Quickdraw routine GetColor() to center the Color Picker on the main screen.


/* 54 */
PickColor
Boolean PickColor( RGBColor *colorPtr )
{
 Point  where;
 
 where.h = -1;
 where.v = -1;
 
 return( GetColor( where, "\pChoose a color...", colorPtr,
 colorPtr ) );
}

HasColorQD() calls Gestalt() to see if Color Quickdraw is installed on this machine. If the third byte of the returned long is positive, Color Quickdraw is installed.


/* 55 */
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() is our standard error handling routine.


/* 56 */
DoError
void  DoError( Str255 errorString )
{
 ParamText( errorString, "\p", "\p", "\p" );
 
 StopAlert( kErrorALRTid, kNullFilterProc );
 
 ExitToShell();
}

Till Next Month

Try experimenting with the code. I especially like messing with different color and grayscale ramps. Try writing a program that blends two offscreen pixmaps on screen using different drawing modes. Try modifying ColorTutor so, as the cursor passes over a pixel, the RGB and HSV values are displayed. Change ColorTutor so it uses the popup CDEF instead of the old-style popups.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Ableton Live 11.3.11 - Record music usin...
Ableton Live lets you create and record music on your Mac. Use digital instruments, pre-recorded sounds, and sampled loops to arrange, produce, and perform your music like never before. Ableton Live... Read more
Affinity Photo 2.2.0 - Digital editing f...
Affinity Photo - redefines the boundaries for professional photo editing software for the Mac. With a meticulous focus on workflow it offers sophisticated tools for enhancing, editing and retouching... Read more
SpamSieve 3.0 - Robust spam filter for m...
SpamSieve is a robust spam filter for major email clients that uses powerful Bayesian spam filtering. SpamSieve understands what your spam looks like in order to block it all, but also learns what... Read more
WhatsApp 2.2338.12 - Desktop client for...
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
Fantastical 3.8.2 - Create calendar even...
Fantastical is the Mac calendar you'll actually enjoy using. Creating an event with Fantastical is quick, easy, and fun: Open Fantastical with a single click or keystroke Type in your event details... Read more
iShowU Instant 1.4.14 - Full-featured sc...
iShowU Instant gives you real-time screen recording like you've never seen before! It is the fastest, most feature-filled real-time screen capture tool from shinywhitebox yet. All of the features you... Read more
Geekbench 6.2.0 - Measure processor and...
Geekbench provides a comprehensive set of benchmarks engineered to quickly and accurately measure processor and memory performance. Designed to make benchmarks easy to run and easy to understand,... Read more
Quicken 7.2.3 - Complete personal financ...
Quicken makes managing your money easier than ever. Whether paying bills, upgrading from Windows, enjoying more reliable downloads, or getting expert product help, Quicken's new and improved features... Read more
EtreCheckPro 6.8.2 - For troubleshooting...
EtreCheck is an app that displays the important details of your system configuration and allow you to copy that information to the Clipboard. It is meant to be used with Apple Support Communities to... Read more
iMazing 2.17.7 - Complete iOS device man...
iMazing is the world’s favourite iOS device manager for Mac and PC. Millions of users every year leverage its powerful capabilities to make the most of their personal or business iPhone and iPad.... Read more

Latest Forum Discussions

See All

Motorsport legends NASCAR announce an up...
NASCAR often gets a bad reputation outside of America, but there is a certain charm to it with its close side-by-side action and its focus on pure speed, but it never managed to really massively break out internationally. Now, there's a chance... | Read more »
Skullgirls Mobile Version 6.0 Update Rel...
I’ve been covering Marie’s upcoming release from Hidden Variable in Skullgirls Mobile (Free) for a while now across the announcement, gameplay | Read more »
Amanita Design Is Hosting a 20th Anniver...
Amanita Design is celebrating its 20th anniversary (wow I’m old!) with a massive discount across its catalogue on iOS, Android, and Steam for two weeks. The announcement mentions up to 85% off on the games, and it looks like the mobile games that... | Read more »
SwitchArcade Round-Up: ‘Operation Wolf R...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for September 21st, 2023. I got back from the Tokyo Game Show at 8 PM, got to the office here at 9:30 PM, and it is presently 11:30 PM. I’ve done what I can today, and I hope you enjoy... | Read more »
Massive “Dark Rebirth” Update Launches f...
It’s been a couple of months since we last checked in on Diablo Immortal and in that time the game has been doing what it’s been doing since its release in June of last year: Bringing out new seasons with new content and features. | Read more »
‘Samba De Amigo Party-To-Go’ Apple Arcad...
SEGA recently released Samba de Amigo: Party-To-Go () on Apple Arcade and Samba de Amigo: Party Central on Nintendo Switch worldwide as the first new entries in the series in ages. | Read more »
The “Clan of the Eagle” DLC Now Availabl...
Following the last paid DLC and free updates for the game, Playdigious just released a new DLC pack for Northgard ($5.99) on mobile. Today’s new DLC is the “Clan of the Eagle" pack that is available on both iOS and Android for $2.99. | Read more »
Let fly the birds of war as a new Clan d...
Name the most Norse bird you can think of, then give it a twist because Playdigious is introducing not the Raven clan, mostly because they already exist, but the Clan of the Eagle in Northgard’s latest DLC. If you find gathering resources a... | Read more »
Out Now: ‘Ghost Detective’, ‘Thunder Ray...
Each and every day new mobile games are hitting the App Store, and so each week we put together a big old list of all the best new releases of the past seven days. Back in the day the App Store would showcase the same games for a week, and then... | Read more »
Urban Open-World RPG ‘Project Mugen’ Fro...
Last month, NetEase Games revealed a new free to play open world RPG tentatively titled Project Mugen for mobile, PC, and consoles. I’ve liked the setting and aesthetic since its first trailer, and today’s new video has the Game Designer and... | Read more »

Price Scanner via MacPrices.net

Apple AirPods 2 with USB-C now in stock and o...
Amazon has Apple’s 2023 AirPods Pro with USB-C now in stock and on sale for $199.99 including free shipping. Their price is $50 off MSRP, and it’s currently the lowest price available for new AirPods... Read more
New low prices: Apple’s 15″ M2 MacBook Airs w...
Amazon has 15″ MacBook Airs with M2 CPUs and 512GB of storage in stock and on sale for $1249 shipped. That’s $250 off Apple’s MSRP, and it’s the lowest price available for these M2-powered MacBook... Read more
New low price: Clearance 16″ Apple MacBook Pr...
B&H Photo has clearance 16″ M1 Max MacBook Pros, 10-core CPU/32-core GPU/1TB SSD/Space Gray or Silver, in stock today for $2399 including free 1-2 day delivery to most US addresses. Their price... Read more
Switch to Red Pocket Mobile and get a new iPh...
Red Pocket Mobile has new Apple iPhone 15 and 15 Pro models on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide service using all the major... Read more
Apple continues to offer a $350 discount on 2...
Apple has Studio Display models available in their Certified Refurbished store for up to $350 off MSRP. Each display comes with Apple’s one-year warranty, with new glass and a case, and ships free.... Read more
Apple’s 16-inch MacBook Pros with M2 Pro CPUs...
Amazon is offering a $250 discount on new Apple 16-inch M2 Pro MacBook Pros for a limited time. Their prices are currently the lowest available for these models from any Apple retailer: – 16″ MacBook... Read more
Closeout Sale: Apple Watch Ultra with Green A...
Adorama haș the Apple Watch Ultra with a Green Alpine Loop on clearance sale for $699 including free shipping. Their price is $100 off original MSRP, and it’s the lowest price we’ve seen for an Apple... Read more
Use this promo code at Verizon to take $150 o...
Verizon is offering a $150 discount on cellular-capable Apple Watch Series 9 and Ultra 2 models for a limited time. Use code WATCH150 at checkout to take advantage of this offer. The fine print: “Up... Read more
New low price: Apple’s 10th generation iPads...
B&H Photo has the 10th generation 64GB WiFi iPad (Blue and Silver colors) in stock and on sale for $379 for a limited time. B&H’s price is $70 off Apple’s MSRP, and it’s the lowest price... Read more
14″ M1 Pro MacBook Pros still available at Ap...
Apple continues to stock Certified Refurbished standard-configuration 14″ MacBook Pros with M1 Pro CPUs for as much as $570 off original MSRP, with models available starting at $1539. Each model... Read more

Jobs Board

Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Retail Key Holder- *Apple* Blossom Mall - Ba...
Retail Key Holder- APPLE BLOSSOM MALL Brand: Bath & Body Works Location: Winchester, VA, US Location Type: On-site Job ID: 03YM1 Job Area: Store: Sales and Support Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.