Even Tinier Pixel Font

line.ctrl
4 min readNov 20, 2017

--

This is a post about the tiny Arduino-based text terminal emulator project. Please see the Hackaday project page for more background.

The Adafruit library comes with a default 6x8 pixel font. My tiny console’s TFT screen is 128 pixels by 128 pixels; that gives me 21 columns by 16 rows of text. I want to fit more text into that space, though. Experimenting with sub-pixel rendering gave me up to 64 columns of text, but it was not actually legible without severe squinting.

Meanwhile, a Hackaday.io commenter named Radomir suggested that I use his 4x6 font instead. At 4 pixels by 6 pixels per character I end up fitting in 32 columns by 21 rows of text on screen. That’s actually not bad.

Horizontal resolution is the main limitation that I am fighting against: normal Unix/Linux console usage generates lines about 60+ characters long; ideally, the terminal would show the full line without text wrapping.

Still, even 32 characters per line is better than 21.

One other cool thing is that Radomir’s font is antialiased. That helps readability: instead of having jagged pixel edges the tiny letters look smoother and more rounded.

Canvassing

The source font data is provided as a PNG image, so my first implementation step was to convert it into a packed C array.

Instead of using the Python utility that Radomir wrote for data conversion (my Windows workstation might not be able to run Python), I just loaded up the font image in a browser tab and opened the debug console. The console allows typing in and running ad-hoc JavaScript so I improvised a conversion script on the spot.

First I created a canvas element, then rendered the font data image onto it. Then I iterated over each character, grabbed pixel data from the canvas and mapped the image colours into an intensity value ranging between 0 and 3. The characters are 4 pixels wide and each pixel intensity value needs two bits for storage, so each row of the pixel matrix totals exactly 8 bits — one byte — very convenient.

Then this whole process hit a dead end because the canvas was “tainted by CORS” and could not be used to read pixel data. Oh, the joys of web programming. I had to set up a local dev server (using the awesome budo utility), enable CORS and then re-type the above code again.

I finally ended up with a tidy array of byte values which I converted to hex and concatenated using commas, 6 per line. For example, a ! is represented like this:

0x30, 0x30, 0x20, 0x00, 0x30, 0x00

The six bytes correspond to the six pixel rows of a character box. We can see that the first three rows form the top part of the exclamation mark, followed by an empty row separating the two parts, and then we have a single pixel row for the "dot”. The last row is extra space for descenders. Most rows are 0x30 which is 00110000 in binary: that’s the single center pixel at full brightness. The 0x20 row is the antialiased bottom edge of the upper part of the exclamation mark.

Of course, Radomir’s Python script already contains the above conversion logic, and it would have saved me time to use it to begin with. But figuring out and running ad-hoc JavaScript in the browser console was more fun than installing Python dependencies on Windows.

Lerping

Changing the Arduino character display code was pretty straightforward due to the effort I put into organizing renderer code. The one cumbersome part was the actual pixel colour calculation.

Since the font is antialiased, partial-intensity pixels in the letters had to be proportionally mixed with the background colour — linear interpolation or lerp. To do that I first bit-shift the individual red, green and blue component values for the foreground and background colours, and divide them by 3. Then it is a matter of multiplying the component values by the pixel intensity and adding to the complement background intensity, and bit-shifting the results back into the 16 bit BGR encoding that the screen controller uses.

Of course the initial few tries resulted in a garbled mess on screen. At first I had trouble loading font data into Flash memory (the Arduino PROGMEM keyword does not seem to play well with extern); then I coded the font data lookup logic incorrectly; and then I mistakenly mirrored all the characters left to right. On top of that, I had to correct rendering issues that arose due to the fact that the character height did not exactly line up with the screen edges.

First run was not particularly readable

But it works now, and it is tiny! Tiny, yet readable, which is something I could not say about my previous experiments with shrinking text.

A key piece still missing is the box-drawing characters (ASCII pipes): most text menu and dialog display programs use them. One of my goals is to use the tiny terminal display as a wearable info dashboard, so that is pretty essential to implement.

Radomir’s anti-aliased 4x6 font is a good middle ground between the unreadable mess that my sub-pixel experiments produced and the space-gobbling original 6x8 font. The latter now feels like Duplo blocks compared to Lego pieces —big, chunky and imprecise. Still, it was a great starting point, and progressive refinement is a core part of any DIY project like this.

Check out more details and pictures on the Hackaday project page!

--

--

line.ctrl

Nick Matantsev: independent web developer by day, building electronics/graphics/art projects in my spare time