kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
Fast color text rendering via PR7682.
rodzic
ff08d3526f
commit
c97758f912
|
@ -178,7 +178,7 @@ The index holds two integers (each occupying 2 bytes) per character. The index
|
||||||
has an entry for every character in the specified range, whether or not that
|
has an entry for every character in the specified range, whether or not that
|
||||||
character exists.
|
character exists.
|
||||||
|
|
||||||
Index entries are offsets into the `_font` bytearray represnting the start and
|
Index entries are offsets into the `_font` bytearray representing the start and
|
||||||
end of the glyph. If the font comprises a set of characters which is not
|
end of the glyph. If the font comprises a set of characters which is not
|
||||||
contiguous, missing characters have an index entry which points to the first
|
contiguous, missing characters have an index entry which points to the first
|
||||||
glyph in the `_font` bytearray. This ensures that the default glyph is
|
glyph in the `_font` bytearray. This ensures that the default glyph is
|
||||||
|
@ -253,6 +253,13 @@ each of the mapping options. They are used with drivers for SSD1306 OLEDs,
|
||||||
SSD1963 LCD displays, the official LCD160CR and the Digital Artists 2.7 inch
|
SSD1963 LCD displays, the official LCD160CR and the Digital Artists 2.7 inch
|
||||||
e-paper display.
|
e-paper display.
|
||||||
|
|
||||||
|
# Writing display drivers
|
||||||
|
|
||||||
|
A guide to writing suitable drivers may be found
|
||||||
|
[here](https://github.com/peterhinch/micropython-nano-gui/blob/master/DRIVERS.md#7-writing-device-drivers).
|
||||||
|
These can be very simple as the `FrameBuffer` base class provides much of the
|
||||||
|
functionality.
|
||||||
|
|
||||||
# Appendix 1. The -i --iterate argument
|
# Appendix 1. The -i --iterate argument
|
||||||
|
|
||||||
This specialist arg causes extra code to be included in the font file, to
|
This specialist arg causes extra code to be included in the font file, to
|
||||||
|
|
|
@ -60,7 +60,7 @@ Labels and Fields (from nanogui.py).
|
||||||
2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays.
|
2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays.
|
||||||
2.2.1 [Constructor](./WRITER.md#221-constructor)
|
2.2.1 [Constructor](./WRITER.md#221-constructor)
|
||||||
2.2.2 [Methods](./WRITER.md#222-methods)
|
2.2.2 [Methods](./WRITER.md#222-methods)
|
||||||
2.2.3 [A performance boost](./WRITER.md#223-a-performance-boost)
|
2.2.3 [Performance](./WRITER.md#223-performance) A firmware enhancement.
|
||||||
3. [Notes](./WRITER.md#3-notes)
|
3. [Notes](./WRITER.md#3-notes)
|
||||||
|
|
||||||
###### [Main README](../README.md)
|
###### [Main README](../README.md)
|
||||||
|
@ -88,6 +88,10 @@ very simple version still exists as `writer_minimal.py`.
|
||||||
|
|
||||||
## 1.1 Release Notes
|
## 1.1 Release Notes
|
||||||
|
|
||||||
|
V0.4.3 Aug 2021
|
||||||
|
Supports fast rendering of glyphs to color displays (PR7682). See
|
||||||
|
[Performance](./WRITER.md#223-performance).
|
||||||
|
|
||||||
V0.4.0 Jan 2021
|
V0.4.0 Jan 2021
|
||||||
Improved handling of the `col_clip` and `wrap` options. Improved accuracy
|
Improved handling of the `col_clip` and `wrap` options. Improved accuracy
|
||||||
avoids needless word wrapping. The clip option now displays as much of the last
|
avoids needless word wrapping. The clip option now displays as much of the last
|
||||||
|
@ -115,8 +119,6 @@ for a non-Pyboard target.
|
||||||
4. `writer_tests.py` Test/demo scripts. Import to see usage information.
|
4. `writer_tests.py` Test/demo scripts. Import to see usage information.
|
||||||
5. `writer_minimal.py` A minimal version for highly resource constrained
|
5. `writer_minimal.py` A minimal version for highly resource constrained
|
||||||
devices.
|
devices.
|
||||||
6. `framebuf_utils.framebuf_utils.mpy` A means of improving rendering speed
|
|
||||||
on color displays. Discussed [in 2.2.3](./WRITER.md#223-a-performance-boost)
|
|
||||||
|
|
||||||
Sample fonts:
|
Sample fonts:
|
||||||
1. `freesans20.py` Variable pitch font file.
|
1. `freesans20.py` Variable pitch font file.
|
||||||
|
@ -264,31 +266,16 @@ The `printstring` method works as per the base class except that the string is
|
||||||
rendered in foreground color on background color (or reversed if `invert` is
|
rendered in foreground color on background color (or reversed if `invert` is
|
||||||
`True`).
|
`True`).
|
||||||
|
|
||||||
### 2.2.3 A performance boost
|
### 2.2.3 Performance
|
||||||
|
|
||||||
Rendering performance of the `Cwriter` class is slow: owing to limitations in
|
A firmware change [PR7682](https://github.com/micropython/micropython/pull/7682)
|
||||||
the `framebuf.blit` method the class renders glyphs one pixel at a time. There
|
has enabled a substantial improvement to text rendering speed. This will be
|
||||||
is a way to improve performance. It was developed by Jim Mussared (@jimmo) and
|
incorporated in V1.17 and is available in daily builds. The `Writer` class
|
||||||
consists of a native C module.
|
checks for suitable firmware. If the firmware lacks this enhancement a slower
|
||||||
|
method of rendering is used.
|
||||||
|
|
||||||
This works well on Pyboards (1.x and D) but I have had no success on other
|
The module has a `fast_mode` variable which is set `True` on import if the
|
||||||
platforms including the Raspberry Pi Pico. The code will silently ignore this
|
firmware supports fast rendering. User code should treat this as read-only.
|
||||||
module on other platforms. The following applies only when run on a Pyboard.
|
|
||||||
|
|
||||||
On import, `writer.py` attempts to import a module `framebuf_utils`. If this
|
|
||||||
succeeds, glyph rendering will be substantially faster. If the file is not
|
|
||||||
present the class will work using normal rendering. If the file is missing or
|
|
||||||
invalid a harmless advisory note is printed and the code will run using normal
|
|
||||||
rendering.
|
|
||||||
|
|
||||||
The directory `framebuf_utils` contains the source file, the makefile and a
|
|
||||||
version of `framebuf_utils.mpy` for `armv7m` architecture (e.g. Pyboards).
|
|
||||||
This allows for recompiling for other architectures if anyone feels like
|
|
||||||
experimenting. However the fact that it crashes the Pico suggests that the code
|
|
||||||
is highly specific to the Pybaord.
|
|
||||||
|
|
||||||
The module has a `fast_mode` variable which is set `True` on import if the mode
|
|
||||||
was successfully engaged. User code should treat this as read-only.
|
|
||||||
|
|
||||||
# 3. Notes
|
# 3. Notes
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Location of top-level MicroPython directory
|
|
||||||
MPY_DIR = ../../..
|
|
||||||
|
|
||||||
# Name of module (different to built-in framebuf so it can coexist)
|
|
||||||
MOD = framebuf_utils
|
|
||||||
|
|
||||||
# Source files (.c or .py)
|
|
||||||
SRC = framebuf_utils.c
|
|
||||||
|
|
||||||
# Architecture to build for (x86, x64, armv7m, xtensa, xtensawin)
|
|
||||||
ARCH = armv7m
|
|
||||||
|
|
||||||
include $(MPY_DIR)/py/dynruntime.mk
|
|
|
@ -1,118 +0,0 @@
|
||||||
#define MICROPY_PY_FRAMEBUF (1)
|
|
||||||
|
|
||||||
#include "py/dynruntime.h"
|
|
||||||
|
|
||||||
#if !defined(__linux__)
|
|
||||||
void *memset(void *s, int c, size_t n) {
|
|
||||||
return mp_fun_table.memset_(s, c, n);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Match definition from modframebuf.c.
|
|
||||||
typedef struct _mp_obj_framebuf_t {
|
|
||||||
mp_obj_base_t base;
|
|
||||||
mp_obj_t buf_obj; // need to store this to prevent GC from reclaiming buf
|
|
||||||
void *buf;
|
|
||||||
uint16_t width, height, stride;
|
|
||||||
uint8_t format;
|
|
||||||
} mp_obj_framebuf_t;
|
|
||||||
|
|
||||||
// This points to the real mp_type_framebuf from modframebuf.c.
|
|
||||||
mp_obj_type_t *mp_type_framebuf;
|
|
||||||
|
|
||||||
// Unbound FrameBuffer.pixel function.
|
|
||||||
mp_obj_t framebuf_pixel_obj;
|
|
||||||
|
|
||||||
// render(dest, src, x, y, fgcolor, bgcolor=0)
|
|
||||||
STATIC mp_obj_t framebuf_render(size_t n_args, const mp_obj_t *args) {
|
|
||||||
// Convert dest/src subclass to the native mp_type_framebuf.
|
|
||||||
mp_obj_t dest_in = mp_obj_cast_to_native_base(args[0], MP_OBJ_FROM_PTR(mp_type_framebuf));
|
|
||||||
if (dest_in == MP_OBJ_NULL) {
|
|
||||||
mp_raise_TypeError(NULL);
|
|
||||||
}
|
|
||||||
mp_obj_framebuf_t *dest = MP_OBJ_TO_PTR(dest_in);
|
|
||||||
|
|
||||||
mp_obj_t source_in = mp_obj_cast_to_native_base(args[1], MP_OBJ_FROM_PTR(mp_type_framebuf));
|
|
||||||
if (source_in == MP_OBJ_NULL) {
|
|
||||||
mp_raise_TypeError(NULL);
|
|
||||||
}
|
|
||||||
mp_obj_framebuf_t *source = MP_OBJ_TO_PTR(source_in);
|
|
||||||
|
|
||||||
// Pre-build args list for calling framebuf.pixel().
|
|
||||||
mp_obj_t args_getpixel[3] = { source_in };
|
|
||||||
mp_obj_t args_setpixel[4] = { dest_in };
|
|
||||||
|
|
||||||
mp_int_t x = mp_obj_get_int(args[2]);
|
|
||||||
mp_int_t y = mp_obj_get_int(args[3]);
|
|
||||||
mp_int_t fgcol = mp_obj_get_int(args[4]);
|
|
||||||
mp_int_t bgcol = 0;
|
|
||||||
if (n_args > 5) {
|
|
||||||
bgcol = mp_obj_get_int(args[5]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
(x >= dest->width) ||
|
|
||||||
(y >= dest->height) ||
|
|
||||||
(-x >= source->width) ||
|
|
||||||
(-y >= source->height)
|
|
||||||
) {
|
|
||||||
// Out of bounds, no-op.
|
|
||||||
return mp_const_none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip.
|
|
||||||
int x0 = MAX(0, x);
|
|
||||||
int y0 = MAX(0, y);
|
|
||||||
int x1 = MAX(0, -x);
|
|
||||||
int y1 = MAX(0, -y);
|
|
||||||
int x0end = MIN(dest->width, x + source->width);
|
|
||||||
int y0end = MIN(dest->height, y + source->height);
|
|
||||||
|
|
||||||
for (; y0 < y0end; ++y0) {
|
|
||||||
int cx1 = x1;
|
|
||||||
for (int cx0 = x0; cx0 < x0end; ++cx0) {
|
|
||||||
// source.pixel(cx1, y1)
|
|
||||||
args_getpixel[1] = MP_OBJ_NEW_SMALL_INT(cx1);
|
|
||||||
args_getpixel[2] = MP_OBJ_NEW_SMALL_INT(y1);
|
|
||||||
uint32_t col = mp_obj_get_int(mp_call_function_n_kw(framebuf_pixel_obj, 3, 0, args_getpixel));
|
|
||||||
|
|
||||||
// dest.pixel(cx0, y0, bgcol/fgcol)
|
|
||||||
args_setpixel[1] = MP_OBJ_NEW_SMALL_INT(cx0);
|
|
||||||
args_setpixel[2] = MP_OBJ_NEW_SMALL_INT(y0);
|
|
||||||
if (col == 0) {
|
|
||||||
args_setpixel[3] = MP_OBJ_NEW_SMALL_INT(bgcol);
|
|
||||||
} else {
|
|
||||||
args_setpixel[3] = MP_OBJ_NEW_SMALL_INT(fgcol);
|
|
||||||
}
|
|
||||||
|
|
||||||
mp_call_function_n_kw(framebuf_pixel_obj, 4, 0, args_setpixel);
|
|
||||||
|
|
||||||
++cx1;
|
|
||||||
}
|
|
||||||
++y1;
|
|
||||||
}
|
|
||||||
return mp_const_none;
|
|
||||||
}
|
|
||||||
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_render_obj, 5, 6, framebuf_render);
|
|
||||||
|
|
||||||
mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
|
|
||||||
MP_DYNRUNTIME_INIT_ENTRY
|
|
||||||
|
|
||||||
// import framebuf
|
|
||||||
mp_obj_t modframebuf = mp_import_name(MP_QSTR_framebuf, mp_const_none, 0);
|
|
||||||
// mp_type_framebuf = framebuf.FrameBuffer
|
|
||||||
mp_type_framebuf = MP_OBJ_TO_PTR(mp_load_attr(modframebuf, MP_QSTR_FrameBuffer));
|
|
||||||
|
|
||||||
// framebuf_pixel_obj = mp_type_framebuf.pixel
|
|
||||||
mp_obj_t dest[2];
|
|
||||||
mp_load_method(MP_OBJ_FROM_PTR(mp_type_framebuf), MP_QSTR_pixel, dest);
|
|
||||||
|
|
||||||
// The resulting reference might be heap allocated due to MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG.
|
|
||||||
// So store a reference to it in globals so the GC knows about it.
|
|
||||||
framebuf_pixel_obj = dest[0];
|
|
||||||
mp_store_global(MP_QSTR_pixel, framebuf_pixel_obj);
|
|
||||||
|
|
||||||
mp_store_global(MP_QSTR_render, MP_OBJ_FROM_PTR(&framebuf_render_obj));
|
|
||||||
|
|
||||||
MP_DYNRUNTIME_INIT_EXIT
|
|
||||||
}
|
|
Plik binarny nie jest wyświetlany.
|
@ -1,32 +0,0 @@
|
||||||
import framebuf_utils
|
|
||||||
import framebuf
|
|
||||||
|
|
||||||
# Emulate a display driver subclassed from framebuf.FrameBuffer
|
|
||||||
class Display(framebuf.FrameBuffer):
|
|
||||||
def __init__(self):
|
|
||||||
self.buf = bytearray(4 * 4 * 2)
|
|
||||||
super().__init__(self.buf, 4, 4, framebuf.RGB565)
|
|
||||||
|
|
||||||
device = Display()
|
|
||||||
|
|
||||||
def foo():
|
|
||||||
width = 2 # Glyph dimensions
|
|
||||||
height = 2
|
|
||||||
i = 0
|
|
||||||
|
|
||||||
while True:
|
|
||||||
buf = bytearray(width * height // 8 + 1)
|
|
||||||
fbc = framebuf.FrameBuffer(buf, width, height, framebuf.MONO_HMSB)
|
|
||||||
fbc.pixel(0, 0, 1)
|
|
||||||
print(buf)
|
|
||||||
|
|
||||||
framebuf_utils.render(device, fbc, 1, 1, 0x5555, 0xaaaa)
|
|
||||||
print(device.buf)
|
|
||||||
print(device.pixel(0, 0))
|
|
||||||
print(device.pixel(1, 1))
|
|
||||||
print(device.pixel(2, 1))
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
print(i)
|
|
||||||
|
|
||||||
foo()
|
|
|
@ -1,9 +1,10 @@
|
||||||
# writer.py Implements the Writer class.
|
# writer.py Implements the Writer class.
|
||||||
# Handles colour, word wrap and tab stops
|
# Handles colour, word wrap and tab stops
|
||||||
|
|
||||||
# V0.40 Jan 2021 Improved handling of word wrap and line clip. Upside-down
|
# V0.4.3 Aug 2021 Support for fast blit to color displays (PR7682).
|
||||||
|
# V0.4.0 Jan 2021 Improved handling of word wrap and line clip. Upside-down
|
||||||
# rendering no longer supported: delegate to device driver.
|
# rendering no longer supported: delegate to device driver.
|
||||||
# V0.35 Sept 2020 Fast rendering option for color displays
|
# V0.3.5 Sept 2020 Fast rendering option for color displays
|
||||||
|
|
||||||
# Released under the MIT License (MIT). See LICENSE.
|
# Released under the MIT License (MIT). See LICENSE.
|
||||||
# Copyright (c) 2019-2021 Peter Hinch
|
# Copyright (c) 2019-2021 Peter Hinch
|
||||||
|
@ -14,35 +15,35 @@
|
||||||
|
|
||||||
# Timings based on a 20 pixel high proportional font, run on a pyboard V1.0.
|
# Timings based on a 20 pixel high proportional font, run on a pyboard V1.0.
|
||||||
# Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max.
|
# Using CWriter's slow rendering: _printchar 9.5ms typ, 13.5ms max.
|
||||||
# Using Writer's fast rendering: _printchar 115μs min 480μs typ 950μs max.
|
|
||||||
|
|
||||||
# CWriter on Pyboard D SF2W at standard clock rate
|
|
||||||
# Fast method 500-600μs typical, up to 1.07ms on larger fonts
|
|
||||||
# Revised fast method 691μs avg, up to 2.14ms on larger fonts
|
|
||||||
# Slow method 2700μs typical, up to 11ms on larger fonts
|
|
||||||
|
|
||||||
import framebuf
|
import framebuf
|
||||||
from uctypes import bytearray_at, addressof
|
from uctypes import bytearray_at, addressof
|
||||||
from sys import platform
|
from sys import implementation
|
||||||
|
import os
|
||||||
|
|
||||||
__version__ = (0, 4, 2)
|
__version__ = (0, 4, 3)
|
||||||
|
|
||||||
fast_mode = platform == 'pyboard'
|
def buildcheck(device):
|
||||||
if fast_mode:
|
if not hasattr(device, 'palette'):
|
||||||
|
return False
|
||||||
|
i0, i1, _ = implementation[1]
|
||||||
|
if i0 > 1 or i1 > 16:
|
||||||
|
return True
|
||||||
|
# After release of V1.17 require that build. Until then check for date.
|
||||||
|
# TODO simplify this once V1.17 is released.
|
||||||
try:
|
try:
|
||||||
try:
|
datestring = os.uname()[3]
|
||||||
from framebuf_utils import render
|
date = datestring.split(' on')[1]
|
||||||
except ImportError: # May be running in GUI. Try relative import.
|
date = date.lstrip()[:10]
|
||||||
try:
|
idate = tuple([int(x) for x in date.split('-')])
|
||||||
from .framebuf_utils import render
|
return idate >= (2021, 8, 25)
|
||||||
except ImportError:
|
except AttributeError:
|
||||||
fast_mode = False
|
return False
|
||||||
except ValueError:
|
|
||||||
fast_mode = False
|
|
||||||
if not fast_mode:
|
|
||||||
print('Ignoring missing or invalid framebuf_utils.mpy.')
|
|
||||||
|
|
||||||
|
|
||||||
|
fast_mode = False # False for mono displays although actually these render fast
|
||||||
|
|
||||||
class DisplayState():
|
class DisplayState():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.text_row = 0
|
self.text_row = 0
|
||||||
|
@ -270,6 +271,8 @@ class CWriter(Writer):
|
||||||
|
|
||||||
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
|
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
|
||||||
super().__init__(device, font, verbose)
|
super().__init__(device, font, verbose)
|
||||||
|
global fast_mode
|
||||||
|
fast_mode = buildcheck(device)
|
||||||
if bgcolor is not None: # Assume monochrome.
|
if bgcolor is not None: # Assume monochrome.
|
||||||
self.bgcolor = bgcolor
|
self.bgcolor = bgcolor
|
||||||
if fgcolor is not None:
|
if fgcolor is not None:
|
||||||
|
@ -285,11 +288,12 @@ class CWriter(Writer):
|
||||||
if self.glyph is None:
|
if self.glyph is None:
|
||||||
return # All done
|
return # All done
|
||||||
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
|
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
|
||||||
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
|
fbc = framebuf.FrameBuffer(buf, self.clip_width, self.char_height, self.map)
|
||||||
fgcolor = self.bgcolor if invert else self.fgcolor
|
palette = self.device.palette
|
||||||
bgcolor = self.fgcolor if invert else self.bgcolor
|
palette.bg(self.fgcolor if invert else self.bgcolor)
|
||||||
# render clips a glyph if outside bounds of destination
|
palette.fg(self.bgcolor if invert else self.fgcolor)
|
||||||
render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor)
|
|
||||||
|
self.device.blit(fbc, s.text_col, s.text_row, -1, palette)
|
||||||
s.text_col += self.char_width
|
s.text_col += self.char_width
|
||||||
self.cpos += 1
|
self.cpos += 1
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue