Making a tiny 2x3 bitmap font

Somehow I got curious: what would be the smallest bitmap font that would still be readable?

why 2x3 grid

There are plenty of existing retro 8x16 bitmap fonts, like the ones used in VGA graphics and text-mode consoles. We should probably be able to go much smaller than that. We only need to encode 26 different symbols. If our encoding is perfect - we need roughly 5 bits of information, or 5 pixels, each being either black or white. But since Latin letters tend to be rectangular - we should probably go with a 3x2 or a 2x3 grid. Vertical orientation is more natural for Latin letters and letters are often taller than wider.

So we choose to lay our font over 6 pixels aligned in a 2x3 grid. That gives us 2^6=64 possible combinations of pixels. We only need to pick 26 most meaningful.

brute force

“If in doubt - use brute force”. Rather than inventing combinations from scratch - let’s render all possible combinations and see what our options are. I used the following jsfiddle:

const c = document.querySelector('canvas').getContext('2d');
const px = 8;
for (let i = 0; i < 8; i++) {
  for (let j = 0; j < 8; j++) {
    for (let b = 0; b < 6; b++) {
      const n = i * 8 + j;
      c.fillStyle = (n & (1 << b)) ? '#000' : '#fff';
      c.fillRect((i * 6 + (b & 1)) * px, (j * 6 + (b >> 1)) * px, px, px);
    }
  }
}

All 6-bit combinations of pixels now look like this:

6-bit letter

I’m sure, you’ve quickly recognized a few letters already. But rather than picking the most obvious ones, I’d suggest to go through the letter frequency table and pick the most recognizable for the most frequent ones first.

frequency table

For the English language, the alphabet ordered by frequency is known to be “ETAOINSRHDLUCMFYWGPBVKXQJZ”. So few of them, yet, so hard to pick the right glyphs. Let’s look at the letters one by one and choose the bitmaps that are absolutely easy to guess. Those will be O, I, D, L, C, P, B, Q, J. There are 3 various glyphs suitable for “o” - big block, small 2x2 block on the baseline and a 2x2 block flying above. I picked 2x2 on the baseline to match with the “circles” in the p,b,q,d letters. For “j” I picked the glyph with a missing pixel in the corner to give an impression of a smoothly bended line, unlike the right angle of “L”:

familiar letters

Now it’s time for some imagination and experiments, because the rest of the letters look more like a cipher than a font. We can sacrifice the quality of “q”, “j” or “z”, but we can’t sacrifice “e” or “t”.

Pangrams

I wanted to automate the process as much as possible, so I used another jsfiddle to immediately try my font in various sentences. I used pangrams - short sentences that contain every letter of the alphabet at least once - as well as some regular sentences. Looking at the frequency table often helped to overcome doubts - give the least readable glyph to the least frequent letters (that’s how “x” got its two dots). I also tried to use the symmetry of the letters to make them guessable (“H”, “v”, “X”):

const font = [54, 61, 55, 62, 51, 19, 39, 29, 42, 26, 38, 53, 56, 44, 60, 31, 47, 22, 45, 43, 52, 24, 63, 33, 27, 30];
const c = document.querySelector('canvas').getContext('2d');
const px = 6;
[
  'abcdefghijklmnopqrstuvwxyz',
  'the quick brown fox jumped over a lazy dog',
  'the sex life of a woodchuck is a',
  'provocative question for most',
  'a very simple readable sentence',
  'to be or not to be',
  'that is the question',
].forEach((s, line) => {
  for (let i = 0; i < s.length; i++) {
    for (let b = 0; b < 6; b++) {
      c.fillStyle = (font[s.charCodeAt(i) - 97] & (1 << b)) ? '#000' : '#fff';
      c.fillRect((i * 3 + (b & 1)) * px, (line * 5 + (b >> 1)) * px, px, px);
    }
  }
});

Font is encoded with 6-bit numbers, where lowest bit 0 is top-left corner, bit 1 is top-right corner, the second line of the glyph is bits 2 and 3, and the last line of the glyph is bits 4 and 5. So, for example, glyph “54” which is letter “a” would look like this:

bits     2^i
 01     1  2                       ##
 23  -> 4  8  -> 54=2+4+16+32 -> ##    (a)
 45    16 32                     ####

If this doesn’t look like ‘a’ to you - keep in mind that it’s the best I could come up with from the given 64 tiny glyphs.

The complete font at the moment of writing looks like this:

font

You may play around with other 2x3 bitmaps and customize the font in this jsfiddle I’ve created.

This might be not the most readable font, but for sure it’s the smallest one. So, answering the original question - the smallest readable font would probably be a 3x3 one, but building a 2x3 font has also been exciting.

I hope you’ve enjoyed this article. You can follow – and contribute to – on Github, Mastodon, Twitter or subscribe via rss.

Jul 26, 2020

See also: Partcl - a tiny command language and more.