Making graphics and fonts for embedded systems

Microcontroller systems with graphical displays require a way to display text. In case of alphanumeric displays (like HD44780) it is easy – just send your ASCII bytes to the display. Graphical displays operate on individual pixels, so firmware must generate the graphics and texts on the fly.

In this post I show the complete multi-step process from a TrueType (.ttf) font file to autogenerated C code that can be used by a graphics library to display texts on a microcontroller. All code (including the embedded graphics library) is available on Github.

Make sure to read also my post about a practical application on an ePaper display.

Because of performance limitations the graphics and fonts are stored as bitmaps. It is simply easy for the microcontroller to copy bytes around, rather than running a rendring engine (though simple vector graphics are also very efficient).

The end goal is to have a regular C arrays of uint8_t bytes with each bit holding an individual pixel that can later be transferred by the firmware in appropriate order to the display. Packing pixels into bits is the most efficient way of storage if compression is not used.

Overview

  1. The starting point for fonts is a .ttf file (the “regular” font format used by Linux/Windows/Mac and other OSes)
  2. The starting point for graphics is a .png file (black and white)
  3. Font file is converted into a bitmap containing all necessary glyphs (“characters”) and JSON file that describes where each glyph starts and ends
  4. Python script reads the .png files and generates appropriate C arrays and reads the JSON file describing where within a bitmap a particular glyph lies
  5. Generated C code can be compiled alongside other project files

Finding font files

You have a lot of preinstalled fonts that come with your operating system, however they may not always look the best on small, low-resolution displays (and due to licensing they may not be free to use).

I pick fonts from the Font Library or Google Fonts. At this point – it is all about aesthetics. Some fonts will look good on your small display, some will not. You have to choose between monospaced (each letter has the same width) and proportional fonts. I also advise against serifs – they usually not look good on small displays.

Turning fonts into bitmaps

Download and build FontBuilder. This program automates the whole process. One note about building it in Gentoo – I had to build it by qtchooser -qt=5 -run-tool=qmake && make . Otherwise it would try to build with Qt4 and fail.

The easiest way to import new font files under Linux is to place them in ~/.fonts/ directory.

Step 1

The application has a set of tabs on the left and a set on the right. I prefer to have the “Full font preview” on the right. On the left you can: pick the font, set font size, tweak font width and height. It always distorts the font, but changing the dimensions up to around 10% usually does not make the font look bad.

Things to look at: the image (of course) and dimensions – especially the height if the embedded graphics library has a limitation (eg. multiple of 8 pixels).

Let’s start with a large font to display digits (example use: a thermometer).
(click to enlarge)

Step 2

Fonts have a lot of different glyphs (characters). Not all of them may be useful in your application (like special symbols or diacritics from languages that will never be supported by your device). Each glyph will consume valuable bytes of microcontroller flash memory, so it makes sense to narrow down the character set using the “Characters” tab on the left.

In my case I limited this large font to digits and a minus sign.
(click to enlarge)

Step 3

In the “Layout” tab select “Line layout”, disable “One pixel separator”. You can also tweak the final bitmap size by adding padding.

To make the final dimensions right you may have to tweak:

  • font size (“Font” tab)
  • font DPI (“Font” tab
  • Scale % (“Font” tab)
  • Padding (“Layout” tab)

Tip: if you leave one pixel padding at the top and bottom of the bitmap you can write lines of text “next to each other” (ie. without extra work from the graphics code on the microcontroller) and the rows will have enough vertical spacing to be legible. Yes – it is wasted space, but the graphics library can be simplified. For example if the library expects multiple-of-8 pixels heights it makes sense to have the font 14-pixels high and add padding pixel on top and the bottom, instead of having to add that padding to a 16-pixel high font later on the microcontroller.

Step 4

When the bitmap looks good and dimensions are right you can simply save the bitmap as a PNG file and its description in JSON format.

The file will be white-on-transparent, which may not be exactly what you want to use. I wanted to have simple black-on-white. The Python code generator can handle transparency, but the font must be at least black. You can do it it any graphics editor or using ImageMagick in the terminal:

this will substitute all white with black and leave transparency alone.

The end result looks like this:

Another font examples

I did exactly the same steps to make a smaller fonts that have all popular glyphs:




That is how they look on a real ePaper display. Continue reading for the code generation instructions.

C code

The PNG files have now to be converted into regular C arrays. I wrote a Python script that does that automatically. It takes the input and output directories as command line arguments. The output files are called resources.h and resources.c.

Let’s begin with the representation of bitmaps in my simple library (it is available on Github). There is a single header that defines the types:

Bitmap type holds the dimensions and pointer to the array of pixels. Glyph type holds the width and offset. Finally the font type combines them both. The code generator outputs the bitmap type, bitmap array, glyph array and finally the font struct.

Let’s look at a generated header:

and the data itself:

The file (limited to a single font and cut for clarity) contains the pixel array, bitmap description and font description. I also made a dirty hack… I decreased the width of the hyphen to make it better fit the display (it was just faster to edit a single number, than cutting the bitmap and changing all offsets).

How the code is generated

The script looks for all .png and .json files in the input directory. There are just three functions – make_image_array,serialize_image_array,make_font_description. Two latter ones simply reformat the data and “print” it in a C-compatible way.

The first function is mildly interesting. The script uses Python Imaging Library (PIL) to read .png files at bitmaps. It reads the pixels from left to right, from top to bottom (like a CRT TV would do) and packs 8 pixels into a single byte, that is added into an array. This is a serious implication to the library running on the microcontroller, because the order has to stay the same to make any sense when displayed.

The display I am targeting (2.9″ ePaper) has a “sideways” orientation, ie. has to be written in from top to bottom, from left to right. The easiest way to implement that is to rotate all images before serializing them into the arrays, so there is a rotate variable in the script. In my case all images are rotated 90 degrees counterclockwise (or 270 degrees clockwise). This also simplifies the graphics library when writing proportional text, because it is read “sideways”, so glyphs can easily have arbitrary widths.

Rate this post