Author Topic: DrawString anti-aliased and with outline  (Read 130 times)

Snarky

  • Global Moderator
  • Mittens Earl
  • Private Insultant
    • I can help with proof reading
    •  
    • I can help with translating
    •  
DrawString anti-aliased and with outline
« on: 03 Dec 2017, 13:33 »
This code is part of the SpeechBubble module I just posted, but since it has more general-purpose application, I thought it deserved its own post.

Displaying text with a TrueType (.ttf) font in AGS has some well-known limitations:

  • If you place anti-aliased text on a semi-transparent background (e.g. as a label on a semi-transparent GUI), the anti-aliasing "pokes holes" in the background. This looks terrible.
  • With anti-aliased text, the AGS auto-outline feature often does not work very well, with gaps and other artifacts.
  • The auto-outline can only be one pixel wide.
  • The outline can only be black. (You can set it to a different color using game.text_shadow_color, but it will affect ALL the text on the screen.)

Here are two functions to work around those issues. They take advantage of the fact that DrawingSurface.DrawImage() does correct alpha-blending. You can use this to draw an anti-aliased string onto any DrawingSurface without artifacts. And by "stamping" multiple copies of the string in a pattern of 1-pixel displacements, you can also create an arbitrarily wide outline of any color you like.

The drawback is that it is slower, particularly if you want to draw a very wide outline (a 5-pixel outline will do something like 20 DrawImage() operations). However, I've found that in practice it runs fast enough to not be a problem for typical uses. (There are also some optimizations still possible in this code, particularly if you don't need the ability to draw the text at <100% opacity.)

Code: Adventure Game Studio
  1. /// The shape of a text outline
  2. enum TextOutlineStyle
  3. {
  4.   /// A circular, rounded outline
  5.   eTextOutlineRounded = 0,
  6.   /// A square "block" outline
  7.   eTextOutlineSquare
  8. };
  9.  
  10. // To work around the AGS bug where antialiasing "pokes holes" in semi-transparent canvases
  11. void drawStringWrappedAA(this DrawingSurface*, int x, int y, int width, FontType font, Alignment alignment, String message, int transparency)
  12. {
  13.   DynamicSprite* textSprite = DynamicSprite.Create(this.Width, this.Height, true);
  14.   DrawingSurface* textSurface = textSprite.GetDrawingSurface();
  15.   textSurface.DrawingColor = this.DrawingColor;
  16.   textSurface.DrawStringWrapped(x, y, width, font, alignment, message);
  17.   textSurface.Release();
  18.   this.DrawImage(0, 0, textSprite.Graphic, transparency);
  19.   textSprite.Delete();
  20. }
  21.  
  22. // Draw a string with outline (make sure the canvas has at least outlineWidth pixels on each side of the string)
  23. void drawStringWrappedOutline(this DrawingSurface*, int x, int y, int width, TextOutlineStyle outlineStyle, FontType font,  Alignment alignment, String message, int transparency, int outlineColor, int outlineWidth)
  24. {
  25.   // This is what we draw on (because we might need to copy with transparency)
  26.   DynamicSprite* outlineSprite = DynamicSprite.Create(this.Width, this.Height, true);
  27.   DrawingSurface* outlineSurface = outlineSprite.GetDrawingSurface();
  28.  
  29.   // This holds multiple horizontal copies of the text
  30.   // We copy it multiple times (shifted vertically) onto the outlineSprite to create the outline
  31.   DynamicSprite* outlineStripSprite = DynamicSprite.Create(this.Width, this.Height, true);
  32.   DrawingSurface* outlineStripSurface = outlineStripSprite.GetDrawingSurface();
  33.  
  34.   // This is our "text stamp" that we use to draw the outline, we copy it onto outlineStripSprite
  35.   DynamicSprite* textSprite = DynamicSprite.Create(this.Width, this.Height, true);
  36.   DrawingSurface* textSurface = textSprite.GetDrawingSurface();
  37.  
  38.   // Draw our text stamp
  39.   textSurface.DrawingColor = outlineColor;
  40.   textSurface.DrawStringWrapped(x, y, width, font, alignment, message);
  41.   textSurface.Release();
  42.  
  43.   switch(outlineStyle)
  44.   {
  45.     case eTextOutlineRounded:
  46.     {
  47.       // Draw Circular outline
  48.       int maxSquare = outlineWidth*outlineWidth+1; // Add 1 for rounding purposes, to avoid "pointy corners"
  49.       int maxWidth = 0;
  50.       outlineStripSurface.DrawImage(0, 0, textSprite.Graphic);
  51.       // We loop from top and bottom to the middle, making the outline wider and wider, to form circular outline
  52.       for(int i = outlineWidth; i > 0; i--)
  53.       {
  54.         // Here's the circular calculation...
  55.         while(i*i + maxWidth*maxWidth <= maxSquare)
  56.         {
  57.           // Increase width of the outline if necessary
  58.           maxWidth++;
  59.           outlineStripSurface.DrawImage(-maxWidth, 0, textSprite.Graphic);
  60.           outlineStripSurface.DrawImage(maxWidth, 0, textSprite.Graphic);
  61.           outlineStripSurface.Release();
  62.           outlineStripSurface = outlineStripSprite.GetDrawingSurface();
  63.         }
  64.         // Draw outline strip above and below
  65.         outlineSurface.DrawImage(0, -i, outlineStripSprite.Graphic);
  66.         outlineSurface.DrawImage(0, i, outlineStripSprite.Graphic);
  67.       }
  68.       // Finally the middle strip
  69.       outlineSurface.DrawImage(0, 0, outlineStripSprite.Graphic);
  70.       break;
  71.     }
  72.     case eTextOutlineSquare:
  73.     {
  74.       // Draw square block outline
  75.       // Just draw the full outline width onto the strip
  76.       for(int i = -outlineWidth; i <= outlineWidth; i++)
  77.         outlineStripSurface.DrawImage(i, 0, textSprite.Graphic);
  78.       outlineStripSurface.Release();
  79.       // Draw the full outline height
  80.       for(int j = -outlineWidth; j <= outlineWidth; j++)
  81.         outlineSurface.DrawImage(0, j, outlineStripSprite.Graphic);
  82.       break;
  83.     }
  84.   }
  85.   textSprite.Delete();
  86.   outlineStripSurface.Release();
  87.   outlineStripSprite.Delete();
  88.  
  89.   /// Now draw the text itself on top of the outline
  90.   outlineSurface.DrawingColor = this.DrawingColor;
  91.   outlineSurface.drawStringWrappedAA(x, y, width, font, alignment, message, 0);
  92.   outlineSurface.Release();
  93.   // ... And copy it onto our canvas
  94.   this.DrawImage(0, 0, outlineSprite.Graphic, transparency);
  95.   outlineSprite.Delete();
  96. }

Monsieur OUXX

  • Cavefish
  • Mittens Vassal
  • Mittens Half Initiate
    • I can help with proof reading
    •  
    • I can help with translating
    •  
    • I can help with voice acting
    •  
Re: DrawString anti-aliased and with outline
« Reply #1 on: 05 Dec 2017, 10:53 »
Would you be kind enough to add a small paragraph with a link to this on the wiki, on the Fonts page?