Apr 99 Tips
Volume Number: 15 (1999)
Issue Number: 4
Column Tag: Tips & Tidbits
Apr 99 Tips
by Jeff Clites <online@mactech.com>
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 <Errors.h>
#include <fp.h>
#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<destheight; y++) {
destbyte = destPm->baseAddr + y*destRowBytes;
for (x=0; x<destwidth; 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<pixsize; i++) {
*destbyte++ = *srcbyte++; // copy color
}
} else {
for (i=0; i<pixsize; 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.