Smoothing Big Fonts On Graphic LCDs

Here’s a neat little trick: take the jaggies out of scaled fonts on the fly! This technique is for use on graphic displays where you might want to scale your fonts up. Normally you’d just write a 2×2 block of pixels for every area where there would have been one pixel and boom, larger font. Problem is, that also multiplies each empty area and you end up with jagged edges in the transitions that really catch your eye.

[David Johnson-Davies] entered big-brain mode and did something much cleverer than the obvious solution of using multiple font files. Turns out if you analyze the smoothing problem you’ll realize that it’s only the angled areas that are to blame, horizontal and vertical scaling are nice and smooth. [David’s] fix looks for checker patterns in what’s being drawn, adding a single pixel in the blank spots to smooth out the edge incredibly well!

The technique has been packaged up in a simple function that [David] wrote to play nicely in the Arduino ecosystem. However, the routine is straightforward and would be quick to implement no matter the language or controller. Keep this one in your back pocket!

Now if all you have on hand is an HD44780 character LCD, that one’s arguably even more fun to hack around on just because you’re so limited on going beyond the hard-coded font set. We’ve seen amazing things like using the custom character slots to play Tetris.

34 thoughts on “Smoothing Big Fonts On Graphic LCDs

  1. Neat. It could be improved by dealing with three-pixel stairstep configurations too though, to fix the glitches in the current implementation (see: the notches in the 3 and 4 at the bottom).

    1. The glitch seems to be due to it not smoothing the pixels if it doesn’t have the checkerboard pattern.

      The solution would be to ignore the empty squares and just smooth everything.
      Ie, if we have two diagonally placed colored blocks, then smooth the neighboring blocks, regardless if they are colored or not. If those pixels are already part of a colored block, then one just adds black to black, so it won’t change.

      1. That would work ok, but not without some drawbacks. Look what it would do to I and J for example. Inside corners would be smoothed on A, B, D, G, 4, 7, etc. which is probably worse than the couple of missing pixels in those few spots. I don’t think it would improve readability.

        1. A simple solution is to have 2 different smoothing algorithms.
          One that takes the non colored blocks into consideration, and one that doesn’t.

          And then just specify what smoothing system to use for each character. That is only 1 bit of extra information.

          Since to what I can see, every time we have the “flaw”, then we don’t seem to have any places where extra smoothing would be problematic. And when we have places where extra smoothing would be problematic, we seem to lack the artifacts. Though, I haven’t run a deep search on every character in the alphabet to see if this is the case, but for those edge cases, one can compromise for one or the other system.

    1. It has other applications though. I came across this problem before in computer mice. at a pixel level they move in a staircase pattern making x axis and y axis movements separately. the mlt04 in the microsoft wmo is the only optical sensor that draws a smooth diagonal line because it has a 1 pixel deadzone and caches the movements.
      some artists still use ball mice to avoid this issue.

        1. Some people love mice and don’t get on with pens.
          Even still, mice are generally much more precise, especially if your tablet is mapped to 2x 27” screens. Pen tablets tend to be faster and more expressive though.

  2. It all depends on the application, microcontroller and memory available. Most simple solution is to upscale, and the smoothing presented here can increase readability a little at the cost of having to post process the bitmap.
    Another solution would be to use a font that scaled to the largest you want to use, then downscale it with bi-linear scaling with sharpening. Something like lanczos scaling (you could store it compressed, ie using run length encoding for example).
    If memory is abundant you could store a font for every scale.
    If cpu processing power is abundant one could use vector fonts that has been optimized for bitmap display. Throw in some rgb subpixel anti aliasing(when using a color display) and it looks as good as android/windows/mac typography.

  3. I recall that Apple laser printers back in early nineties would do this kind of pixel smoothing on bitmaps and fonts before postscript came along. I suspect this style of pixel up-scaling is a solved problem.

  4. Truly one of the best hacks I’ve seen in a while. Upscaling is normally very hard and rarely looks right, but this is very close to something an artist might draw by hand in high res.

    Kind of reminds me of pixel art upscaling algorithms.

  5. And here I thought this was going to be a how-to on how to use those little ubiquitous oled 128×32 and 128×64 displays. I see so many projects out there not using the 128×64 displays properly. Apparently the cheap Chinese manufacturer of these things was kind enough to make the 128×64 displays backwards compatible with the same code/output the 128×32 displays use, but if you dont properly specify the display size when configuring the library the display will make itself look “interlaced” it basically stretches the 128×32 display data into the 128×64 display space by inserting a black row between each active row. This makes your text look like garbage and ruins the geometry of any graphics.

  6. A very similar technique was used in the SAA5050 Teletext chip for ‘character rounding’ the font used in the rendering of teletext pages on TV sets in the 80s – which was also used in the BBC Micro to provide the famous ‘MODE 7’. The SAA5050 was also optimised for interlaced output.

  7. There was a demo back in ’93 or ’94 that used this method with a custom font, each pixel was 1 char wide and it smoothed with 1 whole character filled in the diagonal, one left one right. Very cool font. It was by a Brazilian group, some sub 2k Demo. If I could only find it again…

      1. mov bx,20h ;
        mov al,0f9h ; XXXXXX
        @@again: ; XX XX
        mov dx,ax ; XX
        xor dh,dl ; XXXXXX
        mov dl,dh ; XX
        and dl,0ah ; XX XX
        shr dl,1 ; XXXXXX
        and dl,dh ;
        jz @@skip ; vira
        cmp BYTE PTR [si+bx],010h ;
        je @@skip ; /XXXXXX\
        ; XX/ \XX
        mov dh,dl ; XX\
        and dh,04h ; \XXXXXX\
        and dl,01h ; \XX
        cmp al,10h ; XX\ /XX
        rcl dl,1 ; \XXXXXX/
        or dl,dh ;
        add [si+bx],dl ; Tricky code, eh? :-)
        mov BYTE PTR [si+bx+1],1

  8. This topic has been revisited many times by people doing BBC Micro emulations. (search for jsbeeb). The BBC computer used that same SAA5050 chip, and I think they came up with a similar algorithm. I think it’s a neat trick – especially for a project using a small FPGA with limited on chip memory for the display RAM and for the character generator. Why store big fonts and waste a K or two of ROM space when you can upscale in hardware and smooth out the jaggies?

      1. I can cope with reading data out of a character gen rom and serialising it, but not sure if I am up to decompressing a font definition on the fly. This did make me think though.

        I do have a Spartan-3 based text VGA display for a sequencer project (look Ma, no computers) In my text display, I only permit big fonts to align on even rows and columns. They are double the width and height of regular fonts so it all fits together. I use a two byte representation for each character – one byte for the code point, and one for the attribute byte which determines how the character is displayed – so four bits for the colour, one for reverse video, one for underscore, and one bit for ‘Big Font’ – which I am now going to change to do smoothing as above!

        Ah, this all takes me back about forty years to designing so-called Converged text displays for those Its Better Manually people, trying to find a common symbol set and attribute definition for 5250, 3270, 6580, 5150 … it took weeks then to get a working prototype. With an FPGA I went from an idea to having multi-coloured, highlighted characters on the screen in an afternoon.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.