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)
|
||||
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.1 [Static Method](./WRITER.md#211-static-method)
|
||||
2.1.2.[Constructor](./WRITER.md#212-constructor)
|
||||
2.1.3 [Methods](./WRITER.md#213-methods)
|
||||
2.1.1 [Static Method](./WRITER.md#211-static-method)
|
||||
2.1.2.[Constructor](./WRITER.md#212-constructor)
|
||||
2.1.3 [Methods](./WRITER.md#213-methods)
|
||||
2.2 [The CWriter class](./WRITER.md#22-the-cwriter-class) For colour displays
|
||||
and for upside-down rendering.
|
||||
2.2.1 [Static Method](./WRITER.md#221-static-method)
|
||||
2.2.2 [Constructor](./WRITER.md#222-constructor)
|
||||
2.2.3 [Methods](./WRITER.md#223-methods)
|
||||
2.2.1 [Static Method](./WRITER.md#221-static-method)
|
||||
2.2.2 [Constructor](./WRITER.md#222-constructor)
|
||||
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)
|
||||
|
||||
###### [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.
|
||||
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
|
||||
should be used: the official SSD1306 driver is not compatible with hardware I2C
|
||||
(see [Notes](./WRITER.md#3-notes)).
|
||||
for a non-Pyboard target.
|
||||
|
||||
## 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
|
||||
`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
|
||||
|
||||
Possible future enhancements:
|
||||
|
@ -259,9 +280,4 @@ Possible future enhancements:
|
|||
2. Extend word wrapping to cases where words are separated by tabs or hyphens.
|
||||
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)
|
||||
|
|
|
@ -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.
|
||||
# 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
|
||||
|
||||
# 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
|
||||
# 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 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
|
||||
try:
|
||||
from framebuf_utils import render
|
||||
fast_mode = True
|
||||
except ImportError:
|
||||
fast_mode = False
|
||||
from uctypes import bytearray_at, addressof
|
||||
|
||||
class DisplayState():
|
||||
def __init__(self):
|
||||
|
@ -264,19 +275,24 @@ class CWriter(Writer):
|
|||
self.fgcolor = fgcolor
|
||||
self.def_bgcolor = self.bgcolor
|
||||
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):
|
||||
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
|
||||
def _pchfast(self, char, invert=False, recurse=False):
|
||||
s = self._getstate()
|
||||
self._get_char(char, recurse)
|
||||
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(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()
|
||||
self._get_char(char, recurse)
|
||||
if self.glyph is None:
|
||||
|
@ -309,3 +325,14 @@ class CWriter(Writer):
|
|||
break
|
||||
s.text_col += -char_width if usd else char_width
|
||||
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