fast font rendering on graphical displays

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
Post Reply
v923z
Posts: 168
Joined: Mon Dec 28, 2015 6:19 pm

fast font rendering on graphical displays

Post by v923z » Mon Jan 14, 2019 8:58 pm

Hi all,

I would like to refer to two postings on this board, related to efficiently using graphical displays, and in particular, rendering fonts. One of them is quite old, in which @pythoncoder was circulating his idea of storing fonts in the flash viewtopic.php?f=3&t=2667&. He also presented a python implementation, which, however, suffered from the fact that it is interpreted, therefore, relatively slow. I mentioned some weeks ago that this issue can be alleviated in a C module, and set out to implement it. I wanted to retain the elegance of Peter's font description, so the C code is really nothing else than an almost literal translation of the relevant parts of https://github.com/peterhinch/micropyth ... /writer.py This also means that the font generator routine in https://github.com/peterhinch/micropyth ... t_to_py.py should still be applicable.

I have dumped my code onto https://github.com/v923z/ufont-micropython. If someone doesn't want to compile the code, I can upload the binary firmware to dropbox or something similar.

Since one of the typical uses of a microcontroller is to display the results of measurements, the module contains a scalable set of seven-segment fonts for numbers. These can be rendered by calling

Code: Select all

from ufont import seven_segment
    
buffer = bytearray(1000)
memview = memoryview(buffer)
foreground = bytearray([200, 200])
background = bytearray([0, 0])
size, weight = 20, 3
seven_segment(memview, '1', size, weight, foreground, background)
and then sending the content of memview to the display. Typical rendering times are 50 us for a 20-pixel digit, plus 800 us for data transfer. (I had 21 MHz clock rate on the SPI bus.) The foreground and background colours are arbitrary, the only restriction is that they should fit into 16 bits.

By calling the render method, arbitrary fonts can also be generated, if one passes the font description to the method as

Code: Select all

from ufont import render
    
buffer = bytearray(1000)
memview = memoryview(buffer)
pattern = get_character_from(character, font_descriptor)
height = 20 
foreground = bytearray([200, 200])
background = bytearray([0, 0])
render(memview, pattern, height, foreground, background)
(get_character_from is essentially get_ch in https://github.com/peterhinch/micropyth ... esans20.py)
Typical rendering times are 200 us for a 20-point font, thus one should be able to display a single character in less than a ms. As far as I remember, Peter mentioned 10 ms rendering times for colour displays.

A complete high-level implementation of the driver, as well as detailed speed test can be found under https://github.com/v923z/micropython-te ... test.ipynb

I would appreciate any comments or recommendations.

Best,

Zoltán

User avatar
mattyt
Posts: 410
Joined: Mon Jan 23, 2017 6:39 am

Re: fast font rendering on graphical displays

Post by mattyt » Tue Jan 15, 2019 2:34 am

Hi Zoltán,

I haven't got a lot to offer right now but I'm super interested in your work! I've been experimenting with rendering to displays in MicroPython - including fonts - for a while now. Designing the right levels of abstraction is not easy; I'll try and provide some feedback when I have a chance to try out your speed tests. For the record I'm using the M5Stack for a few projects; it also uses an ILI9341.

I also know the CircuitPython folks are trying to extend their displayio module to include some form of font rendering (see tickets tagged with displayio and the Display Text library). We may be able to combine our efforts...

Regards,
Matt

tannewt
Posts: 51
Joined: Thu Aug 25, 2016 2:43 am

Re: fast font rendering on graphical displays

Post by tannewt » Tue Jan 15, 2019 8:07 am

Hi Matt and Zoltán,
I'm curious about your use cases for text and in what way speed matters. Is it initial time to display a static value or rapidly changing values like a terminal while compiling?

I'm working on text for CircuitPython at the moment and am thinking about it a lot. One thing I definitely want to do is support BDF and maybe PCF and TTF files straight from flash because its much easier than needing a preprocessor. It also means we can support all characters in a font (but probably slowly). We then have a glyph cache that speeds up any subsequent use of a glyph.

displayio's approach to rendering to a display is quite different from a framebuf approach because we only compute final pixels as we need it to send to the display. Before that we store a tree of objects on the screen that store pixel and color info separately. This is done to reduce RAM use and all CircuitPython's C code to manage where and when to update parts of the screen.

So, for example, we have 1-bit per pixel bitmaps for each glyph and multiple sprites that hold the position of each occurrence of it.

Thanks for the brainstorming on this!
~Scott

v923z
Posts: 168
Joined: Mon Dec 28, 2015 6:19 pm

Re: fast font rendering on graphical displays

Post by v923z » Tue Jan 15, 2019 9:08 am

Hi Scott,

Thanks for dropping by. Here are a couple of comments.
tannewt wrote:
Tue Jan 15, 2019 8:07 am
Hi Matt and Zoltán,
I'm curious about your use cases for text and in what way speed matters. Is it initial time to display a static value or rapidly changing values like a terminal while compiling?
This issue is not so much about the static values, but measurements on a human scale. If you have a simple application, where you read the temperature every second, you end up with 5+ characters that you want to update. If you take @pythoncoder's values for the rendering times in python, then displaying a single temperature costs at least 50 ms. This is a visible lag, and my example is really minimalistic.
tannewt wrote:
Tue Jan 15, 2019 8:07 am
I'm working on text for CircuitPython at the moment and am thinking about it a lot. One thing I definitely want to do is support BDF and maybe PCF and TTF files straight from flash because its much easier than needing a preprocessor. It also means we can support all characters in a font (but probably slowly). We then have a glyph cache that speeds up any subsequent use of a glyph.
The problem is that there is no fast way of turning a character description into a string of bytes that you can send to the display. I think, the approach that you advocate here also suffers from the same issue. If you want to display some text in blue, and then the same text in red, then you have to recompute the whole byte stream, even if the only change is the colour. This is what I wanted to speed up with my module. (As far as I know, you can't just instruct the ILI9341 module to change only the colour, but leave everything else in place.
tannewt wrote:
Tue Jan 15, 2019 8:07 am
displayio's approach to rendering to a display is quite different from a framebuf approach because we only compute final pixels as we need it to send to the display. Before that we store a tree of objects on the screen that store pixel and color info separately. This is done to reduce RAM use and all CircuitPython's C code to manage where and when to update parts of the screen.

So, for example, we have 1-bit per pixel bitmaps for each glyph and multiple sprites that hold the position of each occurrence of it.
The speed measurements that I quoted indicate that with @pythoncoder's method, the transfer speed is the bottleneck, and not the computation of the pixels, and one would not need the extra abstraction layer that you mention here. I think that is definitely an advantage, both in terms of complexity, and RAM.

Cheers,
Zoltán

v923z
Posts: 168
Joined: Mon Dec 28, 2015 6:19 pm

Re: fast font rendering on graphical displays

Post by v923z » Tue Jan 15, 2019 9:18 am

One thing I have clean forgotten to mention: for an extra factor of approx. 4 in render time, one can implement aliasing, without any overhead on the RAM. (The transfer times will not be affected.) I don't know, whether this is relevant/interesting or not.

OutoftheBOTS_
Posts: 847
Joined: Mon Nov 20, 2017 10:18 am

Re: fast font rendering on graphical displays

Post by OutoftheBOTS_ » Tue Jan 15, 2019 8:41 pm

I may have some useful input to this discussion from my playing with SPI TFTs on Micropython. I played on both ESP8266 and ESP32 with psRAM.

Graphics requires the most amount of RAM out of about anything you r likely to do in Micro-Python. I could use an approach on ESP32 that I couldn't use on ESP8266 because of the fact ESP32 has more RAM than ESP8266

Updating 1 pixel at a time to the screen is super super slow. Being able to create a buffer that held all of the txt that was going to be written to screen greatly improved speed as a DMA transfer of a whole block via SPI is so much faster and of course the bottle neck is the transfer via SPI.

v923z
Posts: 168
Joined: Mon Dec 28, 2015 6:19 pm

Re: fast font rendering on graphical displays

Post by v923z » Wed Jan 16, 2019 7:02 am

OutoftheBOTS_ wrote:
Tue Jan 15, 2019 8:41 pm
Graphics requires the most amount of RAM out of about anything you r likely to do in Micro-Python. I could use an approach on ESP32 that I couldn't use on ESP8266 because of the fact ESP32 has more RAM than ESP8266

Updating 1 pixel at a time to the screen is super super slow. Being able to create a buffer that held all of the txt that was going to be written to screen greatly improved speed as a DMA transfer of a whole block via SPI is so much faster and of course the bottle neck is the transfer via SPI.
You don't need too much RAM. It is enough, if you can store a single character at a time. Here is the relevant part of my code (https://github.com/v923z/micropython-te ... test.ipynb)

Code: Select all

self.width = 240
self.height = 320
self._buffer_height = 5 # 5-6 seems to minimise the time for clear_screen
self.buffer_length = self.width*self._buffer_height # length in 16-bit integers
self.buffer = bytearray(2*self.buffer_length)
With these 2400 bytes, you can render approx. 30-pixel large characters. I think, you can afford this, even on an ESP8266.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: fast font rendering on graphical displays

Post by pythoncoder » Thu Jan 17, 2019 11:53 am

To throw in some comments on my solution. The Writer class for monochrome displays uses bit blitting which is done in C. Unfortunately the framebuf blit method has no means of mapping a monochrome font onto a colour framebuf. This has been discussed but was never implemented.

Consequently my CWriter class for colour displays does (alas) work one pixel at a time.

That said, I have built GUI libraries based on it (example). With the Pyboard as host response, while not instantaneous, is quick enough for practical touch GUI applications. This may not be the case for slower hosts.
Peter Hinch
Index to my micropython libraries.

v923z
Posts: 168
Joined: Mon Dec 28, 2015 6:19 pm

Re: fast font rendering on graphical displays

Post by v923z » Thu Jan 17, 2019 7:12 pm

pythoncoder wrote:
Thu Jan 17, 2019 11:53 am
To throw in some comments on my solution. The Writer class for monochrome displays uses bit blitting which is done in C. Unfortunately the framebuf blit method has no means of mapping a monochrome font onto a colour framebuf. This has been discussed but was never implemented.
I agree, framebuf would be the logical place for such a thing.
pythoncoder wrote:
Thu Jan 17, 2019 11:53 am
That said, I have built GUI libraries based on it (example). With the Pyboard as host response, while not instantaneous, is quick enough for practical touch GUI applications. This may not be the case for slower hosts.
The problem is not so much the speed of the host, but the sheer volume of data that has to be crunched. And this fact might also explain, why it is not a trivial task to implement this method in the framebuf. If you have a 30-point character, that is going to need close to two kb of RAM, so strings obviously can't be sent in a single chunk. But then it is not obvious at all, what the proper way of blocking the data should be. I think, it is much easier to prepare blocks of data in the high-level interface, because there one knows what kind of data one is dealing with. Your original proposal was a smart one.

One should also keep in mind that the framebuf contains one font, but that can't be change. Your solution is much more flexible.

As for the speed: I have an application, where I need to poll something at 50 Hz. This doesn't necessarily mean that I will want to display anything at 50 Hz, but if I do have to display something, and spend 50 ms just by showing a single number, then I am already out of synch. And 50 ms is something that you would see with naked eyes, therefore, it might be rather disturbing. I believe, a factor of 50 (or 10, if you count transfer, too) was worth the effort, and I thought that others might also profit from a fast implementation of your original idea.

shraddhamane
Posts: 2
Joined: Wed Jan 23, 2019 9:14 am
Contact:

Re: fast font rendering on graphical displays

Post by shraddhamane » Thu Jan 24, 2019 3:59 am

Great information

Post Reply