home *** CD-ROM | disk | FTP | other *** search
/ MacFormat España 21 / macformat_21.iso / Shareware / Programación / VideoToolbox / VideoToolboxSources / StringBounds.c < prev    next >
Text File  |  1995-11-02  |  10KB  |  311 lines

  1. /*
  2. StringBounds.c
  3.  
  4.     void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
  5. Return the smallest bounding box containing all nonzero pixels, and a count of the nonzero pixels.
  6.  
  7.     void StrBounds(char *str,Rect *bounds,long *inkArea);
  8.     void StringBounds(const unsigned char *string,Rect *bounds,long *inkArea);
  9. StrBounds accepts a C string. StringBounds accepts a pascal string.
  10. Both routines count--and compute the minimum bounding rectangle for--all the pixels that
  11. would be set black by calling DrawString with the given string with the
  12. current port's font, size, and style. Coordinates in the *bounds rect are
  13. relative to the current pen position. If no pixels would be set then *bounds is
  14. set to the empty rect (0,0,0,0). The current port is left untouched.
  15.  
  16. The measurements are based on a full rendering of the string in a private
  17. GWorld, without any clipping.
  18.  
  19. The bounds and inkArea arguments are optional; they may be replaced by NULL.
  20.  
  21.     void CharBounds(char a,Rect *bounds,long *inkArea);
  22. Creates a one-char string and calls StringBounds().
  23.  
  24.     double StrOutlineLength(char *s);
  25.     double StringOutlineLength(const unsigned char *s);
  26. Measure the outline length of a string, in pixels. Formerly (before 2/20/95)
  27. I thought it was enough to just count the number of pixels in the 1-pixel thick
  28. outline that one obtains when drawing a PostScript or TrueType character in
  29. outline style. However, I later discovered that this only gives correct results
  30. for vertical and horizontal segments of the the outline. Diagonal segments of
  31. the outline are, necessarily, a stairstep, so a pixel count yields a city-block
  32. path length. For a 45 deg slope the pixel count is too high by a factor of
  33. square root of 2, i.e. by roughly a factor of 1.5. StringOutlineLength() tries
  34. to provide a more accurate estimate of outline length. It creates the 1-pixel
  35. outline and then thickens it by repeatedly ORing the image with itself, shifted
  36. by one pixel: left, right, up, and down. This has the effect of tripling the
  37. thickness of horizontal and vertical lines, from 1 to 3 pixels, and doubling the
  38. thickness of 45 deg diagonal lines. Since the diagonal lines were already
  39. providing about 1.5 times as many pixels as the true length, when doubled, they
  40. provide triple the length, just like the horizontal and vertical lines. Thus, to
  41. a first order, the length measurement should be insensitive to orientation of
  42. the segments of the outline.
  43.  
  44. NOTES
  45.  
  46. bounds->right need not exactly equal the value returned by StringWidth(s),
  47. because StringWidth returns the displacement of the pen position, and some (e.g.
  48. italic) characters extend beyond that, while others (e.g. comma) extend less
  49. far. Similarly, bounds->left need not always be zero.
  50.  
  51. By QuickDraw's convention, each pixel is considered to lie below and to the
  52. right of the point that is used to address it. The bounding rectangle for a
  53. pixel at x,y, is SetRect(&r,x,y,x+1,y+1).
  54.  
  55. The code assumes ForeColor is black and that TextMode is srcOr, which are the 
  56. default settings for the GWorld that we create.
  57.  
  58. HISTORY:
  59. 1/17/94 dgp wrote it, based on discussion with Bart Farell and Manoj Gunwani.
  60. 2/22/94    dgp    added inkArea argument, and made both bounds and inkArea optional.
  61. 2/27/94    dgp since this routine is typically called repeatedly I save time
  62.             (0.2 s per call) by only allocating the GWorld once, and never
  63.             freeing it. It is reused on subsequent calls, resized appropriately
  64.             if necessary.
  65. 9/5/94 dgp removed assumption in printf's that int==short.
  66. 11/2/94 dgp discovered that the clip rect may be nonsense after calling
  67.             UpdateGWorld, so I set it equal to the portRect.
  68. 2/20/95 dgp Added StringOutlineLength and StrOutlineLength.
  69. 2/24/95 dgp Fixed above to give correct length even when stroke is only one pixel thick.
  70. */
  71. #include "VideoToolbox.h"
  72. #define SHOW_BITMAPS 0    // for debugging
  73.  
  74. double StrOutlineLength(char *s);
  75. double StringOutlineLength(const unsigned char *s);
  76.  
  77. void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr);
  78.  
  79. void GetBounds(GWorldPtr window,Rect *rectPtr,Rect *boundsPtr,long *inkAreaPtr)
  80. {
  81.     Rect r,bounds;
  82.     GWorldPtr oldPort;
  83.     GDHandle oldDevice;
  84.     register unsigned long *pix;
  85.     register int x,y;
  86.     register long inkArea=0;
  87.     long n;
  88.  
  89.     // measure the bounding box, and count the black pixels
  90.     GetGWorld(&oldPort,&oldDevice);
  91.     assert(window!=NULL);
  92.     SectRect(&window->portRect,rectPtr,&r);
  93.     SetRect(&bounds,r.right,r.bottom,r.left,r.top);
  94.     n=r.right-r.left;
  95.     pix=(unsigned long *)NewPtr(n*sizeof(*pix));
  96.     if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
  97.         ,__FILE__,__LINE__,n*sizeof(long));
  98.     if(IsGrafPtr(window))SetPort((WindowPtr)window);
  99.     else SetGWorld(window,GetMainDevice());
  100.     OffsetRect(&bounds,-r.left,-r.top);
  101.     for(y=0;y<r.bottom-r.top;y++){
  102.         GetPixelsQuickly(r.left,r.top+y,pix,n);
  103.         for(x=n-1;x>=0;x--)if(pix[x]!=0){
  104.             inkArea++;
  105.             if(x<bounds.left)bounds.left=x;
  106.             if(x>=bounds.right)bounds.right=x+1;
  107.             if(y<bounds.top)bounds.top=y;
  108.             if(y>=bounds.bottom)bounds.bottom=y+1;
  109.         }
  110.     }
  111.     SetGWorld(oldPort,oldDevice);
  112.     DisposePtr((void *)pix);    
  113.     OffsetRect(&bounds,r.left,r.top);
  114.     if(EmptyRect(&bounds))SetRect(&bounds,0,0,0,0);
  115.     if(boundsPtr!=NULL)*boundsPtr=bounds;
  116.     if(inkAreaPtr!=NULL)*inkAreaPtr=inkArea;
  117. }
  118.  
  119. void CharBounds(char a,Rect *boundsPtr,long *inkAreaPtr)
  120. {
  121.     unsigned char string[]="\pA";
  122.     
  123.     string[1]=a;
  124.     StringBounds(string,boundsPtr,inkAreaPtr);
  125. }
  126.  
  127. void StrBounds(char *s,Rect *boundsPtr,long *inkAreaPtr)
  128. {
  129.     StringBounds(c2pstr(s),boundsPtr,inkAreaPtr);
  130.     p2cstr((unsigned char *)s);
  131. }
  132.  
  133. void StringBounds(const unsigned char *s,Rect *boundsPtr,long *inkAreaPtr)
  134. {
  135.     static GWorldPtr our=NULL;
  136.     GWorldPtr old;
  137.     GDHandle oldDevice;
  138.     FontInfo f;
  139.     Rect r;
  140.     int error;
  141.     char string[40];
  142.  
  143.     assert(StackSpace()>4000);
  144.     GetFontInfo(&f);
  145.     SetRect(&r,0,-f.ascent,StringWidth(s),f.descent);    // nominal size
  146.     InsetRect(&r,-(f.widMax+2),-(f.leading+2));         // add margin
  147.  
  148.     // draw string into a new GWorld
  149.     if(our!=NULL){
  150.         DisposeGWorld(our);
  151.         our=NULL;
  152.     }
  153.     if(our==NULL){
  154.         error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal|useTempMem);
  155.         if(error)error=NewGWorld(&our,1,&r,NULL,NULL,keepLocal);
  156.     }else{
  157.         error=UpdateGWorld(&our,1,&r,NULL,NULL,clipPix);
  158.         assert(EqualRect(&r,&our->portRect));
  159.         assert((**our->clipRgn).rgnSize==10);
  160.         (**our->clipRgn).rgnBBox=our->portRect;
  161.     }
  162.     if(error)PrintfExit("StringBounds: NewGWorld/UpdateGWorld error %d.\n",error);
  163.     GetGWorld(&old,&oldDevice);
  164.     SetGWorld(our,NULL);
  165.     TextFace(old->txFace);
  166.     TextFont(old->txFont);
  167.     TextSize(old->txSize);
  168.     EraseRect(&our->portRect);
  169.     MoveTo(0,0);
  170.     DrawString(s);
  171.     SetGWorld(old,oldDevice);
  172.     if(SHOW_BITMAPS){
  173.         PrintfGWorld(our);
  174.         gets(string);
  175.     }
  176.  
  177.     GetBounds(our,&our->portRect,boundsPtr,inkAreaPtr);
  178.     
  179.     if(SHOW_BITMAPS){
  180.         PrintfGWorld(our);
  181.         gets(string);
  182.     }
  183.     if(0){
  184.         DisposeGWorld(our);
  185.         our=NULL;
  186.     }
  187. }
  188.  
  189. double StrOutlineLength(char *s)
  190. {
  191.     double length;
  192.  
  193.     length=StringOutlineLength(c2pstr(s));
  194.     p2cstr((unsigned char *)s);
  195.     return length;
  196. }
  197.  
  198. //#undef SHOW_BITMAPS
  199. //#define SHOW_BITMAPS 1
  200.  
  201. double StringOutlineLength(const unsigned char *s)
  202. {
  203.     static GWorldPtr world=NULL;
  204.     GWorldPtr old;
  205.     GDHandle oldDevice;
  206.     FontInfo f;
  207.     Rect r,rSrc,rDst,rSmall;
  208.     register unsigned long *pix;
  209.     register int x,y;
  210.     register long inkArea=0;
  211.     int n;
  212.     int error;
  213.     char string[40];
  214.  
  215.     assert(StackSpace()>4000);
  216.     GetFontInfo(&f);
  217.     SetRect(&rSmall,0,-f.ascent,StringWidth(s),f.descent);    // nominal size
  218.     InsetRect(&rSmall,-(f.widMax+4),-(f.leading+4));         // add margin
  219.     rSrc=rSmall;
  220.     rSrc.top*=2;    // allow room for double-size text
  221.     rSrc.left*=2;
  222.     rSrc.bottom*=2;
  223.     rSrc.right*=2;
  224.     r=rSrc;
  225.     r.right+=r.right-r.left;    // double rect, to make room for rDst.
  226.     rDst=rSrc;
  227.     OffsetRect(&rDst,rSrc.right-rSrc.left,0);
  228.     InsetRect(&rSrc,1,1);
  229.     InsetRect(&rDst,1,1);
  230.  
  231.     // draw string into a new GWorld
  232.     if(world!=NULL){
  233.         DisposeGWorld(world);
  234.         world=NULL;
  235.     }
  236.     if(world==NULL){
  237.         error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal|useTempMem);
  238.         if(error)error=NewGWorld(&world,1,&r,NULL,NULL,keepLocal);
  239.     }else{
  240.         error=UpdateGWorld(&world,1,&r,NULL,NULL,clipPix);
  241.         assert(EqualRect(&r,&world->portRect));
  242.         assert((**world->clipRgn).rgnSize==10);
  243.         (**world->clipRgn).rgnBBox=world->portRect;
  244.     }
  245.     if(error)PrintfExit("StringOutlineLength: NewGWorld/UpdateGWorld error %d.\n",error);
  246.     GetGWorld(&old,&oldDevice);
  247.     SetGWorld(world,NULL);
  248.     TextFace(old->txFace);
  249.     TextFont(old->txFont);
  250.     TextSize(old->txSize);
  251.     EraseRect(&world->portRect);
  252.     MoveTo(0,0);
  253.     DrawString(s);
  254.     SetGWorld(old,oldDevice);
  255.  
  256.     // Expand text so that all features are at least 2 pixels thick
  257.     error=CopyWindows(world,world,&rSmall,&rDst,srcCopy,NULL);
  258.     error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
  259.  
  260.     // Grow each black pixel into a 3x3 square.
  261.     r=rDst;
  262.     OffsetRect(&r,0,1);
  263.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  264.     OffsetRect(&r,0,-2);
  265.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  266.     OffsetRect(&r,1,1);
  267.     error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
  268.     OffsetRect(&r,-2,0);
  269.     error=CopyWindows(world,world,&rDst,&r,srcOr,NULL);
  270.  
  271.     // Clear the pixels corresponding to the original image, leaving a 1 pixel outline.
  272.     error=CopyWindows(world,world,&rSrc,&rDst,srcBic,NULL);
  273.     error=CopyWindows(world,world,&rDst,&rSrc,srcCopy,NULL);
  274.  
  275.     // Grow each black pixel, so that lines of all orientations are effectively 3 pixels thick.
  276.     r=rDst;
  277.     OffsetRect(&r,0,1);
  278.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  279.     OffsetRect(&r,0,-2);
  280.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  281.     OffsetRect(&r,1,1);
  282.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  283.     OffsetRect(&r,-2,0);
  284.     error=CopyWindows(world,world,&rSrc,&r,srcOr,NULL);
  285.  
  286.     // count the black pixels
  287.     r=rDst;
  288.     n=r.right-r.left;
  289.     pix=(unsigned long *)NewPtr(n*sizeof(*pix));
  290.     if(pix==NULL)PrintfExit("%s line %d: Couldn't allocate %ld bytes.\n"
  291.         ,__FILE__,__LINE__,n*sizeof(long));
  292.     SetGWorld(world,NULL);
  293.     for(y=0;y<r.bottom-r.top;y++){
  294.         GetPixelsQuickly(r.left,r.top+y,pix,n);
  295.         for(x=n-1;x>=0;x--)if(pix[x]!=0){
  296.             inkArea++;
  297.         }
  298.     }
  299.     if(SHOW_BITMAPS){
  300.         PrintfGWorld(world);
  301.         gets(string);
  302.     }
  303.     SetGWorld(old,oldDevice);
  304.     DisposePtr((void *)pix);    
  305.     if(0){
  306.         DisposeGWorld(world);
  307.         world=NULL;
  308.     }
  309.     return inkArea/6.0;
  310. }
  311.