SFML community forums

General => Feature requests => Topic started by: dabo on June 25, 2008, 12:08:43 am

Title: Outlined text
Post by: dabo on June 25, 2008, 12:08:43 am
Is that something that could be implemented in the future? Don't know if it's possible but it doesn't hurt to ask ;)

For example in my game I have white text and occasionally the background becomes partly white which makes the text invisible. Having white text with a black outline would fix this. I guess I could draw a black rectangle behind the text but that's the last option.
Title: Outlined text
Post by: Laurent on June 25, 2008, 02:41:19 am
You could also draw the text twice and slightly offsetted, to create a kind of shadow.
Title: Outlined text
Post by: NewbiZ on June 25, 2008, 02:59:50 am
Quote from: "Laurent"
You could also draw the text twice and slightly offsetted, to create a kind of shadow.

Example: http://newbiz.developpez.com/tutoriels/opengl/heightmap/#LVII-E
:)
Title: Outlined text
Post by: dabo on June 25, 2008, 12:57:49 pm
That's a good option, thanks. But still, I think it would be nice if you could draw outlined text :)
Title: Outlined text
Post by: Laurent on June 25, 2008, 02:32:20 pm
Well, I haven't thought much about it but right now I can't see any way of doing it efficiently.

The "brute force" option would simply be to draw the string 5 times : 1 time in every direction to create the outline, and 1 time on top of it.
Title: Outlined text
Post by: workmad3 on June 25, 2008, 02:47:50 pm
surely the 'brute force' method would really be to draw the string twice. Once with the outline colour at the requested size, and once on top of it at about 0.2p smaller in the text colour to create the outline?
Title: Outlined text
Post by: Laurent on June 25, 2008, 03:02:52 pm
2 times won't be enough, as each character won't be scaled around its own center. You would end up with an offset rather than a scale.

4 times to fake a uniform scale, not less ;)
Title: Outlined text
Post by: workmad3 on June 25, 2008, 03:05:28 pm
hmm... thinking about that, it would require a bit of trickery to change the letter spacing in the smaller version in order to centre them. Still, that's the method I would consider doing as it only involves some (probably minor) calculations and 2 string draws.

The other option I can think of that may be possible is to see if you could get a pixel shader to outline the text. That would manage it in a single pass rather than 2 (or 5) but would mean that with outlines, no other postFX effects could be done on text (unless it was exposed as a postFX function that could be called in another effect). It may not be possible to do entirely in pixel shaders though (the method I can think of would require having a vertex shader that creates a varying variable that can be used in the pixel shader to detect distance from the edge of the character)
Title: Outlined text
Post by: NewbiZ on June 25, 2008, 04:30:03 pm
The simple inner/outter border trick seems to provide pretty nice results as long as the size is >=20.


Code: [Select]
#include <cstdlib>

#include "SFML/Window.hpp"
#include "SFML/Graphics.hpp"
#include <SFML/Graphics/String.hpp>
#include <SFML/Graphics/FontManager.hpp>
#include <SFML/Graphics/Image.hpp>
#include <SFML/Graphics/OpenGL.hpp>
#include <locale>

namespace sf { namespace ext
{
  class OutlinedString : public sf::String
  {
  public:
    OutlinedString(const std::string& Text, const std::string& Font = "", float Size = 32.f):
      String(Text, Font, Size) {}

    OutlinedString(const std::wstring& Text = L"", const std::string& Font = "", float Size = 32.f):
      String(Text, Font, Size) {}

  protected:
    virtual void Render(const RenderWindow& Window) const
    {
      // No text, no rendering :)
    if (GetText().empty())
        return;

    // Get the bitmap font from the font manager
    const priv::FontManager::Font& BitmapFont = priv::FontManager::GetInstance().GetBitmapFont(GetFont(), static_cast<unsigned int>(GetSize()));

    // Set the scaling factor to get the actual size
    float FactorX = GetScaleX() * GetSize() / BitmapFont.CharSize;
    float FactorY = GetScaleY() * GetSize() / BitmapFont.CharSize;
    GLCheck(glScalef(FactorX, FactorY, 1.f));

    // Bind the font texture
    BitmapFont.Texture.Bind();

    float X = 0;
    float Y = 0;

    // Draw the sprite
    glBegin(GL_QUADS);
    for (std::size_t i = 0; i < GetText().size(); ++i)
    {
        // Get the current character
        wchar_t c = GetText()[i];

        // Check if the character is in the charset
        std::map<wchar_t, priv::FontManager::Font::Character>::const_iterator It = BitmapFont.Characters.find(c);
        if (It == BitmapFont.Characters.end())
        {
            // No : add a space and continue to the next character
            X += GetSize();
            continue;
        }
        const priv::FontManager::Font::Character& CurChar = It->second;

        // Get the dimensions of the current character from font description
        const IntRect&   Rect       = CurChar.Rect;
        const FloatRect& Coord      = CurChar.Coord;
        const unsigned int AdvanceX = CurChar.Advance;
        const unsigned int AdvanceY = BitmapFont.CharSize;

        // Handle special characters
        switch (c)
        {
            case L' ' :  X += AdvanceX;        continue;
            case L'\n' : Y += AdvanceY; X = 0; continue;
            case L'\t' : X += AdvanceX * 4;    continue;
            case L'\v' : Y += AdvanceY * 4;    continue;
        }

        // Draw a textured quad for the current character
        glColor4f(0,0,0,1); //TODO: Make the outline color settable
       
        // Outter outline
        glTexCoord2f(Coord.Left,  Coord.Top);    glVertex2f(X + Rect.Left -1, Y + Rect.Top   -1 + AdvanceY);
        glTexCoord2f(Coord.Left,  Coord.Bottom); glVertex2f(X + Rect.Left -1, Y + Rect.Bottom+1 + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Bottom); glVertex2f(X + Rect.Right+1, Y + Rect.Bottom+1 + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Top);    glVertex2f(X + Rect.Right+1, Y + Rect.Top   -1 + AdvanceY);

        // Inner outline
        glTexCoord2f(Coord.Left,  Coord.Top);    glVertex2f(X + Rect.Left +1, Y + Rect.Top   +1 + AdvanceY);
        glTexCoord2f(Coord.Left,  Coord.Bottom); glVertex2f(X + Rect.Left +1, Y + Rect.Bottom-1 + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Bottom); glVertex2f(X + Rect.Right-1, Y + Rect.Bottom-1 + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Top);    glVertex2f(X + Rect.Right-1, Y + Rect.Top   +1 + AdvanceY);

        // Text
        glColor4f( GetColor().r, GetColor().g, GetColor().b, GetColor().a );
        glTexCoord2f(Coord.Left,  Coord.Top);    glVertex2f(X + Rect.Left,  Y + Rect.Top    + AdvanceY);
        glTexCoord2f(Coord.Left,  Coord.Bottom); glVertex2f(X + Rect.Left,  Y + Rect.Bottom + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Bottom); glVertex2f(X + Rect.Right, Y + Rect.Bottom + AdvanceY);
        glTexCoord2f(Coord.Right, Coord.Top);    glVertex2f(X + Rect.Right, Y + Rect.Top    + AdvanceY);

        // Advance to the next character
        X += AdvanceX;
    }
    glEnd();
    }
  };
}}

int main( int argc, char** argv )
{
  sf::RenderWindow app( sf::VideoMode( 800, 600 ), "Outlined text example" );
  app.SetBackgroundColor( sf::Color::Red );
  app.Show( true );
  app.SetCurrent();

  sf::ext::OutlinedString str( "Example outlined string", "classic.ttf", 40.f);
  str.SetColor( sf::Color::White );

  bool done=false;
  while (!done)
  {
   
    // =================================== <Events>
    sf::Event Event;
    while ( app.GetEvent(Event) )
      if (Event.Type == sf::Event::Closed)
        done = false;
    // =================================== </Events>

    // =================================== <Display>
    str.SetColor( sf::Color::White   );str.SetPosition( 10,  10 ); str.SetSize( 50.f ); app.Draw( str  );
    str.SetColor( sf::Color::Blue    );str.SetPosition( 10, 100 ); str.SetSize( 40.f ); app.Draw( str  );
    str.SetColor( sf::Color::Green   );str.SetPosition( 10, 200 ); str.SetSize( 30.f ); app.Draw( str  );
    app.Display();
    // =================================== </Display>
  }

  return EXIT_SUCCESS;
}



(http://www.speednova.cc/screenshots/outline.png)
Title: Outlined text
Post by: Laurent on June 25, 2008, 04:40:50 pm
Yep, with shaders I could provide pixel perfect text rendering with the method you're thinking about, but that's clearly not an option ;)
Title: Outlined text
Post by: dabo on June 25, 2008, 10:57:48 pm
Well if there isen't an efficient way to do it the first suggested option will do, no problem.

NewbiZ: Looks good, but I usually have sizes around 10 - 14. I couldn't compile your code to test it on smaller sizes than the example you showed.
Title: Outlined text
Post by: NewbiZ on June 25, 2008, 11:09:26 pm
This is a quick and dirty implementation which only purpose is to show the result of the inner/outter trick.
You may have to add OpenGL.hpp & FontManager.hpp (from SFML sources headers) to your headers.

As Laurent said, it would be better to draw the outline offseted in each direction instead. Just some lines to change.
Title: Outlined text
Post by: workmad3 on June 26, 2008, 09:19:32 am
I've been thinking about the drawing in all directions and realised there is a slight problem with it... it won't get corners, which would leave the outline looking very bad, especially at higher font sizes if the outline size increases with it.
Title: Outlined text
Post by: Laurent on June 26, 2008, 09:30:23 am
To get the corners, you can still draw the string 8 times :D
Title: Outlined text
Post by: dabo on June 26, 2008, 12:30:16 pm
Seems like a lot of code for just an outline, is that really how they do it in "professional" games?

Example (http://www.sigames.com/graphics/images/1466.jpg)
Title: Outlined text
Post by: Laurent on June 26, 2008, 12:42:31 pm
One other option is to use fonts which are already outlined, either in the font definition file (ttf or whatever) or by using a manually generated bitmap font.

The techniques used by modern games involve shaders, as already mentioned.
Title: Outlined text
Post by: dabo on June 26, 2008, 02:44:02 pm
Ok, thanks.
Title: Outlined text
Post by: workmad3 on June 26, 2008, 08:29:33 pm
Most professional games will use custom bitmap fonts designed for their UI and outlined as necessary with that :)
And if you want to generate some, then I can recommend this:
http://www.codehead.co.uk/cbfg/
Title: Outlined text
Post by: workmad3 on June 26, 2008, 09:14:05 pm
Quote from: "Laurent"
To get the corners, you can still draw the string 8 times :D


I think it would still look wrong with curved characters (like S, C, etc. :))
Title: Outlined text
Post by: dabo on June 26, 2008, 09:44:25 pm
Quote from: "workmad3"
Most professional games will use custom bitmap fonts designed for their UI and outlined as necessary with that :)
And if you want to generate some, then I can recommend this:
http://www.codehead.co.uk/cbfg/


Thanks for the tip, will check it out.
Title: Outlined text
Post by: T.T.H. on June 27, 2008, 04:08:08 pm
First: waaaaant thiiiiiis, toooooo, puleeeeeeeeeze!


Second: SFML is using Freetype2, right? So why not using what Freetype2 already provides, the Glyph Stroker (http://www.freetype.org/freetype2/docs/reference/ft2-glyph_stroker.html)?!

Note #1: I have no clue of Freetype2 myself. I stumbled about the Glyph Stroker after a short Google search because I wanted to have outlined fonts, too, and SFML was using Freetype2, so I looked there first. Never tried it myself though.

Note #2: what you call "outline fonts" is what Freetype2 calls "stroked outlines of fonts" as far as I understand that.
Title: Outlined text
Post by: Laurent on June 27, 2008, 05:05:20 pm
Seems like a good option. I don't know every detail about FreeType, I'll check this one. Thanks for the hint :)
Title: Outlined text
Post by: dewyatt on July 28, 2008, 08:07:18 pm
I've used the stroker feature of FT previously and it looks great.
So I vote for that.
Title: Outlined text
Post by: Laurent on July 29, 2008, 02:48:10 am
No need to vote, it's on my todo-list ;)
Title: Outlined text
Post by: dabo on September 12, 2008, 02:25:06 pm
Has this been added yet or has it been dropped? I found something about it checking the road map but I didn't understand its status.
Title: Outlined text
Post by: Laurent on September 12, 2008, 02:31:26 pm
It has been dropped ;)
Title: Outlined text
Post by: dabo on September 12, 2008, 06:54:32 pm
I'm sad to hear that.
Title: Outlined text
Post by: Laurent on September 12, 2008, 06:58:47 pm
Well, SFML bitmap fonts are generated in white with a proper alpha channel, and then colorized dynamically. How would you handle outlines with that ? The only way would be to generate the outlines alone in a separate texture, and render text in two passes.
Title: Outlined text
Post by: dabo on September 12, 2008, 08:14:07 pm
Quote from: "Laurent"
Well, SFML bitmap fonts are generated in white with a proper alpha channel, and then colorized dynamically. How would you handle outlines with that ? The only way would be to generate the outlines alone in a separate texture, and render text in two passes.


Ok if there isen't a good way to implement it I understand that you dropped it.
Title: Outlined text
Post by: T.T.H. on October 10, 2008, 12:18:02 am
Quote from: "Laurent"
and render text in two passes.

Is that a bad thing?

Personally I think not because 1) outlined text is pretty important for readability in games in my personal opinion and 2) all features have a price. Anyway, you're the boss, your decision.

Just some thoughts: make a derived class from sf::String like sf::OutlinedString which creates a second "texture"(?) with all the letters in it but with a larger spacing between the letters and then apply a linear filter ("opening/closing operation" alias "max/min filter" alias "dilation/erosion" or however you call it) with a N x N matrix whereas the N is the pixel size of the outline. This should make all letters "bold". Afterwards when OutlinedString is drawn first draw the letters from the texture of OutlinedString in one color and then draw the letters of the texture of the base class in another color on top of it.
Title: Outlined text
Post by: sirGustav on November 13, 2008, 06:48:57 pm
sorry for positing in a old topic, but a posssible solution is to drop freetype support and add bmfont  (http://www.angelcode.com/products/bmfont/)support instead. I did that with my game, and I worked out great. My string is rendered roughly the same as sfml does it(I support multiple colors within a single string), so it shouldn't be that hard. As far as I can see I only lost two things when moving to bmfont:
* I cant render white and black texts and get different outlines with the same font but that is a small price to pay. I often keep the outline black and just change the font-color.
* Two or more files instead of a single. This is easily solvable by putting them in a separate directory instead.
Title: Outlined text
Post by: Laurent on November 14, 2008, 07:48:14 am
I won't drop support for generating text from truetype fonts directly, I'll never require every user to download / install / use a bitmap font generator just to get simple text on screen.

I could rather think about a way to add support for external bitmap fonts to the existing system ;)