kopia lustrzana https://github.com/peterhinch/micropython-font-to-py
writer.py: support framebuf_utils.mpy.
rodzic
2d50c93802
commit
66f9e4bf24
|
@ -44,14 +44,15 @@ Labels and Fields (from nanogui.py).
|
||||||
1.3 [Fonts](./WRITER.md#11-fonts)
|
1.3 [Fonts](./WRITER.md#11-fonts)
|
||||||
2. [Writer and CWriter classes](./WRITER.md#2-writer-and-cwriter-classes)
|
2. [Writer and CWriter classes](./WRITER.md#2-writer-and-cwriter-classes)
|
||||||
2.1 [The Writer class](./WRITER.md#21-the-writer-class) For monochrome displays.
|
2.1 [The Writer class](./WRITER.md#21-the-writer-class) For monochrome displays.
|
||||||
2.1.1 [Static Method](./WRITER.md#211-static-method)
|
2.1.1 [Static Method](./WRITER.md#211-static-method)
|
||||||
2.1.2.[Constructor](./WRITER.md#212-constructor)
|
2.1.2.[Constructor](./WRITER.md#212-constructor)
|
||||||
2.1.3 [Methods](./WRITER.md#213-methods)
|
2.1.3 [Methods](./WRITER.md#213-methods)
|
||||||
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
|
||||||
and for upside-down rendering.
|
and for upside-down rendering.
|
||||||
2.2.1 [Static Method](./WRITER.md#221-static-method)
|
2.2.1 [Static Method](./WRITER.md#221-static-method)
|
||||||
2.2.2 [Constructor](./WRITER.md#222-constructor)
|
2.2.2 [Constructor](./WRITER.md#222-constructor)
|
||||||
2.2.3 [Methods](./WRITER.md#223-methods)
|
2.2.3 [Methods](./WRITER.md#223-methods)
|
||||||
|
2.2.4 [A performance boost](./WRITER.md#224-a-performance-boost)
|
||||||
3. [Notes](./WRITER.md#4-notes)
|
3. [Notes](./WRITER.md#4-notes)
|
||||||
|
|
||||||
###### [Main README](../README.md)
|
###### [Main README](../README.md)
|
||||||
|
@ -80,9 +81,7 @@ very simple version still exists as `writer_minimal.py`.
|
||||||
|
|
||||||
Tests and demos assume a 128*64 SSD1306 OLED display connected via I2C or SPI.
|
Tests and demos assume a 128*64 SSD1306 OLED display connected via I2C or SPI.
|
||||||
Wiring is specified in `ssd1306_setup.py`. Edit this to use a different bus or
|
Wiring is specified in `ssd1306_setup.py`. Edit this to use a different bus or
|
||||||
for a non-Pyboard target. At the time of writing the default of software I2C
|
for a non-Pyboard target.
|
||||||
should be used: the official SSD1306 driver is not compatible with hardware I2C
|
|
||||||
(see [Notes](./WRITER.md#3-notes)).
|
|
||||||
|
|
||||||
## 1.2 Files
|
## 1.2 Files
|
||||||
|
|
||||||
|
@ -251,6 +250,28 @@ 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.4 A performance boost
|
||||||
|
|
||||||
|
Rendering performance of the `Cwriter` class is slow: owing to limitations in
|
||||||
|
the `frmebuf.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.
|
||||||
|
|
||||||
|
On import, `writer.py` attempts to import a module `framebuf_utils`. If this
|
||||||
|
succeeds (i.e. a file `framebuf_utils.mpy` is found), glyph rendering will be
|
||||||
|
substantially faster.
|
||||||
|
|
||||||
|
The directory `framebuf_utils` contains the source file, the makefile and a
|
||||||
|
version of `framebuf_utils.mpy` for `armv7m` architecture (e.g. Pyboards).
|
||||||
|
ESP32 users with access to the development toolchain should change `Makefile`
|
||||||
|
to specify the `xtensawin` arch and rebuild.
|
||||||
|
|
||||||
|
It is suggested that moving the appropriate `framebuf_utils.mpy` to the target
|
||||||
|
is only done once the basic operation of an application has been verified.
|
||||||
|
|
||||||
|
The native module does not support the `CWriter.invert_display` option. If this
|
||||||
|
is used, the presence of the native module will have no effect.
|
||||||
|
|
||||||
# 3. Notes
|
# 3. Notes
|
||||||
|
|
||||||
Possible future enhancements:
|
Possible future enhancements:
|
||||||
|
@ -259,9 +280,4 @@ Possible future enhancements:
|
||||||
2. Extend word wrapping to cases where words are separated by tabs or hyphens.
|
2. Extend word wrapping to cases where words are separated by tabs or hyphens.
|
||||||
3. An asynchronous version.
|
3. An asynchronous version.
|
||||||
|
|
||||||
As stated above the official SSD1306 driver is incompatible with hardware I2C
|
|
||||||
and this problem cannot efficiently be fixed. [PR4020](https://github.com/micropython/micropython/pull/4020)
|
|
||||||
proposes an enhncement which will facilitate an improved SSD1306 driver capable
|
|
||||||
of using hard or soft I2C.
|
|
||||||
|
|
||||||
###### [Contents](./WRITER.md#contents)
|
###### [Contents](./WRITER.md#contents)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# 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
|
|
@ -0,0 +1,118 @@
|
||||||
|
#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.
|
@ -0,0 +1,32 @@
|
||||||
|
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,10 +1,10 @@
|
||||||
# writer.py Implements the Writer class.
|
# writer.py Implements the Writer class.
|
||||||
# V0.3 Peter Hinch 11th Aug 2018
|
# V0.35 Peter Hinch Sept 2020 Fast rendering option for color displays
|
||||||
# Handles colour, upside down diplays, word wrap and tab stops
|
# Handles colour, upside down diplays, word wrap and tab stops
|
||||||
|
|
||||||
# The MIT License (MIT)
|
# The MIT License (MIT)
|
||||||
#
|
#
|
||||||
# Copyright (c) 2016 Peter Hinch
|
# Copyright (c) 2016-2020 Peter Hinch
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -32,7 +32,18 @@
|
||||||
# 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.
|
# 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
|
||||||
|
try:
|
||||||
|
from framebuf_utils import render
|
||||||
|
fast_mode = True
|
||||||
|
except ImportError:
|
||||||
|
fast_mode = False
|
||||||
|
from uctypes import bytearray_at, addressof
|
||||||
|
|
||||||
class DisplayState():
|
class DisplayState():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -264,19 +275,24 @@ class CWriter(Writer):
|
||||||
self.fgcolor = fgcolor
|
self.fgcolor = fgcolor
|
||||||
self.def_bgcolor = self.bgcolor
|
self.def_bgcolor = self.bgcolor
|
||||||
self.def_fgcolor = self.fgcolor
|
self.def_fgcolor = self.fgcolor
|
||||||
|
fm = fast_mode and not self.usd
|
||||||
|
self._printchar = self._pchfast if fm else self._pchslow
|
||||||
|
verbose and print('Render {} using fast mode'.format('is' if fm else 'not'))
|
||||||
|
|
||||||
def setcolor(self, fgcolor=None, bgcolor=None):
|
def _pchfast(self, char, invert=False, recurse=False):
|
||||||
if fgcolor is None and bgcolor is None:
|
s = self._getstate()
|
||||||
self.fgcolor = self.def_fgcolor
|
self._get_char(char, recurse)
|
||||||
self.bgcolor = self.def_bgcolor
|
if self.glyph is None:
|
||||||
else:
|
return # All done
|
||||||
if fgcolor is not None:
|
buf = bytearray_at(addressof(self.glyph), len(self.glyph))
|
||||||
self.fgcolor = fgcolor
|
fbc = framebuf.FrameBuffer(buf, self.char_width, self.char_height, self.map)
|
||||||
if bgcolor is not None:
|
fgcolor = self.bgcolor if invert else self.fgcolor
|
||||||
self.bgcolor = bgcolor
|
bgcolor = self.fgcolor if invert else self.bgcolor
|
||||||
return self.fgcolor, self.bgcolor
|
render(self.device, fbc, s.text_col, s.text_row, fgcolor, bgcolor)
|
||||||
|
s.text_col += self.char_width
|
||||||
|
self.cpos += 1
|
||||||
|
|
||||||
def _printchar(self, char, invert=False, recurse=False):
|
def _pchslow(self, char, invert=False, recurse=False):
|
||||||
s = self._getstate()
|
s = self._getstate()
|
||||||
self._get_char(char, recurse)
|
self._get_char(char, recurse)
|
||||||
if self.glyph is None:
|
if self.glyph is None:
|
||||||
|
@ -309,3 +325,14 @@ class CWriter(Writer):
|
||||||
break
|
break
|
||||||
s.text_col += -char_width if usd else char_width
|
s.text_col += -char_width if usd else char_width
|
||||||
self.cpos += 1
|
self.cpos += 1
|
||||||
|
|
||||||
|
def setcolor(self, fgcolor=None, bgcolor=None):
|
||||||
|
if fgcolor is None and bgcolor is None:
|
||||||
|
self.fgcolor = self.def_fgcolor
|
||||||
|
self.bgcolor = self.def_bgcolor
|
||||||
|
else:
|
||||||
|
if fgcolor is not None:
|
||||||
|
self.fgcolor = fgcolor
|
||||||
|
if bgcolor is not None:
|
||||||
|
self.bgcolor = bgcolor
|
||||||
|
return self.fgcolor, self.bgcolor
|
||||||
|
|
Ładowanie…
Reference in New Issue