@@ -0,0 +1,309 @@ | |||
# ***************************************************************************** | |||
# * | File : Pico_ePaper-5.65.py | |||
# * | Author : Waveshare team | |||
# * | Function : Electronic paper driver | |||
# * | Info : | |||
# *---------------- | |||
# * | This version: V1.0 | |||
# * | Date : 2021-06-04 | |||
# # | Info : python demo | |||
# ----------------------------------------------------------------------------- | |||
# Permission is hereby granted, free of charge, to any person obtaining a copy | |||
# of this software and associated documnetation files (the "Software"), to deal | |||
# in the Software without restriction, including without limitation the rights | |||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
# copies of the Software, and to permit persons to whom the Software is | |||
# furished to do so, subject to the following conditions: | |||
# | |||
# The above copyright notice and this permission notice shall be included in | |||
# all copies or substantial portions of the Software. | |||
# | |||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |||
# THE SOFTWARE. | |||
# | |||
# ----------------- | |||
# Source: https://github.com/waveshare/Pico_ePaper_Code/blob/main/python/Pico-ePaper-5.65f.py | |||
# Wiki: https://www.waveshare.com/wiki/Pico-ePaper-5.65 | |||
# | |||
from machine import Pin, SPI | |||
import framebuf | |||
import utime | |||
# Display resolution | |||
EPD_WIDTH = 600 | |||
EPD_HEIGHT = 448 | |||
RST_PIN = 12 | |||
DC_PIN = 8 | |||
CS_PIN = 9 | |||
BUSY_PIN = 13 | |||
class EPD_5in65(framebuf.FrameBuffer): | |||
def __init__(self): | |||
self.reset_pin = Pin(RST_PIN, Pin.OUT) | |||
self.busy_pin = Pin(BUSY_PIN, Pin.IN, Pin.PULL_UP) | |||
self.cs_pin = Pin(CS_PIN, Pin.OUT) | |||
self.width = EPD_WIDTH | |||
self.height = EPD_HEIGHT | |||
self.Black = 0x00 | |||
self.White = 0x01 | |||
self.Green = 0x02 | |||
self.Blue = 0x03 | |||
self.Red = 0x04 | |||
self.Yellow = 0x05 | |||
self.Orange = 0x06 | |||
self.Clean = 0x07 | |||
self.spi = SPI(1) | |||
self.spi.init(baudrate=4000_000) | |||
self.dc_pin = Pin(DC_PIN, Pin.OUT) | |||
self.buffer = bytearray(self.height * self.width // 2) | |||
super().__init__(self.buffer, self.width, self.height, framebuf.GS4_HMSB) | |||
self.EPD_5IN65F_Init() | |||
def digital_write(self, pin, value): | |||
pin.value(value) | |||
def digital_read(self, pin): | |||
return pin.value() | |||
def delay_ms(self, delaytime): | |||
utime.sleep(delaytime / 1000.0) | |||
def spi_writebyte(self, data): | |||
self.spi.write(bytearray(data)) | |||
def module_exit(self): | |||
self.digital_write(self.reset_pin, 0) | |||
print("ePaper exited") | |||
# Hardware reset | |||
def reset(self): | |||
self.digital_write(self.reset_pin, 1) | |||
self.delay_ms(200) | |||
self.digital_write(self.reset_pin, 0) | |||
self.delay_ms(1) | |||
self.digital_write(self.reset_pin, 1) | |||
self.delay_ms(200) | |||
print("ePaper reset") | |||
def send_command(self, command): | |||
self.digital_write(self.dc_pin, 0) | |||
self.digital_write(self.cs_pin, 0) | |||
self.spi_writebyte([command]) | |||
self.digital_write(self.cs_pin, 1) | |||
def send_data(self, data): | |||
self.digital_write(self.dc_pin, 1) | |||
self.digital_write(self.cs_pin, 0) | |||
self.spi_writebyte([data]) | |||
self.digital_write(self.cs_pin, 1) | |||
def send_data1(self, buf): | |||
self.digital_write(self.dc_pin, 1) | |||
self.digital_write(self.cs_pin, 0) | |||
self.spi.write(bytearray(buf)) | |||
self.digital_write(self.cs_pin, 1) | |||
def BusyHigh(self): | |||
while(self.digital_read(self.busy_pin) == 0): | |||
self.delay_ms(1) | |||
def BusyLow(self): | |||
while(self.digital_read(self.busy_pin) == 1): | |||
self.delay_ms(1) | |||
def EPD_5IN65F_Init(self): | |||
self.reset(); | |||
self.BusyHigh(); | |||
self.send_command(0x00); | |||
self.send_data(0xEF); | |||
self.send_data(0x08); | |||
self.send_command(0x01); | |||
self.send_data(0x37); | |||
self.send_data(0x00); | |||
self.send_data(0x23); | |||
self.send_data(0x23); | |||
self.send_command(0x03); | |||
self.send_data(0x00); | |||
self.send_command(0x06); | |||
self.send_data(0xC7); | |||
self.send_data(0xC7); | |||
self.send_data(0x1D); | |||
self.send_command(0x30); | |||
self.send_data(0x3C); | |||
self.send_command(0x41); | |||
self.send_data(0x00); | |||
self.send_command(0x50); | |||
self.send_data(0x37); | |||
self.send_command(0x60); | |||
self.send_data(0x22); | |||
self.send_command(0x61); | |||
self.send_data(0x02); | |||
self.send_data(0x58); | |||
self.send_data(0x01); | |||
self.send_data(0xC0); | |||
self.send_command(0xE3); | |||
self.send_data(0xAA); | |||
self.delay_ms(100); | |||
self.send_command(0x50); | |||
self.send_data(0x37); | |||
print("ePaper inited") | |||
def EPD_5IN65F_Clear(self,color): | |||
self.send_command(0x61) # Set Resolution setting | |||
self.send_data(0x02) | |||
self.send_data(0x58) | |||
self.send_data(0x01) | |||
self.send_data(0xC0) | |||
self.send_command(0x10) | |||
for i in range(0,int(self.width / 2)): | |||
self.send_data1([(color<<4)|color] * self.height) | |||
self.send_command(0x04) # 0x04 | |||
self.BusyHigh() | |||
self.send_command(0x12) # 0x12 | |||
self.BusyHigh() | |||
self.send_command(0x02) # 0x02 | |||
self.BusyLow() | |||
self.delay_ms(500) | |||
print("ePaper cleared") | |||
def EPD_5IN65F_Display(self,image): | |||
self.send_command(0x61) # Set Resolution setting | |||
self.send_data(0x02) | |||
self.send_data(0x58) | |||
self.send_data(0x01) | |||
self.send_data(0xC0) | |||
self.send_command(0x10) | |||
for i in range(0, int(self.width // 2)): | |||
self.send_data1(image[(i*self.height):((i+1)*self.height)]) | |||
self.send_command(0x04) # 0x04 | |||
self.BusyHigh() | |||
self.send_command(0x12) # 0x12 | |||
self.BusyHigh() | |||
self.send_command(0x02) # 0x02 | |||
self.BusyLow() | |||
self.delay_ms(200) | |||
print("ePaper displayed image") | |||
def EPD_5IN65F_Display_part(self,image,xstart,ystart,image_width,image_heigh): | |||
self.send_command(0x61) # Set Resolution setting | |||
self.send_data(0x02) | |||
self.send_data(0x58) | |||
self.send_data(0x01) | |||
self.send_data(0xC0) | |||
self.send_command(0x10) | |||
for i in range(0, self.height): | |||
for j in range(0, int(self.width / 2)): | |||
if((i<(image_heigh+ystart)) & (i>(ystart-1) ) & (j<(image_width+xstart)/2) & (j>(xstart/2 - 1))): | |||
self.send_data(image[(j-xstart/2) + (image_width/2*(i-ystart))]) | |||
else: | |||
self.send_data(0x11) | |||
self.send_command(0x04) # 0x04 | |||
self.BusyHigh() | |||
self.send_command(0x12) # 0x12 | |||
self.BusyHigh() | |||
self.send_command(0x02) # 0x02 | |||
self.BusyLow() | |||
self.delay_ms(200) | |||
print("ePaper displayed part") | |||
def Sleep(self): | |||
self.delay_ms(100); | |||
self.send_command(0x07); | |||
self.send_data(0xA5); | |||
self.delay_ms(100); | |||
self.digital_write(self.reset_pin, 1) | |||
print("ePaper entered sleep") | |||
if __name__=='__main__': | |||
print("ePaper test starting") | |||
epd = EPD_5in65() | |||
epd.EPD_5IN65F_Clear(epd.White) | |||
epd.fill(0xff) | |||
epd.text("Waveshare", 5, 5, epd.Black) | |||
epd.text("Pico_ePaper-5.65", 5, 20, epd.Black) | |||
epd.text("Raspberry Pico", 5, 35, epd.Black) | |||
epd.EPD_5IN65F_Display(epd.buffer) | |||
print("Test: text") | |||
epd.delay_ms(5000) | |||
epd.vline(10, 60, 60, epd.Black) | |||
epd.vline(90, 60, 60, epd.Black) | |||
epd.hline(10, 60, 80, epd.Black) | |||
epd.hline(10, 120, 80, epd.Black) | |||
epd.line(10, 60, 90, 120, epd.Black) | |||
epd.line(90, 60, 10, 120, epd.Black) | |||
epd.rect(10, 136, 50, 80, epd.Black) | |||
epd.fill_rect(70, 136, 50, 80, epd.Black) | |||
epd.EPD_5IN65F_Display(epd.buffer) | |||
print("Test: black lines and rects") | |||
epd.delay_ms(5000) | |||
epd.text('Black',200,11,epd.Black) | |||
epd.fill_rect(300, 0, 300, 30, epd.Black) | |||
epd.text('White',200,41,epd.White) | |||
epd.fill_rect(300, 30, 300, 30, epd.White) | |||
epd.text('Green',200,71,epd.Green) | |||
epd.fill_rect(300, 60, 300, 30, epd.Green) | |||
epd.text('Blue',200,101,epd.Blue) | |||
epd.fill_rect(300, 90, 300, 30, epd.Blue) | |||
epd.text('Red',200,131,epd.Red) | |||
epd.fill_rect(300, 120, 300, 30, epd.Red) | |||
epd.text('Yellow',200,161,epd.Yellow) | |||
epd.fill_rect(300, 150, 300, 30, epd.Yellow) | |||
epd.text('Orange',200,191,epd.Orange) | |||
epd.fill_rect(300, 180, 300, 30, epd.Orange) | |||
epd.text('Clean',200,221,epd.Black) | |||
epd.fill_rect(300, 210, 300, 30, epd.Clean) | |||
epd.EPD_5IN65F_Display(epd.buffer) | |||
print("Test: color names") | |||
epd.delay_ms(5000) | |||
j = 0 | |||
for i in range(-250,600): | |||
epd.line(i, 238, i+250, 448, j) | |||
if (i%30==0) : | |||
j = j+1 | |||
j = j%7 | |||
epd.EPD_5IN65F_Display(epd.buffer) | |||
print("Test: color lines") | |||
epd.delay_ms(5000) | |||
epd.EPD_5IN65F_Clear(epd.White) | |||
epd.Sleep() | |||
print("END") | |||
@@ -0,0 +1,11 @@ | |||
#!/bin/bash | |||
# additional options: | |||
# -posterize 3 | |||
SRC=$1 | |||
DST=$2 | |||
shift 2 | |||
convert "$SRC" $@ -resize 600x448 -dither FloydSteinberg -type Palette -remap palette.bmp "$DST" | |||
@@ -0,0 +1,382 @@ | |||
# -*- coding: utf-8 -*- | |||
"""A small Python module for BMP image processing. | |||
- Author: Quan Lin | |||
- Adapted by: Dejvino | |||
- Adapted from: https://github.com/jacklinquan/micropython-microbmp | |||
- License: MIT | |||
""" | |||
from struct import pack, unpack | |||
# Project Version | |||
__version__ = "0.1.0" | |||
__all__ = ["MicroBMP"] | |||
class MicroBMP(object): | |||
def __init__(self, width=None, height=None, depth=None, palette=None, data_callback=None): | |||
# BMP Header | |||
self.BMP_id = b"BM" | |||
self.BMP_size = None | |||
self.BMP_reserved1 = b"\x00\x00" | |||
self.BMP_reserved2 = b"\x00\x00" | |||
self.BMP_offset = None | |||
# DIB Header | |||
self.DIB_len = 40 | |||
self.DIB_w = width | |||
self.DIB_h = height | |||
self.DIB_planes_num = 1 | |||
self.DIB_depth = depth | |||
self.DIB_comp = 0 | |||
self.DIB_raw_size = None | |||
self.DIB_hres = 2835 # 72 DPI * 39.3701 inches/metre. | |||
self.DIB_vres = 2835 | |||
self.DIB_num_in_plt = None | |||
self.DIB_extra = None | |||
self.palette = palette | |||
self.parray = None # Pixel array | |||
self.ppb = None # Number of pixels per byte for depth <= 8. | |||
self.pmask = None # Pixel Mask | |||
self.row_size = None | |||
self.padded_row_size = None | |||
self.data_callback = data_callback | |||
self.initialised = False | |||
self._init() | |||
def __getitem__(self, key): | |||
assert self.initialised, "Image not initialised!" | |||
assert key[0] < self.DIB_w and key[1] < self.DIB_h, "Out of image boundary!" | |||
# Pixels are arranged in HLSB format with high bits being the leftmost | |||
pindex = key[1] * self.DIB_w + key[0] # Pixel index | |||
if self.DIB_depth <= 8: | |||
return self._extract_from_bytes(self.parray, pindex) | |||
else: | |||
pindex *= 3 | |||
if (len(key) > 2) and (key[2] in (0, 1, 2)): | |||
return self.parray[pindex + key[2]] | |||
else: | |||
return ( | |||
self.parray[pindex], | |||
self.parray[pindex + 1], | |||
self.parray[pindex + 2], | |||
) | |||
def __setitem__(self, key, value): | |||
assert self.initialised, "Image not initialised!" | |||
assert key[0] < self.DIB_w and key[1] < self.DIB_h, "Out of image boundary!" | |||
# Pixels are arranged in HLSB format with high bits being the leftmost | |||
pindex = key[1] * self.DIB_w + key[0] # Pixel index | |||
if self.DIB_depth <= 8: | |||
self._fill_in_bytes(self.parray, pindex, value) | |||
else: | |||
pindex *= 3 | |||
if (len(key) > 2) and (key[2] in (0, 1, 2)): | |||
self.parray[pindex + key[2]] = value | |||
else: | |||
self.parray[pindex] = value[0] | |||
self.parray[pindex + 1] = value[1] | |||
self.parray[pindex + 2] = value[2] | |||
def __str__(self): | |||
if not self.initialised: | |||
return repr(self) | |||
return "BMP image, {}, {}-bit, {}x{} pixels, {} bytes".format( | |||
"indexed" if self.DIB_depth <= 8 else "RGB", | |||
self.DIB_depth, | |||
self.DIB_w, | |||
self.DIB_h, | |||
self.BMP_size, | |||
) | |||
def _init(self): | |||
if None in (self.DIB_w, self.DIB_h, self.DIB_depth): | |||
self.initialised = False | |||
return self.initialised | |||
assert self.BMP_id == b"BM", "BMP id ({}) must be b'BM'!".format(self.BMP_id) | |||
assert ( | |||
len(self.BMP_reserved1) == 2 and len(self.BMP_reserved2) == 2 | |||
), "Length of BMP reserved fields ({}+{}) must be 2+2!".format( | |||
len(self.BMP_reserved1), len(self.BMP_reserved2) | |||
) | |||
assert self.DIB_planes_num == 1, "DIB planes number ({}) must be 1!".format( | |||
self.DIB_planes_num | |||
) | |||
assert self.DIB_depth in ( | |||
1, | |||
2, | |||
4, | |||
8, | |||
24, | |||
), "Colour depth ({}) must be in (1, 2, 4, 8, 24)!".format(self.DIB_depth) | |||
assert ( | |||
self.DIB_comp == 0 | |||
or (self.DIB_depth == 8 and self.DIB_comp == 1) | |||
or (self.DIB_depth == 4 and self.DIB_comp == 2) | |||
), "Colour depth + compression ({}+{}) must be X+0/8+1/4+2!".format( | |||
self.DIB_depth, self.DIB_comp | |||
) | |||
if self.DIB_depth <= 8: | |||
self.ppb = 8 // self.DIB_depth | |||
self.pmask = 0xFF >> (8 - self.DIB_depth) | |||
if self.palette is None: | |||
# Default palette is black and white or full size grey scale. | |||
self.DIB_num_in_plt = 2 ** self.DIB_depth | |||
self.palette = [None for i in range(self.DIB_num_in_plt)] | |||
for i in range(self.DIB_num_in_plt): | |||
# Assignment that suits all: 1/2/4/8-bit colour depth. | |||
s = 255 * i // (self.DIB_num_in_plt - 1) | |||
self.palette[i] = bytearray([s, s, s]) | |||
else: | |||
self.DIB_num_in_plt = len(self.palette) | |||
else: | |||
self.ppb = None | |||
self.pmask = None | |||
self.DIB_num_in_plt = 0 | |||
self.palette = None | |||
#if self.parray is None: | |||
# if self.DIB_depth <= 8: | |||
# div, mod = divmod(self.DIB_w * self.DIB_h, self.ppb) | |||
# self.parray = bytearray(div + (1 if mod else 0)) | |||
# else: | |||
# self.parray = bytearray(self.DIB_w * self.DIB_h * 3) | |||
plt_size = self.DIB_num_in_plt * 4 | |||
self.BMP_offset = 14 + self.DIB_len + plt_size | |||
self.row_size = self._size_from_width(self.DIB_w) | |||
self.padded_row_size = self._padded_size_from_size(self.row_size) | |||
if self.DIB_comp == 0: | |||
self.DIB_raw_size = self.padded_row_size * self.DIB_h | |||
self.BMP_size = self.BMP_offset + self.DIB_raw_size | |||
self.initialised = True | |||
return self.initialised | |||
def _size_from_width(self, width): | |||
return (width * self.DIB_depth + 7) // 8 | |||
def _padded_size_from_size(self, size): | |||
return (size + 3) // 4 * 4 | |||
def _extract_from_bytes(self, data, index): | |||
# One formula that suits all: 1/2/4/8-bit colour depth. | |||
byte_index, pos_in_byte = divmod(index, self.ppb) | |||
shift = 8 - self.DIB_depth * (pos_in_byte + 1) | |||
return (data[byte_index] >> shift) & self.pmask | |||
def _fill_in_bytes(self, data, index, value): | |||
# One formula that suits all: 1/2/4/8-bit colour depth. | |||
byte_index, pos_in_byte = divmod(index, self.ppb) | |||
shift = 8 - self.DIB_depth * (pos_in_byte + 1) | |||
value &= self.pmask | |||
data[byte_index] = (data[byte_index] & ~(self.pmask << shift)) + ( | |||
value << shift | |||
) | |||
def _decode_rle(self, bf_io): | |||
# Only bottom-up bitmap can be compressed. | |||
x, y = 0, self.DIB_h - 1 | |||
while True: | |||
data = bf_io.read(2) | |||
if data[0] == 0: | |||
if data[1] == 0: | |||
x, y = 0, y - 1 | |||
elif data[1] == 1: | |||
return | |||
elif data[1] == 2: | |||
data = bf_io.read(2) | |||
x, y = x + data[0], y - data[1] | |||
else: | |||
num_of_pixels = data[1] | |||
num_to_read = (self._size_from_width(num_of_pixels) + 1) // 2 * 2 | |||
data = bf_io.read(num_to_read) | |||
for i in range(num_of_pixels): | |||
self[x, y] = self._extract_from_bytes(data, i) | |||
x += 1 | |||
else: | |||
b = bytes([data[1]]) | |||
for i in range(data[0]): | |||
self[x, y] = self._extract_from_bytes(b, i % self.ppb) | |||
x += 1 | |||
def read_io(self, bf_io): | |||
print("BMP reading file") | |||
# BMP Header | |||
data = bf_io.read(14) | |||
self.BMP_id = data[0:2] | |||
self.BMP_size = unpack("<I", data[2:6])[0] | |||
self.BMP_reserved1 = data[6:8] | |||
self.BMP_reserved2 = data[8:10] | |||
self.BMP_offset = unpack("<I", data[10:14])[0] | |||
# DIB Header | |||
data = bf_io.read(4) | |||
self.DIB_len = unpack("<I", data[0:4])[0] | |||
data = bf_io.read(self.DIB_len - 4) | |||
( | |||
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, | |||
) = unpack("<iiHHIIii", data[0:28]) | |||
print("BMP metadata: ", str([ | |||
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, | |||
])) | |||
DIB_plt_num_info = unpack("<I", data[28:32])[0] | |||
DIB_plt_important_num_info = unpack("<I", data[32:36])[0] | |||
if self.DIB_len > 40: | |||
self.DIB_extra = data[36:] | |||
# Palette | |||
if self.DIB_depth <= 8: | |||
if DIB_plt_num_info == 0: | |||
self.DIB_num_in_plt = 2 ** self.DIB_depth | |||
else: | |||
self.DIB_num_in_plt = DIB_plt_num_info | |||
self.palette = [None for i in range(self.DIB_num_in_plt)] | |||
for i in range(self.DIB_num_in_plt): | |||
data = bf_io.read(4) | |||
colour = bytearray([data[2], data[1], data[0]]) | |||
self.palette[i] = colour | |||
# In case self.DIB_h < 0 for top-down format. | |||
if self.DIB_h < 0: | |||
self.DIB_h = -self.DIB_h | |||
is_top_down = True | |||
else: | |||
is_top_down = False | |||
self.parray = None | |||
assert self._init(), "Failed to initialize the image!" | |||
# Pixels | |||
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) | |||
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) | |||
pixel = pixel_data if self.palette is None else self.palette[pixel_data] | |||
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]) | |||
else: | |||
# BI_RLE8 or BI_RLE4 | |||
self._decode_rle(bf_io) | |||
print("BMP done") | |||
return self | |||
def write_io(self, bf_io, force_40B_DIB=False): | |||
if force_40B_DIB: | |||
self.DIB_len = 40 | |||
self.DIB_extra = None | |||
# Only uncompressed image is supported to write. | |||
self.DIB_comp = 0 | |||
assert self._init(), "Failed to initialize the image!" | |||
# BMP Header | |||
bf_io.write(self.BMP_id) | |||
bf_io.write(pack("<I", self.BMP_size)) | |||
bf_io.write(self.BMP_reserved1) | |||
bf_io.write(self.BMP_reserved2) | |||
bf_io.write(pack("<I", self.BMP_offset)) | |||
# DIB Header | |||
bf_io.write( | |||
pack( | |||
"<IiiHHIIiiII", | |||
self.DIB_len, | |||
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.DIB_num_in_plt, | |||
self.DIB_num_in_plt, | |||
) | |||
) | |||
if self.DIB_len > 40: | |||
bf_io.write(self.DIB_extra) | |||
# Palette | |||
if self.DIB_depth <= 8: | |||
for colour in self.palette: | |||
bf_io.write(bytes([colour[2], colour[1], colour[0], 0])) | |||
# Pixels | |||
for h in range(self.DIB_h): | |||
# BMP last row comes first. | |||
y = self.DIB_h - h - 1 | |||
if self.DIB_depth <= 8: | |||
d = 0 | |||
for x in range(self.DIB_w): | |||
self[x, y] %= self.DIB_num_in_plt | |||
# One formula that suits all: 1/2/4/8-bit colour depth. | |||
d = (d << (self.DIB_depth % 8)) + self[x, y] | |||
if x % self.ppb == self.ppb - 1: | |||
# Got a whole byte. | |||
bf_io.write(bytes([d])) | |||
d = 0 | |||
if x % self.ppb != self.ppb - 1: | |||
# Last byte if width does not fit in whole bytes. | |||
d <<= ( | |||
8 | |||
- self.DIB_depth | |||
- (x % self.ppb) * (2 ** (self.DIB_depth - 1)) | |||
) | |||
bf_io.write(bytes([d])) | |||
d = 0 | |||
else: | |||
for x in range(self.DIB_w): | |||
r, g, b = self[x, y] | |||
bf_io.write(bytes([b, g, r])) | |||
# Pad row to multiple of 4 bytes with 0x00. | |||
bf_io.write(b"\x00" * (self.padded_row_size - self.row_size)) | |||
num_of_bytes = bf_io.tell() | |||
return num_of_bytes | |||
def load(self, file_path): | |||
with open(file_path, "rb") as file: | |||
self.read_io(file) | |||
return self | |||
def save(self, file_path, force_40B_DIB=False): | |||
with open(file_path, "wb") as file: | |||
num_of_bytes = self.write_io(file, force_40B_DIB) | |||
return num_of_bytes |
@@ -0,0 +1,68 @@ | |||
import epaper | |||
import microbmp | |||
import time | |||
import gc | |||
colormap = [ | |||
[0x00, 0x00, 0x00], # black | |||
[0xff, 0xff, 0xff], # white | |||
[0x00, 0xdd, 0x00], # green | |||
[0x00, 0x00, 0xee], # blue | |||
[0x00, 0xdd, 0x00], # red | |||
[0xff, 0xdd, 0x00], # yellow | |||
[0xff, 0x88, 0x00], # orange | |||
] | |||
images = ["lambo.bmp", "fallout.bmp", "nasa.bmp"] | |||
gc.enable() | |||
def init_display(): | |||
#print("ePaper init ", str(time.localtime())) | |||
epd = epaper.EPD_5in65() | |||
epd.fill(epd.White) | |||
return epd | |||
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 | |||
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 | |||
#print("PXL ", str([color, r,g,b, pixel])) | |||
epd.pixel(x, y, pixel) | |||
#print("BMP ", filename, " loading ", str(time.localtime())) | |||
epd.fill(epd.White) | |||
microbmp.MicroBMP(data_callback=callback).load(filename) | |||
#print("BMP loaded ", str(time.localtime())) | |||
epd.EPD_5IN65F_Display(epd.buffer) | |||
#print("ePaper printed ", str(time.localtime())) | |||
# MAIN | |||
epd = init_display() | |||
while True: | |||
for filename in images: | |||
epd.EPD_5IN65F_Init() | |||
print("TV loading image ", filename) | |||
draw_image(filename) | |||
epd.Sleep() | |||
print("TV showing ", filename) | |||
gc.collect() | |||
epd.delay_ms(10000) |