Lesson 4: Bitmap Fonts

Author: Marius Andra. Link to original: http://cone3d.gamedev.net/cgi-bin/index.pl?page=tutorials/gfxsdl/tut4 (English).
Tags: 2d, c++, game, SDL, игры Submitted by eReS 26.10.2010. Public material.

Translations of this material:

into Ukrainian: Урок 4. Растрові шрифти. 24% translated in draft.
Submitted for translation by eReS 26.10.2010

Text

Hi all! This lesson is about drawing bitmap fonts on a surface in SDL. They are called bitmap fonts because our fonts will be stored in one big bitmap that contains all of them. Here's what one of our font map looks like:

Our font system will consist of 2 files: font.cpp and font.h. The lesson itself will also have the file lesson4.cpp. I decided to put the font routines into seperate files because this way it's much easier to use the same code in many programs - just copy the 2 files to your new project, add an '#include "font.h"' line and you're all set.

Note: I won't talk about what code goes into which file. You can put all the code into one big file if you want (if you like to follow this tutorial while coding your font routines). I will only talk about the functions. If you want to look at the code in many files, look at the source. Also this code could really easilly be put into a class. I won't use classes this time.

All our fonts will be stored inside one structure called SDLFont. It looks like this:

// Structure to hold our font

struct SDLFont

{

SDL_Surface *font; // The SDL Surface for the font image

int width; // Width of the SDL Surface (=height)

int charWidth; // Width of one block character in the font

int *widths; // Real widths of all the fonts

unsigned char *data; // The raw font data

};

The SDL_Structure font will hold the font image map. Width is the width of the font (for easy access) and since the font image is a square, the width = height. charWidth is the with of one "character cell" on the font (width/16). Since mostly all of the characters in the font have different real widths, then the array widths will hold the widths of all the characters. Data will hold the raw image data for the SDL_Surface.

Now here's one function that you should remember from the previous lessons (if you have read them). It simply blits one part of a surface onto an other part of some other surface. It will be used when drawing the font.

void fontDrawIMG(SDL_Surface *screen, SDL_Surface *img, int x,

int y, int w, int h, int x2, int y2)

{

SDL_Rect dest;

dest.x = x;

dest.y = y;

SDL_Rect src;

src.x = x2;

src.y = y2;

src.w = w;

src.h = h;

SDL_BlitSurface(img, &src, screen, &dest);

}

Before using a font, we need to initalize it. We have the function initFont for that. It returns a new SDLFont structure. initFont takes the directory of the font as a parameter (the font consists of 3 files that must be one directory). It also takes the rgb color value of the font and an alpha value. If you aren't sure what RGB means, check this page: http://whatis.techtarget.com/definition/0,,sid9_gci212900,00.html. Alpha tells us how much % stuff will shine through the font. For the r, g, b and a values, give floating point numbers from 0 to 1. You may need to experiment with the different settings.

SDLFont *initFont(char *fontdir,float r, float g, float b, float a)

So, if you would want to use a fonts you'd do:

SDLFont *font;

font = initFont("data/fonts/arial", 1, 1, 0, 0.5);

That code would create you a half transparent arial font (if, of course you have an arial font in "data/fonts/arial") with the color yellow.

Anyway, back to initFont. We have some variables at the top of initFont. Most of them are temporary. We'll only return the tempFont variable with the function.

SDLFont *initFont(char *fontdir,float r, float g, float b, float a)

{

// some variables

SDLFont *tempFont; // a temporary font

FILE *fp; // file pointer

char tempString[100]; // temporary string

unsigned char tmp; // temporary unsigned char

int width; // the width of the font

SDL_Surface *tempSurface; // temporary surface

The next lines make the string tempString equal the path of 'font.ini'. If for example the parameter fontdir would be "data/font", then after this operation tempString would be "data/font/font.ini". We read in the width of the font from the ini file. On error we return 0.

// find out about the size of a font from the ini file

sprintf(tempString,"%s/%s",fontdir,"font.ini");

fp = fopen(tempString, "rb");

if( fp == NULL )

{

return 0;

}

fscanf(fp, "%d", &width);

fclose(fp);

Now we will create our font. We first 'new' the tempFont structure. After that we allocate width*width*4 bytes of space for the image data. There are width*width pixels in our font image and since the image will have 4 channels - RGBA, we multiply it by 4. We then give the font structure the width of the font and the width of one character cell (width/16).

// let's create our font structure now

tempFont = new SDLFont;

tempFont->data = new unsigned char[width*width*4];

tempFont->width = width;

tempFont->charWidth = width/16;

Now we must read in the font data. For that we make tempString equal the path to font.raw - the raw grayscale image data of the font. We then load in the font one pixel at a time. When reading in the font image, we store 255 in the rgb channels of the image multiplied by the r, g and b function parameters (to get the correct color). And in the alpha channel of the new SDL surface we store the brightness of the pixel (grayscale color) multiplied by the function parameter a (as in alpha). We store 255*{r,g,b} instead of the font brightness*{r,g,b} in the rgb channels to get a good smooth font at the edges. If we can't read in the font data (some error occurred), we'll return 0.

// open the font raw data file and read everything in

sprintf(tempString,"%s/%s",fontdir,"font.raw");

fp = fopen(tempString, "rb");

if( fp != NULL )

{

for(int i=0;i<width*width;i++)

{

tmp = fgetc(fp);

tempFont->data[i*4] = (unsigned char)255*(unsigned char)r;

tempFont->data[i*4+1] = (unsigned char)255*(unsigned char)g;

tempFont->data[i*4+2] = (unsigned char)255*(unsigned char)b;

tempFont->data[i*4+3] = (unsigned char)(((float)tmp)*a);

}

} else {

return 0;

}

fclose(fp);

Now we will create a new SDL_Surface. This process can be used elsewhere as well, not only in the creation of THIS SDL_Surface. Most of the following code comes from the SDL_CreateRGBSurfaceFrom and SDL_CreateRGBSurface SDL Documentation pages. Check them out for more info on the subject. The r,g,b,a masks tell SDL the byte order of the r,g,b,a values. But what's with the #ifs? From the SDL Doc: SDL interprets each pixel as a 32-bit number, so our masks must depend on the endianness (byte order) of the machine. We create the real surface with the SDL_CreateRGBSurfaceFrom function. As you might have noticed, there's also a SDL_CreateRGBSurface function, that creates a new surface, but doesn't add any data to it. Now, to SDL_CreateRGBSurfaceFrom we pass the image data, the width and the height of the file, the number of bits per pixel, the number of bytes for one row of the image and the rgba masks. We also convert the surface to the display format for faster blitting.

// now let's create a SDL surface for the font

Uint32 rmask,gmask,bmask,amask;

#if SDL_BYTEORDER == SDL_BIG_ENDIAN

rmask = 0xff000000;

gmask = 0x00ff0000;

bmask = 0x0000ff00;

amask = 0x000000ff;

#else

rmask = 0x000000ff;

gmask = 0x0000ff00;

bmask = 0x00ff0000;

amask = 0xff000000;

#endif

tempFont->font = SDL_CreateRGBSurfaceFrom(tempFont->data,

width, width, 32, width*4, rmask, gmask, bmask, amask);

tempFont->font = SDL_DisplayFormatAlpha(tempSurface);

SDL_FreeSurface(tempSurface);

Now let's also load in the widths of the fonts. The code should be easy to follow.

// let's create a variable to hold all the widths of the font

tempFont->widths = new int[256];

// now read in the information about the width of each character

Pages: ← previous Ctrl next
1 2 3