Made from 100% programmer art.

Related Posts

Easier Game Controller Input in SDL with SDL_GameController

SDL_GameController is a better way to manage game controllers, compared to raw SDL_Joystick code, however it's not well documented. Here is how I use it.
Read More…

Component Based Game Engine From Scratch Part 2

This time I explain how to mimic Unity's GetComponent<> system using C++ templates.
Read More…

Component Based Game Engine From Scratch Part 1

This is going to be the first part in a continuing series where I try to explain how and why I'm creating my own game engine using C++ and the SDL library.
Read More…

Text printing using bitmap characters in C++ and SDL

Back in the mists of history, sometime around the 90s it was quite common for games and demos to display text on the screen. Since we’re talking about old computers with barely any usable RAM, the text was stored as images. A giant single image with every character placed within it. To display words programmers had to be a bit inventive. That’s the topic of today’s post.

Back in ye olde times, parts of the image were copied to the screen in a way similar to the newspaper ransom notes you used to get in bad TV shows.

Since we’re talking about mid 90s demos, those words were mostly messages declaring how superior one group of coders was, compared to other rival groups of coders. What did we do before ready access to the Internet? We sat reading scrolling text -

Storing the characters

Your typical “font” (OK, typeface) was an elaborate, hand drawn set of characters that only bothered with the ones needed for its intended purpose. If the person drawing the characters had any sense, they were conveniently laid out in ASCII order. For the purposes of this blog post, we’re going to assume they are. Here’s our unsuspecting collection of characters, you might recognise them -

I spent hours staring at this font as a kid, typing in BASIC listings

I spent hours staring at this font as a kid, typing in BASIC listings

Printing the characters - an overview

Let’s figure out what we’re trying to achieve. If this were an exam paper question, it might be phrased like this:

A bitmap font is defined as a rectangular image, consisting of a grid of characters, arranged in ASCII order. Given a string of text, write an algorithm to display the appropriate characters from the bitmap font. Each character is 16x16 pixels, all characters are arranged in a grid.

Abstracting out the important parts leaves us with the following

  • One image containing 16x16 pixel images of characters
  • Characters in ASCII order
  • A string of text is input
  • Correct 16x16 pixel characters chosen and displayed

Getting each character from the bitmap

To find each character relies on realising the ASCII character set follows a very sensible and logical order, and that by using some simple maths, we can figure out which character to use.

ASCII-Table.svg: ZZT32, derivative work: LanoxxthShaddow [Public domain], [via Wikimedia Commons]

ASCII-Table.svg: ZZT32, derivative work: LanoxxthShaddow [Public domain], [via Wikimedia Commons]

Every programming language worth using (and some that aren’t) have the ability to convert between ASCII characters and their corresponding ASCII code. It’s very easy to take a character, such as [space] and turn it into the decimal value 32.

The clever part is to imagine the bitmap image as a giant array; element zero contains a space, element 1 contains an exclamation and so on. If we take 32 away from the ASCII code, we end up with an index into the image - with some minor fudging to cope with each image being 16x16 pixels, and a bit more fudging to cope with the image being a rectangle, and not a single strip. More on that later, the general idea being to not think of the character set as a 2D image, but as a 1D array.

Turning a 1D array index into a 2D location

So how do we convert “I want character number 14” into “give me character 14,0”, and then “character 14,0 is pixel co-ordinates 250,0”?

Easy, more basic maths! Well, basic if you’ve ever done a computer science course before. Teaching this stuff is my day job, so sit tight for a few minutes while you learn something you already knew -

Integer division, remainders and the modulo operator

Remember being taught maths in primary school, before you learnt what decimal numbers were? Just to distract you for a moment, isn’t it confusing how we call both integers and floating point numbers “decimal”? Look at the ASCII table above, it contains a column saying “decimal”, but they’re not decimal numbers, the space character is code 32, not 32.0 or 32.000 it’s just plain 32. Let’s use the correct names from now on. This is computer science, not maths, so whole numbers are integers, numbers with a decimal point in them are called floating point numbers (they could also be called “real” numbers, too).

So anyway, before you learnt all that confusion, maths was easy. You added up things - one apple, three bananas, five people. You could then divide things between people. I have five apples, there are five people, everyone gets an apple each. I have five apples, there are two people, everyone gets two apples each and there is one left over. This is primary school, remember. Nobody has a knife so no apples get chopped up, we don’t know about halves yet.

Turns out, that kind of maths is totally valid, and has an actual name. We call it integer maths. Integer maths contains two basic functions

  • DIV - How many “whole ones” are in the answer?
  • MOD - How many “remainders” are in the answer?

So in our apples example, five apples DIV two people = two apples each, so four apples used up.

5 DIV 2 = 2

And five apples MOD two people = one apple left over

5 MOD 2 = 1

I could have used androids in my explanation instead, which might have worked better since you can’t cut an android in half.

Converting a 1D index into a 2D index in an array

So, to convert an index in a 1D array into a 2D index into a grid, we need to just know how wide the 2D grid is. Then we need the 1D index and some MOD/DIV maths, like this

xPosition = (index % width)
yPosition = (index / width)

And then to convert those indices into pixel locations, all we need to know is the width and height of each bitmap character, which in our case is 16 pixels. This gives us the following nice pieces of code

xPosition = (index % width) * pixelWidth
yPosition = (index / width) * pixelHeight

Putting this on a display

To recap our imaginary exam question again, but crossing out the parts we’ve done:

A bitmap font is defined as a rectangular image, consisting of a grid of characters, arranged in ASCII order. Given a string of text, write an algorithm to display the appropriate characters from the bitmap font. Each character is 16x16 pixels, all characters are arranged in a grid.

All that’s left is to display the characters. So far I’ve tried to be implementation independent, it shouldn’t matter what language or graphical library you’re using, the point is the algorithm. However, there’s a little technique that we can apply that may or may not work on your system.

Don’t draw directly to the screen

In a modern computer, this doesn’t happen anyway. It’s not 1990 and we’re not directly poking bytes into the VGA card’s RAM. We’re organising bytes in some RAM somewhere that will eventually wind its way through a pipeline and into a texture that gets mapped onto a 2D quad representing a 2D screen, inside a modern 3D graphics card. This stuff is cool, but irrelevant to us right now, remember it though, we might come back here one day.

Think of your screen as layers in Photoshop, or sheets of plastic stacked on top of each other. Stick all your sprites to one sheet, stick the text on another. I have a layer for just the text being printed.

To do this efficiently requires a bit more fun. It’s wasteful to create a “text layer” that is the same size of the screen, if all you’re writing is a line of text saying “Score: 100”. It’d be better if the layer was the exact size it needed to be. So we need an algorithm that can take a string of text, and work out its size. To be extra fancy, let’s make it cope with newlines, so text can be on more than one line.

Remember some things we know about our character set:

  • 16x16 pixel characters
  • Arranged in a grid

If I have one line of text, this is easy. Count the number of characters, multiply by 16 for the width. The height is always 16. What about multiple lines?

Working out the bounding rectangle for ASCII text

Solving this problem requires a programmer’s best tool - that one trick they don’t want you to know, lateral thinking. The text is arranged in lines, we can assume there’s always at least one line. How are “lines” stored in a string? With the \n escape character. A two line piece of text is really stored like this

“this is line one\nthis is line two.”

I hope this renders properly, I half expect some part of this website to interpret the \ n as being an actual newline.

So by counting the number of newlines in a string, we can work out its height. What about the width? That is just the longest line of text. To find that out, we need to count how many characters before coming to a new line, and remember the biggest.

This awkward explanation is why we write pseudocode, here’s the routine

INT maxWidth = 0      // Maximum line width we found
INT numLines = 1      // Number of lines we found, assuming at least one
INT currentWidth = 0  // Current width we've counted up to so far

// Go through each character
for (all characters in input string):
    // Add 1 to the currentWidth if we've not yet found a newline
    if (character is not newline):
        currentWidth++
    else
        // If we've found a newline, add 1 to the newline counter
        numLines++
        // And if that line length is bigger than any
        // we've seen before, remember it
        if (currentWidth > maxWidth)
            maxWidth = currentWidth
        // Reset the current line width ready to start again
        currentWidth = 0
    end
end
// Deal with the situation where there is only one line of text
if (currentWidth > maxWidth)
    maxWidth = currentWidth

Putting it all together

To end this, we just need to glue the two routines together, with a small bit of implementation-specific glue to handle the actual copying of the pixel data.

Here’s the source code written in C++ for SDL2. There’s a small “hack” to cope with the ZX Spectrum’s weird handling of the £ character, you won’t need that.

Adding support for non-ASCII ordered bitmaps and removing the hardcoded image and pixel sizes are challenges left to the reader ;-)

SDL_Texture *printString(std::string text, SDL_Renderer *renderer)
{
	int maxWidth = 0;
	int numLines = 1;
	int currentWidth = 0;
	for (auto const i : text) {
		if (i != '\n')
			currentWidth++;
		else {
			numLines++;
			if (currentWidth > maxWidth)
				maxWidth = currentWidth;
			currentWidth = 0;
		}
	}
	if (currentWidth > maxWidth)
		maxWidth = currentWidth;
	int textWidth = maxWidth * 16;
	int textHeight = numLines * 16;
	SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, textWidth, textHeight);
	//SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND);
	SDL_SetRenderTarget(renderer, texture);
	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
	SDL_RenderClear(renderer);
	int x = 0; int y = 0;
	for (int i = 0; i &lt; text.length(); i++) {
		if (text[i] != '\n') {
			// Figure out what character it is
			int code = text[i] - 32;
			if (text[i] == '£')
				code = 64;
			SDL_Rect src = { (code % 16)*16,(code / 16)*16,16,16 };
			SDL_Rect dest = { x, y, 16, 16 };
			SDL_RenderCopy(renderer, bitmapFont, &src, &dest);
			x+=16;
		} else {
			x = 0;
			y+=16;
		}
	}
	SDL_SetRenderTarget(renderer, NULL);
	return texture;
}

I’m now wondering whether I should set this as a challenge to my A-Level students. If I pre-loaded them with a few graphical routines, I’m sure it’d be within their capacity to think up the main parts of the algorithm.

Did you like this post?

if you did, it'd be really nice if you shared it, or left a comment below

Leave comments here!

Anonymous comments are OK, but may be deleted or moderated before appearing on here