GfxLCD and drawing a text

Finally, something that is missing from the start, the ability to write some text on display. In Doton we needed an ability to display numbers and we have done it via images. It was a partial solution but good enough. But we want more 🙂

So this time fonts, text and another step in endless struggle with displaying anything anywhere 🙂

Planning

Ok. The plan is simple, write a text somehow:) But how?
First, I thought that we could use a PIL library. It would generate an image and image can be displayed with ease. But this is not what I intended. If you want you can do this anytime. Without any improvements.
What I want, is a custom way of doing this and a built-in font. A font can be described in hex format, each row translated to bits gives us a map of pixels.
So we will create an abstract class and font classes with an array of chars that describe how letters look alike.

Drawing a font, how to do it?
We have a map of pixels so we will go row by row and enable pixel on required coordination. And this is a task for draw_pixel function 🙂

Setup

In Chip class add abstract functions for drawing text:

    @property
    def font(self):
        """get current font"""
        return self.options['font']

    @font.setter
    def font(self, font):
        """set ttf font"""
        self.options['font'] = font

    @abc.abstractmethod
    def draw_text(self, pos_x, pos_y, text):
        """draw a text"""
        pass

This forces implementation in chip drivers.
For now, the font is not set but later we will create a default one. And each subclass can set other fonts as default.

Font

We need an abstract class that would define font methods. For sure we need to return hex array for required letter and font size. This also implies that all letters in the font have the same size.

Font size is required when we calculate an x-offset and width of the font.

"""Font abstract"""
import abc


class Font(metaclass=abc.ABCMeta):
    font = []
    size = (0, 0)

    def __init__(self):
        pass

    def get(self, letter):
        """return array with letter"""
        return self.font[ord(letter)]

    @property
    def size(self):
        """get font size"""
        return self.size

All classes that inherit it should define a letter size and a dictionary with hex representation of chars:

from gfxlcd.abstract.font import Font


class Font8x8(Font):
    size = (8, 8)
    font = [
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0000 (nul)
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0001
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0002
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0003
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0004
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0005
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0006
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0007
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0008
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0009
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000A
        
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000B
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000C
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000D
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000E
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+000F
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0010
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0011
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0012
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0013
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0014
        
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0015
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0016
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0017
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0018
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0019
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001A
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001B
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001C
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001D
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001E
        
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+001F
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0020 (space)
        [ 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00],   #   U+0021 (!)
        [ 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0022 (")
        [ 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00],   #   U+0023 (#)
        [ 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00],   #   U+0024 ($)
        [ 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00],   #   U+0025 (%)
        [ 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00],   #   U+0026 (&)
        [ 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0027 (')
        [ 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00],   #   U+0028 (()
        
        [ 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00],   #   U+0029 ())
        [ 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00],   #   U+002A (*)
        [ 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00],   #   U+002B (+)
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06],   #   U+002C (,)
        [ 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00],   #   U+002D (-)
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00],   #   U+002E (.)
        [ 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00],   #   U+002F (/)
        [ 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00],   #   U+0030 (0)
        [ 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00],   #   U+0031 (1)
        [ 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00],   #   U+0032 (2)
        
        [ 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00],   #   U+0033 (3)
        [ 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00],   #   U+0034 (4)
        [ 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00],   #   U+0035 (5)
        [ 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00],   #   U+0036 (6)
        [ 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00],   #   U+0037 (7)
        [ 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00],   #   U+0038 (8)
        [ 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00],   #   U+0039 (9)
        [ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00],   #   U+003A (:)
        [ 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06],   #   U+003B (#  )
        [ 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00],   #   U+003C (<) [ 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00], # U+003D (=) [ 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00], # U+003E (>)
        [ 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00],   #   U+003F (?)
        [ 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00],   #   U+0040 (@)
        [ 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00],   #   U+0041 (A)
        [ 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00],   #   U+0042 (B)
        [ 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00],   #   U+0043 (C)
        [ 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00],   #   U+0044 (D)
        [ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00],   #   U+0045 (E)
        [ 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00],   #   U+0046 (F)
        
        [ 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00],   #   U+0047 (G)
        [ 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00],   #   U+0048 (H)
        [ 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00],   #   U+0049 (I)
        [ 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00],   #   U+004A (J)
        [ 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00],   #   U+004B (K)
        [ 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00],   #   U+004C (L)
        [ 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00],   #   U+004D (M)
        [ 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00],   #   U+004E (N)
        [ 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00],   #   U+004F (O)
        [ 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00],   #   U+0050 (P)
        
        [ 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00],   #   U+0051 (Q)
        [ 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00],   #   U+0052 (R)
        [ 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00],   #   U+0053 (S)
        [ 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00],   #   U+0054 (T)
        [ 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00],   #   U+0055 (U)
        [ 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00],   #   U+0056 (V)
        [ 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00],   #   U+0057 (W)
        [ 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00],   #   U+0058 (X)
        [ 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00],   #   U+0059 (Y)
        [ 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00],   #   U+005A (Z)
        
        [ 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00],   #   U+005B ([)
        [ 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00],   #   U+005C (\)
        [ 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00],   #   U+005D (])
        [ 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00],   #   U+005E (^)
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF],   #   U+005F (_)
        [ 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+0060 (`)
        [ 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00],   #   U+0061 (a)
        [ 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00],   #   U+0062 (b)
        [ 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00],   #   U+0063 (c)
        [ 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00],   #   U+0064 (d)
        [ 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00],   #   U+0065 (e)
        
        [ 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00],   #   U+0066 (f)
        [ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F],   #   U+0067 (g)
        [ 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00],   #   U+0068 (h)
        [ 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00],   #   U+0069 (i)
        [ 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E],   #   U+006A (j)
        [ 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00],   #   U+006B (k)
        [ 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00],   #   U+006C (l)
        [ 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00],   #   U+006D (m)
        [ 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00],   #   U+006E (n)
        [ 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00],   #   U+006F (o)
        
        [ 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F],   #   U+0070 (p)
        [ 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78],   #   U+0071 (q)
        [ 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00],   #   U+0072 (r)
        [ 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00],   #   U+0073 (s)
        [ 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00],   #   U+0074 (t)
        [ 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00],   #   U+0075 (u)
        [ 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00],   #   U+0076 (v)
        [ 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00],   #   U+0077 (w)
        [ 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00],   #   U+0078 (x)
        [ 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F],   #   U+0079 (y)
        [ 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00],   #   U+007A (z)
        
        [ 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00],   #   U+007B ([)
        [ 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00],   #   U+007C (|)
        [ 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00],   #   U+007D (])
        [ 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],   #   U+007E (~)
        [ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]    #   U+007F
    ]

I made some shortcuts and took 8×8 font from https://github.com/dhepper/font8x8.

Draw text

We may implement drawing function. Seems it can be universal because we use draw_pixel. So let’s put it in Pixel class:

    def draw_text(self, pos_x, pos_y, text):
        """draw a text"""
        font = self.options['font']
        idx = 0
        for letter in text:
            self._draw_letter(pos_x + idx, pos_y, letter)
            idx += font.size[0]

    def _draw_letter(self, pos_x, pos_y, letter):
        """draw a letter"""
        font = self.options['font']
        bits = font.size[0]
        for row, data in enumerate(font.get(letter)):
            for bit in range(bits):
                if data & 0x01:
                    self.draw_pixel(pos_x + bit, pos_y + row)
                data >>= 1

How does it work?
We have a function responsible for drawing a text. It calls draw_letter with the correct offset. And the correct offset is increased by font width.
Imagine that we have a letter a, size of char is 8×8 and hex for it is:

[0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00],   #   U+0061 (a)

Function draw_text call draw_letter and main loop iterate over each row of the letter.
Next, each row is drawn via draw_pixel. In the case of the letter a, first two rows are empty, and the third row has 0x1E value.
Knowing its size (8 pixels) we check what bits are set and draw a pixel corresponding to it.

Demos

I’m gonna put this line in Chip class so all LCDs would have a default font 8×8:

self.options['font'] = Font8x8()

and write a demo scripts for NJU and SSD chip:

import RPi.GPIO
import sys
from PIL import Image
sys.path.append("../../")
from gfxlcd.driver.nju6450.gpio import GPIO
from gfxlcd.driver.nju6450.nju6450 import NJU6450
RPi.GPIO.setmode(RPi.GPIO.BCM)

lcd = NJU6450(122, 32, GPIO())
lcd.rotation = 0
lcd.init()
lcd.auto_flush = False

lcd.draw_text(25, 1, "Star Wars")

image_file = Image.open("assets/20x20.png")
lcd.threshold = 0

lcd.draw_image(0, 0, image_file)

lcd.flush(True)

The demo for SSD1306 only differ in LCD creation (and import 🙂 ):

lcd = SSD1306(128, 64, SPI())

What about ILI9325?
No change required, we just need a demo script:

import RPi.GPIO
import sys
from PIL import Image
sys.path.append("../../")
from gfxlcd.driver.ili9325.gpio import GPIO as ILIGPIO
from gfxlcd.driver.ili9325.ili9325 import ILI9325
RPi.GPIO.setmode(RPi.GPIO.BCM)

drv = ILIGPIO()
drv.pins['LED'] = 6
drv.pins['CS'] = 18
lcd = ILI9325(240, 320, drv)
lcd.rotation = 0
lcd.init()
lcd.auto_flush = False

lcd.color = (255, 255, 255)
lcd.draw_text(25, 1, "Star Wars")
lcd.draw_text(30, 10, "Death Star")

image_file = Image.open("assets/20x20.png")
lcd.transparency_color = (0, 0, 0)

lcd.draw_image(0, 0, image_file)

Good the same code works on all supported displays.

Summary

We finally have this nice ability to display a text. And we have our first font with size 8×8 pixels. It looks nice on small display. On bigger ILI text is small but readable. So it works 🙂
Task completed.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s