Adafruit_GFX library with fonts

Adafruit have released a new version of the GFX library with fonts. Hooray!

I have been experimenting with a 320 x 240 ILI9341 display to see what we are getting.

There are 48 font files included but only a few can be fitted into my Leonardo setup with that display.

The fonts are called up with a line like:

tft.setFont(&FreeSerifBold24pt7b);

To simplify the process of viewing different fonts on an actual dislay I created the attached code that makes things a little simpler.

No more explanation, read the comments included and just try it!

I expect some nice tutorials will appear on the Adafruit website soon.

Any_GFX_Font.zip (2.38 KB)

There are problems with the new Font code. It works ok with a 32k Uno / Leonardo. A Mega, Due or M0 Pro must be < 64k.

It is simple enough to fix for the ARM chips for > 64k. It becomes a problem with the MEGA2560 when some Flash data is in near memory and some in far memory.

I am not sure how you can handle this in an Arduino program.

The new fonts look very smart.

David.

The fonts load in the first part of FLASH, then the executable so to be clear it is the font images that must not go over the 64K boundary. The total sketch size can be bigger than 64K.

I found I can load 16 extra fonts (ff1-ff16) and it still works. Sketch size on a Mega is then:

Sketch uses 73,090 bytes (28%) of program storage space. Maximum is 253,952 bytes.

The sketch executable can get bigger, but using FLASH stored strings like this:

tft.print(F("01234567890ABCDEF"));

also pushes upwards towards the 64K near memory limit for FLASH. Also the font files are all different sizes, so fewer than 16 of the larger fonts must be used.

This problem arises because the Mega uses a 16 bit address and memory has to be accessed in pages, thus over 64K (0xFFFF) a different instruction has to be used to page in the extra memory. Adafruit may add code to circumvent this issue, but this could be tricky as my understanding is that the first part of the executable code must be in the first 64K for the processor to boot via the Arduino boot loader.

The indicator I get that the 64K FLASH limit has been exceeded is that the sketch fails completely to execute when uploaded. For the example Any_GFX_Font sketch this happens at about 74K total size.

The DUE has a bigger addressable space so I would expect it to cope with larger (>64K) font sets and not have the same limitation in the bootloader.

Here is a simple mod to make the new font rendering 2 to 5x faster. Suggestion has been passed to Adafruit via GitHub.

Particularly beneficial for larger and bold fonts.

Replace code segment in AdafruitGFX.cpp drawChar() function with code that follows (original code marked by <<<<<<start/end is retained to aid performance comparison)

#define FAST_TEXT // Comment out to revert to old method for speed comparison

#ifdef FAST_TEXT
    uint16_t hpc = 0; // Horizontal foreground pixel count
    for(yy=0; yy<h; yy++) {
      for(xx=0; xx<w; xx++) {
        if(bit == 0) {
          bits = pgm_read_byte(&bitmap[bo++]);
          bit  = 0x80;
        }
        if(bits & bit) hpc++;
        else {
          if (hpc) {
            if(size == 1) {
              drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
            } else {
              fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
            }
            hpc=0;
          }
        }
        bit >>= 1;
      }
      // Draw pixels for this line as we are about to increment yy
      if (hpc) {
        if(size == 1) {
          drawFastHLine(x+xo+xx-hpc, y+yo+yy, hpc, color);
        } else {
          fillRect(x+(xo16+xx-hpc)*size, y+(yo16+yy)*size, size*hpc, size, color);
        }
        hpc=0;
      }
    }
#else
   // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< original code start
    for(yy=0; yy<h; yy++) {
      for(xx=0; xx<w; xx++) {
        if(!(bit++ & 7)) {
          bits = pgm_read_byte(&bitmap[bo++]);
        }
        if(bits & 0x80) {
          if(size == 1) {
            drawPixel(x+xo+xx, y+yo+yy, color);
          } else {
            fillRect(x+(xo16+xx)*size, y+(yo16+yy)*size, size, size, color);
          }
        }
        bits <<= 1;
      }
    }
   // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< original code end
#endif

I am not particularly worried by speed. I can do that later.

What does worry me is the portability problems. i.e. MEGA2560 with a font crossing 64k barrier in Flash memory.

I would agree that no one is going to load all 48 fonts in a real application. However, it is important to get the design correct in the first place. Otherwise it will come back and bite the punter one day. After all, a 2560 sketch is quite likely to contain a lot of data.

Apparently __memx cannot be used by the G++ compiler.
The current Adafruit code only works on a Due if the Fonts are < 64k.

It would seem sensible to use integers in the Font data in Flash. You can always calculate where to read the data with pgm_read_byte_far()

There are several ways to store / draw Fonts. e.g. Marek's ILI9341_due library.
Since it is very good idea (tm) to use a base class like "Adafruit_GFX", it seems wise to choose a robust strategy.

David.

Hi David, yes, you are right about these issues. No doubt they could/will be fixed sometime.

The 64K issues with the Mega has been solved in the past according to this website. But other than reading that page I don't plan to start investigating further as I am going to be very short of free time soon :frowning:

The biggest problem I think at the moment is that no background is rendered. Reading the comments in the cpp file indicate that this will be added though. Effectively a graphics image with background will be generated on a virtual screen (canvas) in RAM (lots will be needed!) then rendered to screen. This will be faster and DMA can then be used on a DUE but may preclude using an UNO. This canvas approach is needed as the glyph boxes can overlap and hence is more difficult to render with background.

I have incorporated and tested the Adafruit font functions into my own library variant (the TFT_HX8357(B/C) library) and it is working really well. I suspect the font file format will become generally accepted as the default for the Arduino platform so adding that compatibility seemed sensible!

Your link looks fairly "old". I will have a read later.

To be honest, I don't see that the overlap is an issue unless using some very oblique fonts.
The background is not much of a problem. Most punters do not even realise that you can draw a plain background colour.

If you positively choose a background, you can positively choose a "rubout" rectangle if your new text is narrower. In much the same way that you "pad" with the legacy font with spaces.

Personally, I can live with regular variable-width Fonts. i.e. glyphs are not essential. I can also live with x1 Fonts. My experiments with 9pt x2 versus 18pt x1 are pretty clear. x2 looks horrible.

David.

Unfortunately some of the fonts like the italics are sufficiently oblique to overlap and so the code must cater for this to be compatible with the existing and user fonts.

Try this code change (change is contained between //>>>>> lines) to the drawChar and you will see what is happening:

    for (xx = 0; xx < w; xx++) {
      if (bit == 0) {
        bits = pgm_read_byte(&bitmap[bo++]);
        bit  = 0x80;
      }
      if (bits & bit) {
        if (size == 1) {
          drawPixel(x + xo + xx, y + yo + yy, color);
        } else {
          fillRect(x + (xo16 + xx)*size, y + (yo16 + yy)*size, size, size, color);
        }
      }
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
      // Temporary code to test draw speed (and minimal flicker!) with background
      // Yes there are still display artifacts but that is not the point!
      else {
        if (size == 1) {
          drawPixel(x + xo + xx, y + yo + yy, bg);
        } else {
          fillRect(x + (xo16 + xx)*size, y + (yo16 + yy)*size, size, size, bg);
        }
      }
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
      bit >>= 1;
    }
  }

As an example "f" will show overlap in font "FreeSerifItalic18pt7b", so printing "ef" in that font with the background draw code above erases part of the "e".

The approach taken by Adafruit is good as only the smallest box that contains any character is drawn. So printing a . takes little time.

P.S. A "glyph" is just a generic name for any font character. Also a "space" does not exist and is simple printed as a cursor move. So if folk try padding with spaces it will not work.

I take your point about "glyph".

All the same, if you want to replace some existing text, you know how big a box you are prepared to print it in. So you would be calling getTextBounds() to see how big your new text will be. In much the same way as you do with a monospaced Font e.g. charWidth * strlen().

With a background printing function you can draw the text plus a coloured rectangle to complete the box.
Yes, I agree that it would be difficult to replace one letter in the middle of some text. After all, it might be wider or narrower than the existing occupant.

In practice, with the transparent print, you will print the background rectangle first. Then write the text on top.
From a hardware point of view, it is more efficient to write background and foreground in one go.

The punter has to choose her priorities. I am not fond of Oblique or Serifs. If you use them, the redrawing is little slower. I am not sure of which style of font handling is the best compromise. We are talking 128x160 to 480x800 class of display. Nothing like PC monitors.

David.

Hi David, all good points.

You are right users must set aside a box and see if the font fits in it. There are a few "Gotchas" though as glyphs do not always sit "on" the baseline, this is obvious for a "y" but also happens with numbers. To compensate for an optical illusion the larger characters with rounded bottoms(!) sit lower. So a "3" sits lower than a "2".

I know you are not interested in speed, but I ran a test on the speed improvement that post #3 suggest. (Mega + ILI9341 with SPI interface)

To print "Hello world" in font ff40 (FreeSerifBold24pt7b) 4 times took 1.1 seconds (yawn).

After the mod it took 266ms. So that is a 4x speed improvement which makes it a worthwhile tweak. Even small fonts get rendered faster.

Edit: 5/1/16 corrected speed improvement factor... I blame the home made mead!

I have not bothered to time my library. Oops. It was longer than I thought : 347ms

That is without any changes. On a 2.4" UNO shield with a ST7781 controller.

(219ms on a M0 Pro with S6D0154 controller)

I am going to the pub. I will try your speed-up later.

David.

To maintain a "level playing field" I have been working with stock Adafruit libraries straight off GitHub (aka Adafruit GFX+ILI9341 libraries) with just that single post #3 tweak.

Your results are very good.

P.S. Finally got around to buying a DUE and some cheap STM32 boards to play with.

I have put the performance enhanced copy of the Adafruit_GFX library here.

This just has changes to the drawChar() function for drawing the free fonts 2-5 times faster on an AVR processor (yet to be tested with a Due). The speed test case being to print "Hello world" in any font (lower case w used deliberately). Conveniently the large and bold fonts show the greatest performance gain.

This is a seamless replacement library in all cases because it uses a generic function for the speed up which already exists within the standard library. The change has been suggested to Adafruit so this tweak may end up in their library.

There are some #defines in the drawChar() function so you can test the performance gains with your own setup. See the inline comments.