Using Custom Fonts or Bitmap Fonts in MIDP 2.0 Part I

Jump to part: 1 | 2


Have you ever wondered how those other games display scores and text with different styles and colors? You probably found out you can't do that with drawString() and setFont() alone. The truth is that those numbers and letters were drawn using images or what we call Bitmap Fonts.

Ok, so that's really old news. But I'll have you know that about a decade and a half ago, it was the only decent way to draw numbers and text on your game screen. I think that makes it sound even much older...moving on...

Bitmap fonts also solves some portability issues in your game as different phones sometimes have different font sizes and can really screw up your design if your relying only on the drawString() method. Using bitmap fonts ensure that those scores and text will look exactly the same on your target phones.

In this tutorial you will be shown how to use bitmap fonts with a little help from the setClip() method and type casting. If you need a jump starter on clipping, you can follow the tutorial on Clipping Images or Displaying Only Parts of an Image before you continue with this tutorial.

We'll be using the new project templates which you can find here:
Clean MIDP 2.0 Game Templates


The ASCII Table
Like I mentioned earlier, images are used to draw those numbers and letters. Actually, it's an image strip where each frame contains one character. Here's an example of a bitmap font image:

Click to see actual size.
Bitmap font sample zoomed in 2x.
Frame size: 9x10 pixels.
Click on the image to see what it actually looks like.


Since it's an image, you have a higher degree of freedom when it comes to customizing the way the font looks. Just remember that you can cause someones eyes to bleed and ultimately delete your game from their phones if you choose a repulsive set of colors and a barely readable font size. What am I saying?!!

The characters on the bitmap font image are arranged based on the standard ASCII table :

ASCII table

This makes it easy for us to determine the location of a character on the bitmap font image using this formula:


positionX = ((int)theCharacter) * frameWidth;


Type casting a char data type to an int gives you the ordinal value of the character, meaning it's Decimal value in the ASCII table.

You may have noticed that there are black and gray rectangles at the beginning of the image. I placed them there as markers for non-printable characters. Because the first 32 characters in the ASCII table are non-printable, meaning they can't be drawn on the screen. Even the last character in the table, the Delete character, cannot be drawn. So only the characters whose Decimal value is in-between 31 and 127 can be drawn.


Creating the Bimap Font Writer : the clsFont Class
You can now open the project templates you just downloaded in NetBeans. We will start by making a new class called "clsFont". A demo in how to create a new class can be found in part II of the tutorial Basic MIDP 2.0 Game Template : Creating the GameCanvas. When your done, you should now see something like this:


package MyGame;

public class clsFont {

/** Creates a new instance of clsFont */
public clsFont() {
}

}



Next, we'll declare some global variables under the class declaration:


public class clsFont {

// additional space between characters
public int charS = 0;

// max clipping area
public int screenW = 176;
public int screenH = 208;

// flag: set to true to use the Graphics.drawString() method
// this is just used as a fail-safe
public boolean useDefault = false;

// height of characters
public int charH = 10;

// lookup table for character widths
public int[] charW = {
// first 32 characters
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9,
// space
9,
// everything else :P
3, 5, 8, 8, 7, 8, 3, 5, 5, 6,
7, 3, 7, 3, 9, 6, 4, 6, 6, 6,
6, 6, 6, 6, 6, 3, 3, 6, 6, 6,
6, 9, 6, 6, 6, 6, 6, 6, 6, 6,
3, 6, 6, 6, 9, 6, 6, 6, 6, 6,
6, 7, 6, 6, 9, 6, 6, 6, 5, 9,
5, 4, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 3, 4, 6, 3, 9, 6, 6, 6,
6, 6, 6, 6, 6, 6, 9, 6, 6, 6,
5, 3, 5, 4,
// delete character
9};

// the bitmap font image
public Image imgFont;


Press ALT+Shift+F or choose "Fix Imports" from the "Source" menu so NetBeans can automagically add those missing import statements.

The variable charS is used to define additional space between characters. You can change it if you feel that the characters are too close together and hard to read. It can also be useful if you want to do a spring effect animation and make the characters bounce sideways.

The screenW and screenH defines the maximum screen area we can draw on. They are used to make sure that the clipping rectangle always falls in the area defined. You will know more about that later.

Next we have the useDefault variable. This is purely optional and we'll use that to signal if the image file failed to load. More on this later too.

The charH variable stores the maximum height of the characters and is used to adjust the height of the clipping rectangle.

The integer array charW[] holds the pre-calculated widths of each character. We use this to calculate the exact place where each character in a given string should be drawn and how much space each of them should occupy. This way, the string displayed will look more natural. It also helps save some screen space since the space used by each character is adjusted to only how much space is needed. Unlike monoblock style fonts where each character uses the same width even if the characters themselves only take up half as much space. One thing to note about the values stored in charW[] is that they already include adequate character spacing. But you can still use the charS variable to adjust the spacing if necessary.

The last variable imgFont will hold the actual bitmap font image.

We will now add the load() method for loading the image and the unload() method for cleaning up when we're done using the class. Add these lines after the class constructor:


/** Creates a new instance of clsFont */
public clsFont() {
}

public boolean load(String imagePath){
useDefault = false;
try{
// load the bitmap font
if (imgFont != null){
imgFont = null;
}
imgFont = Image.createImage(imagePath);
} catch (Exception ex){
// oohh we got an error then use the fail-safe
useDefault = true;
}
return (!useDefault);
}

public void unload(){
// make sure the object get's destroyed
imgFont = null;
}



The load() method takes the string parameter imagePath which defines the path to the bitmap font image that we want to load. If it fails to do so, the useDefault variable will be set to true. You can check this variable in your game so you know if the bitmap font got loaded or not and act accordingly. The load() method also returns the inverse value of useDefault so you can use the method in your condition statement to load the bitmap font and at the same time check if it was loaded as in the example below:


if (!myFont.load("/images/fonts.png")){
/*
...do something to handle the error
when the image fails to load...
...
...
*/
}




Next we will add the drawChar() method which will be used to draw a single character on the screen. Add it beneath the unload() method:


public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){
// non printable characters don't need to be drawn
if (cIndex < 33){
return;
}

// neither does the delete character
if (cIndex > 126){
return;
}

// get the characters position
int cx = cIndex * 9;

// reset the clipping rectangle
g.setClip(0, 0, screenW, screenH);

// resize and reposition the clipping rectangle
// to where the character must be drawn
g.clipRect(x, y, w, h);

// draw the character inside the clipping rectangle
g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT);
}



The drawChar() method takes a few parameters:
  • Graphics g - the graphics object used to draw the image
  • int cIndex - the ordinal value of the character to be drawn
  • int x - the horizontal position of the character and clipping rectangle on the screen
  • int y - the vertical position of the character and clipping rectangle on the screen
  • int w - the width of the character and clipping rectangle
  • int h - the height of the character and clipping rectangle

The drawChar() method checks to see if the character supposed to be drawn is a non-printable character by checking the value of cIndex. It then computes the position of the character on the image and stores it in for later use. The method resets the clipping rectangle to fullscreen using the setClip() method and adjusts the clipping rectangle to exactly where the character should be drawn and matches it's size by using the clipRect() method. It then draws the character on the screen and inside the clipping rectangle.

Using setClip() in conjunction with clipRect() is a good way of ensuring that the resulting clipping rectangle is within the phones screen boundaries so that the drawing methods will not draw anything outside the screen. Some phones behave erratically when you try draw stuff outside the screen and produce garbled output.

Let's add the final method for the clsFont class, the drawString() method. This is the method we will call in our game to draw the strings, scores, and other text. Add it under the drawChar() method:


public void drawString(Graphics g, String sTxt, int x, int y){

// get the strings length
int len = sTxt.length();

// set the starting position
int cx = x;

// if nothing to draw return
if (len == 0) {
return;
}

// our fail-safe
if (useDefault){
g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT);
return;
}

// loop through all the characters in the string
for (int i = 0; i < len; i++){

// get current character
char c = sTxt.charAt(i);

// get ordinal value or ASCII equivalent
int cIndex = (int)c;

// lookup the width of the character
int w = charW[cIndex];

// draw the character
drawChar(g, cIndex, cx, y, w, charH);

// go to the next drawing position
cx += (w + charS);
}
}


The drawString() method parameters:
  • Graphics g - the Graphics object that will be used to draw the string
  • String sTxt - the string to be drawn
  • int x - the horizontal starting position of the string on the screen
  • int y - the vertical position of the string on the screen


The drawString() method first gets the length of the string and the X position where the first character will be drawn. It then checks the length of the string to see if the string is not empty, otherwise it exits the method.

Now you will get to see what the useDefault is used for. If it's set to true, meaning the bitmap font image wasn't loaded, the regular drawString() method of the Graphics object is used to draw the string instead of using the bitmap fonts.

The drawString() method loops through each character of the string and gets the ordinal value of each character. The ordinal values are used to get each of the characters widths from the lookup table charW[]. It is also passed to the drawChar() method to define the width of the clipping rectangle. After the current character has been drawn, the width is added to the current drawing position and the method proceeds with the next character. If you put a value other than 0 in charS, it will also be added to the current drawing position.

Here's the completed clsFont source code so you can check your work:


package MyGame;

import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

public class clsFont {
// additional space between characters
public int charS = 0;

// max clipping area
public int screenW = 176;
public int screenH = 208;

// flag: set to true to use the Graphics.drawString() method
// this is just used as a fail-safe
public boolean useDefault = false;

// height of characters
public int charH = 10;

// lookup table for character widths
public int[] charW = {
// first 32 characters
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
9, 9,
// space
9,
// everything else XD
3, 5, 8, 8, 7, 8, 3, 5, 5, 6,
7, 3, 7, 3, 9, 6, 4, 6, 6, 6,
6, 6, 6, 6, 6, 3, 3, 6, 6, 6,
6, 9, 6, 6, 6, 6, 6, 6, 6, 6,
3, 6, 6, 6, 9, 6, 6, 6, 6, 6,
6, 7, 6, 6, 9, 6, 6, 6, 5, 9,
5, 4, 6, 4, 6, 6, 6, 6, 6, 6,
6, 6, 3, 4, 6, 3, 9, 6, 6, 6,
6, 6, 6, 6, 6, 6, 9, 6, 6, 6,
5, 3, 5, 4,
// delete
9};

// the bitmap font image
private Image imgFont;

/** Creates a new instance of clsFont */
public clsFont() {
}

public boolean load(String imagePath){
useDefault = false;
try{
// load the bitmap font
if (imgFont != null){
imgFont = null;
}
imgFont = Image.createImage(imagePath);
} catch (Exception ex){
// oohh we got an error then use the fail-safe
useDefault = true;
}
return (!useDefault);
}

public void unload(){
// make sure the object get's destroyed
imgFont = null;
}

public void drawChar(Graphics g, int cIndex, int x, int y, int w, int h){
// non printable characters don't need to be drawn
if (cIndex < 33){
return;
}

// neither does the delete character
if (cIndex > 126){
return;
}

// get the characters position
int cx = cIndex * 9;

// reset the clipping rectangle
g.setClip(0, 0, screenW, screenH);

// resize and reposition the clipping rectangle
// to where the character must be drawn
g.clipRect(x, y, w, h);

// draw the character inside the clipping rectangle
g.drawImage(imgFont, x - cx, y, Graphics.TOP | Graphics.LEFT);
}

public void drawString(Graphics g, String sTxt, int x, int y){
// get the strings length
int len = sTxt.length();

// set the starting position
int cx = x;

// if nothing to draw return
if (len == 0) {
return;
}

// our fail-safe
if (useDefault){
g.drawString(sTxt, x, y, Graphics.TOP | Graphics.LEFT);
return;
}

// loop through all the characters in the string
for (int i = 0; i < len; i++){

// get current character
char c = sTxt.charAt(i);

// get ordinal value or ASCII equivalent
int cIndex = (int)c;

// lookup the width of the character
int w = charW[cIndex];

// draw the character
drawChar(g, cIndex, cx, y, w, charH);

// go to the next drawing position
cx += (w + charS);
}
}

}


Press Shift+F11 or choose "Clean and Build Main Project" from the "Build" menu to test-build your project and to see if anything is missing. You can also choose to click on the toolbar icon with the broom glyph to clean and build your project. If everything went well, we can move on to the next step: Word-Wrap and More.

6 comments   |   post a comment
Thank you! your blog is great!

When we will see a post about a Canvas Form?

Thank you!
Many institutions limit access to their online information. Making this information available will be an asset to all.
Custom Paper Writing
thank you ! that's good article !
said...
Thanks for giving your time and sharing your knowledge.

The font seems to be 8x9, not 9x10 and the emulator hangs if I use 9x10.

Is the setClip/draw vs drawRegion just habit from MIDL 1.0?
said...
The original image for the bitmap font is 9x10...I guess photobucket does some weird stuff to the image. I should have uploaded a zipped file instead.

I always used the setClip/clipRect combination because. I read from multiple sources that drawRegion has a memory leak in most oem implementations. I must say I have never experienced the problem myself...probably never will since I don't use drawRegion at all. The problem was found on MIDP 2.0 devices so it may not apply to more recent versions of MIDP.
said...
This came up in a discussion at http://gamedev.stackexchange.com/questions/35884 where someone who used your code failed to realise that it leaves the clip set to the area of the last letter. I hope this note helps anyone else who runs into the same problem.

I also suggest some small improvements there.