writer.py: support framebuf_utils.mpy.

pull/32/head
Peter Hinch 2020-10-02 17:50:51 +01:00
rodzic 2d50c93802
commit 66f9e4bf24
6 zmienionych plików z 233 dodań i 27 usunięć

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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.

Wyświetl plik

@ -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()

Wyświetl plik

@ -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