From c0eee33bec9b71308ce6e2b7e69380f34cb25d6a Mon Sep 17 00:00:00 2001 From: Dejvino Date: Thu, 20 Apr 2023 05:31:56 +0200 Subject: [PATCH] Performance optimization of image loading - inlining --- microbmp.py | 47 +++++++++++++++++++------------- test.py | 77 ++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/microbmp.py b/microbmp.py index 8a623e0..7584b98 100644 --- a/microbmp.py +++ b/microbmp.py @@ -15,7 +15,7 @@ __all__ = ["MicroBMP"] class MicroBMP(object): - def __init__(self, width=None, height=None, depth=None, palette=None, data_callback=None): + def __init__(self, width=None, height=None, depth=None, palette=None, header_callback=None, data_callback=None): # BMP Header self.BMP_id = b"BM" self.BMP_size = None @@ -44,6 +44,7 @@ class MicroBMP(object): self.row_size = None self.padded_row_size = None + self.header_callback = header_callback self.data_callback = data_callback self.initialised = False @@ -211,7 +212,7 @@ class MicroBMP(object): x += 1 def read_io(self, bf_io): - print("BMP reading file") + #print("BMP reading file") # BMP Header data = bf_io.read(14) self.BMP_id = data[0:2] @@ -234,18 +235,7 @@ class MicroBMP(object): self.DIB_hres, self.DIB_vres, ) = unpack(" 40: @@ -270,31 +260,52 @@ class MicroBMP(object): else: is_top_down = False + header = [ + self.DIB_w, + self.DIB_h, + self.DIB_planes_num, + self.DIB_depth, + self.DIB_comp, + self.DIB_raw_size, + self.DIB_hres, + self.DIB_vres, + self.palette, + ] + #print("BMP metadata: ", str(header)) + + if self.header_callback is not None: + self.header_callback(header) self.parray = None assert self._init(), "Failed to initialize the image!" # Pixels - print("BMP reading data") + #print("BMP reading data") if self.DIB_comp == 0: # BI_RGB for h in range(self.DIB_h): y = h if is_top_down else self.DIB_h - h - 1 data = bf_io.read(self.padded_row_size) + #pixels_row = [] for x in range(self.DIB_w): if self.DIB_depth <= 8: - #self[x, y] = self._extract_from_bytes(data, x) - pixel_data = self._extract_from_bytes(data, x) + ##self[x, y] = self._extract_from_bytes(data, x) + byte_index, pos_in_byte = divmod(x, self.ppb) + shift = 8 - self.DIB_depth * (pos_in_byte + 1) + pixel_data = (data[byte_index] >> shift) & self.pmask + #pixel_data = self._extract_from_bytes(data, x) pixel = pixel_data if self.palette is None else self.palette[pixel_data] + ##pixels_row.append(pixel) self.data_callback(x, y, pixel) else: v = x * 3 # BMP colour is in BGR order. self[x, y] = (data[v + 2], data[v + 1], data[v]) + #self.data_callback(0, y, pixels_row) else: # BI_RLE8 or BI_RLE4 self._decode_rle(bf_io) - print("BMP done") + #print("BMP done") return self def write_io(self, bf_io, force_40B_DIB=False): diff --git a/test.py b/test.py index 0a2c049..de3bf67 100644 --- a/test.py +++ b/test.py @@ -1,9 +1,11 @@ import epaper import microbmp import time +import random import gc -colormap = [ +epd_resolution = [600, 448] +epd_colormap = [ [0x00, 0x00, 0x00], # black [0xff, 0xff, 0xff], # white [0x00, 0xdd, 0x00], # green @@ -25,32 +27,63 @@ def init_display(): def draw_image(filename): global epd - global colormap - def color_distance(c1, c2): - def dist(a, b): - return abs(a - b) - return dist(c1[0], c2[0]) + dist(c1[1], c2[1]) + dist(c1[2], c2[2]) - def callback(x, y, color): + global epd_colormap + offset_x = 0 + offset_y = 0 + color_map = {} + def header_callback(header): + print("header callback: ", str(header)) + global epd_resolution + w = header[0] + h = header[1] + offset_x = (epd_resolution[0] - w)//2 + offset_y = (epd_resolution[1] - h)//2 + print("offset: ", offset_x, offset_y) + def pixel_callback(x, y, color): global epd - global colormap - best_index = 0 - best_score = 256 - for index in range(len(colormap)): - c = colormap[index] - score = color_distance(c, color) - if score < best_score: - best_score = score - best_index = index - pixel = best_index + global epd_colormap + + # translate color to color index + color_index = 0 + color_key = color[0] + color[1] << 8 + color[2] << 16 + if color_key in color_map: + color_index = color_map[color_key] + else: + # search for the best color + best_index = 0 + best_score = 1000 + for index in range(len(epd_colormap)): + c1 = epd_colormap[index] + c2 = color + score = abs(c1[0] - c2[0]) + abs(c1[1] - c2[1]) + abs(c1[2] - c2[2]) + if score < best_score: + best_score = score + best_index = index + if score < 20: + break + color_index = best_index + color_map[color_key] = color_index + + # hack directly into the 4-bit color buffer instead of: + # epd.pixel(offset_x + x, offset_y + y, color_to_index(color)) + buffer_pos = (offset_x + x + (offset_y + y) * epd_resolution[0]) + buffer_index = buffer_pos // 2 + if buffer_pos % 2 == 1: + buffer_pos = buffer_pos << 4 + epd.buffer[buffer_index] = color_index + def pixel_row_callback(x, y, colors): + pass + #global epd #print("PXL ", str([color, r,g,b, pixel])) - epd.pixel(x, y, pixel) + #for i in range(len(colors)): + # epd.pixel(offset_x + x + i, offset_y + y, color_to_index(colors[i])) - #print("BMP ", filename, " loading ", str(time.localtime())) + print(str(time.localtime()), " BMP ", filename, " loading") epd.fill(epd.White) - microbmp.MicroBMP(data_callback=callback).load(filename) - #print("BMP loaded ", str(time.localtime())) + microbmp.MicroBMP(header_callback=header_callback, data_callback=pixel_callback).load(filename) + print(str(time.localtime()), " BMP loaded") epd.EPD_5IN65F_Display(epd.buffer) - #print("ePaper printed ", str(time.localtime())) + print(str(time.localtime()), " ePaper printed") # MAIN