Android Games

Android Game Development – Using Bitmap Fonts

Android is an OS running on many types of devices with different screen sizes. Because of that it is pretty difficult to address font related issues regarding both size and appearance on these platforms.
To use a consistent font face across devices I have used bitmap fonts. That is, each character is represented by a bitmap.

We can create an image for each letter in the alphabet and number for example and whenever we want to display a text, we draw the images of the characters from the string at the given position.
But wait! Isn’t loading an image for every character overkill? Yes it is. We can use android’s utilities for manipulating bitmaps to our advantage.

All we need is a bitmap containing all the characters we want to use and slice it up into individual bitmaps. To do this the easy way we will use a monospaced font. A monospaced font is a font whose letters and characters occupy the same horizontal space. In other words, the characters have the same width.

A simple character map can look like this.

Note that the background is transparent.
The above character map is arranged in a grid. The width of a character is 8 pixels. The height is 12 pixels. Note that the second row starts at 15 pixels so there is a gap between the rows. It’s just a choice and also how Photoshop broke the lines and I didn’t bother to change them.

The above image shows the beginning of the first row and how the characters are organised into cells.
Following this it is easy to slice it up. A simple for iteration will do the trick. To keep things simple I have created a map of only the English alphabet, digits and a few punctuation marks. You can extend it as you wish. Use a monospaced font in Photoshop or Gimp or whatever image editor you fancy and create your own sheet.
Here are plenty of bitmap fonts to choose from.

How can we use this? My idea to display text onto the screen is to create a drawString method that takes the text to be displayed as a parameter along with the position where we want to display it.

Something like this will do it:

void drawString(Canvas canvas, String text, int x, int y)

I also pass the canvas object in, onto which I want to draw the text. This is just for simplicity. In case of an OpenGL renderer we will have to use billboards (squares with textures). But for the purpose of slicing images and displaying fonts let’s stick with this approach.

Create a simple Android project that use simple 2D canvas. We will draw onto that.
I implemented the SurfaceView to hold my canvas and called it DrawingPanel.
In its constructor I simply register it to receive events when touching the surface and load the resources. The resources are in fact just the images of the glyphs/characters.

Download the following image file and drag it into your eclipse’s projects resource folder under: /res/drawable-mdpi for ADP to generate the id for the resource.

Create the DrawingPanel class.

public class DrawingPanel extends SurfaceView implements SurfaceHolder.Callback {

	private Canvas canvas;		// the canvas to draw on
	private Glyphs glyphs;		// the glyphs

	public DrawingPanel(Context context) {
		super(context);
		// adding the panel to handle events
		getHolder().addCallback(this);

		// initialise resources
		loadResources();

		// making the Panel focusable so it can handle events
		setFocusable(true);
	}

	/** Loads the images of the glyphs */
	private void loadResources() {
		this.glyphs = new Glyphs(BitmapFactory.decodeResource(getResources(), R.drawable.glyphs_green));
		Log.d(TAG, "Green glyphs loaded");
	}

I omitted the other methods that need to be implemented as they are just stubs.

The canvas variable is used to draw the text onto and is obtained at every touch event. You’ll see later in the onTouchEvent method.
The most interesting class is the Glyphs class which holds the association of characters with images. The glyphs variable is instantiated in the loadResources() method. It calls the constructor of the Glyphs class with the character sheet image copied earlier.

Check out the Glyphs class:

package net.obviam.fonts;

import java.util.HashMap;
import java.util.Map;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.util.Log;

/**
 * @author impaler
 *
 */
public class Glyphs {

	private static final String TAG = Glyphs.class.getSimpleName();
	private Bitmap bitmap;	// bitmap containing the character map/sheet

	// Map to associate a bitmap to each character
	private Map<Character, Bitmap> glyphs = new HashMap<Character, Bitmap>(62);

	private int width;	// width in pixels of one character
	private int height;	// height in pixels of one character

	// the characters in the English alphabet
	private char[] charactersL = new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
			'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
			'u', 'v', 'w', 'x', 'y', 'z' };
	private char[] charactersU = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G',
			'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
			'U', 'V', 'W', 'X', 'Y', 'Z' };
	private char[] numbers = new char[] { '1', '2', '3', '4', '5', '6', '7',
			'8', '9', '0' };

	public Glyphs(Bitmap bitmap) {
		super();
		this.bitmap = bitmap;
		this.width = 8;
		this.height = 12;
		// Cutting up the glyphs
		// Starting with the first row - lower cases
		for (int i = 0; i < 26; i++) {
			glyphs.put(charactersL[i], Bitmap.createBitmap(bitmap,
					0 + (i * width), 0, width, height));
		}
		Log.d(TAG, "Lowercases initialised");

		// Continuing with the second row - upper cases
		// Note that the row starts at 15px - hardcoded
		for (int i = 0; i < 26; i++) {
			glyphs.put(charactersU[i], Bitmap.createBitmap(bitmap,
					0 + (i * width), 15, width, height));
		}
		// row 3 for numbers
		Log.d(TAG, "Uppercases initialised");
		for (int i = 0; i < 10; i++) {
			glyphs.put(numbers[i], Bitmap.createBitmap(bitmap,
					0 + (i * width), 30, width, height));
		}
		Log.d(TAG, "Numbers initialised");

		// TODO - 4th row for punctuation
	}

	public Bitmap getBitmap() {
		return bitmap;
	}

	/**
	 * Draws the string onto the canvas at <code>x</code> and <code>y</code>
	 * @param text
	 */
	public void drawString(Canvas canvas, String text, int x, int y) {
		if (canvas == null) {
			Log.d(TAG, "Canvas is null");
		}
		for (int i = 0; i < text.length(); i++) {
			Character ch = text.charAt(i);
			if (glyphs.get(ch) != null) {
				canvas.drawBitmap(glyphs.get(ch), x + (i * width), y, null);
			}
		}
	}
}

The line

private Map<Character, Bitmap> glyphs = new HashMap<Character, Bitmap>(62);

creates the map which associates a bitmap to each character.
To load this up we need the bitmaps for each character. I have created 3 arrays for the characters for which I have bitmaps.
charactersL[] holds the lower case letters of the English alphabet, charactersU[] the upper case letters and numbers[] holds the numbers. As an exercise add the punctuation array as this one is missing.

Note that the order of the characters is the same as in the character sheet.
For convenience I have created an array for each row from the image. To associate the images with the characters I will iterate through them and cut out the respective image from the sheet based on the index. Having a fixed width for the characters, makes all this dead easy. Examine carefully and understand the constructor. It does the slicing and association.

The drawString(Canvas canvas, String text, int x, int y) method does the drawing. It takes the position where to draw, iterates through the passed in text and draws every character progressively. It is easy to calculate the horizontal offset as each character has the same width.

That is it. To try it out add the following method to the DrawingPanel:

public boolean onTouchEvent(MotionEvent event) {
	// draw text at touch
	try {
		canvas = getHolder().lockCanvas();
		synchronized (getHolder()) {
			if (event.getAction() == MotionEvent.ACTION_DOWN
					|| event.getAction() == MotionEvent.ACTION_MOVE) {
				// clear the screen
				canvas.drawColor(Color.BLACK);
				// draw glyphs
				glyphs.drawString(canvas, "Drawing string at "
						+ (int) event.getX() + " " + (int) event.getY(),
						(int) event.getX(), (int) event.getY());
			}
			if (event.getAction() == MotionEvent.ACTION_UP) {
				canvas.drawColor(Color.BLACK);
				glyphs.drawString(canvas, "Drawn string at "
						+ (int) event.getX() + " " + (int) event.getY(),
						(int) event.getX(), (int) event.getY());
			}
		}
	} finally {
		if (canvas != null) {
			getHolder().unlockCanvasAndPost(canvas);
		}
	}
	// event was handled
	return true;
}

Every time we touch the screen, we try to get hold of the canvas to be able to draw onto it. In case of a touch or drag we simply clear the canvas and draw the string onto it.
When we stopped touching the screen we display a slightly different text at the last position.
Finally we release the canvas. Make sure you return true to signal that the event was handled.

That is it. One last thing needed is to set the DrawingPanel to be our view. This one is done in the activity created with the project. I have also disabled the title.

public class PrintingActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // turn off title
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(new DrawingPanel(this));
    }
}

The resulting application should look like this:

To do this with OpenGL follow the same principle and use the bitmaps for textures. I will publish an OpenGL version of this article at some point so stay tuned.

Download the code here (obviam.bitmapfont.tar.gz).

Reference: Using Bitmap Fonts in Android from our JCG partner Tamas Jano from “Against The Grain” blog.

Do not forget to check out our new Android Game ArkDroid (screenshots below). You feedback will be more than helpful!
Related Articles:
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Dani Torrens
Dani Torrens
12 years ago

Hi !! First of all I’d like to say thanks for these nice articles. 
I had a problem in this tutorial because BitmapFactory scaled glyphs image depending of the screen pixels density. I need to pass options to BitmapFactory.decodeResources this way :
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inScaled = false;
this.glyphs = new Glyphs(BitmapFactory.decodeResource(getResources(), R.drawable.glyphs_green,opts));

Thanks again and sorry my poor english ( I’m spanish ).

Claudiu Colteu
11 years ago

the glyphs png needs to go in /res/drawable-nodpi

Back to top button