Skip to content

Commit 7b112cb

Browse files
authored
Merge pull request #317 from hchargois/optimize-rgb565-serialization
2 parents 485e2ee + abdb581 commit 7b112cb

File tree

3 files changed

+60
-27
lines changed

3 files changed

+60
-27
lines changed

library/lcd/lcd_comm_rev_a.py

Lines changed: 51 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@
1616
# You should have received a copy of the GNU General Public License
1717
# along with this program. If not, see <https://www.gnu.org/licenses/>.
1818

19-
import struct
2019
import time
2120

2221
from serial.tools.list_ports import comports
22+
import numpy as np
2323

2424
from library.lcd.lcd_comm import *
2525
from library.log import logger
@@ -130,54 +130,78 @@ def SetOrientation(self, orientation: Orientation = Orientation.PORTRAIT):
130130
byteBuffer[10] = (height & 255)
131131
self.lcd_serial.write(bytes(byteBuffer))
132132

133+
@staticmethod
134+
def imageToRGB565LE(image: Image):
135+
if image.mode not in ["RGB", "RGBA"]:
136+
# we need the first 3 channels to be R, G and B
137+
image = image.convert("RGB")
138+
139+
rgb = np.asarray(image)
140+
141+
# flatten the first 2 dimensions (width and height) into a single stream
142+
# of RGB pixels
143+
rgb = rgb.reshape((image.size[1] * image.size[0], -1))
144+
145+
# extract R, G, B channels and promote them to 16 bits
146+
r = rgb[:, 0].astype(np.uint16)
147+
g = rgb[:, 1].astype(np.uint16)
148+
b = rgb[:, 2].astype(np.uint16)
149+
150+
# construct RGB565
151+
r = (r >> 3)
152+
g = (g >> 2)
153+
b = (b >> 3)
154+
rgb565 = (r << 11) | (g << 5) | b
155+
156+
# serialize to little-endian
157+
return rgb565.newbyteorder('<').tobytes()
158+
133159
def DisplayPILImage(
134160
self,
135161
image: Image,
136162
x: int = 0, y: int = 0,
137163
image_width: int = 0,
138164
image_height: int = 0
139165
):
166+
width, height = self.get_width(), self.get_height()
167+
140168
# If the image height/width isn't provided, use the native image size
141169
if not image_height:
142170
image_height = image.size[1]
143171
if not image_width:
144172
image_width = image.size[0]
145173

146-
# If our image is bigger than our display, resize it to fit our screen
147-
if image.size[1] > self.get_height():
148-
image_height = self.get_height()
149-
if image.size[0] > self.get_width():
150-
image_width = self.get_width()
151-
152-
assert x <= self.get_width(), 'Image X coordinate must be <= display width'
153-
assert y <= self.get_height(), 'Image Y coordinate must be <= display height'
174+
assert x <= width, 'Image X coordinate must be <= display width'
175+
assert y <= height, 'Image Y coordinate must be <= display height'
154176
assert image_height > 0, 'Image height must be > 0'
155177
assert image_width > 0, 'Image width must be > 0'
156178

179+
# If our image size + the (x, y) position offsets are bigger than
180+
# our display, reduce the image size to fit our screen
181+
if x + image_width > width:
182+
image_width = width - x
183+
if y + image_height > height:
184+
image_height = height - y
185+
186+
if image_width != image.size[0] or image_height != image.size[1]:
187+
image = image.crop((0, 0, image_width, image_height))
188+
157189
(x0, y0) = (x, y)
158190
(x1, y1) = (x + image_width - 1, y + image_height - 1)
159191

160-
self.SendCommand(Command.DISPLAY_BITMAP, x0, y0, x1, y1)
161-
162-
pix = image.load()
163-
line = bytes()
192+
rgb565le = self.imageToRGB565LE(image)
164193

165194
# Lock queue mutex then queue all the requests for the image data
166195
with self.update_queue_mutex:
167-
for h in range(image_height):
168-
for w in range(image_width):
169-
R = pix[w, h][0] >> 3
170-
G = pix[w, h][1] >> 2
171-
B = pix[w, h][2] >> 3
172-
173-
rgb = (R << 11) | (G << 5) | B
174-
line += struct.pack('<H', rgb)
196+
self.SendCommand(Command.DISPLAY_BITMAP, x0, y0, x1, y1)
175197

176-
# Send image data by multiple of "display width" bytes
177-
if len(line) >= self.get_width() * 8:
178-
self.SendLine(line)
179-
line = bytes()
198+
# Send image data by multiple of "display width" bytes
199+
start = 0
200+
end = width * 8
201+
while end <= len(rgb565le):
202+
self.SendLine(rgb565le[start:end])
203+
start, end = end, end + width * 8
180204

181205
# Write last line if needed
182-
if len(line) > 0:
183-
self.SendLine(line)
206+
if start != len(rgb565le):
207+
self.SendLine(rgb565le[start:])

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Python packages requirements
22
Pillow~=9.5.0 # Image generation
33
pyserial~=3.5 # Serial linl to communicate with the display
4+
numpy~=1.19 # Efficient image serialization
45
PyYAML~=6.0 # For themes files
56
psutil~=5.9.5 # CPU / disk / network metrics
67
GPUtil~=1.4.0 # Nvidia GPU

simple-program.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import os
2323
import signal
2424
import sys
25+
import time
2526
from datetime import datetime
2627

2728
# Import only the modules for LCD communication
@@ -117,7 +118,11 @@ def sighandler(signum, frame):
117118
background = f"res/backgrounds/{REVISION}/example{size}_landscape.png"
118119

119120
# Display sample picture
121+
logger.debug("setting background picture")
122+
start = time.perf_counter()
120123
lcd_comm.DisplayBitmap(background)
124+
end = time.perf_counter()
125+
logger.debug(f"background picture set (took {end-start:.3f} s)")
121126

122127
# Display sample text
123128
lcd_comm.DisplayText("Basic text", 50, 100)
@@ -140,6 +145,7 @@ def sighandler(signum, frame):
140145
# Display the current time and some progress bars as fast as possible
141146
bar_value = 0
142147
while not stop:
148+
start = time.perf_counter()
143149
lcd_comm.DisplayText(str(datetime.now().time()), 160, 2,
144150
font="roboto/Roboto-Bold.ttf",
145151
font_size=20,
@@ -184,6 +190,8 @@ def sighandler(signum, frame):
184190
background_image=background)
185191

186192
bar_value = (bar_value + 2) % 101
193+
end = time.perf_counter()
194+
logger.debug(f"refresh done (took {end-start:.3f} s)")
187195

188196
# Close serial connection at exit
189197
lcd_comm.closeSerial()

0 commit comments

Comments
 (0)