242 lines
11 KiB
Python
242 lines
11 KiB
Python
'''framebuf pure python implement
|
|
'''
|
|
MVLSB = 0
|
|
MONO_VLSB = 0
|
|
MONO_VMSB = 7
|
|
RGB565 = 1
|
|
GS2_HMSB = 5
|
|
GS4_HMSB = 2
|
|
GS8 = 6
|
|
MONO_HLSB = 3 # in fact, it is MSB
|
|
MONO_HMSB = 4 # in fact, it is LSB
|
|
|
|
MAX = lambda x, y: x if x > y else y
|
|
MIN = lambda x, y: x if x < y else y
|
|
|
|
# source at @micropython: /ports/stm32/font_petme128_8x8.h
|
|
font_petme128_8x8 = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00OO\x00\x00\x00\x00\x07\x07\x00\x00\x07\x07\x00\x14\x7f\x7f\x14\x14\x7f\x7f\x14\x00$.kk:\x12\x00\x00c3\x18\x0cfc\x00\x002\x7fMMwrP\x00\x00\x00\x04\x06\x03\x01\x00\x00\x00\x1c>cA\x00\x00\x00\x00Ac>\x1c\x00\x00\x08*>\x1c\x1c>*\x08\x00\x08\x08>>\x08\x08\x00\x00\x00\x80\xe0`\x00\x00\x00\x00\x08\x08\x08\x08\x08\x08\x00\x00\x00\x00``\x00\x00\x00\x00@`0\x18\x0c\x06\x02\x00>\x7fIE\x7f>\x00\x00@D\x7f\x7f@@\x00\x00bsQIOF\x00\x00"cII\x7f6\x00\x00\x18\x18\x14\x16\x7f\x7f\x10\x00\'gEE}9\x00\x00>\x7fII{2\x00\x00\x03\x03y}\x07\x03\x00\x006\x7fII\x7f6\x00\x00&oII\x7f>\x00\x00\x00\x00$$\x00\x00\x00\x00\x00\x80\xe4d\x00\x00\x00\x00\x08\x1c6cAA\x00\x00\x14\x14\x14\x14\x14\x14\x00\x00AAc6\x1c\x08\x00\x00\x02\x03QY\x0f\x06\x00\x00>\x7fAMO.\x00\x00|~\x0b\x0b~|\x00\x00\x7f\x7fII\x7f6\x00\x00>\x7fAAc"\x00\x00\x7f\x7fAc>\x1c\x00\x00\x7f\x7fIIAA\x00\x00\x7f\x7f\t\t\x01\x01\x00\x00>\x7fAI{:\x00\x00\x7f\x7f\x08\x08\x7f\x7f\x00\x00\x00A\x7f\x7fA\x00\x00\x00 `A\x7f?\x01\x00\x00\x7f\x7f\x1c6cA\x00\x00\x7f\x7f@@@@\x00\x00\x7f\x7f\x06\x0c\x06\x7f\x7f\x00\x7f\x7f\x0e\x1c\x7f\x7f\x00\x00>\x7fAA\x7f>\x00\x00\x7f\x7f\t\t\x0f\x06\x00\x00\x1e?!a\x7f^\x00\x00\x7f\x7f\x199oF\x00\x00&oII{2\x00\x00\x01\x01\x7f\x7f\x01\x01\x00\x00?\x7f@@\x7f?\x00\x00\x1f?``?\x1f\x00\x00\x7f\x7f0\x180\x7f\x7f\x00cw\x1c\x1cwc\x00\x00\x07\x0fxx\x0f\x07\x00\x00aqYMGC\x00\x00\x00\x7f\x7fAA\x00\x00\x00\x02\x06\x0c\x180`@\x00\x00AA\x7f\x7f\x00\x00\x00\x08\x0c\x06\x06\x0c\x08\x00\xc0\xc0\xc0\xc0\xc0\xc0\xc0\xc0\x00\x00\x01\x03\x06\x04\x00\x00\x00 tTT|x\x00\x00\x7f\x7fDD|8\x00\x008|DDl(\x00\x008|DD\x7f\x7f\x00\x008|TT\\X\x00\x00\x08~\x7f\t\x03\x02\x00\x00\x98\xbc\xa4\xa4\xfc|\x00\x00\x7f\x7f\x04\x04|x\x00\x00\x00\x00}}\x00\x00\x00\x00@\xc0\x80\x80\xfd}\x00\x00\x7f\x7f08lD\x00\x00\x00A\x7f\x7f@\x00\x00\x00||\x180\x18||\x00||\x04\x04|x\x00\x008|DD|8\x00\x00\xfc\xfc$$<\x18\x00\x00\x18<$$\xfc\xfc\x00\x00||\x04\x04\x0c\x08\x00\x00H\\TTt \x00\x04\x04?\x7fDd \x00\x00<|@@|<\x00\x00\x1c<``<\x1c\x00\x00\x1c|0\x180|\x1c\x00Dl88lD\x00\x00\x9c\xbc\xa0\xa0\xfc|\x00\x00Ddt\\LD\x00\x00\x08\x08>wAA\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00AAw>\x08\x08\x00\x00\x02\x03\x01\x03\x02\x03\x01\xaaU\xaaU\xaaU\xaaU'
|
|
|
|
class AbstractFormat(object):
|
|
def __init__(self, buffer, width, height, format, stride = None):
|
|
self.buffer = buffer
|
|
self.width = width
|
|
self.height = height
|
|
self.format = format
|
|
self.stride = stride if stride else width
|
|
pass
|
|
def _set_pixel(self, x, y, c): pass
|
|
def _get_pixel(self, x, y): pass
|
|
def _fill_rect(self, x, y, w, h, c): pass
|
|
def __repr__(self):
|
|
txt = '<framebuffer.FrameBuffer object Width: {:}, Height: {:}>\n'.format(self.width, self.height)
|
|
for y in range(self.height):
|
|
for x in range(self.width):
|
|
txt += '██' if self._get_pixel(x, y) != 0 else ' '
|
|
txt += '\n'
|
|
return txt[:-1]
|
|
|
|
class FormatMonoVertical(AbstractFormat):
|
|
def _set_pixel(self, x, y, c):
|
|
index = (y >> 3) * self.stride + x
|
|
offset = y & 0x07 if self.format == MONO_VLSB else 7 - (y & 0x07)
|
|
self.buffer[index] = (self.buffer[index] & ~(0x01 << offset)) | ((1 if c != 0 else 0) << offset)
|
|
def _get_pixel(self, x, y):
|
|
index = (y >> 3) * self.stride + x
|
|
offset = y & 0x07 if self.format == MONO_VLSB else 7 - (y & 0x07)
|
|
return (self.buffer[index] >> offset) & 0x01
|
|
def _fill_rect(self, x, y, w, h, c):
|
|
reverse = self.format == MONO_VLSB
|
|
while h > 0:
|
|
h -= 1
|
|
bp = (y >> 3) * self.stride + x
|
|
offset = y & 0x07 if reverse else 7 - (y & 0x07)
|
|
for _ in range(w, 0, -1):
|
|
self.buffer[bp] = (self.buffer[bp] & ~(0x01 << offset)) | ((1 if c != 0 else 0) << offset)
|
|
bp += 1
|
|
y += 1
|
|
class FormatMonoHorizontal(AbstractFormat):
|
|
def _set_pixel(self, x, y, c):
|
|
index = (x + y * self.stride) >> 3
|
|
offset = x & 0x07 if self.format == MONO_HMSB else 7 - (x & 0x07)
|
|
self.buffer[index] = (self.buffer[index] & ~(0x01 << offset)) | ((1 if c != 0 else 0) << offset)
|
|
def _get_pixel(self, x, y):
|
|
index = (x + y * self.stride) >> 3
|
|
offset = x & 0x07 if self.format == MONO_HMSB else 7 - (x & 0x07)
|
|
return (self.buffer[index] >> offset) & 0x01
|
|
def _fill_rect(self, x, y, w, h, c):
|
|
reverse = self.format == MONO_HMSB
|
|
advance = self.stride >> 3
|
|
while w > 0:
|
|
w -= 1
|
|
bp = (x >> 3) + y * advance
|
|
offset = x & 7 if reverse else 7 - (x & 7)
|
|
for _ in range(h, 0, -1):
|
|
self.buffer[bp] = (self.buffer[bp] & ~(0x01 << offset)) | ((1 if c != 0 else 0) << offset)
|
|
bp += advance
|
|
x += 1
|
|
class FormatRGB565(AbstractFormat):
|
|
def _set_pixel(self, x, y, c):
|
|
offset = (x + y*self.stride) * 2 # 16bit
|
|
self.buffer[offset:offset+2] = c.to_bytes(2, 'little')
|
|
def _get_pixel(self, x, y):
|
|
offset = (x + y*self.stride) * 2
|
|
return int.from_bytes(self.buffer[offset:offset+2], 'little')
|
|
def _fill_rect(self, x, y, w, h, c):
|
|
bp = (x + y*self.stride) * 2
|
|
while h > 0:
|
|
h -= 1
|
|
for _ in range(w, 0, -1):
|
|
self.buffer[bp:bp+2] = c.to_bytes(2, 'little')
|
|
bp += 2
|
|
bp += (self.stride - w) * 2
|
|
|
|
class FrameBuffer(object):
|
|
def __init__(self, buffer, width, height, format, stride = None):
|
|
self.buffer = buffer
|
|
self.width = width
|
|
self.height = height
|
|
self.format = format
|
|
self.stride = stride if stride else width
|
|
if self.format == MONO_HMSB or self.format == MONO_HLSB:
|
|
self.stride = (self.stride + 7) & ~7
|
|
self.__format = FormatMonoHorizontal(self.buffer, self.width, self.height, self.format, self.stride)
|
|
elif self.format == MONO_VMSB or self.format == MONO_VLSB:
|
|
self.__format = FormatMonoVertical(self.buffer, self.width, self.height, self.format, self.stride)
|
|
elif self.format == RGB565:
|
|
self.__format = FormatRGB565(self.buffer, self.width, self.height, self.format, self.stride)
|
|
else:
|
|
self.__format = FormatRGB565(self.buffer, self.width, self.height, self.format, self.stride)
|
|
print('Warning: this framebuf fotmat is not implement yet, use RGB565 instead.')
|
|
def __repr__(self):
|
|
return self.__format.__repr__()
|
|
def fill(self, c):
|
|
self.__format._fill_rect(0, 0, self.width, self.height, c)
|
|
def pixel(self, x, y, c=None):
|
|
if 0 <= x and x < self.width and 0 <= y and y < self.height:
|
|
if c == None:
|
|
return self.__format._get_pixel(x, y)
|
|
else:
|
|
self.__format._set_pixel(x, y, c)
|
|
return None
|
|
def hline(self, x, y, w, c):
|
|
self.fill_rect(x, y, w, 1, c)
|
|
def vline(self, x, y, h, c):
|
|
self.fill_rect(x, y, 1, h, c)
|
|
def line(self, x1, y1, x2, y2, c):
|
|
dx = x2 - x1
|
|
if dx > 0:
|
|
sx = 1
|
|
else:
|
|
dx = -dx
|
|
sx = -1
|
|
dy = y2 - y1
|
|
if dy > 0:
|
|
sy = 1
|
|
else:
|
|
dy = -dy
|
|
sy = -1
|
|
if dy > dx:
|
|
temp = x1
|
|
x1 = y1
|
|
y1 = temp
|
|
temp = dx
|
|
dx = dy
|
|
dy = temp
|
|
temp = sx
|
|
sx = sy
|
|
sy = temp
|
|
steep = True
|
|
else:
|
|
steep = False
|
|
e = 2 * dy - dx
|
|
for i in range(dx):# (mp_int_t i = 0; i < dx; ++i) {
|
|
if steep:
|
|
if 0 <= y1 and y1 < self.width and 0 <= x1 and x1 < self.height:
|
|
self.__format._set_pixel(y1, x1, c)
|
|
else:
|
|
if 0 <= x1 and x1 < self.width and 0 <= y1 and y1 < self.height:
|
|
self.__format._set_pixel(x1, y1, c)
|
|
while e >= 0:
|
|
y1 += sy
|
|
e -= 2 * dx
|
|
x1 += sx
|
|
e += 2 * dy
|
|
if 0 <= x2 and x2 < self.width and 0 <= y2 and y2 < self.height:
|
|
self.__format._set_pixel(x2, y2, c)
|
|
def rect(self, x, y, w, h, c):
|
|
self.fill_rect(x, y, w, 1, c)
|
|
self.fill_rect(x, y + h - 1, w, 1, c)
|
|
self.fill_rect(x, y, 1, h, c)
|
|
self.fill_rect(x + w - 1, y, 1, h, c)
|
|
def fill_rect(self, x, y, w, h, c):
|
|
xend = MIN(self.width, x + w)
|
|
yend = MIN(self.height, y + h)
|
|
x = MAX(x, 0)
|
|
y = MAX(y, 0)
|
|
self.__format._fill_rect(x, y, xend - x, yend - y, c)
|
|
def blit(self, fbuf, x, y, key=None):
|
|
if (x >= self.width) or (y >= self.height) or (-x >= fbuf.width) or (-y >= fbuf.height):
|
|
# Out of bounds, no-op.
|
|
return
|
|
# Clip.
|
|
x0 = MAX(0, x)
|
|
y0 = MAX(0, y)
|
|
x1 = MAX(0, -x)
|
|
y1 = MAX(0, -y)
|
|
x0end = MIN(self.width, x + fbuf.width)
|
|
y0end = MIN(self.height, y + fbuf.height)
|
|
# for (; y0 < y0end; ++y0)
|
|
while y0 < y0end:
|
|
cx1 = x1
|
|
for cx0 in range(x0, x0end, 1):
|
|
col = fbuf.__format._get_pixel(cx1, y1)
|
|
if col != key:
|
|
self.__format._set_pixel(cx0, y0, col)
|
|
cx1 += 1
|
|
y0 += 1
|
|
y1 += 1
|
|
|
|
# not require function below
|
|
def scroll(self, xstep, ystep):
|
|
if xstep < 0:
|
|
sx = 0
|
|
xend = self.width + xstep
|
|
dx = 1
|
|
else:
|
|
sx = self.width - 1
|
|
xend = xstep - 1
|
|
dx = -1
|
|
if ystep < 0:
|
|
y = 0
|
|
yend = self.height + ystep
|
|
dy = 1
|
|
else:
|
|
y = self.height - 1
|
|
yend = ystep - 1
|
|
dy = -1
|
|
while y != yend:
|
|
for x in range(sx, xend, dx):
|
|
self.__format._set_pixel(x, y, self.__format._get_pixel(x - xstep, y - ystep))
|
|
y += dy
|
|
def text(self, t:str, x, y, c=1):
|
|
# loop over chars
|
|
for chr in t.encode():
|
|
# get char and make sure its in range of font
|
|
if chr < 32 or chr > 127:
|
|
chr = 127
|
|
# get char data
|
|
chr_data_offset = (chr - 32) * 8
|
|
# chr_data = font_petme128_8x8[(chr - 32) * 8: (chr - 32) * 8 + 8]
|
|
# loop over char data
|
|
for j in range(8):
|
|
if 0 <= x and x < self.width: # clip x
|
|
vline_data = font_petme128_8x8[chr_data_offset+j]; # each byte is a column of 8 pixels, LSB at top
|
|
m_y = y
|
|
while vline_data > 0:
|
|
if vline_data & 1: # only draw if pixel set
|
|
if 0 <= m_y and m_y < self.height: # clip y
|
|
self.__format._set_pixel(x, m_y, c)
|
|
m_y += 1
|
|
vline_data >>= 1
|
|
x += 1
|