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:
- 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.)
/// 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();
}
Would you be kind enough to add a small paragraph with a link to this on the wiki, on the Fonts page?