Gigi Labs

Please follow Gigi Labs for the latest articles.

Saturday, March 29, 2014

SDL2: Displaying text with SDL_ttf

Hello again! :)

[Update 2015-11-14: This article is out of date. Check out the latest version at Gigi Labs.]

In this article, we're going to learn how we can write text in our window. To do this, we'll use the SDL_ttf library.

First, we need to set up a project to work with SDL2. To do this, follow the instructions in my earlier article, "SDL2: Setting up SDL2 in Visual Studio (2013 or any other)".

Next, we need to actually download and use the SDL_ttf library. This is very similar to what we did with SDL_image in "SDL2: Loading Images with SDL_image". First, download the Visual C++ Development Libraries from the SDL_ttf 2.0 homepage:


From the archive you just downloaded, you need to do the same as in "SDL2: Loading Images with SDL_image" to:

  1. Copy SDL_ttf.h to the include folder where your other SDL2 files reside.
  2. Copy SDL2_ttf.lib to the lib\x86 folder where your other SDL2 files reside.
  3. After compiling your project (as it is, without using SDL_ttf just yet), copy the DLL files from the lib\x86 folder, as well as the regular SDL2.dll, to the Debug folder where your project's executable is created.

Then, in your project's properties, go to Linker -> Input and add the following: SDL2_ttf.lib in the Additional Dependencies field. This field should now contain the following:

SDL2.lib; SDL2main.lib; SDL2_ttf.lib

Good. Now, let's start with the following code which is a modified version of the code from about halfway through "SDL2: Displaying an Image in the Window":

#include <SDL.h>

int main(int argc, char ** argv)
{
 bool quit = false;
 SDL_Event event;

 SDL_Init(SDL_INIT_VIDEO);

 SDL_Window * window = SDL_CreateWindow("SDL_ttf in SDL2",
  SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640,
  480, 0);
 SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);

 while (!quit)
 {
  SDL_WaitEvent(&event);

  switch (event.type)
  {
  case SDL_QUIT:
   quit = true;
   break;
  }
 }

 SDL_DestroyRenderer(renderer);
 SDL_DestroyWindow(window);
 SDL_Quit();

 return 0;
}

The first thing we need to do in order to use SDL_ttf is include the relevant header file:

#include <SDL_ttf.h>

Then, we initialise the SDL_ttf library right after we call SDL_Init():

 TTF_Init();

...and we clean it up just before we call SDL_Quit():

 TTF_Quit();

Right after we initialise our renderer, we can now load a font into memory:

 TTF_Font * font = TTF_OpenFont("arial.ttf", 25);

TTF_OpenFont() takes two parameters. The first is the path to the TrueType Font (TTF) that it needs to load. The second is the font size (in points, not pixels). In this case we're loading Arial with a size of 25.

A font is a resource like any other, so we need to free the resources it uses near the end:

 TTF_CloseFont(font);

We can now render some text to an SDL_Surface using TTF_RenderText_Solid(). which takes the font we just created, a string to render, and an SDL_Color which we are passing in as white in this case:

 SDL_Color color = { 255, 255, 255 };
 SDL_Surface * surface = TTF_RenderText_Solid(font,
  "Welcome to Programmer's Ranch", color);

We can then create a texture from this surface as we did in "SDL2: Displaying an Image in the Window":

 SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer,
  surface);

And yet again, we should not forget to release the resources we just allocated near the end, so let's do that right away:

 SDL_DestroyTexture(texture);
 SDL_FreeSurface(surface);

Now all we need is to actually render the texture. We've done this before; just add the following just before the end of your while loop:

  SDL_RenderCopy(renderer, texture, NULL, NULL);
  SDL_RenderPresent(renderer);

Okay, now before we actually run this program, we need to put our Arial TTF font somewhere where our program can find it. Go to C:\Windows\Fonts, and fron there copy the Arial font into the Debug folder where your executable is compiled. This will result in several TTF files, although we're only going to use arial.ttf.

Now if you've read my SDL_image tutorials before, you'll know that the program runs from a different directory when running from Visual Studio and when running directly from the executable - in fact if you run the program now from Visual Studio, it will crash; but if you run the executable directly it will work fine. Rather than replicating our TTF file across folders to satisfy both scenarios, here's a little trick to make it work without further ado.

Go into your project properties, and then under Configuration Properties -> Debugging, there's a field called Working Directory. Change this from $(ProjectDir) (the default setting) to $(SolutionDir)$(Configuration)\ :


Great, now let's admire the fruit of our work:


Nooooooooooooooo! This isn't quite what we were expecting, right? This is happening because the texture is being stretched to fill the contents of the window. The solution is to supply the dimensions occupied by the text in the dstrect parameter of SDL_RenderCopy() (as we did in "SDL2: Displaying an Image in the Window"). But how can we know these dimensions?

If you check out Will Usher's SDL_ttf tutorial, you'll realise that a function called SDL_QueryTexture() can give you exactly this information (and more). So before our while loop, let's add the following code:

 int texW = 0;
 int texH = 0;
 SDL_QueryTexture(texture, NULL, NULL, &texW, &texH);
 SDL_Rect dstrect = { 0, 0, texW, texH };

Finally, we can pass dstrect in our call to SDL_RenderCopy():

  SDL_RenderCopy(renderer, texture, NULL, &dstrect);

Let's run the program now:


Much better! :)

In this article, we learned how to use the SDL_ttf to render text using TTF fonts in SDL2. Using the SDL_ttf library in a project was just the same as with SDL_image. To actually use fonts, we first rendered text to a surface, then passed it to a texture, and finally to the GPU. We used SDL_QueryTexture() to obtain the dimensions of the texture, so that we could render the text in exactly as much space as it needed. We also learned how we can set up our project to use the same path regardless of whether we're running from Visual Studio or directly from the executable.

Below is the full code for this article. Come back again for more! :)

#include <SDL.h>
#include <SDL_ttf.h>

int main(int argc, char ** argv)
{
 bool quit = false;
 SDL_Event event;

 SDL_Init(SDL_INIT_VIDEO);
 TTF_Init();

 SDL_Window * window = SDL_CreateWindow("SDL_ttf in SDL2",
  SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640,
  480, 0);
 SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, 0);

 TTF_Font * font = TTF_OpenFont("arial.ttf", 25);
 const char * error = TTF_GetError();
 SDL_Color color = { 255, 255, 255 };
 SDL_Surface * surface = TTF_RenderText_Solid(font,
  "Welcome to Programmer's Ranch", color);
 SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer,
  surface);

 int texW = 0;
 int texH = 0;
 SDL_QueryTexture(texture, NULL, NULL, &texW, &texH);
 SDL_Rect dstrect = { 0, 0, texW, texH };

 while (!quit)
 {
  SDL_WaitEvent(&event);

  switch (event.type)
  {
  case SDL_QUIT:
   quit = true;
   break;
  }

  SDL_RenderCopy(renderer, texture, NULL, &dstrect);
  SDL_RenderPresent(renderer);
 }

 SDL_DestroyTexture(texture);
 SDL_FreeSurface(surface);
 TTF_CloseFont(font);

 SDL_DestroyRenderer(renderer);
 SDL_DestroyWindow(window);
 TTF_Quit();
 SDL_Quit();

 return 0;
}

7 comments:

  1. Hey, how do you deal with this when you've got a background to the Window? I've got a Window with a White background, and in my Event (while) loop, I keep calling Window.refresh and RenderPresent one after the other. This creates a flickering in my screen.

    ReplyDelete
    Replies
    1. What is this window.refresh you mention? May be because of that. Can you send me your code so that I can take a look at it?

      Delete
    2. Oh, I'm using a Python Wrapper for SDL2. So my syntax is different. I posted the question on Stack Overflow last night, and it's been addressed here: http://stackoverflow.com/questions/24709312/pysdl2-renderer-or-window-surface-for-handling-colors-and-text/24710884#24710884

      Delete
  2. Hello!

    I'd like to know what I should do when using multiple Textures or sprites in a script. I can see you have used a RenderCopy call for that one texture. But surely when adding multiple textures, this becomes more complicated. The RenderCopy call takes only one. Would you need to nest a second while loop in the main loop, and iterate though a list or sequence of all existing sprites to render them?

    Sorry for asking so many questions.

    ReplyDelete
    Replies
    1. Check out the SDL2 Migration Guide:
      https://wiki.libsdl.org/MigrationGuide#If_your_game_wants_to_blit_surfaces_to_the_screen

      The paragraph at the end of this section says:

      "At this point, your 1.2 game had a bunch of SDL_Surfaces, which it would SDL_BlitSurface() to the screen surface to compose the final framebuffer, and eventually SDL_Flip() to the screen. For SDL 2.0, you have a bunch of SDL_Textures, that you will SDL_RenderCopy() to your Renderer to compose the final framebuffer, and eventually SDL_RenderPresent() to the screen. It's that simple. If these textures never need modification, you might find your framerate has just gone through the roof, too."

      So... yes, you'd RenderCopy() all your textures in a loop, but then do just one single RenderPresent() at the end.

      Delete
  3. This is by far the only tutorial that helped me. Very useful and instructions are clear, easy to follow. Thank you for this!

    ReplyDelete

Note: Only a member of this blog may post a comment.