Squeezing 64 Characters into 128 Pixels, Part 2
This is a post about the tiny Arduino-based text terminal emulator project. Please see the Hackaday project page for more background.
As I stated in the first part of this write-up, I am trying to show a full text interface on a very small screen. With a screen resolution of 128 pixels and a 6x8 pixel font, “normal” rendering was getting me 21 characters per line. A 4x6 font (helpfully suggested in the Hackaday.io comments) would get me more — 32 characters — but that is still not enough. With a technique called “sub-pixel rendering” I increased the number to 64 characters per line. Much, much better.
That’s two pixels per character — pretty intense density! Too bad the screen looked like a multi-coloured mess.
Smoothing the Pixels
Luckily, I have nerdy friends, and one of them has already experimented with sub-pixel rendering before, including the more advanced techniques described in this source reference. And the advice I got for my predicament is that my implementation was not balancing out the colours against pixel element neighbours.
For each dot in a displayed character, sub-pixel rendering lights up either a red or green or blue element of a pixel alone, instead of all three — this is how we get finer detail than what normal rendering allows. But of course that means that when several dots are next to each other, they show up as different hues and “fight” each other.
To fix that, we diffuse some of the energy for any given dot into the adjoining colour elements. The neighbouring colours end up “neutralizing” the main sub-pixel hue (the pixel components complement each other) and hence the eye is less distracted.
Essentially, it’s a blur effect.
I will admit that I was very skeptical about this. My worry was that the blur effect would completely wash out the fine detail that my tiny two-pixel characters need for any semblance of readability.
And I was wrong. It worked pretty darn well!
It goes to show just how amazing and wacky the human eye is: averaging out the colours totally worked in removing the hues from character edges, yet still kept the text readable — actually even more readable than before due to the lack of colour fringing.
The fine detail did start to disappear when I tried to apply a second layer of blur per the GRC reference suggestions. The second pass is supposed to reduce the fringing even more, so I figured I’d try it out. As expected, the letters looked more “neutral” gray, but definitely not as crisp.
Hence I reverted the blur to the smaller initial amount.
It looks much better in person, I swear! The camera that took the picture gets a bit confused by the moire effect of the TFT pixel matrix, but human eye can see the text quite well (even if it takes a bit of squinting).
Applying blur worked surprisingly well. But there are still bugs to sort out.
One big problem is memory usage. Blurring makes neighbouring character areas overlap each other a bit. I could no longer just blit out one character at a time, and had to add a proper ASCII screen buffer. That added a whole kilobyte of consumed runtime memory to the sketch. The Arduino Uno only has two kilobytes to spare in total, so with the USB keyboard library overhead I actually barely have enough memory for the call stack! Definitely had some crashes happen because of stack overflow.
My next effort might be to fix that first, likely by offloading keyboard input onto a separate Arduino chip.
Meanwhile, I am still in awe of how simple pixel trickery produces such readable results. This is why I like creative restrictions and minimalism — something interesting always comes up in the process.