Using Bitmap Fonts in Android

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.

Character Sheet

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.

Magnified Map

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.

Glyphs

Glyphs

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:

String with bitmap font

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).
LinkedInRedditTumblrDiggTechnorati FavoritesShare

12 Responses - Add Yours+

  1. dougllas says:

    but if the strings are in another language

  2. kkoenen says:

    I fully implemented this.. and then found out about canvas.drawText . .. haha.. So, why is this better or on what occasion should I use bitmap-fonts in stead of drawText ?

    thnx

    • Impaler says:

      When you use OpenGL you will need to create a texture out of a glyph and it will be very useful.
      For Canvas this has already been done for you.

  3. SecondDerivative says:

    Thanks for the awesome example! It was exactly what I was looking for. The sample project works fine. Unfortunately, when I add the code to my project it doesn’t seem parse the bitmap correctly. For example, with the uppercase letters, I only get the top half of the letter.. and the columns are messed up too. When I print a “P” I get half of an M and half of an N. I didn’t change the code at all, and I’m really quite stumped about what might be causing this.

  4. ahmad says:

    thanks for your sample,
    but there is problem.
    if I want to show a text in textview or any other widget in android, I think this approach does not work. if I am wrong please notice me, or give a solution.
    thanks.

  5. Thank you! Your post was very helpful. It was also very good that your example code has worked out of the box (out of the zip file *g*)! :-)

  6. Hey mate,

    Nice tutorial, do you mind if i use some content to create a post on my own blog? (http://p-xr.com)

    Thanks for the time invested.

    p-xr

  7. Elliott says:

    Dang, you cover everything man. What is your paypal? You deserve some donations.

  8. Kratos says:

    Thanks for this. It’s very helpful and informative.

    Will wait for your next posts. OpenGL perhaps?