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
|
||||
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
|
||||
contiguous, missing characters have an index entry which points to the first
|
||||
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
|
||||
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
|
||||
|
||||
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.1 [Constructor](./WRITER.md#221-constructor)
|
||||
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)
|
||||
|
||||
###### [Main README](../README.md)
|
||||
|
@ -88,6 +88,10 @@ very simple version still exists as `writer_minimal.py`.
|
|||
|
||||
## 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
|
||||
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
|
||||
|
@ -115,8 +119,6 @@ for a non-Pyboard target.
|
|||
4. `writer_tests.py` Test/demo scripts. Import to see usage information.
|
||||
5. `writer_minimal.py` A minimal version for highly resource constrained
|
||||
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:
|
||||
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
|
||||
`True`).
|
||||
|
||||
### 2.2.3 A performance boost
|
||||
### 2.2.3 Performance
|
||||
|
||||
Rendering performance of the `Cwriter` class is slow: owing to limitations in
|
||||
the `framebuf.blit` method the class renders glyphs one pixel at a time. There
|
||||
is a way to improve performance. It was developed by Jim Mussared (@jimmo) and
|
||||
consists of a native C module.
|
||||
A firmware change [PR7682](https://github.com/micropython/micropython/pull/7682)
|
||||
has enabled a substantial improvement to text rendering speed. This will be
|
||||
incorporated in V1.17 and is available in daily builds. The `Writer` class
|
||||
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
|
||||
platforms including the Raspberry Pi Pico. The code will silently ignore this
|
||||
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.
|
||||
The module has a `fast_mode` variable which is set `True` on import if the
|
||||
firmware supports fast rendering. User code should treat this as read-only.
|
||||
|
||||
# 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.
|
||||
# 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.
|
||||
# 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.
|
||||
# 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.
|
||||
# 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
|
||||
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'
|
||||
if fast_mode:
|
||||
def buildcheck(device):
|
||||
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:
|
||||
from framebuf_utils import render
|
||||
except ImportError: # May be running in GUI. Try relative import.
|
||||
try:
|
||||
from .framebuf_utils import render
|
||||
except ImportError:
|
||||
fast_mode = False
|
||||
except ValueError:
|
||||
fast_mode = False
|
||||
if not fast_mode:
|
||||
print('Ignoring missing or invalid framebuf_utils.mpy.')
|
||||
datestring = os.uname()[3]
|
||||
date = datestring.split(' on')[1]
|
||||
date = date.lstrip()[:10]
|
||||
idate = tuple([int(x) for x in date.split('-')])
|
||||
return idate >= (2021, 8, 25)
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
fast_mode = False # False for mono displays although actually these render fast
|
||||
|
||||
class DisplayState():
|
||||
def __init__(self):
|
||||
self.text_row = 0
|
||||
|
@ -270,6 +271,8 @@ class CWriter(Writer):
|
|||
|
||||
def __init__(self, device, font, fgcolor=None, bgcolor=None, verbose=True):
|
||||
super().__init__(device, font, verbose)
|
||||
global fast_mode
|
||||
fast_mode = buildcheck(device)
|
||||
if bgcolor is not None: # Assume monochrome.
|
||||
self.bgcolor = bgcolor
|
||||
if fgcolor is not None:
|
||||
|
@ -285,11 +288,12 @@ class CWriter(Writer):
|
|||
if self.glyph is None:
|
||||
return # All done
|
||||
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
|
||||
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
|
||||
fgcolor = self.bgcolor if invert else self.fgcolor
|
||||
bgcolor = self.fgcolor if invert else self.bgcolor
|
||||
# render clips a glyph if outside bounds of destination
|
||||
render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor)
|
||||
fbc = framebuf.FrameBuffer(buf, self.clip_width, self.char_height, self.map)
|
||||
palette = self.device.palette
|
||||
palette.bg(self.fgcolor if invert else self.bgcolor)
|
||||
palette.fg(self.bgcolor if invert else self.fgcolor)
|
||||
|
||||
self.device.blit(fbc, s.text_col, s.text_row, -1, palette)
|
||||
s.text_col += self.char_width
|
||||
self.cpos += 1
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue