Apr 99 Tips
Volume Number: 15
Issue Number: 4
Column Tag: Tips & Tidbits
Apr 99 Tips
by Jeff Clites
PixMap Rotation
CopyBits is one of the most powerful and ubiquitous of the Mac OS toolbox calls. It can
scale, convert colors, and mask in a variety of ways. About the only thing it can't do is
rotate an image. Similarly, we all know how to use DrawString to put text on the
screen - but when we need to draw rotated text (say, for labeling a graph), we find no
toolbox call up to the task. The routines below serve both needs.
RotatePixMap takes pointers to two PixMaps, and rotates the first into the second,
rotating through a specified number of degrees (counter-clockwise).
DrawRotatedString uses RotatePixMap to draw a string rotated to any angle, and then
moves the graphics cursor accordingly. It also illustrates how to create a pair of pixel
maps, draw into one, rotate into the other, and copy to the screen; a similar technique
was used to rotate Clarus the dogcow in the figure below.
The code here only rotates pixel maps with 8-bit color depth or higher; it will return
-1 if given a 1-bit image. It uses GWorlds, so requires at least System 7, but will
work on both 68k and PowerPC machines. These functions form a valuable addition to
the standard toolbox.
#include
#include
#include "Rotate.h
static void GetTrigs(float degrees, double_t *sinang,
double_t *cosang)
{
// compute sine and cosine of the angle given in degrees;
// check for special cases to avoid any rounding error
if (degrees == 0) {
*sinang = 0; *cosang = 1;
} else if (degrees == 90 || degrees == -270) {
*sinang = -1; *cosang = 0;
} else if (degrees == 180 || degrees == -180) {
*sinang = 0; *cosang = -1;
} else if (degrees == 270 || degrees == -90) {
*sinang = 1; *cosang = 0;
} else {
double_t radians = -degrees * pi / 180.0;
*cosang = cos(radians);
*sinang = sin(radians);
}
}
OSErr RotatePixMap(PixMapPtr srcPm,
PixMapPtr destPm,
float degrees)
{
double_t cosang, sinang;
short x,y,x2,y2,i;
char *srcbyte, *destbyte;
short pixsize = srcPm->pixelSize / 8;
unsigned char blankByte = pixsize == 1 ? 0 : 255;
short srcRowBytes = srcPm->rowBytes & 0x7FFF;
short destRowBytes = destPm->rowBytes
& 0x7FFF;
short srcwidth = srcPm->bounds.right
- srcPm->bounds.left,
destwidth = destPm->bounds.right
- destPm->bounds.left,
srcheight = srcPm->bounds.bottom
- srcPm->bounds.top,
destheight = destPm->bounds.bottom
- destPm->bounds.top;
short srcCx = srcwidth/2,
srcCy = srcheight/2;
short destCx = destwidth/2,
destCy = destheight/2;
if (destPm->pixelSize/8
!= pixsize || pixsize < 1)
return -1; // requires 8 bit pixmap or higher

GetTrigs(degrees, &sinang, &cosang);

// loop over each element in dest, copying from source or
// setting to white
for (y=0; y
destbyte = destPm->baseAddr + y*destRowBytes;
for (x=0; x
x2 = srcCx + cosang*(x-destCx) + sinang*(y-destCy);
y2 = srcCy + cosang*(y-destCy) - sinang*(x-destCx);
if (x2 >= 0 && x2 < srcwidth
&& y2 >= 0 && y2 < srcheight) {
srcbyte = srcPm->baseAddr + y2*srcRowBytes
+ x2*pixsize;
for (i=0; i
*destbyte++ = *srcbyte++; // copy color
}
} else {
for (i=0; i
*destbyte++ = blankByte; // fill in blank
}
}
}
}
return noErr;
}
OSErr DrawRotatedString(Str255 s, float degrees)
{
FontInfo fi;
short cheight, cwidth, bufsize;
Rect srcR, destR, screenR;
GWorldPtr srcGW, destGW;
PixMapHandle srcPmH, destPmH;
PixMapPtr srcPm, destPm;
CGrafPtr origPort;
GDHandle origDevice;
OSErr err=noErr;
double_t cosang, sinang;
// find the string size
GetFontInfo( &fi );
cheight = fi.ascent + fi.descent + fi.leading;
cwidth = StringWidth(s);

// set buffer size (allow room for rotation)
bufsize = (cheight > cwidth ?
cheight*2 : cwidth*2);

// create an offscreen GWorld in which to draw the string,
// and another in which to draw the rotated version
SetRect( &srcR, 0, 0, bufsize, cheight*2 );
err = NewGWorld( &srcGW, 0, &srcR,
NULL, NULL, 0 );
if (err) return err;
SetRect( &destR, 0, 0, bufsize, bufsize );
err = NewGWorld( &destGW, 0, &destR,
NULL, NULL, 0 );
if (err) { DisposeGWorld( srcGW ); return err; }
srcPmH = GetGWorldPixMap( srcGW );
LockPixels( srcPmH );
srcPm = *srcPmH;
destPmH = GetGWorldPixMap( destGW );
LockPixels( destPmH );
destPm = *destPmH;
// store original port, and draw string to source GWorld
GetGWorld( &origPort, &origDevice );
SetGWorld( srcGW, NULL );
TextFont( origPort->txFont );
TextSize( origPort->txSize );
TextFace( origPort->txFace );
EraseRect( &srcR );
MoveTo( bufsize/2, cheight );
DrawString( s );
// rotate into the dest buffer
RotatePixMap( srcPm, destPm, degrees );
// copy to screen in OR mode, to avoid clobbering background
SetGWorld( origPort, origDevice );
SetRect( &screenR, 0, 0, bufsize, bufsize );
OffsetRect( &screenR,
origPort->pnLoc.h - bufsize/2,
origPort->pnLoc.v - bufsize/2 );
CopyBits((BitMap*)destPm,
(BitMap*)(*origPort->portPixMap),
&destR, &screenR, srcOr, NULL );

// release memory
UnlockPixels( srcPmH );
DisposeGWorld( srcGW );

UnlockPixels( destPmH );
DisposeGWorld( destGW );

// finally, move the pen by the proper amount and direction
GetTrigs(degrees, &sinang, &cosang);
Move(cosang*cwidth + 0.5, sinang*cwidth+0.5);
return err;
}
Figure 1. Clarus the dogcow is rotated through 30 degrees using RotatePixMap.
DrawRotatedString is used to surround him with moofs.
Joseph J. Strout <joe@strout.net>
You can download a demo of this Tip from ftp://ftp.mactech.com/src.