Raspberry Pi Pico 2/MicroPython read bmp images and draw on ST7789 display pixel-by-pixel.
Follow the former exercises of Raspberry Pi Pico 2/MicroPython display on 1.54" 240x240 ST7789 SPI
IPS
and Python code to read .bmp info from header (work on Desktop Python and
MicroPython), this exercise read bmp images, and draw on ST7789 display
pixel-by-pixel.
Exercise code:
mpy_pico2_bmp.py
"""
Raspberry Pi Pico/MicroPython exercise
to display on 1.54" IPS 240x240 with SPI ST7789 driver
Pixel-by-Pixel
Read, decode bmp in /images/ directory, and disply on 240x240 SPI ST7789 display.
Target:
- 240x240 RGB565 .bmp
- 240x240 RGB888 .bmp
ref:
Using library: russhughes/st7789py_mpy
https://github.com/russhughes/st7789py_mpy
to install st7789py_mpy on Raspberry Pi Pico 2/MicroPython,
https://coxxect.blogspot.com/2024/10/raspberry-pi-pico-2micropython-display.html
to know more about bmp structure:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html
to convert jpg to RGB565/RGB888 bmp using GIMP, read the video in:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html
Python/cv2 code to batch convert jpg to 240x240 RGB888 bmp,
check the Python code in this post,
https://coxxect.blogspot.com/2024/11/raspberry-pi-pico-2micropython-read-bmp.html.
Connection:
-----------
GND GND
VCC 3V3
SCL GP18
SDA GP19
RES GP20
DC GP21
CS GP17
BLK GP22
GP16 (dummy, not used)
"""
import os, sys
from machine import Pin, SPI
import time
import st7789py as st7789
disp_sck = 18 # default SCK of SPI(0)
disp_mosi = 19 # default MOSI of SPI(0)
disp_miso = 16 # not use
disp_res = 20
disp_dc = 21
disp_cs = 17
disp_blk = 22
print("====================================")
print(sys.implementation[0], os.uname()[3],
"\nrun on", os.uname()[4])
print("====================================")
DISP_WIDTH = 240
DISP_HEIGHT = 240
# it's found that:
# - cannot set baudrate
# - even I set miso=None, miso will be assigned deault GP16 or previous assigned miso
disp_spi = SPI(0, baudrate=60000000, sck=Pin(disp_sck), mosi=Pin(disp_mosi), miso=None)
print(disp_spi)
display = st7789.ST7789(disp_spi,
DISP_WIDTH, DISP_HEIGHT,
reset=Pin(disp_res, Pin.OUT),
cs=Pin(disp_cs, Pin.OUT),
dc=Pin(disp_dc, Pin.OUT),
backlight=Pin(disp_blk, Pin.OUT))
print(st7789.__name__, display.width, "x", display.height)
display.fill(st7789.WHITE)
time.sleep(0.5)
display.fill(st7789.BLACK)
# Read bmp
img_path = '/images/'
bmp_file = img_path + 'img_01_240_RGB565.bmp'
def read_bmp(filename):
print("Read:", filename)
with open(filename, 'rb') as f:
bmp_data = f.read()
return bmp_data
def get_compression_method(num):
switcher = {
0: "BI_RGB",
1: "BI_RLE8",
2: "BI_RLE4",
3: "BI_BITFIELDS",
4: "BI_JPEG",
5: "BI_PNG",
6: "BI_ALPHABITFIELDS",
11: "BI_CMYK",
12: "BI_CMYKRLE8",
13: "BI_CMYKRLE4",
}
return switcher.get(num, "unknown")
bmp_data = read_bmp(bmp_file)
#buffer = bytearray(bmp_data)
print("len(bmp_data)", type(bmp_data), len(bmp_data))
#print(bmp_data)
print("Signature:",chr(bmp_data[0]), chr(bmp_data[1]))
size_of_bmp_file = int.from_bytes(bmp_data[2:6], 'little')
print("--- size_of_bmp_file = ", size_of_bmp_file)
print("Reserved:", bmp_data[6], bmp_data[7])
print("Reserved:", bmp_data[8], bmp_data[9])
offset = int.from_bytes(bmp_data[10:14], 'little')
print("--- offset = ", offset)
size_of_header = int.from_bytes(bmp_data[14:18], 'little')
print("--- size_of_header:", size_of_header)
image_width = int.from_bytes(bmp_data[18:22], 'little')
print("--- image_width:", image_width)
image_height = int.from_bytes(bmp_data[22:26], 'little')
print("--- image_height:", image_height)
number_of_color_planes = int.from_bytes(bmp_data[26:28], 'little')
print("--- number_of_color_planes (must be 1):", number_of_color_planes)
num_of_bits_per_pixel = int.from_bytes(bmp_data[28:30], 'little')
print("--- num_of_bits_per_pixel:", num_of_bits_per_pixel)
compression_method = int.from_bytes(bmp_data[30:34], 'little')
print("--- compression_method:", compression_method, get_compression_method(compression_method))
image_size = int.from_bytes(bmp_data[34:38], 'little')
print("--- image_size:", image_size)
horizontal_resolution = int.from_bytes(bmp_data[38:42], 'little')
print("--- horizontal_resolution:", horizontal_resolution)
vertical_resolution = int.from_bytes(bmp_data[42:46], 'little')
print("--- vertical_resolution:", vertical_resolution)
number_of_colors = int.from_bytes(bmp_data[46:50], 'little')
print("--- number_of_colors:", number_of_colors)
number_of_important_colors = int.from_bytes(bmp_data[50:54], 'little')
print("--- number_of_important_colors:", number_of_important_colors)
print("=============================================")
print("What concerned are:")
print("Signature:",chr(bmp_data[0]), chr(bmp_data[1]))
print("offset:", offset)
print("image_width:", image_width)
print("image_height:", image_height)
print("num_of_bits_per_pixel:", num_of_bits_per_pixel)
print("=============================================")
IMG_WIDTH = 240
IMG_HEIGHT = 240
# Verify the bmp image
if chr(bmp_data[0]) != 'B' or chr(bmp_data[1]) != 'M':
print("Must be bmp")
elif image_width != IMG_WIDTH or image_height != IMG_HEIGHT:
print("Must be 240 x 240")
else:
if num_of_bits_per_pixel == 24:
print("Assume " + bmp_file + " is 240x240 RGB888 bmp, continuous.")
byte_per_pixel = 3
for y in range(IMG_HEIGHT):
y_offset = (y*IMG_WIDTH*byte_per_pixel) + offset
for x in range(IMG_WIDTH):
i = (x*byte_per_pixel) + y_offset
# Because bmp start from bottom-left,
# so we have to invert the y order.
display.pixel(x, IMG_HEIGHT-y, st7789.color565(bmp_data[i+2],
bmp_data[i+1],
bmp_data[i]))
elif num_of_bits_per_pixel == 16:
print("Assume " + bmp_file + " is 240x240 RGB565 bmp, continuous.")
byte_per_pixel = 2
for y in range(IMG_HEIGHT):
y_offset = (y*IMG_WIDTH*byte_per_pixel) + offset
for x in range(IMG_WIDTH):
i = (x*byte_per_pixel) + y_offset
# Because bmp start from bottom-left,
# so we have to invert the y order.
display.pixel(x, IMG_HEIGHT-y, int.from_bytes(bmp_data[i:i+2], 'little'))
else:
print("Must be 16 or 24 bit per pixel")
mpy_pico2_bmp_slideshow.py, in slideshow.
"""
Raspberry Pi Pico/MicroPython exercise
to display on 1.54" IPS 240x240 with SPI ST7789 driver
Pixel-by-Pixel
Read, decode bmp images in /images/ directory, and disply on 240x240 SPI ST7789 display in Slideshow.
Target:
- 240x240 RGB565 .bmp
- 240x240 RGB888 .bmp
ref:
Using library: russhughes/st7789py_mpy
https://github.com/russhughes/st7789py_mpy
to install st7789py_mpy on Raspberry Pi Pico 2/MicroPython,
https://coxxect.blogspot.com/2024/10/raspberry-pi-pico-2micropython-display.html
to know more about bmp structure:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html
to convert jpg to RGB565/RGB888 bmp using GIMP, read the video in:
https://coxxect.blogspot.com/2024/10/python-code-to-read-bmp-info-from.html
Python/cv2 code to batch convert jpg to 240x240 RGB888 bmp,
check the Python code in this post,
https://coxxect.blogspot.com/2024/11/raspberry-pi-pico-2micropython-read-bmp.html.
Connection:
-----------
GND GND
VCC 3V3
SCL GP18
SDA GP19
RES GP20
DC GP21
CS GP17
BLK GP22
GP16 (dummy, not used)
"""
import os, sys
from machine import Pin, SPI
import time
import st7789py as st7789
disp_sck = 18 # default SCK of SPI(0)
disp_mosi = 19 # default MOSI of SPI(0)
disp_miso = 16 # not use
disp_res = 20
disp_dc = 21
disp_cs = 17
disp_blk = 22
print("====================================")
print(sys.implementation[0], os.uname()[3],
"\nrun on", os.uname()[4])
print("====================================")
DISP_WIDTH = 240
DISP_HEIGHT = 240
# it's found that:
# - cannot set baudrate
# - even I set miso=None, miso will be assigned deault GP16 or previous assigned miso
disp_spi = SPI(0, baudrate=60000000, sck=Pin(disp_sck), mosi=Pin(disp_mosi), miso=None)
print(disp_spi)
display = st7789.ST7789(disp_spi,
DISP_WIDTH, DISP_HEIGHT,
reset=Pin(disp_res, Pin.OUT),
cs=Pin(disp_cs, Pin.OUT),
dc=Pin(disp_dc, Pin.OUT),
backlight=Pin(disp_blk, Pin.OUT))
print(st7789.__name__, display.width, "x", display.height)
display.fill(st7789.WHITE)
time.sleep(0.5)
display.fill(st7789.BLACK)
# Read bmp
img_path = '/images/'
def read_bmp(filename):
print("Read:", filename)
with open(filename, 'rb') as f:
bmp_data = f.read()
return bmp_data
def get_compression_method(num):
switcher = {
0: "BI_RGB",
1: "BI_RLE8",
2: "BI_RLE4",
3: "BI_BITFIELDS",
4: "BI_JPEG",
5: "BI_PNG",
6: "BI_ALPHABITFIELDS",
11: "BI_CMYK",
12: "BI_CMYKRLE8",
13: "BI_CMYKRLE4",
}
return switcher.get(num, "unknown")
def displat_bmp(bmp_file):
bmp_data = read_bmp(bmp_file)
offset = int.from_bytes(bmp_data[10:14], 'little')
image_width = int.from_bytes(bmp_data[18:22], 'little')
image_height = int.from_bytes(bmp_data[22:26], 'little')
num_of_bits_per_pixel = int.from_bytes(bmp_data[28:30], 'little')
print("=============================================")
print("What concerned are:")
print("Signature:",chr(bmp_data[0]), chr(bmp_data[1]))
print("offset:", offset)
print("image_width:", image_width)
print("image_height:", image_height)
print("num_of_bits_per_pixel:", num_of_bits_per_pixel)
print("=============================================")
IMG_WIDTH = 240
IMG_HEIGHT = 240
# Verify the bmp image
if chr(bmp_data[0]) != 'B' or chr(bmp_data[1]) != 'M':
print("Must be bmp")
elif image_width != IMG_WIDTH or image_height != IMG_HEIGHT:
print("Must be 240 x 240")
else:
if num_of_bits_per_pixel == 24:
print("Assume " + bmp_file + " is 240x240 RGB888 bmp, continuous.")
byte_per_pixel = 3
for y in range(IMG_HEIGHT):
y_offset = (y*IMG_WIDTH*byte_per_pixel) + offset
for x in range(IMG_WIDTH):
i = (x*byte_per_pixel) + y_offset
# Because bmp start from bottom-left,
# so we have to invert the y order.
display.pixel(x, IMG_HEIGHT-y, st7789.color565(bmp_data[i+2],
bmp_data[i+1],
bmp_data[i]))
elif num_of_bits_per_pixel == 16:
print("Assume " + bmp_file + " is 240x240 RGB565 bmp, continuous.")
byte_per_pixel = 2
for y in range(IMG_HEIGHT):
y_offset = (y*IMG_WIDTH*byte_per_pixel) + offset
for x in range(IMG_WIDTH):
i = (x*byte_per_pixel) + y_offset
# Because bmp start from bottom-left,
# so we have to invert the y order.
display.pixel(x, IMG_HEIGHT-y, int.from_bytes(bmp_data[i:i+2], 'little'))
else:
print("Must be 16 or 24 bit per pixel")
while True:
for b_file in os.listdir(img_path):
if b_file.endswith('.bmp'):
print()
bmp_file_full_path = img_path + b_file
start_time = time.ticks_ms()
displat_bmp(bmp_file_full_path)
end_time = time.ticks_ms()
print("time to draw: ", time.ticks_diff(end_time, start_time), "ms")
time.sleep(2)
py_cv_batch_resize_RGB888.py, run on desktop (Windows 11) to prepare images in 240x240 RGB888 bmp.
"""
Python/cv2 to convert all jpg in 'images' folder to 240*240 RGB888 bmp in 'resized_images' folder.
OpenCV is needed.
To install OpenCV in Python virtual environment, read:
ref: https://coxxect.blogspot.com/2024/10/create-python-virtual-environment-in.html
"""
import os
import cv2
def resize_and_save_as_bmp(input_folder, output_folder, size):
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for filename in os.listdir(input_folder):
if filename.endswith('.jpg'):
img_path = os.path.join(input_folder, filename)
img = cv2.imread(img_path)
resized_img = cv2.resize(img, size)
# Save as RGB888 BMP
bmp_filename = os.path.splitext(filename)[0] + '_240_RGB888.bmp'
bmp_path = os.path.join(output_folder, bmp_filename)
cv2.imwrite(bmp_path, resized_img)
input_folder = 'images'
output_folder = 'resized_images'
size = (240, 240)
resize_and_save_as_bmp(input_folder, output_folder, size)
Updated@2024-11-07: For easier reference in the future post to resize/convert images, it is published in separate post Resize jpg and convert to bmp in RGB888 and RGB565 mode, using Python/GIMP. updated with RGB565 mode added.Added@2024-11-09:
It is a slow approach. Because it draw in pixel-by-pixel, each involve a write sequency. But it help me understand bmp much more.
Here is another approach, form a bytearray passing to st7789pt blit_buffer() method, such that the driver write the whole buffer in one sequency to improve the speed much more.
Read: Raspberry Pi Pico 2/MicroPython display bmp images on 240x240 ST7789 SPI Display
Comments
Post a Comment