home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacFormat España 21
/
macformat_21.iso
/
Shareware
/
Programación
/
VideoToolbox
/
VideoToolboxSources
/
StringBounds.c
< prev
next >
Wrap
Text File
|
1995-11-02
|
10KB
|
311 lines
/*
StringBounds.c
void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
Return the smallest bounding box containing all nonzero pixels, and a count of the nonzero pixels.
void StrBounds(char *str,Rect *bounds,long *inkArea);
void StringBounds(const unsigned char *string,Rect *bounds,long *inkArea);
StrBounds accepts a C string. StringBounds accepts a pascal string.
Both routines count--and compute the minimum bounding rectangle for--all the pixels that
would be set black by calling DrawString with the given string with the
current port's font, size, and style. Coordinates in the *bounds rect are
relative to the current pen position. If no pixels would be set then *bounds is
set to the empty rect (0,0,0,0). The current port is left untouched.
The measurements are based on a full rendering of the string in a private
GWorld, without any clipping.
The bounds and inkArea arguments are optional; they may be replaced by NULL.
void CharBounds(char a,Rect *bounds,long *inkArea);
Creates a one-char string and calls StringBounds().
double StrOutlineLength(char *s);
double StringOutlineLength(const unsigned char *s);
Measure the outline length of a string, in pixels. Formerly (before 2/20/95)
I thought it was enough to just count the number of pixels in the 1-pixel thick
outline that one obtains when drawing a PostScript or TrueType character in
outline style. However, I later discovered that this only gives correct results
for vertical and horizontal segments of the the outline. Diagonal segments of
the outline are, necessarily, a stairstep, so a pixel count yields a city-block
path length. For a 45 deg slope the pixel count is too high by a factor of
square root of 2, i.e. by roughly a factor of 1.5. StringOutlineLength() tries
to provide a more accurate estimate of outline length. It creates the 1-pixel
outline and then thickens it by repeatedly ORing the image with itself, shifted
by one pixel: left, right, up, and down. This has the effect of tripling the
thickness of horizontal and vertical lines, from 1 to 3 pixels, and doubling the
thickness of 45 deg diagonal lines. Since the diagonal lines were already
providing about 1.5 times as many pixels as the true length, when doubled, they
provide triple the length, just like the horizontal and vertical lines. Thus, to
a first order, the length measurement should be insensitive to orientation of
the segments of the outline.
NOTES
bounds->right need not exactly equal the value returned by StringWidth(s),
because StringWidth returns the displacement of the pen position, and some (e.g.
italic) characters extend beyond that, while others (e.g. comma) extend less
far. Similarly, bounds->left need not always be zero.
By QuickDraw's convention, each pixel is considered to lie below and to the
right of the point that is used to address it. The bounding rectangle for a
pixel at x,y, is SetRect(&r,x,y,x+1,y+1).
The code assumes ForeColor is black and that TextMode is srcOr, which are the
default settings for the GWorld that we create.
HISTORY:
1/17/94 dgp wrote it, based on discussion with Bart Farell and Manoj Gunwani.
2/22/94 dgp added inkArea argument, and made both bounds and inkArea optional.
2/27/94 dgp since this routine is typically called repeatedly I save time
(0.2 s per call) by only allocating the GWorld once, and never
freeing it. It is reused on subsequent calls, resized appropriately
if necessary.
9/5/94 dgp removed assumption in printf's that int==short.
11/2/94 dgp discovered that the clip rect may be nonsense after calling
UpdateGWorld, so I set it equal to the portRect.
2/20/95 dgp Added StringOutlineLength and StrOutlineLength.
2/24/95 dgp Fixed above to give correct length even when stroke is only one pixel thick.
*/
#include "VideoToolbox.h"
#define SHOW_BITMAPS 0 // for debugging
double StrOutlineLength(char *s);
double StringOutlineLength(const unsigned char *s);
void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr)
{
Rect r,bounds;
GWorldPtr oldPort;
GDHandle oldDevice;
register unsigned long *pix;
register int x,y;
register long inkArea=0;
long n;
// measure the bounding box, and count the black pixels
GetGWorld(&oldPort,&oldDevice);
assert(window!=NULL);
SectRect(&window->portRect,rectPtr,&r);
SetRect(&bounds,r.right,r.bottom,r.left,r.top);
n=r.right-r.left;
pix=(unsigned long *)NewPtr(n*sizeof(*pix));
if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
,__FILE__,__LINE__,n*sizeof(long));
if(IsGrafPtr(window))SetPort((WindowPtr)window);
else SetGWorld(window,GetMainDevice());
OffsetRect(&bounds,-r.left,-r.top);
for(y=0;y<r.bottom-r.top;y++){
GetPixelsQuickly(r.left,r.top+y,pix,n);
for(x=n-1;x>=0;x--)if(pix[x]!=0){
inkArea++;
if(x<bounds.left)bounds.left=x;
if(x>=bounds.right)bounds.right=x+1;
if(y<bounds.top)bounds.top=y;
if(y>=bounds.bottom)bounds.bottom=y+1;
}
}
SetGWorld(oldPort,oldDevice);
DisposePtr((void *)pix);
OffsetRect(&bounds,r.left,r.top);
if(EmptyRect(&bounds))SetRect(&bounds,0,0,0,0);
if(boundsPtr!=NULL)*boundsPtr=bounds;
if(inkAreaPtr!=NULL)*inkAreaPtr=inkArea;
}
void CharBounds(char a,Rect *boundsPtr,long *inkAreaPtr)
{
unsigned char string[]="\pA";
string[1]=a;
StringBounds(string,boundsPtr,inkAreaPtr);
}
void StrBounds(char *s,Rect *boundsPtr,long *inkAreaPtr)
{
StringBounds(c2pstr(s),boundsPtr,inkAreaPtr);
p2cstr((unsigned char *)s);
}
void StringBounds(const unsigned char *s,Rect *boundsPtr,long *inkAreaPtr)
{
static GWorldPtr our=NULL;
GWorldPtr old;
GDHandle oldDevice;
FontInfo f;
Rect r;
int error;
char string[40];
assert(StackSpace()>4000);
GetFontInfo(&f);
SetRect(&r,0,-f.ascent,StringWidth(s),f.descent); // nominal size
InsetRect(&r,-(f.widMax+2),-(f.leading+2)); // add margin
// draw string into a new GWorld
if(our!=NULL){
DisposeGWorld(our);
our=NULL;
}
if(our==NULL){
error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal|useTempMem);
if(error)error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal);
}else{
error=UpdateGWorld(&our,1,&r,NULL,NULL,clipPix);
assert(EqualRect(&r,&our->portRect));
assert((**our->clipRgn).rgnSize==10);
(**our->clipRgn).rgnBBox=our->portRect;
}
if(error)PrintfExit("StringBounds: NewGWorld/UpdateGWorld error %d.\n",error);
GetGWorld(&old,&oldDevice);
SetGWorld(our,NULL);
TextFace(old->txFace);
TextFont(old->txFont);
TextSize(old->txSize);
EraseRect(&our->portRect);
MoveTo(0,0);
DrawString(s);
SetGWorld(old,oldDevice);
if(SHOW_BITMAPS){
PrintfGWorld(our);
gets(string);
}
GetBounds(our,&our->portRect,boundsPtr,inkAreaPtr);
if(SHOW_BITMAPS){
PrintfGWorld(our);
gets(string);
}
if(0){
DisposeGWorld(our);
our=NULL;
}
}
double StrOutlineLength(char *s)
{
double length;
length=StringOutlineLength(c2pstr(s));
p2cstr((unsigned char *)s);
return length;
}
//#undef SHOW_BITMAPS
//#define SHOW_BITMAPS 1
double StringOutlineLength(const unsigned char *s)
{
static GWorldPtr world=NULL;
GWorldPtr old;
GDHandle oldDevice;
FontInfo f;
Rect r,rSrc,rDst,rSmall;
register unsigned long *pix;
register int x,y;
register long inkArea=0;
int n;
int error;
char string[40];
assert(StackSpace()>4000);
GetFontInfo(&f);
SetRect(&rSmall,0,-f.ascent,StringWidth(s),f.descent); // nominal size
InsetRect(&rSmall,-(f.widMax+4),-(f.leading+4)); // add margin
rSrc=rSmall;
rSrc.top*=2; // allow room for double-size text
rSrc.left*=2;
rSrc.bottom*=2;
rSrc.right*=2;
r=rSrc;
r.right+=r.right-r.left; // double rect, to make room for rDst.
rDst=rSrc;
OffsetRect(&rDst,rSrc.right-rSrc.left,0);
InsetRect(&rSrc,1,1);
InsetRect(&rDst,1,1);
// draw string into a new GWorld
if(world!=NULL){
DisposeGWorld(world);
world=NULL;
}
if(world==NULL){
error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal|useTempMem);
if(error)error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal);
}else{
error=UpdateGWorld(&world,1,&r,NULL,NULL,clipPix);
assert(EqualRect(&r,&world->portRect));
assert((**world->clipRgn).rgnSize==10);
(**world->clipRgn).rgnBBox=world->portRect;
}
if(error)PrintfExit("StringOutlineLength: NewGWorld/UpdateGWorld error %d.\n",error);
GetGWorld(&old,&oldDevice);
SetGWorld(world,NULL);
TextFace(old->txFace);
TextFont(old->txFont);
TextSize(old->txSize);
EraseRect(&world->portRect);
MoveTo(0,0);
DrawString(s);
SetGWorld(old,oldDevice);
// Expand text so that all features are at least 2 pixels thick
error=CopyWindows(world,world,&rSmall,&rDst,srcCopy,NULL);
error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
// Grow each black pixel into a 3x3 square.
r=rDst;
OffsetRect(&r,0,1);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
OffsetRect(&r,0,-2);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
OffsetRect(&r,1,1);
error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
OffsetRect(&r,-2,0);
error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
// Clear the pixels corresponding to the original image, leaving a 1 pixel outline.
error=CopyWindows(world,world,&rSrc,&rDst,srcBic,NULL);
error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
// Grow each black pixel, so that lines of all orientations are effectively 3 pixels thick.
r=rDst;
OffsetRect(&r,0,1);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
OffsetRect(&r,0,-2);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
OffsetRect(&r,1,1);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
OffsetRect(&r,-2,0);
error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
// count the black pixels
r=rDst;
n=r.right-r.left;
pix=(unsigned long *)NewPtr(n*sizeof(*pix));
if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
,__FILE__,__LINE__,n*sizeof(long));
SetGWorld(world,NULL);
for(y=0;y<r.bottom-r.top;y++){
GetPixelsQuickly(r.left,r.top+y,pix,n);
for(x=n-1;x>=0;x--)if(pix[x]!=0){
inkArea++;
}
}
if(SHOW_BITMAPS){
PrintfGWorld(world);
gets(string);
}
SetGWorld(old,oldDevice);
DisposePtr((void *)pix);
if(0){
DisposeGWorld(world);
world=NULL;
}
return inkArea/6.0;
}