kopia lustrzana https://github.com/peterhinch/micropython-micro-gui
First pass at bitmap and qrcode widgets.
rodzic
3f7be4ebba
commit
c03ed90796
83
README.md
83
README.md
|
@ -2519,17 +2519,94 @@ different callback if the application required it.
|
|||
|
||||
## 6.19 BitMap Widget
|
||||
|
||||
```python
|
||||
from gui.widgets import BitMap
|
||||
```
|
||||
|
||||
This renders a monochrome bitmap stored in a file to a rectangular region. The
|
||||
bitmap file format is C source code generated by the Linux `bitmap` editor. The
|
||||
data may be rendered in any color. Data and colors can be changed at run time.
|
||||
bitmap may be rendered in any color. Data and colors can be changed at run time.
|
||||
|
||||
Not suitable for animation due to blocking time.
|
||||
Question generator in coroutine.
|
||||
Constructor mandatory positional args:
|
||||
1. `writer` A `Writer` instance.
|
||||
2. `row` Location on screen.
|
||||
3. `col`
|
||||
4. `height` Image height in pixels. Dimensions must exactly match the image file.
|
||||
5. `width` Image width in pixels.
|
||||
|
||||
Keyword only args:
|
||||
* `fgcolor=None` Foreground (1) color of image.
|
||||
* `bgcolor=None` Background (0) color.
|
||||
* `bdcolor=RED` Border color.
|
||||
|
||||
Methods:__
|
||||
* `value` mandatory arg `fn` path to an image file. Causes the `BitMap` image
|
||||
to be updated from the file. Blocks for a period depending on filesystem
|
||||
performance.
|
||||
* `color` args `fgcolor=None`, `bgcolor=None`. Causes the image colors to be
|
||||
changed. The file will be re-read and the image updated.
|
||||
|
||||
The widget is designed to minimise RAM usage at cost of performance. This is
|
||||
because images may be large. When an update occurs there will be a brief "dead
|
||||
time" when the GUI is unresponsive. This is not noticeable if the image is
|
||||
displayed when a screen initialises, or if it changes in response to a user
|
||||
action. Use in animations is questionable.
|
||||
|
||||
See `gui/demos/bitmap.py` for a usage example.
|
||||
|
||||
###### [Contents](./README.md#0-contents)
|
||||
|
||||
## 6.20 QRMap Widget
|
||||
|
||||
```python
|
||||
from gui.widgets import QRMap
|
||||
```
|
||||
|
||||
This renders QR codes generated using the [uQR](https://github.com/JASchilz/uQR)
|
||||
application. Images may be scaled to render them at larger sizes. Please see
|
||||
the notes below on performance and RAM usage.
|
||||
|
||||
Constructor positional args:
|
||||
1. `writer` A `Writer` instance.
|
||||
2. `row` Location on screen.
|
||||
3. `col`
|
||||
4. `version=4` Defines the size of the image: see below.
|
||||
5. `scale=1`
|
||||
|
||||
Keyword only args:
|
||||
* `bdcolor=RED` Border color.
|
||||
* `buf=None` Allows use of a pre-allocated image buffer.
|
||||
|
||||
Methods:__
|
||||
* `value` mandatory arg `text` a string for display as a QR code. This method
|
||||
can throw a `ValueError` if the string cannot be accommodated in the chosen
|
||||
code size (i.e. `version`).
|
||||
* `__call__` Synonym for `value`.
|
||||
|
||||
Static Method:__
|
||||
* `make_buffer` args `version`, `scale`. Returns a buffer big enough to hold
|
||||
the QR code bitmap. Use of this is optional: it is a solution if memory errors
|
||||
are encountered when instantiating a `QRMap`.
|
||||
|
||||
Note on image sizes. The size of a QR code bitmap depends on the `version` and
|
||||
`scale` parameters according to this formula:
|
||||
`edge_length_in_pixels = (4 * version + 17) * scale`
|
||||
To this must be added a mandatory 4 pixel border around every edge. So the
|
||||
height and width occupied on screen is:
|
||||
`dimension = (4 * version + 25) * scale`
|
||||
|
||||
Performance
|
||||
The uQR `get_matrix()` method blocks: in my testing for about 750ms. A `QRMap`
|
||||
buffers the scaled matrix and renders it using bit blitting. Blocking by
|
||||
`QRMap` methods is minimal; refreshing a screen with the same contents is fast.
|
||||
|
||||
The `uQR` library is large, and compiling it uses a substantial amount of RAM.
|
||||
If memory errors are encountered try cross-compiling or the use of frozen byte
|
||||
code.
|
||||
|
||||
See `gui/demos/qrcode.py` for a usage example. The demo expects `uQR.py` to be
|
||||
located in the root directory of the target.
|
||||
|
||||
###### [Contents](./README.md#0-contents)
|
||||
|
||||
# 7. Graph Plotting
|
||||
|
|
|
@ -29,9 +29,9 @@ class BaseScreen(Screen):
|
|||
self.image = 0
|
||||
|
||||
def cb(self, _):
|
||||
self.graphic.value(f"/moon/m{self.image:02d}")
|
||||
self.graphic.value(f"/gui/fonts/bitmaps/m{self.image:02d}")
|
||||
self.image += 1
|
||||
self.image %= 28
|
||||
self.image %= 4
|
||||
if self.image == 3:
|
||||
self.graphic.color(BLUE)
|
||||
else:
|
||||
|
|
|
@ -4,17 +4,12 @@
|
|||
# Copyright (c) 2022 Peter Hinch
|
||||
|
||||
# hardware_setup must be imported before other modules because of RAM use.
|
||||
import gc
|
||||
import hardware_setup # Create a display instance
|
||||
from uQR import QRCode
|
||||
from gui.core.ugui import Screen, ssd
|
||||
from gui.widgets import Label, Button, CloseButton, QRMap
|
||||
# Create buffer for bitmapped graphic before fragmentation sets in
|
||||
scale = 3 # Magnification of graphic
|
||||
qr_ht = scale * 41
|
||||
qr_wd = scale * 41
|
||||
qr_buf = QRMap.make_buffer(qr_ht, qr_wd)
|
||||
gc.collect()
|
||||
scale = 2 # Magnification of graphic
|
||||
version = 4
|
||||
#qr_buf = QRMap.make_buffer(version, scale)
|
||||
from gui.core.writer import CWriter
|
||||
import gui.fonts.arial10 as arial10
|
||||
from gui.core.colors import *
|
||||
|
@ -24,23 +19,19 @@ class BaseScreen(Screen):
|
|||
|
||||
def __init__(self):
|
||||
|
||||
def my_callback(button, graphic, qr):
|
||||
qr.clear()
|
||||
qr.add_data("https://en.wikipedia.org/wiki/QR_code")
|
||||
graphic.value(qr.get_matrix())
|
||||
def my_callback(button, graphic):
|
||||
graphic("https://en.wikipedia.org/wiki/QR_code")
|
||||
|
||||
super().__init__()
|
||||
wri = CWriter(ssd, arial10, GREEN, BLACK)
|
||||
col = 2
|
||||
row = 2
|
||||
Label(wri, row, col, "QR code Demo.")
|
||||
row = 50
|
||||
graphic = QRMap(wri, row, col, (qr_ht, qr_wd), scale, fgcolor=BLACK, bgcolor=WHITE, buf=qr_buf)
|
||||
qr = QRCode(version=4) # Gives 41x41 matrix
|
||||
qr.add_data("uQR rocks!")
|
||||
graphic.value(qr.get_matrix())
|
||||
col = 160
|
||||
Button(wri, row, col, text="URL", callback=my_callback, args=(graphic, qr))
|
||||
row = 25
|
||||
graphic = QRMap(wri, row, col, version, scale)
|
||||
graphic("uQR rocks!")
|
||||
col = 120
|
||||
Button(wri, row, col, text="URL", callback=my_callback, args=(graphic,))
|
||||
CloseButton(wri) # Quit the application
|
||||
|
||||
def test():
|
||||
|
|
|
@ -13,16 +13,17 @@ import uasyncio as asyncio
|
|||
from machine import Pin
|
||||
|
||||
class Encoder:
|
||||
delay = 100 # Pause (ms) for motion to stop/limit callback frequency
|
||||
|
||||
def __init__(self, pin_x, pin_y, v=0, div=1, vmin=None, vmax=None,
|
||||
mod=None, callback=lambda a, b : None, args=()):
|
||||
mod=None, callback=lambda a, b : None, args=(), delay=100):
|
||||
self._pin_x = pin_x
|
||||
self._pin_y = pin_y
|
||||
self._x = pin_x()
|
||||
self._y = pin_y()
|
||||
self._v = v * div # Initialise hardware value
|
||||
self._cv = v # Current (divided) value
|
||||
self.delay = delay # Pause (ms) for motion to stop/limit callback frequency
|
||||
|
||||
if ((vmin is not None) and v < vmin) or ((vmax is not None) and v > vmax):
|
||||
raise ValueError('Incompatible args: must have vmin <= v <= vmax')
|
||||
self._tsf = asyncio.ThreadSafeFlag()
|
||||
|
|
|
@ -23,7 +23,7 @@ class BitMap(Widget):
|
|||
if self._value is None:
|
||||
return
|
||||
with open(self._value, "r") as f:
|
||||
g = self.gen_bytes(f)
|
||||
g = self._gen_bytes(f)
|
||||
bit = 1
|
||||
wrap = False
|
||||
for row in range(self.height):
|
||||
|
@ -38,7 +38,7 @@ class BitMap(Widget):
|
|||
byte = next(g)
|
||||
bit = 1
|
||||
|
||||
def gen_bytes(self, f): # Yield data bytes from file stream
|
||||
def _gen_bytes(self, f): # Yield data bytes from file stream
|
||||
f.readline()
|
||||
f.readline() # Advance file pointer to data start
|
||||
s = f.readline()
|
||||
|
|
|
@ -2,77 +2,62 @@
|
|||
|
||||
# Released under the MIT License (MIT). See LICENSE.
|
||||
# Copyright (c) 2022 Peter Hinch
|
||||
import gc
|
||||
from framebuf import FrameBuffer, MONO_HLSB
|
||||
from gui.core.ugui import Widget
|
||||
from gui.core.colors import *
|
||||
from gui.core.ugui import ssd
|
||||
from uQR import QRCode
|
||||
|
||||
from utime import ticks_diff, ticks_ms
|
||||
|
||||
class QRMap(Widget):
|
||||
@staticmethod
|
||||
def len_side(version):
|
||||
return 4 * version + 17
|
||||
|
||||
@staticmethod
|
||||
def make_buffer(height, width): # Given dimensions in pixels
|
||||
w = (width >> 3) + int(width & 7 > 0)
|
||||
return bytearray(height * w)
|
||||
def make_buffer(version, scale):
|
||||
side = QRMap.len_side(version) * scale
|
||||
width = (side >> 3) + int(side & 7 > 0) # Width in bytes
|
||||
return bytearray(side * width)
|
||||
|
||||
def __init__(self, writer, row, col, image, scale=1, *, fgcolor=None, bgcolor=None, bdcolor=RED, buf=None):
|
||||
def __init__(self, writer, row, col, version=4, scale=1, *, bdcolor=RED, buf=None):
|
||||
self._version = version
|
||||
self._scale = scale
|
||||
self._image = image
|
||||
try:
|
||||
height, width = self.dimensions()
|
||||
except OSError:
|
||||
print(f"Failed to access {obj}.")
|
||||
raise
|
||||
super().__init__(writer, row, col, height, width, fgcolor, bgcolor, bdcolor, False)
|
||||
self._iside = self.len_side(version) # Dimension of unscaled QR image less border
|
||||
side = self._iside * scale
|
||||
# Widget allows 4 * scale border around each edge
|
||||
border = 4 * scale
|
||||
wside = side + 2 * border # Widget dimension
|
||||
super().__init__(writer, row, col, wside, wside, BLACK, WHITE, bdcolor, False)
|
||||
super()._set_callbacks(self._update, ())
|
||||
if buf is None:
|
||||
buf = QRMap.make_buffer(height, width)
|
||||
else:
|
||||
if len(buf) != ((width >> 3) + int(width & 7 > 0)) * height:
|
||||
raise OSError("Buffer size does not match width and height.")
|
||||
self._fb = FrameBuffer(buf, width, height, MONO_HLSB)
|
||||
if isinstance(image, list):
|
||||
self.value(image)
|
||||
buf = QRMap.make_buffer(version, scale)
|
||||
self._fb = FrameBuffer(buf, side, side, MONO_HLSB)
|
||||
self._irow = row + border
|
||||
self._icol = col + border
|
||||
self._qr = QRCode(version, border=0)
|
||||
|
||||
def show(self):
|
||||
if super().show(True): # Draw or erase border
|
||||
if super().show(False): # Show white border
|
||||
palette = ssd.palette
|
||||
palette.bg(self.bgcolor)
|
||||
palette.fg(self.fgcolor)
|
||||
ssd.blit(self._fb, self.col, self.row, -1, palette)
|
||||
ssd.blit(self._fb, self._icol, self._irow, -1, palette)
|
||||
|
||||
def color(self, fgcolor=None, bgcolor=None):
|
||||
if fgcolor is not None:
|
||||
self.fgcolor = fgcolor
|
||||
if bgcolor is not None:
|
||||
self.bgcolor = bgcolor
|
||||
self.draw = True
|
||||
|
||||
def dimensions(self): # Dimensions of current image in pixels
|
||||
obj = self._image
|
||||
if isinstance(obj, list): # 2d list of booleans
|
||||
return len(obj) * self._scale, len(obj[0] * self._scale)
|
||||
if isinstance(obj, tuple):
|
||||
return obj
|
||||
raise OSError
|
||||
|
||||
def value(self, obj):
|
||||
self._image = obj
|
||||
self._fb.fill(self.bgcolor) # In case tuple was passed or image smaller than buffer
|
||||
if isinstance(obj, list): # 2d list of booleans
|
||||
wd, ht = self.dimensions()
|
||||
s = self._scale
|
||||
if wd > self.width or ht > self.height:
|
||||
print('Object too large for buffer', wd, self.width, ht, self.height)
|
||||
else:
|
||||
print(f"Object is {wd} x {ht} pixels")
|
||||
for row in range(ht//s):
|
||||
for col in range(wd//s):
|
||||
v = obj[row][col]
|
||||
for nc in range(s):
|
||||
for nr in range(s):
|
||||
self._fb.pixel(col * s + nc, row * s + nr, v)
|
||||
else:
|
||||
print(f"Invalid QR code {obj}.")
|
||||
self.draw = True
|
||||
gc.collect()
|
||||
def _update(self, _): # Runs when value changes
|
||||
t = ticks_ms()
|
||||
qr = self._qr
|
||||
qr.clear()
|
||||
qr.add_data(self._value)
|
||||
matrix = qr.get_matrix() # 750ms. Rest of the routine adds 50ms
|
||||
if qr.version != self._version:
|
||||
raise ValueError("Text too long for QR version.")
|
||||
wd = self._iside
|
||||
s = self._scale
|
||||
for row in range(wd):
|
||||
for col in range(wd):
|
||||
v = matrix[row][col]
|
||||
for nc in range(s):
|
||||
for nr in range(s):
|
||||
self._fb.pixel(col * s + nc, row * s + nr, v)
|
||||
|
|
|
@ -42,7 +42,7 @@ pcs = Pin(10, Pin.OUT, value=1)
|
|||
spi = SPI(0, baudrate=30_000_000)
|
||||
gc.collect() # Precaution before instantiating framebuf
|
||||
ssd = SSD(spi, pcs, pdc, prst, usd=True)
|
||||
|
||||
gc.collect()
|
||||
from gui.core.ugui import Display, quiet
|
||||
# quiet()
|
||||
# Create and export a Display instance
|
||||
|
|
Ładowanie…
Reference in New Issue