

|
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.

- SPREAD THE WORD:
- Slashdot
- Digg
- Del.icio.us
- Newsvine