GFXwithSDLPart4.pdf

(401 KB) Pobierz
GFX with SDL
GFX with SDL
Lesson 4: Bitmap Fonts
by Marius Andra
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 separate
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
1
634482.001.png 634482.002.png
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 easily 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_Surface *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 fonts. 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 initialize 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:
2
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 want to use a font 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
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).
3
SDL_Surface *tempSurface; // temporary surface
// 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 (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 rgw 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
// now let's create a SDL surface for the font
Uint32 rmask,gmask,bmask,amask;
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
for faster blitting.
4
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
sprintf(tempString,"%s/%s",fontdir,"font.dat");
fp = fopen(tempString, "rb");
if( fp != NULL )
{
for(int i=0;i<256;i++)
{
tmp = fgetc(fp);
tempFont->widths[i]=tmp;
}
}
fclose(fp);
And finally, we return the tempFont.
/// return the new font
return tempFont;
}
And that's it with initFont. Now let's go on with drawString. drawString takes the
surface to draw to, the font to draw with, the position where to draw and the
string to draw as arguments. It looks like this:
void drawString(SDL_Surface *screen, SDLFont *font, int x, int y,
char *str, ...)
Since drawString uses the "variable number arguments" thingy, we are gonna
have to "decipher" it. That's done with the following lines. (Take a look at the
Ground Up lesson 8 for more info on functions with a variable number of
arguments)
{
char string[1024]; // Temporary string
va_list ap; // Pointer To List Of Arguments
va_start(ap, str); // Parses The String For Variables
vsprintf(string, str, ap); // Converts Symbols To Actual Numbers
va_end(ap); // Results Are Stored In Text
5
Zgłoś jeśli naruszono regulamin