Adventure Game Studio | Forums

AGS Support => Advanced Technical Forum => Topic started by: Snarky on 03 Dec 2017, 13:33

Title: DrawString anti-aliased and with outline
Post by: Snarky on 03 Dec 2017, 13:33
This code is part of the SpeechBubble module (http://www.adventuregamestudio.co.uk/forums/index.php?topic=55542) 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:


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: [Select]
/// The shape of a text outline
enum TextOutlineStyle
{
  /// A circular, rounded outline
  eTextOutlineRounded = 0,
  /// A square "block" outline
  eTextOutlineSquare
};

// To work around the AGS bug where antialiasing "pokes holes" in semi-transparent canvases
void drawStringWrappedAA(this DrawingSurface*, int x, int y, int width, FontType font, Alignment alignment, String message, int transparency)
{
  DynamicSprite* textSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* textSurface = textSprite.GetDrawingSurface();
  textSurface.DrawingColor = this.DrawingColor;
  textSurface.DrawStringWrapped(x, y, width, font, alignment, message);
  textSurface.Release();
  this.DrawImage(0, 0, textSprite.Graphic, transparency);
  textSprite.Delete();
}

// Draw a string with outline (make sure the canvas has at least outlineWidth pixels on each side of the string)
void drawStringWrappedOutline(this DrawingSurface*, int x, int y, int width, TextOutlineStyle outlineStyle, FontType font,  Alignment alignment, String message, int transparency, int outlineColor, int outlineWidth)
{
  // This is what we draw on (because we might need to copy with transparency)
  DynamicSprite* outlineSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* outlineSurface = outlineSprite.GetDrawingSurface();
 
  // This holds multiple horizontal copies of the text
  // We copy it multiple times (shifted vertically) onto the outlineSprite to create the outline
  DynamicSprite* outlineStripSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* outlineStripSurface = outlineStripSprite.GetDrawingSurface();
 
  // This is our "text stamp" that we use to draw the outline, we copy it onto outlineStripSprite
  DynamicSprite* textSprite = DynamicSprite.Create(this.Width, this.Height, true);
  DrawingSurface* textSurface = textSprite.GetDrawingSurface();
 
  // Draw our text stamp
  textSurface.DrawingColor = outlineColor;
  textSurface.DrawStringWrapped(x, y, width, font, alignment, message);
  textSurface.Release();
 
  switch(outlineStyle)
  {
    case eTextOutlineRounded:
    {
      // Draw Circular outline
      int maxSquare = outlineWidth*outlineWidth+1; // Add 1 for rounding purposes, to avoid "pointy corners"
      int maxWidth = 0;
      outlineStripSurface.DrawImage(0, 0, textSprite.Graphic);
      // We loop from top and bottom to the middle, making the outline wider and wider, to form circular outline
      for(int i = outlineWidth; i > 0; i--)
      {
        // Here's the circular calculation...
        while(i*i + maxWidth*maxWidth <= maxSquare)
        {
          // Increase width of the outline if necessary
          maxWidth++;
          outlineStripSurface.DrawImage(-maxWidth, 0, textSprite.Graphic);
          outlineStripSurface.DrawImage(maxWidth, 0, textSprite.Graphic);
          outlineStripSurface.Release();
          outlineStripSurface = outlineStripSprite.GetDrawingSurface();
        }
        // Draw outline strip above and below
        outlineSurface.DrawImage(0, -i, outlineStripSprite.Graphic);
        outlineSurface.DrawImage(0, i, outlineStripSprite.Graphic);
      }
      // Finally the middle strip
      outlineSurface.DrawImage(0, 0, outlineStripSprite.Graphic);
      break;
    }
    case eTextOutlineSquare:
    {
      // Draw square block outline
      // Just draw the full outline width onto the strip
      for(int i = -outlineWidth; i <= outlineWidth; i++)
        outlineStripSurface.DrawImage(i, 0, textSprite.Graphic);
      outlineStripSurface.Release();
      // Draw the full outline height
      for(int j = -outlineWidth; j <= outlineWidth; j++)
        outlineSurface.DrawImage(0, j, outlineStripSprite.Graphic);
      break;
    }
  }
  textSprite.Delete();
  outlineStripSurface.Release();
  outlineStripSprite.Delete();
 
  /// Now draw the text itself on top of the outline
  outlineSurface.DrawingColor = this.DrawingColor;
  outlineSurface.drawStringWrappedAA(x, y, width, font, alignment, message, 0);
  outlineSurface.Release();
  // ... And copy it onto our canvas
  this.DrawImage(0, 0, outlineSprite.Graphic, transparency);
  outlineSprite.Delete();
}
Title: Re: DrawString anti-aliased and with outline
Post by: Monsieur OUXX 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?