""" framebuf-extend Micropython library for the framebuf-extend ======================================================= @dahanzimin From the Mixly Team """ import esp, time, gc, math from framebuf import * class Font_Ascall: '''Ascall code font reading data''' #字库格式:2字节字宽和高,后逐列式,按满字节低位在前 font4x5_code=b'\x04\x05\x00\x00\x00\x00\x00\x17\x00\x00\x03\x00\x03\x00\x1f\n\x1f\x00\x16\x1f\x1a\x00\x19\x04\x13\x00\n\x15\x1a\x00\x00\x01\x03\x00\x00\x0e\x11\x00\x11\x0e\x00\x00\x15\x0e\x15\x00\x04\x0e\x04\x00\x00\x08\x18\x00\x04\x04\x04\x00\x18\x18\x00\x00\x18\x04\x03\x00\x1f\x11\x1f\x00\x12\x1f\x10\x00\x1d\x15\x17\x00\x15\x15\x1f\x00\x07\x04\x1f\x00\x17\x15\x1d\x00\x1f\x15\x1d\x00\x01\x01\x1f\x00\x1f\x15\x1f\x00\x17\x15\x1f\x00\x1b\x1b\x00\x00\x00\x0b\x1b\x00\x04\n\x11\x00\n\n\n\x00\x11\n\x04\x00\x01\x15\x07\x00\x0e\x15\x16\x00\x1e\x05\x1e\x00\x1f\x15\n\x00\x0e\x11\x11\x00\x1f\x11\x0e\x00\x1f\x15\x15\x00\x1f\x05\x05\x00\x0e\x15\x1d\x00\x1f\x04\x1f\x00\x11\x1f\x11\x00\x08\x11\x0f\x00\x1f\x0c\x12\x00\x1f\x10\x10\x00\x1f\x02\x1f\x00\x1e\x04\x0f\x00\x0e\x11\x0e\x00\x1f\x05\x02\x00\x0e\x19\x1e\x00\x1f\t\x16\x00\x12\x15\t\x00\x01\x1f\x01\x00\x1f\x10\x1f\x00\x0f\x10\x0f\x00\x1f\x08\x1f\x00\x1b\x04\x1b\x00\x07\x1c\x07\x00\x19\x15\x13\x00\x00\x1f\x11\x00\x03\x0c\x10\x00\x11\x1f\x00\x00\x02\x01\x02\x00\x10\x10\x10\x00\x00\x03\x02\x00\n\x16\x1e\x00\x1f\x14\x08\x00\x0c\x12\x12\x00\x08\x14\x1f\x00\x0c\x1a\x14\x00\x04\x1e\x05\x00\x14\x1a\x1e\x00\x1f\x04\x18\x00\x00\x1d\x00\x00\x10\x1d\x00\x00\x1e\x08\x14\x00\x00\x1e\x00\x00\x1e\x04\x1e\x00\x1e\x02\x1e\x00\x0c\x12\x0c\x00\x1e\n\x04\x00\x04\n\x1e\x00\x1e\x04\x02\x00\x12\x15\t\x00\x04\x1e\x14\x00\x1e\x10\x1e\x00\x0e\x10\x0e\x00\x1e\x08\x1e\x00\x12\x0c\x12\x00\x16\x18\x0e\x00\x19\x15\x13\x00\x04\x1f\x11\x00\x00\x1f\x00\x00\x11\x1f\x04\x00\x02\x06\x04\x00\x15\n\x15\x00' font5x5_code=b'\x05\x05\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x03\x00\x03\x00\n\x1f\n\x1f\n\n\x17\x15\x1d\n\x13\t\x04\x12\x19\n\x15\x15\n\x10\x00\x03\x00\x00\x00\x00\x0e\x11\x00\x00\x00\x11\x0e\x00\x00\x00\n\x04\n\x00\x00\x04\x0e\x04\x00\x00\x10\x08\x00\x00\x00\x04\x04\x04\x00\x00\x08\x00\x00\x00\x10\x08\x04\x02\x01\x0e\x11\x11\x0e\x00\x00\x12\x1f\x10\x00\x19\x15\x15\x12\x00\t\x11\x15\x0b\x00\x0c\n\t\x1f\x08\x17\x15\x15\x15\t\x08\x14\x16\x15\x08\x11\t\x05\x03\x01\n\x15\x15\x15\n\x02\x15\r\x05\x02\x00\n\x00\x00\x00\x00\x10\n\x00\x00\x00\x04\n\x11\x00\x00\n\n\n\x00\x00\x11\n\x04\x00\x02\x01\x15\x05\x02\x0e\x11\x15\t\x0e\x1e\x05\x05\x1e\x00\x1f\x15\x15\n\x00\x0e\x11\x11\x11\x00\x1f\x11\x11\x0e\x00\x1f\x15\x15\x11\x00\x1f\x05\x05\x01\x00\x0e\x11\x11\x15\x0c\x1f\x04\x04\x1f\x00\x11\x1f\x11\x00\x00\t\x11\x11\x0f\x01\x1f\x04\n\x11\x00\x1f\x10\x10\x10\x00\x1f\x02\x04\x02\x1f\x1f\x02\x04\x08\x1f\x0e\x11\x11\x0e\x00\x1f\x05\x05\x02\x00\x06\t\x19\x16\x00\x1f\x05\x05\n\x10\x12\x15\x15\x08\x00\x01\x01\x1f\x01\x01\x0f\x10\x10\x0f\x00\x07\x08\x10\x08\x07\x07\x08\x10\x08\x07\x1b\x04\x04\x1b\x00\x01\x02\x1c\x02\x01\x19\x15\x13\x11\x00\x00\x1f\x11\x11\x00\x01\x02\x04\x08\x10\x00\x11\x11\x1f\x00\x00\x02\x01\x02\x00\x10\x10\x10\x10\x10\x00\x01\x02\x00\x00\x0c\x12\x12\x1e\x10\x1f\x14\x14\x08\x00\x0c\x12\x12\x12\x00\x08\x14\x14\x1f\x00\x0e\x15\x15\x12\x00\x04\x1e\x05\x01\x00\x02\x15\x15\x0f\x00\x1f\x04\x04\x18\x00\x00\x1d\x00\x00\x00\x00 \x1d\x00\x1f\x04\n\x10\x00\x00\x0f\x10\x10\x00\x1e\x02\x04\x02\x1e\x1e\x02\x02\x1c\x00\x0c\x12\x12\x0c\x00\x1e\n\n\x04\x00\x04\n\n\x1e\x00\x1c\x02\x02\x02\x00\x10\x14\n\x02\x00\x00\x0f\x14\x14\x10\x0e\x10\x10\x1e\x10\x06\x08\x10\x08\x06\x1e\x10\x08\x10\x1e\x12\x0c\x0c\x12\x00\x12\x14\x08\x04\x02\x12\x1a\x16\x12\x00\x00\x04\x1f\x11\x00\x00\x1f\x00\x00\x00\x11\x1f\x04\x00\x00\x00\x04\x04\x08\x08\x02\x06\x06\x06\x06' font5x8_code=b'\x05\x08\x00\x00\x00\x00\x00\x00\x00_\x00\x00\x00\x07\x00\x07\x00\x14\x7f\x14\x7f\x14$*\x7f*\x12#\x13\x08db6IV P\x00\x08\x07\x03\x00\x00\x1c"A\x00\x00A"\x1c\x00*\x1c\x7f\x1c*\x08\x08>\x08\x08\x00\x80p0\x00\x08\x08\x08\x08\x08\x00\x00``\x00 \x10\x08\x04\x02>QIE>\x00B\x7f@\x00rIIIF!AIM3\x18\x14\x12\x7f\x10\'EEE9A]YN|\x12\x11\x12|\x7fIII6>AAA"\x7fAAA>\x7fIIIA\x7f\t\t\t\x01>AAQs\x7f\x08\x08\x08\x7f\x00A\x7fA\x00 @A?\x01\x7f\x08\x14"A\x7f@@@@\x7f\x02\x1c\x02\x7f\x7f\x04\x08\x10\x7f>AAA>\x7f\t\t\t\x06>AQ!^\x7f\t\x19)F&III2\x03\x01\x7f\x01\x03?@@@?\x1f @ \x1f?@8@?c\x14\x08\x14c\x03\x04x\x04\x03aYIMC\x00\x7fAAA\x02\x04\x08\x10 \x00AAA\x7f\x04\x02\x01\x02\x04@@@@@\x00\x03\x07\x08\x00 TTx@\x7f(DD88DDD(8DD(\x7f8TTT\x18\x00\x08~\t\x02\x18\xa4\xa4\x9cx\x7f\x08\x04\x04x\x00D}@\x00 @@=\x00\x7f\x10(D\x00\x00A\x7f@\x00|\x04x\x04x|\x08\x04\x04x8DDD8\xfc\x18$$\x18\x18$$\x18\xfc|\x08\x04\x04\x08HTTT$\x04\x04?D$<@@ |\x1c @ \x1c<@0@> 26 buffer = bytearray(self.height*(font_width // 8 + 1)) esp.flash_read(self.start_address + font_address, buffer) return buffer, (font_width, self.height) class Image: def load(self, path, invert=0): self.invert = invert with open(path, 'rb') as file: image_type = file.read(2).decode() file.seek(0) img_arrays = bytearray(file.read()) if image_type == 'P4': buffer = self._pbm_decode(img_arrays) elif image_type == 'BM': buffer = self._bmp_decode(img_arrays) else: raise TypeError("Unsupported image format {}".format(image_type)) gc.collect() return buffer def load_py(self, name, invert=0): self.invert = invert image_type = name[0:2] if image_type == b'P4': buffer = self._pbm_decode(bytearray(name)) elif image_type == b'BM': buffer = self._bmp_decode(bytearray(name)) else: raise TypeError("Unsupported image format {}".format(image_type)) gc.collect() return buffer def _pbm_decode(self, img_arrays): next_value = bytearray() pnm_header = [] stat = True index = 3 while stat: next_byte = bytes([img_arrays[index]]) if next_byte == b"#": while bytes([img_arrays[index]]) not in [b"", b"\n"]: index += 1 if not next_byte.isdigit(): if next_value: pnm_header.append(int("".join(["%c" % char for char in next_value]))) next_value = bytearray() else: next_value += next_byte if len(pnm_header) == 2: stat = False index += 1 pixel_arrays = img_arrays[index:] if self.invert == 1: for i in range(len(pixel_arrays)): pixel_arrays[i] = (~pixel_arrays[i]) & 0xff return pixel_arrays,(pnm_header[0], pnm_header[1]) def _bmp_decode(self, img_arrays): file_size = int.from_bytes(img_arrays[2:6], 'little') offset = int.from_bytes(img_arrays[10:14], 'little') width = int.from_bytes(img_arrays[18:22], 'little') height = int.from_bytes(img_arrays[22:26], 'little') bpp = int.from_bytes(img_arrays[28:30], 'little') if bpp != 1: raise TypeError("Only support 1 bit color bmp") line_bytes_size = (bpp * width + 31) // 32 * 4 array_size = width * abs(height) // 8 pixel_arrays = bytearray(array_size) array_row = width // 8 + 1 if width % 8 else width // 8 array_col = height for i in range(array_col): for j in range(array_row): index = -(array_row * (i + 1) - j) _offset = offset + i * line_bytes_size + j if self.invert == 0: pixel_byte = (~img_arrays[_offset]) & 0xff else: pixel_byte = img_arrays[_offset] pixel_arrays[index] = pixel_byte return pixel_arrays,(width, height) class FrameBuffer_Base(FrameBuffer): """Inheritance and Extension""" def __init__(self, buf, width, height, *args, **kw): super().__init__(buf, width, height, *args, **kw) self.width = width self.height = height self._buffer = buf def show(self): print("External inheritance is required to override this method") def write(self): self.show() def shift(self, x, y, rotate=False, sync=True): """Shift pixels by x and y""" if x > 0: # Shift Right for _ in range(x): for row in range(0, self.height): last_pixel = super().pixel(self.width - 1, row) if rotate else 0 for col in range(self.width - 1, 0, -1): super().pixel(col, row, super().pixel(col - 1, row)) super().pixel(0, row, last_pixel) elif x < 0: # Shift Left for _ in range(-x): for row in range(0, self.height): last_pixel = super().pixel(0, row) if rotate else 0 for col in range(0, self.width - 1): super().pixel(col, row, super().pixel(col + 1, row)) super().pixel(self.width - 1, row, last_pixel) if y > 0: # Shift Up for _ in range(y): for col in range(0, self.width): last_pixel = super().pixel(col, self.height - 1) if rotate else 0 for row in range(self.height - 1, 0, -1): super().pixel(col, row, super().pixel(col, row - 1)) super().pixel(col, 0, last_pixel) elif y < 0: # Shift Down for _ in range(-y): for col in range(0, self.width): last_pixel = super().pixel(col, 0) if rotate else 0 for row in range(0, self.height - 1): super().pixel(col, row, super().pixel(col, row + 1)) super().pixel(col, self.height - 1, last_pixel) if sync: self.show() def shift_right(self, num, rotate=False, sync=True): """Shift all pixels right""" self.shift(num, 0, rotate, sync) def shift_left(self, num, rotate=False, sync=True): """Shift all pixels left""" self.shift(-num, 0, rotate, sync) def shift_up(self, num, rotate=False, sync=True): """Shift all pixels up""" self.shift(0, -num, rotate, sync) def shift_down(self, num, rotate=False, sync=True): """Shift all pixels down""" self.shift(0, num, rotate, sync) def map_invert(self, own): """Graph invert operation""" if type(own) in [list, bytes, tuple, bytearray]: result=bytearray() for i in range(len(own)): result.append(~ own[i]) return result else: raise ValueError("This graphic operation is not supported") def map_add(self, own, other): """Graph union operation""" if type(own) in [list, bytes, tuple, bytearray] and type(other) in [list, bytes, tuple, bytearray]: result=bytearray() for i in range(min(len(own), len(other))): result.append(own[i] | other[i]) return result else: raise ValueError("This graphic operation is not supported") def map_sub(self, own, other): """Graphic subtraction operation""" if type(own) in [list, bytes, tuple, bytearray] and type(other) in [list, bytes, tuple, bytearray]: result=bytearray() for i in range(min(len(own), len(other))): result.append((own[i] ^ other[i]) & own[i]) return result else: raise ValueError("This graphic operation is not supported") def set_buffer(self, buffer, sync=True): for i in range(min(len(buffer),len(self._buffer))): self._buffer[i] = self._buffer[i] | buffer[i] if sync: self.show() def get_buffer(self): return self._buffer def pointern(self, x=None, y=None, l=None, angle=0, color=0xffff, bg_color=0x0, sync=True): x = self.width // 2 if x is None else x y = self.height // 2 if y is None else y l = min(self.height // 2 , self.width // 2) if l is None else l radian = math.radians(angle) if sync: super().fill(bg_color) super().line(x, y, round(x + l * math.sin(radian)), round(y - l * math.cos(radian)), color) if sync: self.show() def pixel(self, x, y, color=None, sync=True): if color is None: return super().pixel(x, y) else: super().pixel(x, y, color) if sync: self.show() def vline(self, x, y, h, c, sync=True): super().vline(x, y, h, c) if sync: self.show() def hline(self, x, y, w, c, sync=True): super().hline(x, y, w, c) if sync: self.show() def line(self, x1, y1, x2, y2, c, sync=True): super().line(x1, y1, x2, y2, c) if sync: self.show() def rect(self, x, y, w, h, c, sync=True): super().rect(x, y, w, h, c) if sync: self.show() def fill_rect(self, x, y, w, h, c, sync=True): super().fill_rect(x, y, w, h, c) if sync: self.show() def ellipse(self, x, y, xr, yr, c, f=False, sync=True): super().ellipse(x, y, xr, yr, c, f) if sync: self.show() def fill(self, c, sync=True): super().fill(c) if sync: self.show() class FrameBuffer_Ascall(FrameBuffer_Base): '''FrameBuffer for Ascall''' def font(self, font): """Font selection or externally defined font code""" self._font = Font_Ascall(font) def bitmap(self, buffer, x=0, y=0): """Graphic model display(buffer,(width,height))""" buffer_info, (width, height) = buffer if x < -width or x >= self.width or y < -height or y >= self.height: return #Limit reasonable display area for char_x in range(width): for char_y in range(height): if (buffer_info[char_x] >> char_y) & 0x1: self.pixel(x + char_x, y + char_y, 1, sync=False) if height <= self.height else self.pixel(y + char_y, self.height - (x + char_x), 1, sync=False) def shows(self, data, space=0, center=True, sync=True): """Display character""" if data is not None: self.fill(0, sync=False) if type(data) in [list, bytes, tuple, bytearray]: self.set_buffer(data, sync) else: data=str(data) x = (self.width - len(data) * (self._font.font_width + space) + space) // 2 if center else 0 for char in data: self.bitmap(self._font.chardata(char), x) x = self._font.font_width + x + space if sync: self.show() def frame(self, data, delay=500): """Display one frame per character""" if data is not None: if type(data) in [list,tuple]: for dat in data: if type(dat) in [list,bytes,tuple,bytearray]: self.fill(0, sync=False) self.set_buffer(data, True) time.sleep_ms(delay) else: data=str(data) x=(self.width - self._font.font_width) // 2 for char in data: self.fill(0, sync=False) self.bitmap(self._font.chardata(char), x) self.show() time.sleep_ms(delay) def scroll(self, data, space=0, speed=100): """Scrolling characters""" if data is not None: data = str(data) str_len = len(data) * (self._font.font_width + space) - space for i in range(str_len + self.width + 1): x = -i + self.width self.fill(0, sync=False) for char in data: self.bitmap(self._font.chardata(char),x) x = self._font.font_width + x + space self.show() time.sleep_ms(speed) class FrameBuffer_Uincode(FrameBuffer_Base): '''FrameBuffer for Uincode''' def font(self, font_address=0x700000): """Font selection or externally defined font code""" self._font = Font_Uincode(font_address) def shift(self, x, y, rotate=False, sync=True): '''Reshaping Inheritance Methods''' super().scroll(x, y) if sync: self.show() def image(self, path, x=None, y=None, size=None, invert=0, color=0xffff, bold=0, sync=True): """Set buffer to value of Python Imaging Library image""" if type(path) is str : buffer_info, (width, height) = Image().load(path, invert) elif type(path) in [bytes, bytearray]: buffer_info, (width, height) = Image().load_py(path, invert) else: raise ValueError("invalid input") if width > self.width or height > self.height: raise ValueError("Image must be less than display ({0}x{1}).".format(self.width, self.height)) size = min(self.height // height, self.width // width) if size is None else size size = max(round(size), 1) x =(self.width - width * size) // 2 if x is None else x y =(self.height - height * size) // 2 if y is None else y if sync: self.fill_rect(x, y, width * size, height * size, 0, sync=False) self.bitmap((buffer_info,(width, height)), x, y, size, bold, color) if sync: self.show() def bitmap(self, buffer, x=0, y=0, size=1, bold=0, color=0xffff): """Graphic model display(buffer,(width,height))""" buffer_info,(width,height)=buffer if x < -width*size or x >= self.width or y < -height*size or y >= self.height: return #Limit reasonable display area bytewidth = (width + 7) // 8 for j in range(height): for i in range(width): if buffer_info[j * bytewidth + i // 8] & (0x80 >> (i & 7)): self.fill_rect(x + i * size, y + j * size, int(size + bold), int(size + bold), color, sync=False) def _take_buffer(self, strs, space, size=1): '''Get character lattice information first''' font_buffer = [] font_len = 0 for c in strs: buffer = self._font.chardata(c) font_buffer.append(buffer) font_len = font_len + buffer[1][0] * size + space return font_len, font_buffer def shows(self, data, space=0, center=True, x=0, y=None, size=None, color=0xffff, bold=0, bg_color=0x0, sync=True): """Display character""" if data is not None: if type(data) in [list, bytes, tuple, bytearray]: if sync: self.fill(bg_color, sync=False) self.set_buffer(data, sync) else: yy = y if size is None: font_len, font_buffer = self._take_buffer(str(data), space, 1) size = min((self.width // font_len) if font_len > 0 else 1, self.height // self._font.height) size = max(round(size), 1) font_len, font_buffer = self._take_buffer(str(data), space, size) x = (self.width - font_len + space) // 2 if center else x y = (self.height - self._font.height * size) // 2 if y is None else y if sync: if yy is None: self.fill(bg_color, sync=False) else: self.fill_rect(0, y - 1, self.width, self._font.height * size + 2, bg_color, sync=False) for buffer in font_buffer: #Display character self.bitmap(buffer, x, y, size, bold, color) x = buffer[1][0] * size + x + space if sync: self.show() def texts(self, data, space_x=0, space_y=1, x=0, y=0, size=1, color=0xffff, bold=0, bg_color=0x0, sync=True): size = max(round(size), 1) lines = data.split('\n') if sync: self.fill(bg_color, sync=False) for line in lines: for char in line: buffer = self._font.chardata(char) if x > self.width - buffer[1][0] * size: x = 0 y = buffer[1][1] * size + y + space_y if y > self.height: if sync: self.show() return None self.bitmap(buffer, x, y, size, bold, color) x = buffer[1][0] * size + x + space_x x = 0 y = self._font.height * size + y + space_y if sync: self.show() def frame(self, data, delay=500, size=None, color=0xffff, bold=0, bg_color=0x0): """Display one frame per character""" if data is not None: if type(data) in [list, tuple]: for dat in data: if type(dat) in [list, bytes, tuple, bytearray]: self.fill(bg_color, sync=False) self.set_buffer(data, True) time.sleep_ms(delay) else: size = self.height // (self._font.height * 3) if size is None else size size = max(round(size), 1) _, font_buffer = self._take_buffer(str(data), 0) for buffer in font_buffer: x=(self.width - buffer[1][0] * size) // 2 y=(self.height - buffer[1][1] * size) // 2 self.fill(bg_color, sync=False) self.bitmap(buffer, x, y, size, bold, color) self.show() time.sleep_ms(delay) def scroll(self, data, space=0, speed=20, y=None, size=None, step= None, color=0xffff, bold=0, bg_color=0x0): """Scrolling characters""" if data is not None: size = self.height // (self._font.height * 3) if size is None else size size = max(round(size), 1) step = max(self.width // 30, 1)if step is None else step font_len, font_buffer = self._take_buffer(str(data), space, size) for i in range(0, font_len - space + self.width, step): x = -i + self.width y = (self.height - self._font.height * size) // 2 if y is None else y self.fill_rect(x - 2 , y - 2 , self.width -x + 4, font_buffer[0][1][1] * size + 4, bg_color, sync=False) for buffer in font_buffer: self.bitmap(buffer, x, y, size, bold, color) x = buffer[1][0] * size + x + space self.show() time.sleep_ms(speed)