update writer to support rotation and be ~3x faster

pull/3/head
Brian Cappello 2017-06-24 09:06:54 -04:00
rodzic 0de8876e67
commit 85f90b0900
6 zmienionych plików z 126 dodań i 71 usunięć

Wyświetl plik

@ -18,9 +18,9 @@ display = Display(args_required_by_driver)
wri_serif = Writer(display, freeserif) wri_serif = Writer(display, freeserif)
wri_sans = Writer(display, freesans20) wri_sans = Writer(display, freesans20)
Writer.set_clip(True, True) Writer.set_clip(True, True)
wri_serif.printstring('Tuesday\n') wri_serif.draw_text('Tuesday\n')
wri_sans.printstring('8 Nov 2016\n') wri_sans.draw_text('8 Nov 2016\n')
wri_sans.printstring('10.30am') wri_sans.draw_text('10.30am')
display.show() # Display the result display.show() # Display the result
``` ```
@ -33,10 +33,10 @@ display and a complete example of an SSD1306 driver may be found
The ``Writer`` class exposes the following class methods: The ``Writer`` class exposes the following class methods:
1. ``set_textpos`` Args: ``row``, ``col``. This determines where on screen any 1. ``set_position`` Args: ``x``, ``y``. This determines where on screen any
subsequent text is to be rendered. The initial value is (0, 0) - the top left subsequent text is to be rendered. The initial value is (0, 0) - the top left
corner. Arguments are in pixels with positive values representing down and corner. Arguments are in pixels with positive values representing right and
right respectively. They reference the top left hand corner of the first down respectively. They reference the top left hand corner of the first
character to be output. character to be output.
2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are 2. ``set_clip`` Args: boolean ``row_clip``, ``col_clip``. If these are
``True``, characters will be clipped if they extend beyond the boundaries of ``True``, characters will be clipped if they extend beyond the boundaries of
@ -49,7 +49,7 @@ of characters is maintained regardless of the font in use.
## Method ## Method
1. ``printstring`` Arg: a text string. Outputs a text string at the current 1. ``draw_text`` Arg: a text string. Outputs a text string at the current
insertion point. Newline characters are honoured. insertion point. Newline characters are honoured.
## Note on the Writer class ## Note on the Writer class
@ -110,7 +110,7 @@ buffer/device.
### Specifying the font file ### Specifying the font file
Each font file has a ``get_ch()`` function accepting an ASCII character as its Each font file has a ``get_char()`` function accepting an ASCII character as its
argument. It returns a memoryview instance providing access to a bytearray argument. It returns a memoryview instance providing access to a bytearray
corresponding to the individual glyph. The layout of this data is determined by corresponding to the individual glyph. The layout of this data is determined by
the command line arguments presented to the ``font_to_py.py`` utility. It is the command line arguments presented to the ``font_to_py.py`` utility. It is
@ -171,7 +171,7 @@ b'\x1b\x01\x35\x01\x4f\x01\x75\x01\x9e\x01\xb2\x01\xcc\x01\xe0\x01'\
_mvfont = memoryview(_font) _mvfont = memoryview(_font)
# Boilerplate code omitted # Boilerplate code omitted
def get_ch(ch): def get_char(ch):
# validate ch, if out of range use '?' # validate ch, if out of range use '?'
# get offsets into _font and retrieve char width # get offsets into _font and retrieve char width
# Return: memoryview of bitmap, height and width # Return: memoryview of bitmap, height and width
@ -187,7 +187,7 @@ will fit the available space. If it will fit on the assumption that all chars
are maximum width, it can be rendered rapidly without doing a character by are maximum width, it can be rendered rapidly without doing a character by
character check. character check.
``get_ch()`` returns a memoryview of an individual glyph with its dimensions ``get_char()`` returns a memoryview of an individual glyph with its dimensions
and contains all the bytes required to render the character including trailing and contains all the bytes required to render the character including trailing
space. space.

Wyświetl plik

@ -114,7 +114,7 @@ gc.collect()
micropython.mem_info() micropython.mem_info()
def foo(): def foo():
addr, height, width = freeserif.get_ch('a') addr, height, width = freeserif.get_char('a')
foo() foo()

Wyświetl plik

@ -65,7 +65,7 @@ all pixels of a character are rendered identically.
Converting font files programmatically works best for larger fonts. For small Converting font files programmatically works best for larger fonts. For small
fonts, like the 8*8 default used by the SSD1306 driver, it is best to use fonts, like the 8*8 default used by the SSD1306 driver, it is best to use
hand-designed binary font files: these are optiised for rendering at a specific hand-designed binary font files: these are optimised for rendering at a specific
size. size.
# Font file interface # Font file interface
@ -79,13 +79,11 @@ provided to font-to-py:
``hmap`` Returns ``True`` if font is horizontally mapped. Should return ``True`` ``hmap`` Returns ``True`` if font is horizontally mapped. Should return ``True``
``reverse`` Returns ``True`` if bit reversal was specified. Should return ``False`` ``reverse`` Returns ``True`` if bit reversal was specified. Should return ``False``
``monospaced`` Returns ``True`` if monospaced rendering was specified. ``monospaced`` Returns ``True`` if monospaced rendering was specified.
``min_ch`` Returns the ordinal value of the lowest character in the file.
``max_ch`` Returns the ordinal value of the highest character in the file.
Glyphs are returned with the ``get_ch`` method. Its argument is a character Glyphs are returned with the ``get_char`` method. Its argument is a character
and it returns the following values: and it returns the following values:
* A ``memoryview`` object containg the glyph bytes. * A ``memoryview`` object containing the glyph bytes.
* The height in pixels. * The height in pixels.
* The character width in pixels. * The character width in pixels.

Wyświetl plik

@ -71,9 +71,9 @@ else: # I2C
serif = Writer(display, freeserif) serif = Writer(display, freeserif)
sans = Writer(display, freesans20) sans = Writer(display, freesans20)
Writer.set_clip(True, True) # Disable auto scrolling and wrapping. Writer.set_clip(True, True) # Disable auto scrolling and wrapping.
serif.printstring('Tuesday\n') serif.draw_text('Tuesday\n')
sans.printstring('8 Nov 2016\n') sans.draw_text('8 Nov 2016\n')
sans.printstring('10.30am') sans.draw_text('10.30am')
display.show() display.show()

Wyświetl plik

@ -143,7 +143,7 @@ def test_font(fontfile, string):
height = myfont.height() height = myfont.height()
for row in range(height): for row in range(height):
for char in string: for char in string:
data, _, width = myfont.get_ch(char) data, _, width = myfont.get_char(char)
if myfont.hmap(): if myfont.hmap():
render_row_hmap(data, row, height, width, myfont.reverse()) render_row_hmap(data, row, height, width, myfont.reverse())
else: else:
@ -167,7 +167,7 @@ def test_file(fontfile, height, string, *, minchar=32, maxchar=126, defchar=ord(
height = myfont.height() height = myfont.height()
for row in range(height): for row in range(height):
for char in string: for char in string:
data, _, width = myfont.get_ch(char) data, _, width = myfont.get_char(char)
if myfont.hmap(): if myfont.hmap():
render_row_hmap(data, row, height, width, myfont.reverse()) render_row_hmap(data, row, height, width, myfont.reverse())
else: else:

157
writer.py
Wyświetl plik

@ -29,71 +29,128 @@
class Writer(object): class Writer(object):
text_row = 0 # attributes common to all Writer instances # these attributes and set_position are common to all Writer instances
text_col = 0 x_pos = 0
row_clip = False # Clip or scroll when screen full y_pos = 0
col_clip = False # Clip or new line when row is full device = None
screen_height = 0
screen_width = 0
draw_pixel = None
@classmethod @classmethod
def set_textpos(cls, row, col): def set_position(cls, x, y):
cls.text_row = row cls.x_pos = x
cls.text_col = col cls.y_pos = y
@classmethod def __init__(self, device, font, rotation=None):
def set_clip(cls, row_clip, col_clip):
cls.row_clip = row_clip
cls.col_clip = col_clip
def __init__(self, device, font):
super().__init__() super().__init__()
self.device = device self.device = device
self.set_font(font)
self.set_rotation(rotation)
def set_font(self, font):
self.font = font self.font = font
self._draw_char = self._draw_vmap_char
if font.hmap(): if font.hmap():
raise OSError('Font must be vertically mapped') self._draw_char = self._draw_hmap_char
self.screenwidth = device.width # In pixels
self.screenheight = device.height @classmethod
def set_rotation(cls, rotation=None):
rotation = 0 if not rotation else rotation % 360
if not rotation:
cls.draw_pixel = cls._draw_pixel
elif rotation == 90:
cls.draw_pixel = cls._draw_pixel_90
elif rotation == 180:
cls.draw_pixel = cls._draw_pixel_180
elif rotation == 270:
cls.draw_pixel = cls._draw_pixel_270
else:
raise ValueError('rotation must be falsy or one of 90, 180 or 270')
if not rotation or rotation == 180:
cls.screen_width = cls.device.width
cls.screen_height = cls.device.height
else:
cls.screen_width = cls.device.height
cls.screen_height = cls.device.width
def _draw_pixel(self, x, y, color):
self.device.pixel(x, y, color)
def _draw_pixel_90(self, x, y, color):
self.device.pixel(self.device.width - y, x, color)
def _draw_pixel_180(self, x, y, color):
self.device.pixel(self.device.width - x, self.device.height - y, color)
def _draw_pixel_270(self, x, y, color):
self.device.pixel(y, self.device.height - x, color)
def _newline(self): def _newline(self):
height = self.font.height() Writer.x_pos = 0
Writer.text_row += height Writer.y_pos += self.font.height()
Writer.text_col = 0
margin = self.screenheight - (Writer.text_row + height)
if margin < 0:
if not Writer.row_clip:
self.device.scroll(0, margin)
Writer.text_row += margin
def printstring(self, string): def draw_text(self, string):
for char in string: for char in string:
self._printchar(char) self._draw_char(char)
def _printchar(self, char): def _draw_hmap_char(self, char):
if char == '\n': if char == '\n':
self._newline() self._newline()
return return
glyph, char_height, char_width = self.font.get_ch(char)
if Writer.text_row + char_height > self.screenheight: glyph, char_height, char_width = self.font.get_char(char)
if Writer.row_clip:
return if Writer.x_pos + char_width > self.screen_width:
self._newline()
div, mod = divmod(char_width, 8)
bytes_per_row = div + 1 if mod else div
for glyph_row_i in range(char_height):
glyph_row_start = glyph_row_i * bytes_per_row
glyph_row = int.from_bytes(
glyph[glyph_row_start:glyph_row_start + bytes_per_row],
'little'
)
if not glyph_row:
continue
x = Writer.x_pos
y = Writer.y_pos + glyph_row_i
for glyph_col_i in range(char_width):
if glyph_row & (1 << glyph_col_i):
self.draw_pixel(x, y, 1)
x += 1
Writer.x_pos += char_width
def _draw_vmap_char(self, char):
if char == '\n':
self._newline()
return
glyph, char_height, char_width = self.font.get_char(char)
if Writer.x_pos + char_width > self.screen_width:
self._newline() self._newline()
if Writer.text_col + char_width > self.screenwidth:
if Writer.col_clip:
return
else:
self._newline()
div, mod = divmod(char_height, 8) div, mod = divmod(char_height, 8)
gbytes = div + 1 if mod else div # No. of bytes per column of glyph bytes_per_col = div + 1 if mod else div
device = self.device
for scol in range(char_width): # Source column for glyph_col_i in range(char_width):
dcol = scol + Writer.text_col # Destination column glyph_col_start = glyph_col_i * bytes_per_col
drow = Writer.text_row # Destination row glyph_col = int.from_bytes(
for srow in range(char_height): # Source row glyph[glyph_col_start:glyph_col_start + bytes_per_col],
gbyte, gbit = divmod(srow, 8) 'little'
if drow >= self.screenheight: )
break if not glyph_col:
if gbit == 0: # Next glyph byte continue
data = glyph[scol * gbytes + gbyte] x = Writer.x_pos + glyph_col_i
device.pixel(dcol, drow, data & (1 << gbit)) y = Writer.y_pos
drow += 1 for glyph_row_i in range(char_height):
Writer.text_col += char_width if glyph_col & (1 << glyph_row_i):
self.draw_pixel(x, y, 1)
y += 1
Writer.x_pos += char_width