In this tutorial, we will look at drawing single-line text to the screen.
When drawinng our text, we will effectively just be cutting out a series of sprites. We will cut these sprites out from a larger bitmap image, called font.bmp. I have created a font image, shown below. Feel free to use this font image in any of your own projects.
We will draw our text by specifying the coordinates of the bottom left corner of the text. We will then iterate through each character in the inputted string, and calculate the coordinates of its corresponding sprite inside the font.bmp file. To do this, we will first need to load in our font as a bitmap structure. After this, we need to draw the corresponding sprite. We will need to slightly modify our drawSprite function from the previous tutorial, as we want to be able to set our text to whatever colour we want, rather than to just display the bitmap. We can accomplish this by adding an "isPlainColour" property to our sprite structure. If this is set to 1, we should replace all of the non-transparent pixels in the sprite with a custom colour. If it is set to 0, we should render the actual pixels that make up the sprite.
To get the coordinates of a character on the font image, we need to create a getCoordinatesFromCharacter function.
Before we can start writing our drawSingleLineText function, we will need to add some code to our main.h file. We should include the string.h header file, as we will need some of its functions later on. We should then add four new properties to our Sprite structure. The first should be an integer (or a charater), and should be the isPlainColour property. If this is set to 1, then all the non-transparent pixels in the sprite should be set to a given colour. If not, then the regular sprite should be drawn. The next three are all of the uint8_t type. They are the red, green and blue values of the colour to draw if the sprite is in plain colour mode.
Next, we should define a new constant. This should be the size of a character and should be set to 8, as each character in the font bitmap I will be using is 8 pixels wide and 8 pixels tall. This constant should be called CHARACTER_SIZE. We can define this constant with the line "#define CHARACTER_SIZE 8". Next, we will need to declare a new global variable. This will be the font bitmap. I will call this bmp_font.
Inside are render.c file, we will need to load our font bitmap. We should do this inside the loadAllBitmaps function. We can load our bitmap with the line "loadBMPFile("font.bmp", &bmp_font);".
Now we need to modify our drawSprite function to draw sprites in plain colour mode if it is enabled. We should do this inside the transparency check if statement (i.e. the line "if(srcPixel->red != TRANSPARENT_RED && srcPixel->green != TRANSPARENT_GREEN && srcPixel->blue != TRANSPARENT_BLUE){"). Inside this if statement, we want to check if plain colour mode has been enabled. We can do this with the line "if(sprite->isPlainColour){". If plain colour mode is enabled, we want to set the red, green and blue values of the pixel to the red, green and blue values stored in the sprite structure. We can do this with the lines: "pixel->red = sprite->red;", "pixel->green = sprite->green;" and "pixel->blue = sprite->blue;".
If plain colour mode is disabled (i.e., in the else block of the "if(sprite->isPlainColour){" statement), then we want to draw the pixels on the source bitmap to the screen. This can be done with the lines: "pixel->red = srcPixel->red;", "pixel->green = srcPixel->green;" and "pixel->blue = srcPixel->blue;". These are the same lines that we wrote in the previous tutorial for drawing an individual pixel to the screen, so they should be familiar.
That should complete our drawSprite function.
Next, we need to write a function to get the coordinates of a character sprite on the source bitmap image. This should be done inside the render.c file. I will call this function "getCoordinatesFromCharacter". This should be a void function, and should take three parameters. The first is the character (named c). The second is a pointer to an integer that will store the x coordinate of the sprite on the source bitmap (named x). The last is a pointer to an integer that will store the y oorinate of the sprite on the source bitmap (named y). We can create this with the line "void getCoordinatesFromCharacter(char c, int * x, int * y){".
The font bitmap I will be using only contains upper case letters. Because of this, the first thing we should do is check if the character is a lower case letter. If so we want to convert it to its upper case form. We can check if the character is a lower case letter using the line "if(c >= 'a' && c <= 'z'){". This simply checks if the value of c is inbetween the integer values of 'a' and 'z' inclusively. If the character is a lower case letter, we want to convert it to its upper case form by subtracting the differene between a lower and upper case 'a'. We can convert a lower case letter to upper case with the line: "c -= ('a' - 'A');".
Now we want to check if our character is an upper case letter. If so, we want to set our x and y pointers to the correct coordinates for the bottom left corner of the corresponding character. We can check if the character is an upper case letter with the line "if(c >= 'A' && c <= 'Z'){". My font bitmap is made up of two rows of 8x8 characters. Each row is 26 characters long. All of the upper case letters are on the top row. This means that we can set the x coordinate to: (integer value of our letter - integer value of 'A') * CHARACTER_SIZE. In code, this can be done with the line: "*x = (c - ('A')) * CHARACTER_SIZE;". We should set the y coordinate of the character to the height of the bitmap subtract the size of one character, using the line: "*y = bmp_font.infoHeader.biHeight - CHARACTER_SIZE;". Remember that the point (0, 0) is the bottom left coordinate of the source bitmap.
If the character is not an upper-case letter, then we next want to check if it is a number. The digits 0 to 9 make up the first 10 characters of the second row. We can check if the character is a number using the line: "} else if(c >= '0' && c <= '9'){". If the character is a number, then we want to set its x coordinate to the value of the digit (equal to the character value - the integer value of '0') multiplied by CHARACTER_SIZE. This an be done with the line: "*x = (c - '0') * CHARACTER_SIZE;". We want to set the y coordinate to the bottom of the second row. This can be done with the line: "*y = bmp_font.infoHeader.biHeight - 2 * CHARACTER_SIZE;".
If the character is not a number, then we will want to check for each special character on the font bitmap and set its corresponding x and y coordinates. We should set the y coordinate of the sprite to the bottom of the second row, as all of the special characters are locate here on our bitmap image. This can be done with the line: "*y = bmp_font.infoHeader.biHeight - 2 * CHARACTER_SIZE;". Then, we can start a switch-case statement to check for each drawn special character. This should be done with the line: "switch(c){". Inside this switch-case statement, we need to check for the following special characters:
We should check for a '.' with the line: "case '.':{". If this is found, we should set the x coordinate with the line "*x = 10 * CHARACTER_SIZE;".
We should check for a ',' with the line: "case ',':{". If this is found, we should set the x coordinate with the line "*x = 11 * CHARACTER_SIZE;".
We should check for a ':' with the line: "case ':':{". If this is found, we should set the x coordinate with the line "*x = 12 * CHARACTER_SIZE;".
We should check for a '!' with the line: "case '!':{". If this is found, we should set the x coordinate with the line "*x = 13 * CHARACTER_SIZE;".
We should check for a '?' with the line: "case '?':{". If this is found, we should set the x coordinate with the line "*x = 14 * CHARACTER_SIZE;".
We should check for a '-' with the line: "case '-':{". If this is found, we should set the x coordinate with the line "*x = 15 * CHARACTER_SIZE;".
We should check for a '+' with the line: "case '+':{". If this is found, we should set the x coordinate with the line "*x = 16 * CHARACTER_SIZE;".
If any other character is inputted, we should process it with a default case, like so: "default:{". Inside this default case, we should set the x coordinate with the line: "*x = 17 * CHARACTER_SIZE;". This will result the coordinates of the blank space character being sent back.
Now we are ready to write our drawSingleLineText function. This should be a void function, and should take seven parameters. The first two should be the x and y coordinates of the bottom left corner of the line of text. These should be integers. The next parameter should be a string, and is the text to display. The next parameter is an integer, and is the size of each character in pixels. The last three are all of the uint8_t type, and are the red, green and blue values used to draw the sprite. We can create this function with the line: "void drawSingleLineText(int x, int y, char text[], float scale, uint8_t red, uint8_t green, uint8_t blue){".
Inside this function, we should first retrieve the length of our text and save it to an integer variable called "length". This can be done with the line "int length = strlen(text);". After this, we should get the scale of our sprite and save it to a floating point variable. The scale is equal to the inputted character size (charSize) divided by the size of a character on the bitmap image. This can be done with the following line of code: "float scale = (float) charSize / CHARACTER_SIZE;".
Next, we need to create a sprite for drawing characters. We can create our sprite with the line: "Sprite character;". Next, we need to set the properties of this sprite. To set the bitmap pointer of the sprite, we should use the line: "character.bitmap = &bmp_font;". To set the x an y scales of the sprite, we should use the lines: "character.xScale = scale;" and "character.yScale = scale;". Next we should set the centre coordinates. We want to draw from the bottom left corner, so should set the centre coordinates to the point (0,0). This can be done with the lines: "character.centreX = 0;" and "character.centreY = 0;". After this we should enable plain colour mode with the line: "character.isPlainColour = 1;". To finish off the initial properties, we should set the RGB values of the sprite to the RGB values passed into the function. This can be done with the lines: "character.red = red;", "character.green = green;" and "character.blue = blue;".
Now we need to iterate through each character. To do this, we should first create a loop counter with the line: "int i;". After this, we can start the loop with the line: "for(i = 0; i < length; i++){".
Inside the loop, we should first get the coordinates of our character on the font bitmap. We should use the getCoordinatesFromCharacter function from this, and save the output to the sprite startX and startY properties. We can do this with the line: "getCoordinatesFromCharacter(text[i], &character.startX, &character.startY);". Once we have retrieved the starting coordinates, we can calculate the ending coordinates using the lines: "character.endX = character.startX + CHARACTER_SIZE;" and "character.endY = character.startY + CHARACTER_SIZE;".
With the coordinates on our character on the font bitmap calculated, we can now draw the current character using the line: "drawSprite(x + i * scale * CHARACTER_SIZE, y, &character);". That finishes our drawSingleLineText function.
With our drawSingleLineText function written, we can now call it inside the render function. As an example, I will draw three lines of text to the screen, using the lines: 'drawSingleLineText(50, 400, "Hello world!", 48, 255, 0, 0);', 'drawSingleLineText(200, 250, "This is a test", 16, 0, 255, 0);' and 'drawSingleLineText(100, 100, "0123456789.,:!?-+", 8, 0, 0, 255);'.
Below is the code for this tutorial. I have only included the main.h and render.c files, as we have made no changes to our main.c file. I will be compiling the code with the MinGW compiler. I will use the compile command "gcc main.c -lgdi32 -o app" for this. You should be able to use any compiler so long as you are able to link the gdi32 library.
main.h
render.c
Running our program should give the following output:
That's all for this tutorial. In the next tutorial, we will look at drawing multi-line text.