TweetFollow Us on Twitter

QD Antialiasing Techniques

Volume Number: 13 (1997)
Issue Number: 1
Column Tag: Tips & Graphics Workshop

Antialiasing with Color QuickDraw

By Martin James Murrett II, MacPants Software

Techniques used in AntiAliasMan to smooth the edges of Text, Line, Ovals and other shapes

I have been interested in antialiasing for a long time. It is a process in which the edges of a graphic are dithered with the background to produce a smoothing effect. My first experiment took about a week to render on screen, and showed a very poorly antialiased black circle over a white background. Since then, I have not only drawn that circle even slower, but have also released two versions of AntiAliasMan, a C library with a wide array of antialiasing functions, ranging from the basic circle to antialiased text, rounded rectangles, and lines, in any color, with or without disturbing the background. This article describes how the newest version of AntiAliasMan (2.0) works.

The first and most important area that this article will cover, before the code, is the area of antialiasing theory used in this code. An antialiasing effect can be achieved by drawing a large image off-screen, then dithering it down to a small image. My first two efforts used a method of enlarging the background, drawing a large image on it, and dithering down the whole mess.

One day it occurred to me that I could write a fast dithering mechanism by taking four char pointers, four rows at a time, and using them to get an index for a four-bit off-screen GWorld. I finally decided to implement my theoretical dithering routine in AntiAliasMan. Upon trying it out (once the bugs were gone), I realized that I was getting a 0-16, not 0-15 index. This lingered in my head for a little while. Then, one night (morning) at about 3 A.M., it occurred to me that I could mask the index with 0x08, shift the result three bits right, and subtract it from the original index to get a 0-15 index. This was incorrect. A value of 16 (0x10) has only one bit set, the fifth. I realized this the next day, and I had a working dithering routine.

Because Apple likes you, they made a routine called CopyBits that will change a grayscale image into a color image, complete with a mode parameter and some other goodies. Without CopyBits, AntiAliasMan would not exist. This is how the four-bit grayscale image is changed to text or ovals or whatever else you are antialiasing.

The uses for AntiAliasMan are limitless. Use it in all of your PowerMac applications, regardless of what they do. Write your own LDEFs and MDEFs for antialiased lists and menus. Write system extensions that give Word 6.0 a reason to go slowly, by patching DrawString to antialias text when Word 6.0 is the active process. With its new DitherMan dithering engine, AntiAliasMan is even practical for 680x0 series Macs.

General Stuff

I have only three rules: use CopyBits if at all practical, don't call SetRect, and don't wear your pants inside out. Of these three, only the first two are enforced in the code. I like CopyBits because it is ridiculously fast on a PowerMac, compared to any custom pixel copying routine (if you don't have a PowerPC compiler). It is guaranteed to work in the future, and your code automatically benefits from graphic accelerators. I hate SetRect because it adds to code size while decreasing code speed. Worse yet, is occupies a valuable A-trap spot (there are only 4,096 available). I am currently working on a patch that will make SetRect useful.

The code that follows presents the sore of AntiAliasMan two file segments: all of AntiAliasMan.h and AntiAliasManInit() from AntiAliasMan.c.

Listing 1: AntiAliasMan.h

#ifndef __ANTIALIASMAN__
#define __ANTIALIASMAN__

#ifndef __QDOFFSCREEN__
#include <QDOffscreen.h>
#endif

#ifndef __QUICKDRAW__
#include <Quickdraw.h>
#endif

pascal void AntiAliasManInit( void );

pascal OSErrDrawAntiAliasManString( ConstStr255Param s );
pascal OSErrDrawAntiAliasManChar( short ch );

pascal OSErrStdAntiAliasManRRect( GrafVerb verb, Rect *r,
 short ovalWidth, short ovalHeight );
pascal OSErrStdAntiAliasManOval( GrafVerb verb, Rect *r );
pascal OSErrStdAntiAliasManArc( GrafVerb verb, Rect *r,
 short startAngle, short arcAngle );

pascal OSErrAntiAliasManLineTo( short h, short v );
pascal OSErrAntiAliasManLine( short dh, short dv );

/* these macros make it easier to call the various AntiAliasMan functions, requiring less typing, and more 
familiar names */

#define DrawAAString( s ) DrawAntiAliasManString( s )

#define StdAARRect( v, r, ow, oh ) \
 StdAntiAliasManRRect( v, r, ow, oh )
#define StdAAOval( v, r ) StdAntiAliasManOval( v, r )
#define StdAAArc( v, r, sa, aa ) \
 StdAntiAliasManArc( v, r, sa, aa )

#define FrameAntiAliasManOval( r ) \
 StdAntiAliasManOval( frame, r )
#define PaintAntiAliasManOval( r ) \
 StdAntiAliasManOval( paint, r )
#define EraseAntiAliasManOval( r ) \
 StdAntiAliasManOval( erase, r )
#define InvertAntiAliasManOval( r )\
 StdAntiAliasManOval( invert, r )
#define FillAntiAliasManOval( r, p ) \
 StdAntiAliasManOval( fill, r )

#define FrameAAOval( r )  \
 StdAntiAliasManOval( frame, r )
#define PaintAAOval( r )  \
 StdAntiAliasManOval( paint, r )
#define EraseAAOval( r )  \
 StdAntiAliasManOval( erase, r )
#define InvertAAOval( r ) \
 StdAntiAliasManOval( invert, r )
#define FillAAOval( r, p )\
 StdAntiAliasManOval( fill, r )

#define FrameAntiAliasManRoundRect( r, ow, oh )          \
 StdAntiAliasManRRect( frame, r, ow, oh )
#define PaintAntiAliasManRoundRect( r, ow, oh )          \
 StdAntiAliasManRRect( paint, r, ow, oh )
#define EraseAntiAliasManRoundRect( r, ow, oh )          \
 StdAntiAliasManRRect( erase, r, ow, oh )
#define InvertAntiAliasManRoundRect( r, ow, oh )   \
 StdAntiAliasManRRect( invert, r, ow, oh )
#define FillAntiAliasManRoundRect( r, ow, oh, p )  \
 StdAntiAliasManRRect( fill, r, ow, oh )

#define FrameAARoundRect( r, ow, oh )\
 StdAntiAliasManRRect( frame, r, ow, oh )
#define PaintAARoundRect( r, ow, oh )\
 StdAntiAliasManRRect( paint, r, ow, oh )
#define EraseAARoundRect( r, ow, oh )\
 StdAntiAliasManRRect( erase, r, ow, oh )
#define InvertAARoundRect( r, ow, oh ) \
 StdAntiAliasManRRect( invert, r, ow, oh )
#define FillAARoundRect( r, ow, oh, p )\
 StdAntiAliasManRRect( fill, r, ow, oh )

#define FrameAAArc( r, sa, aa )    \
 StdAntiAliasManArc( frame, r, sa, aa )
#define PaintAAArc( r, sa, aa )    \
 StdAntiAliasManArc( paint, r, sa, aa )
#define EraseAAArc( r, sa, aa )    \
 StdAntiAliasManArc( erase, r, sa, aa )
#define InvertAAArc( r, sa, aa )   \
 StdAntiAliasManArc( invert, r, sa, aa )
#define FillAAArc( r, sa, aa, p )  \
 StdAntiAliasManArc( fill, r, sa, aa )

#define FrameAntiAliasManArc( r, sa, aa )                \
 StdAntiAliasManArc( frame, r, sa, aa )
#define PaintAntiAliasManArc( r, sa, aa )                \
 StdAntiAliasManArc( paint, r, sa, aa )
#define EraseAntiAliasManArc( r, sa, aa )                \
 StdAntiAliasManArc( erase, r, sa, aa )
#define InvertAntiAliasManArc( r, sa, aa )         \
 StdAntiAliasManArc( invert, r, sa, aa )
#define FillAntiAliasManArc( r, sa, aa, p )        \
 StdAntiAliasManArc( fill, r, sa, aa )

#define AALineTo( h, v )  AntiAliasManLineTo( h, v )
#define AALine( dh, dv )  AntiAliasManLine( dh, dv )

#endif
Listing 2: AntiAliasMan.c: Macros, AntiAliasManInit, and DitherMan

#include "AntiAliasMan2.h"

#ifndef nil
#define nil 0L
#endif

/* Low returns the lower of s1 and s2 */
#define Low( s1, s2 )( ( s1 > s2 ) ? s2 : s1 )
/* High returns the higher of s1 and s2 */
#define High( s1, s2 )  ( ( s1 > s2 ) ? s1 : s2 )
/* Abs returns the absolute value of s */
#define Abs( s ) ( s > 0 ? s : -( s ) )

#define uchar  unsigned char
#define ushort unsigned short
#define ulong  unsigned long

MSetRect 
/* MSetRect sets a rectangle in less code and fewer cycles than a call to SetRect */
#define MSetRect( r, lt, tp,\
 rt, bm ) \
{\
 r.top = tp;\
 r.left = lt;    \
 r.bottom = bm;  \
 r.right = rt;   \
}

CreateGWorlds 
/* CreateGWorlds creates the two GWorlds: the one-bit GWorld, x4GW, and the four-bit GWorld, x1GW. 
if an error occurs, CreateGWorlds cleans up, calls the routine specified in r, and returns the error. */
#define CreateGWorlds( r )\
{\
 err = NewGWorld( &x4GW, 1, \
 &x4Rect, nil, nil, 0 );  \
 if( err || x4GW == nil ) \
 { \
 r;\
 if( err != noErr )\
 return( err );  \
 return( memFullErr );    \
 } \
 \
/* GetCTable( 32 + x ) yeilds a gray scale gradient color table of depth x. since we're using four-bit color, 
we pass 32 + 4, or 36 */
 x1Tab = GetCTable( 36 ); \
 if( x1Tab == nil )\
 { \
 DisposeGWorld( x4GW );   \
 r;\
 return( memFullErr );    \
 } \
 err = NewGWorld( &x1GW, 4, \
 &x1Rect, x1Tab, nil, 0 );\
 if( err || x1GW == nil ) \
 { \
 DisposeGWorld( x4GW );   \
 DisposeCTable( x1Tab );  \
 r;\
 if( err != noErr )\
 return( err );  \
 return( memFullErr );    \
 } \
}

LockGWorlds
/* LockGWorlds saves and locks the two GWorlds' pixMaps, storing x4GW's pixMap in x4Map, and x1GW's 
pixMap in x1Map. if, for some reason, the GWorlds cannot be locked, LockGWorlds cleans up, calls the 
routine specified in r, and returns notLockedErr. LockGWorlds also puts dstGW's pixMap into dstMap. */
#define LockGWorlds( r )  \
{\
 x4Map = x4GW->portPixMap;\
 x1Map = x1GW->portPixMap;\
 dstMap = dstGW->portPixMap;\
 \
 locked = \
 LockPixels( x4Map );\
 if( !locked )   \
 { \
 DisposeGWorld( x4GW );   \
 DisposeGWorld( x1GW );   \
 \
 r;\
 \
 return( notLockedErr );  \
 } \
 locked = \
 LockPixels( x1Map );\
 if( !locked )   \
 { \
 DisposeGWorld( x4GW );   \
 DisposeGWorld( x1GW );   \
 \
 r;\
 \
 return( notLockedErr );  \
 } \
}



DisposeGWorlds
/* DisposeGWorlds unlocks both pixMaps, then disposes of the two GWorlds */
#define DisposeGWorlds()  \
{\
 UnlockPixels( x4Map );   \
 UnlockPixels( x1Map );   \
 \
 DisposeGWorld( x4GW );   \
 DisposeGWorld( x1GW );   \
}

void  DitherMan( PixMapHandle srcMap, PixMapHandle dstMap,
 ushort width );
/* truebits is a 16-ushort array, with each item set to the number of true (1) bits in its index. */
static ushort  truebits[ 16 ] =
 { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 };
/* fulltruebits will be initialized by AntiAliasManInit to a format specified in the AntiAliasManInit routine description. 
*/
ushort  fulltruebits[ 256 ];
/* inited is true if AntiAliasManInit has been called. */
Boolean inited = false;

AntiAliasManInit
/* AntiAliasManInit initializes a global array to the following:
    high half-byte of index i: number of bits set to true (1) in the high half-byte of i
    low half-byte of index i: number of bits set to true (1) in the low half-byte of i
    then the routine sets a global variable to tell itslef that it has been initialized. */
pascal void AntiAliasManInit( void )
{
 ushort one, two;
 for( one = 0; one <= 0x000f; one++ )
 {
 for( two = 0; two <= 0x000f; two++ )
 {
 fulltruebits[ ( one << 4 ) + two ] =
 ( truebits[ one ] << 8 ) + truebits[ two ];
 }
 }

 inited = true;
}

DitherMan
/* DitherMan takes eight bits from four rows at a time from srcMap (a one-bit PixMapHandle), adds them up, 
and decrements either half-byte if it exceeds 0xf. it then uses this value for two indexes in dstMap, a four-byte 
PixMapHandle. */
void    DitherMan( PixMapHandle srcMap, PixMapHandle dstMap,
 ushort width )
{
 ushort y, x, height, dstval;
 ulong  srcRowBytes, dstRowBytes;
 uchar  *src, *dst;
 uchar  *rowone, *rowtwo, *rowthree, *rowfour;
 uchar  *dstrow;
 height = ( *dstMap )->bounds.bottom;
 src = ( *srcMap )->baseAddr;
 dst = ( *dstMap )->baseAddr;
 srcRowBytes = ( *srcMap )->rowBytes & 0x7fff;
 dstRowBytes = ( *dstMap )->rowBytes & 0x7fff;

 for ( y = 0; y < height; y++ )
 {
 rowone = src;
 rowtwo = rowone + srcRowBytes;
 rowthree = rowtwo + srcRowBytes;
 rowfour = rowthree + srcRowBytes;
 dstrow = dst;
 for ( x = 0; x < width; x += 2 )
 {
 dstval = fulltruebits[ *( rowone++ ) ];
 dstval += fulltruebits[ *( rowtwo++ ) ];
 dstval += fulltruebits[ *( rowthree++ ) ];
 dstval += fulltruebits[ *( rowfour++ ) ];

/* the following line changes a 0x00-0x10 value to a 0x00-0x0f value. it works like this:
    all byte values over 0x0f have the fifth bit set. a mask of 0x10 strips all but this bit, so we now have a value 
of 0x10 or 0x00. shifting right four bits gives us a value of 0x01 or 0x00. so we subtract this value, and, if 
the index is over 0x0f, (like 0x10) it will be decremented. otherwise, nothing happens. since we are working 
with two bytes at a time, our mask changes to 0x1010, giving us values of 0x0000, 0x0010, 0x1000, or 0x1010 
when the bits are stripped, and subsequent values of 0x0000, 0x0001, 0x0100, and 0x0101, respectively, 
when the result is shifted right four bits. */
 dstval -= ( ( dstval & 0x1010 ) >> 4 );
/* to get a valid value for our dstMap, we must compress our word into a byte, so what we do is add dstval 
to dstval >> 4, squishing the low half-byte of our high byte into the high half-byte of our low byte.

    Example:
    1.   0x0X0Y
    2.   ( char )0x0X0Y + ( char )( 0x0X0Y >> 4 )
    3.   0x0Y + 0xX0
    4.   0xXY */
 *( dstrow++ ) = dstval + ( dstval >> 4 );
 }
 src += srcRowBytes * 4;
 dst += dstRowBytes;
 }
}

Antialiased Text

The most exciting area of AntiAliasMan is its ability to draw antialiased text. First, DrawAntiAliasManString() creates two off-screen GWorlds: one at one bit per pixel, at the maximum size of a character of four times the current font size; the other at four bits per pixel, with a four-bit grayscale gradient for a ColorTable, the size of the largest possible character in the current font and size. Next, DrawAntiAliasManString() drops into a loop, during which the one-bit off-screen is erased, filled with a character, dithered to the four-bit off-screen, and copied to the current port. When all of this has been completed, the off-screens are disposed, and the function returns noErr.

Listing 3: AntiAliasMan.c: DrawAntiAliasManString

DrawAntiAliasManString
/* DrawAntiAliasManString draws a string of antialiased text, using all of the standard Quickdraw globals 
*/
pascal OSErrDrawAntiAliasManString( ConstStr255Param s )
{
 CTabHandle x1Tab;
 FontInfo x4Font,x1Font;
 Rect   x4Rect, x1Rect, dstRect;
 GWorldPtrx4GW,  x1GW,  dstGW;
 PixMapHandle  x4Map,x1Map, dstMap;
 GDHandle dstGD;
 OSErr  err;
 Booleanlocked;
 short  i, charwidth;
 short  txFont, txSize, txFace, txMode;
 Point  pnLoc;
/* initialize AntiAliasMan if it hasn't been done already. */
 if( !inited ) AntiAliasManInit();

 GetPen( &pnLoc );
 GetGWorld( &dstGW, &dstGD );


/* save all text information for use in out GWorld */
 txFont = dstGW->txFont;
 txSize = dstGW->txSize;
 txFace = ( short )dstGW->txFace;
 txMode = dstGW->txMode;

 GetFontInfo( &x1Font );
 TextSize( txSize * 4 );
 GetFontInfo( &x4Font );
 TextSize( txSize );

/* make our GWorlds' rectangles the size of the largest possible character */
 MSetRect( x4Rect, 0, 0, x4Font.widMax,
 x4Font.ascent + x4Font.descent );
 MSetRect( x1Rect, 0, 0, x1Font.widMax,
 x1Font.ascent + x1Font.descent );

/* make and lock our GWorlds */
 CreateGWorlds( DrawString( s ) );
 LockGWorlds( DrawString( s ) );

 SetGWorld( x4GW, nil );

/* set up for text drawing:
    use the same font as dstGW.
    use the same style as dstGW.
    use srcCopy unless the text mode is grayishTextOr; then use grayishTextOr and set txMode (used in 
CopyBits) to srcOr.
    use a font size four times that of dstGW; out text will be shrunk by a factor of four. */
 TextFont( txFont );
 TextFace( txFace );
 if( txMode == grayishTextOr )
 {
 TextMode( grayishTextOr );
 txMode = srcOr;
 }
 else
 {
 TextMode( srcCopy );
 }
 TextSize( txSize * 4 );

 SetGWorld( dstGW, dstGD );

 for( i = 1; i <= s[ 0 ]; i++ )
 {
 charwidth = CharWidth( s[ i ] );

/* make a rectangle the size of character #i */
 MSetRect( dstRect, pnLoc.h, pnLoc.v - x1Font.ascent,
 pnLoc.h + charwidth, pnLoc.v + x1Font.descent );
 MSetRect( x1Rect, 0, 0, charwidth,
 x1Font.ascent + x1Font.descent - 1 );

/* clear the large gworld of gibberish and previous characters */
 SetGWorld( x4GW, nil );
 EraseRect( &x4Rect );

 MoveTo( 0, x4Font.ascent );
 DrawChar( s[ i ] );

/* dither the large GWorld to the small GWorld */
 DitherMan( x4Map, x1Map, charwidth );

 SetGWorld( dstGW, dstGD );
/* copy the four-bit image from the offscreen to dstGW, coloring it and styling it along the way. */
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap, &x1Rect,
 &dstRect, txMode, nil );
 pnLoc.h += charwidth;
 }
/* Move the Quickdraw pen to where it should be, now that more text has been printed */
 MoveTo( pnLoc.h, pnLoc.v );

/* clean up */
 DisposeGWorlds();
 return( noErr );
}

Antialiased Ovals (and Arcs)

Ovals are by far the easiest things to antialias, as long as you are reasonable about the method you use. The best way to antialias an oval is the same way that text is done. Draw an oval in a big, one-bit off-screen, dither it to a small, four-bit off-screen, and copy it to the screen. Here's the code that will do just that.

Note that by changing all calls to StdOval, FrameOval, PaintOval, EraseOval, and InvertOval or calls to StdArc, FrameArc, PaintArc, EraseArc, and InvertArc, respectively, and adding on the extra arguments (startAngle and arcAngle), this routine can be used to make antialiased arcs. However, to the best of my knowledge there have been no more than seven useful, practical calls to StdArc or any of its counterparts since its introduction in 1984, so I have left out source for that separate routine.

Listing 4: AntiAliasMan.c: StdAntiAliasManOval

StdAntiAliasManOval
/* StdAntiAliasManOval draws an antialiased oval in dstRect, of type verb (fill is unsupported at this time 
*/
pascal OSErrStdAntiAliasManOval( GrafVerb verb,
 Rect *dstRect )
{
 CTabHandle x1Tab;
 Rect   x4Rect, x1Rect;
 GWorldPtrx4GW, x1GW,dstGW;
 PixMapHandle  x4Map, x1Map, dstMap;
 GDHandle dstGD;
 OSErr  err;
 Booleanlocked;
 PenState pen;
 short  mode;

 GetGWorld( &dstGW, &dstGD );
 GetPenState( &pen );

/* if AntiAliasManInit has not been called, call it now. */
 if( !inited ) AntiAliasManInit();

/* set up our rectangles for CreateGWorlds */
 MSetRect( x4Rect, 0, 0, ( dstRect->right - dstRect->left )
 * 4,
 ( dstRect->bottom - dstRect->top ) * 4 );
 MSetRect( x1Rect, 0, 0, ( dstRect->right - dstRect->left ),
 ( dstRect->bottom - dstRect->top ) );

/* make and lock our GWorlds */
 CreateGWorlds(  StdOval( verb, dstRect ) );
 LockGWorlds(  StdOval( verb, dstRect ) );

 SetGWorld( x4GW, nil );

/* Set up for and draw the oval:
    the pnSize (pen size) should be four times that of the destination port, because the image drawn will be 
shrunk by an x and y factor of four.
    the background of the port must be erased to prevent gibberish from being copied to the screen. */
 pen.pnSize.v *= 4;
 pen.pnSize.h *= 4;
 SetPenState( &pen );
 EraseRect( &x4Rect );
 switch( verb )
 {
 case frame:
/* if the verb is frame, the mode should be srcOr, so that the black pixels colored by FrameOval are replaced 
with the forecolor of the destination port. */

 FrameOval( &x4Rect );
 mode = srcOr;
 break;
 case paint:
/* if the verb is paint, the mode should be srcOr, so that the black pixels colored by PaintOval are replaced 
with the forecolor of the destination port. */
 PaintOval( &x4Rect );
 mode = srcOr;
 break;
 case erase:
/* if the verb is erase, the mode should be srcBic, so that the black pixels colored by PaintOval are replaced 
with the backcolor of the destination port. */
 PaintOval( &x4Rect );
 mode = srcBic;
 break;
 case invert:
/* if the verb is invert, the mode should be srcXor, so that the black pixels colored by PaintOval are inverted 
in the destination port. */
 PaintOval( &x4Rect );
 mode = srcXor;
 break;
 case fill:
 default:
/* if the verb is fill or something else unsupported, the mode should be srcOr, so as to emulate PaintAAOval, 
a weak substitute, but better than nothing. */
 PaintOval( &x4Rect );
 mode = srcOr;
 break;
 }

/* DitherMan dithers a one-bit offscreen to a four-bit, 1/4th size offscreen */
 DitherMan( x4Map, x1Map, x1Rect.right );

 SetGWorld( dstGW, dstGD );
/* setting the GWorld to dstGW will set up the forecolor and backcolor and whatever else; passing mode 
to CopyBits will draw the shape as intended. */
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap, &x1Rect,
 dstRect, mode, nil );
/* clean up */
 DisposeGWorlds();

 return( noErr );
}

Antialiased Rounded Rectangles

An oval is pretty straightforward. On the other hand, a rounded rectangle (roundrect) is not. As previously mentioned, roundrects are part round and part straight. Therefore, the best way to antialias them is to make a rectangle, and place antialiased corners in place of its corners. The following code does that very well, making it one of AntiAliasMan's fastest functions. This code makes an antialiased oval in much the same fashion as StdAntiAliasManOval(). It draws the rectangle (sans corners) directly into the port, and then copies the corners from the four-bit off-screen to the port.

Listing 5: AntiAliasMan.c: StdAntiAliasManRRect

StdAntiAliasManRRect
/* StdAntiAliasManRRect acts just as StdRRect does: it draws a rounded rectangle in dstRect, with curves 
of ovalWidth and ovalHeight, of type verb. */
pascal OSErrStdAntiAliasManRRect( GrafVerb verb, Rect *dstRect,
 short ovalWidth, short ovalHeight )
{
 CTabHandle x1Tab;
 Rect   x4Rect, x1Rect;
 GWorldPtrx4GW, x1GW, dstGW;
 PixMapHandle  x4Map, x1Map, dstMap;
 GDHandle dstGD;
 OSErr  err;
 Booleanlocked;

 Rect   upperLeftQuad, upperRightQuad, lowerLeftQuad,
 lowerRightQuad, upperLeftCorner, upperRightCorner,
 lowerLeftCorner, lowerRightCorner,
 left, center, right;
 short  bigOvalWidth, bigOvalHeight;
 PenState pen;
 short  mode;

/* set up our global array if it's not set up already */
 if( !inited ) AntiAliasManInit();

 GetGWorld( &dstGW, &dstGD );
 GetPenState( &pen );

 bigOvalWidth = ovalWidth * 4;
 bigOvalHeight = ovalHeight * 4;

 MSetRect( x4Rect, 0, 0, bigOvalWidth, bigOvalHeight );
 MSetRect( x1Rect, 0, 0, ovalWidth, ovalHeight );

/* make and lock our GWorlds */
 CreateGWorlds(
 StdRRect( verb, dstRect, ovalWidth, ovalHeight ) );
 LockGWorlds(
 StdRRect( verb, dstRect, ovalWidth, ovalHeight ) );

 ovalWidth /= 2;
 ovalHeight /= 2;
 bigOvalWidth /= 2;
 bigOvalHeight /= 2;

/* the following eight lines set up the source and destination rectangles for the corners of the roundrect. 
the source (Quad) rectangles are each one-fourth of x1GW; the destination (Corner) rectangles are the four 
corners of the roundrect in dstGW. */
 MSetRect( upperLeftQuad, 0, 0, ovalWidth, ovalHeight );
 MSetRect( upperRightQuad, ovalWidth, 0, ovalWidth * 2,
 ovalHeight );
 MSetRect( lowerLeftQuad, 0, ovalHeight, ovalWidth,
 ovalHeight * 2 );
 MSetRect( lowerRightQuad, ovalWidth, ovalHeight, ovalWidth * 2,
 ovalHeight * 2 );

 MSetRect( upperLeftCorner, dstRect->left, dstRect->top,
 dstRect->left + ovalWidth, dstRect->top + ovalHeight );
 MSetRect( upperRightCorner, dstRect->right - ovalWidth,
 dstRect->top, dstRect->right, dstRect->top + ovalHeight );
 MSetRect( lowerLeftCorner, dstRect->left,
 dstRect->bottom - ovalHeight, dstRect->left + ovalWidth,
 dstRect->bottom );
 MSetRect( lowerRightCorner, dstRect->right - ovalWidth,
 dstRect->bottom - ovalHeight, dstRect->right,
 dstRect->bottom );

 SetGWorld( x4GW, nil );
 pen.pnSize.h *= 4;
 pen.pnSize.v *= 4;
 SetPenState( &pen );
 EraseRect( &x4Rect );
 switch( verb )
 {
 case frame:
/* if the verb is frame, the mode should be srcOr, so that the black pixels colored by FrameOval are replaced 
with the forecolor of the destination port. */
 FrameOval( &x4Rect );
 mode = srcOr;
 break;
 case paint:
/* if the verb is paint, the mode should be srcOr, so that the black pixels colored by PaintOval are replaced 
with the forecolor of the destination port. */
 PaintOval( &x4Rect );
 mode = srcOr;
 break;
 case erase:

/* if the verb is erase, the mode should be srcBic, so that the black pixels colored by PaintOval are replaced 
with the backcolor of the destination port. */
 PaintOval( &x4Rect );
 mode = srcBic;
 break;
 case invert:
/* if the verb is invert, the mode should be srcXor, so that the black pixels colored by PaintOval are inverted 
in the destination port. */
 PaintOval( &x4Rect );
 mode = srcXor;
 break;
 case fill:
 default:
/* if the verb is fill or something else unsupported, the mode should be srcOr, so as to emulate PaintAARRect, 
a weak substitute, but better than nothing. */
 PaintOval( &x4Rect );
 mode = srcOr;
 verb = paint;
 break;
 }

/* dither all four corners */
 DitherMan( x4Map, x1Map, x1Rect.right );
 SetGWorld( dstGW, dstGD );
 switch( verb )
 {
 case frame:
/* if the verb is frame, draw the four walls of the rectangle with MoveTo and LineTo */
// top
 MoveTo( dstRect->left + ovalWidth, dstRect->top );
 LineTo( dstRect->right - ovalWidth, dstRect->top );
// left
 MoveTo( dstRect->left, dstRect->top + ovalHeight );
 LineTo( dstRect->left, dstRect->bottom - ovalHeight );
// bottom
 MoveTo( dstRect->left + ovalWidth, dstRect->bottom - 1 );
 LineTo( dstRect->right - ovalWidth, dstRect->bottom - 1 );
// right
 MoveTo( dstRect->right - 1, dstRect->top + ovalHeight );
 LineTo( dstRect->right - 1, dstRect->bottom - ovalHeight );
 break;


 case paint:
 case erase:
 case invert:
 case fill:
/* otherwise, use three rectengles to do the trick: left, right, and bottom. left occupies the area from the left 
border to the right edge of the left corner ovals, from the bottom of the top ovals to the top of the bottom 
ones; center occupies the area bordered by left.right dstRect->top, right.left, and dstRect->bottom; right 
occupies the area beginning et the left border of the right corners and the bottom of the top corners, going 
until the right border of dstRect, and the top of the bottom corners. */
 MSetRect( left, dstRect->left, dstRect->top + ovalHeight,
 dstRect->left + ovalWidth, dstRect->bottom - ovalHeight );
 MSetRect( center, dstRect->left + ovalWidth, dstRect->top,
 dstRect->right - ovalWidth, dstRect->bottom );
 MSetRect( right, dstRect->right - ovalWidth,
 dstRect->top + ovalHeight, dstRect->right,
 dstRect->bottom - ovalHeight );
 StdRect( verb, &left );
 StdRect( verb, &center );
 StdRect( verb, &right );
 break;
 }

/* the following four lines copy the corners from x1GW to dstGW. */
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap,
 &upperLeftQuad, &upperLeftCorner, mode, nil );
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap,
 &upperRightQuad, &upperRightCorner, mode, nil );
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap,
 &lowerLeftQuad, &lowerLeftCorner, mode, nil );
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap,
 &lowerRightQuad, &lowerRightCorner, mode, nil );

/* clean up */
 DisposeGWorlds();

 return( noErr );
}

Antialiased Lines

This section is hiding at the very end of the article because it has ugly code associated with it, and I am sure that there are faster ways of achieving its purpose. However, it is the only easy-to-use routine that I have seen, so I am including it. The routine operates just as the rest of the routines included here - by making a large, one-bit off-screen, dithering it to a four-bit off-screen, and copying what is there to the screen.

Listing 6: AntiAliasMan.c: AntiAliasManLineTo

AntiAliasManLineTo
/* AntiAliasManLineTo draws an antialiased line from the current pen location to h, v. */
pascal OSErrAntiAliasManLineTo( short h, short v )
{
 CTabHandle x1Tab;
 Rect   x4Rect, x1Rect, dstRect;
 GWorldPtrx4GW, x1GW, dstGW;
 PixMapHandle  x4Map, x1Map, dstMap;
 GDHandle dstGD;

 OSErr  err;
 Booleanlocked;

 short  dh, dv;
 PenState pen;
 short  mode;

 GetPenState( &pen );
 dh = Abs( h - pen.pnLoc.h );
 dv = Abs( v - pen.pnLoc.v );
/* if the line it straight along the x or y axis, there is no reason to antialias it. */
 if( dh == 0 || dv == 0 ) LineTo( h, v );
 else
 {
/* make sure AntiAliasManInit gets called */
 if( !inited ) AntiAliasManInit();

 GetGWorld( &dstGW, &dstGD );

/* set up the rectangles for CreateGWorlds */
 MSetRect( x4Rect, 0, 0, ( dh + pen.pnSize.h * 2 ) * 4,
 ( dv + pen.pnSize.v * 2 ) * 4 );
 MSetRect( x1Rect, 0, 0, dh + pen.pnSize.h * 2,
 dv + pen.pnSize.v * 2 );
 MSetRect( dstRect, Low( h, pen.pnLoc.h ) - pen.pnSize.h,
 Low( v, pen.pnLoc.v ) - pen.pnSize.v,
 High( h, pen.pnLoc.h ) + pen.pnSize.h,
 High( v, pen.pnLoc.v ) + pen.pnSize.v );

/* create and lock our GWorlds */
 CreateGWorlds(  LineTo( h, v ) );
 LockGWorlds(  LineTo( h, v ) );

 switch( pen.pnMode )
 {
 case srcCopy:
 case srcOr:
 case blend:
 case subPin:
 case transparent:
 case adMin:
/* all of these modes essentially draw lines in the foreground color, so we use srcOr */
 mode = srcOr;
 break;
 case notSrcCopy:
 case srcBic:
 case addPin:
 case addMax:
/* all of these modes essentially draw lines in the background color, so we use srcBic */
 mode = srcBic;
 break;
 case srcXor:
 case addOver:
 case subOver:
/* all of these modes essentially invert lines, so we use srcXor */
 mode = srcXor;
 break;
 default:
/* otherwise, default to srcOr */
 mode = srcOr;
 }

 SetGWorld( x4GW, nil );
/* make our pnSize four times the original for a correct size when shrunk */
 pen.pnSize.v *= 4;
 pen.pnSize.h *= 4;
 pen.pnMode = srcCopy;
 SetPenState( &pen );
/* clear the GWorld of gibberish */
 EraseRect( &x4Rect );
/* aargh! I can't look! it's too hideous
    the following two lines (yes, there are only two lines there) figure out how the line
    is aligned in the offscreen, then draw it. */
 MoveTo( ( pen.pnLoc.h - dstRect.left + ( pen.pnLoc.h == dstRect.left 
+ pen.pnSize.h ? pen.pnSize.h * 2 : 0 ) ) * 4,
 ( pen.pnLoc.v - dstRect.top + ( pen.pnLoc.v == dstRect.top + pen.pnSize.v 
? pen.pnSize.v * 2 : 0 ) ) * 4 );
 LineTo( ( h - dstRect.left + ( h == dstRect.left + pen.pnSize.h ? pen.pnSize.h 
* 2 : 0 ) ) * 4,
 ( v - dstRect.top + ( v == dstRect.top + pen.pnSize.v ? pen.pnSize.v 
* 2 : 0 ) ) * 4 );
/* call DitherMan to dither our big line to a little one */
 DitherMan( x4Map, x1Map, x1Rect.right );
 SetGWorld( dstGW, dstGD );
/* CopyBits will colorize and stylize our line for us */
 CopyBits( ( BitMapPtr )*x1Map, ( BitMapPtr )*dstMap, &x1Rect,
 &dstRect, mode, nil );
/* relocate the pen and clean up */
 MoveTo( h, v );
 DisposeGWorlds();
 }

 return( noErr );
}

Conclusion

I hope this article helps you write good code and good applications that have antialiased text, ovals, arcs, rounded rectangles, and lines, without using huge pictures that have the images pre-antialiased. As far as other resources go, I can refer you to very little - almost all of the code presented here is completely of my own creation. However, I was told that Graphics Gems, volume I, has some good line antialiasing code, but when I looked at it, I decided that it was definitely not worth the effort to decipher its graphic format, etc., so I forgot about it. That is my only reference.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Apple iMovie 10.1.6 - Edit personal vide...
With an all-new design, Apple iMovie lets you enjoy your videos like never before. Browse your clips more easily, instantly share your favorite moments, and create beautiful HD movies and Hollywood-... Read more
Jamf Pro 9.99.0 - Powerful sysadmin/ente...
Jamf Pro (formerly Casper Suite) is the EMM tool that delights IT pros and the users they support by delivering on the promise of unified endpoint management for Apple devices. At Jamf, connecting... Read more
Adobe Lightroom 6.10.1 - Import, develop...
Adobe Lightroom is available as part of Adobe Creative Cloud for as little as $9.99/month bundled with Photoshop CC as part of the photography package. Lightroom 6 is also available for purchase as a... Read more
VueScan 9.5.78 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more
TechTool Pro 9.5.1 - Hard drive and syst...
TechTool Pro has long been one of the foremost utilities for keeping your Mac running smoothly and efficiently. With the release of version 9, it has become more proficient than ever. TechTool... Read more
iPhoto Library Manager 4.2.7 - Manage mu...
iPhoto Library Manager allows you to organize your photos among multiple iPhoto libraries, rather than having to store all of your photos in one giant library. You can browse the photos in all your... Read more
Apple iMovie 10.1.6 - Edit personal vide...
With an all-new design, Apple iMovie lets you enjoy your videos like never before. Browse your clips more easily, instantly share your favorite moments, and create beautiful HD movies and Hollywood-... Read more
TechTool Pro 9.5.1 - Hard drive and syst...
TechTool Pro has long been one of the foremost utilities for keeping your Mac running smoothly and efficiently. With the release of version 9, it has become more proficient than ever. TechTool... Read more
Jamf Pro 9.99.0 - Powerful sysadmin/ente...
Jamf Pro (formerly Casper Suite) is the EMM tool that delights IT pros and the users they support by delivering on the promise of unified endpoint management for Apple devices. At Jamf, connecting... Read more
VueScan 9.5.78 - Scanner software with a...
VueScan is a scanning program that works with most high-quality flatbed and film scanners to produce scans that have excellent color fidelity and color balance. VueScan is easy to use, and has... Read more

Latest Forum Discussions

See All

Goat Simulator PAYDAY (Games)
Goat Simulator PAYDAY 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: ** IMPORTANT - SUPPORTED DEVICES **iPhone 4S, iPad 2, iPod Touch 5 or better Goat Simulator: Payday is the most... | Read more »
Zombie Gunship Survival Beginner's...
The much anticipated Zombie Gunship Survival is here. In this latest entry in the Zombie Gunship franchise, you're tasked with supporting ground troops and protecting your base from the zombie horde. There's a lot of rich base building fun, and... | Read more »
Mordheim: Warband Skirmish (Games)
Mordheim: Warband Skirmish 1.2.2 Device: iOS Universal Category: Games Price: $3.99, Version: 1.2.2 (iTunes) Description: Explore the ruins of the City of Mordheim, clash with other scavenging warbands and collect Wyrdstone -... | Read more »
Mordheim: Warband Skirmish brings tablet...
Legendary Games has just launched Mordheim: Warband Skirmish, a new turn-based action game for iOS and Android. | Read more »
Magikarp Jump splashes onto Android worl...
If you're tired ofPokémon GObut still want something to satisfy your mobilePokémon fix,Magikarp Jumpmay just do the trick. It's out now on Android devices the world over. While it looks like a simple arcade jumper, there's quite a bit more to it... | Read more »
Purrfectly charming open-world RPG Cat Q...
Cat Quest, an expansive open-world RPG from former Koei-Tecmo developers, got a new gameplay trailer today. The video showcases the combat and exploration features of this feline-themed RPG. Cat puns abound as you travel across a large map in a... | Read more »
Jaipur: A Card Game of Duels (Games)
Jaipur: A Card Game of Duels 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: ** WARNING: iPad 2, iPad Mini 1 & iPhone 4S are NOT compatible. ** *** Special Launch Price for a limited... | Read more »
Subdivision Infinity (Games)
Subdivision Infinity 1.03 Device: iOS Universal Category: Games Price: $2.99, Version: 1.03 (iTunes) Description: Launch sale! 40% Off! Subdivision Infinity is an immersive and pulse pounding sci-fi 3D space shooter. https://www.... | Read more »
Clash of Clans' gets a huge new upd...
Clash of Clans just got a massive new update, and that's not hyperbole. The update easily tacks on a whole new game's worth of content to the hit base building game. In the update, that mysterious boat on the edge of the map has been repaired and... | Read more »
Thimbleweed Park officially headed to iO...
Welp, it's official. Thimbleweed Park will be getting a mobile version. After lots of wondering and speculation, the developers confirmed it today. Thimbleweed Park will be available on both iOS and Android sometime in the near future. There's no... | Read more »

Price Scanner via MacPrices.net

Huawei Unveils New ‘Business-Styled’ MateBook...
Huawei has introduced a trio of new MateBook laptops, expanding its mobile portfolio and building on its success in delivering attractive and powerful high-end devices. The company claims the HUAWEI... Read more
Deal! Gold 12-inch 1.2GHz Retina MacBook for...
Amazon has the 2016 Gold 12″ 1.2GHz Retina MacBook (MLHF2LL/A) on sale for $350 off MSRP for a limited time. Shipping is free: - 12″ 1.2GHz Gold Retina MacBook: $1249.99 $350 off MSRP We expect this... Read more
13-inch 2.0GHz MacBook Pros on sale for $100...
B&H has the non-Touch Bar 13″ 2.0GHz MacBook Pros in stock today and on sale for $100 off MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: - 13″ 2.0GHz MacBook Pro Space... Read more
15-inch 2.2GHz Retina MacBook Pro, Apple refu...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off MSRP, and it’s the lowest price available for a 15″ MacBook Pro. An Apple one-year warranty is... Read more
Apple refurbished 9-inch and 12-inch iPad Pro...
Apple has Certified Refurbished 9″ and 12″ Apple iPad Pros available for up to $160 off the cost of new iPads. An Apple one-year warranty is included with each model, and shipping is free: - 32GB 9″... Read more
Apple Certified Refurbished iMacs available f...
Apple has Certified Refurbished 2015 21″ & 27″ iMacs available for up to $350 off MSRP. Apple’s one-year warranty is standard, and shipping is free. The following models are available: - 21″ 3.... Read more
Sale! 15-inch 2.6GHz Silver Touch Bar MacBook...
DataVision has the 15″ 2.6GHz Silver Touch Bar MacBook Pro (MLW72LL/A) on sale for $2199 including free shipping. Their price is $200 off MSRP, and it’s the lowest price available for this model (... Read more
A Kaby Lake Processor Upgrade For The MacBook...
Now they tell me! Well, actually Apple hasn’t said anything official on the subject, but last week Bloomberg News’s Mark Gurman and Alex Webb cited unnamed “people familiar with the matter”... Read more
Kodak’s Camera-First Smartphone EKTRA Launche...
The Eastman Kodak Company and Bullitt Group have announced the availability of a U.S. GSM version of the KODAK EKTRA Smartphone. The U.S. launch coincides with a software update addressing requests... Read more
Apple Launches App Development Curriculum for...
Apple today launched a new app development curriculum designed for students who want to pursue careers in the fast-growing app economy. The curriculum is available as a free download today from Apple... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Systems Engineer - California Polyte...
Cal Poly, San Luis Obispo Apple Systems Engineer Department: ITS - Customer & Tech Support (134900) College/Division: Academic Affairs Salary Range: Position Read more
Best Buy *Apple* Computing Master - Best Bu...
**508718BR** **Job Title:** Best Buy Apple Computing Master **Location Number:** 001526-Odessa-Store **Job Description:** **What does a Best Buy Apple Computing Read more
Data Engineer - *Apple* Media Products - Ap...
Changing the world is all in a day's work at Apple . If you love innovation, here's your chance to make a career of it. You'll work hard. But the job comes with more Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.