Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch scrutable numpy #17

Merged
merged 5 commits into from
Oct 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/320x240.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python3
from ST7789 import ST7789, BG_SPI_CS_FRONT
from PIL import Image, ImageDraw

import random
import time

# Buttons
BUTTON_A = 5
BUTTON_B = 6
BUTTON_X = 16
BUTTON_Y = 24

# Onboard RGB LED
LED_R = 17
LED_G = 27
LED_B = 22

# General
SPI_PORT = 0
SPI_CS = 1
SPI_DC = 9
BACKLIGHT = 13

# Screen dimensions
WIDTH = 320
HEIGHT = 240

buffer = Image.new("RGB", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(buffer)

draw.rectangle((0, 0, 50, 50), (255, 0, 0))
draw.rectangle((320-50, 0, 320, 50), (0, 255, 0))
draw.rectangle((0, 240-50, 50, 240), (0, 0, 255))
draw.rectangle((320-50, 240-50, 320, 240), (255, 255, 0))

display = ST7789(
port=SPI_PORT,
cs=SPI_CS,
dc=SPI_DC,
backlight=BACKLIGHT,
width=WIDTH,
height=HEIGHT,
rotation=180,
spi_speed_hz=60 * 1000 * 1000
)

while True:
display.display(buffer)
time.sleep(1.0 / 60)
22 changes: 17 additions & 5 deletions examples/framerate.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,33 @@
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Rectangular LCD

Running at: {}MHz on a {} display.
""".format(sys.argv[0], SPI_SPEED_MHZ, display_type))

try:
width, height, rotation, backlight, offset_left, offset_top = {
"square": (240, 240, 90, 19, 0, 0),
"round": (240, 240, 90, 19, 40, 0),
"rect": (240, 135, 0, 19, 40, 43),
"dhmini": (320, 240, 180, 13, 0, 0)
}[display_type]
except IndexError:
raise RuntimeError("Unsupported display type: {}".format(display_type))

# Create ST7789 LCD display class.
disp = ST7789.ST7789(
height=135 if display_type == "rect" else 240,
rotation=0 if display_type == "rect" else 90,
width=width,
height=height,
rotation=rotation,
port=0,
cs=ST7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # 18 for back BG slot, 19 for front BG slot.
backlight=backlight, # 18 for back BG slot, 19 for front BG slot.
spi_speed_hz=SPI_SPEED_MHZ * 1000000,
offset_left=0 if display_type == "square" else 40,
offset_top=53 if display_type == "rect" else 0
offset_left=offset_left,
offset_top=offset_top
)

WIDTH = disp.width
Expand Down
35 changes: 22 additions & 13 deletions library/ST7789/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ def __init__(self, port, cs, dc, backlight=None, rst=None, width=240,
:param spi_speed_hz: SPI speed (in Hz)

"""
if rotation not in [0, 90, 180, 270]:
raise ValueError("Invalid rotation {}".format(rotation))

if width != height and rotation in [90, 270]:
raise ValueError("Invalid rotation {} for {}x{} resolution".format(rotation, width, height))

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
Expand Down Expand Up @@ -328,25 +333,29 @@ def display(self, image):
"""
# Set address bounds to entire display.
self.set_window()
# Convert image to array of 18bit 666 RGB data bytes.
# Unfortunate that this copy has to occur, but the SPI byte writing
# function needs to take an array of bytes and PIL doesn't natively
# store images in 18-bit 666 RGB format.
pixelbytes = list(self.image_to_data(image, self._rotation))

# Convert image to 16bit RGB565 format and
# flatten into bytes.
pixelbytes = self.image_to_data(image, self._rotation)

# Write data to hardware.
for i in range(0, len(pixelbytes), 4096):
self.data(pixelbytes[i:i + 4096])

def image_to_data(self, image, rotation=0):
"""Generator function to convert a PIL image to 16-bit 565 RGB bytes."""
# NumPy is much faster at doing this. NumPy code provided by:
# Keith (https://www.blogger.com/profile/02555547344016007163)
if not isinstance(image, np.ndarray):
image = np.array(image.convert('RGB'))

pb = np.rot90(image, rotation // 90).astype('uint8')
# Rotate the image
pb = np.rot90(image, rotation // 90).astype('uint16')

# Mask and shift the 888 RGB into 565 RGB
red = (pb[..., [0]] & 0xf8) << 8
green = (pb[..., [1]] & 0xfc) << 3
blue = (pb[..., [2]] & 0xf8) >> 3

# Stick 'em together
result = red | green | blue

result = np.zeros((self._height, self._width, 2), dtype=np.uint8)
result[..., [0]] = np.add(np.bitwise_and(pb[..., [0]], 0xF8), np.right_shift(pb[..., [1]], 5))
result[..., [1]] = np.add(np.bitwise_and(np.left_shift(pb[..., [1]], 3), 0xE0), np.right_shift(pb[..., [2]], 3))
return result.flatten().tolist()
# Output the raw bytes
return result.byteswap().tobytes()
8 changes: 8 additions & 0 deletions library/tests/test_dimensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ def test_240_240(GPIO, spidev):
assert display.width == 240
assert display.height == 240


def test_240_135(GPIO, spidev):
import ST7789
display = ST7789.ST7789(port=0, cs=0, dc=24, width=240, height=135, rotation=0)
assert display.width == 240
assert display.height == 135


def test_320_240(GPIO, spidev):
import ST7789
display = ST7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=0)
assert display.width == 320
assert display.height == 240
28 changes: 28 additions & 0 deletions library/tests/test_setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,32 @@
import pytest

def test_setup(GPIO, spidev):
import ST7789
display = ST7789.ST7789(port=0, cs=0, dc=24)
del display


def test_backlight(GPIO, spidev):
import ST7789
display = ST7789.ST7789(port=0, cs=0, dc=24, backlight=19)
display.set_backlight(1)


def test_reset(GPIO, spidev):
import ST7789
display = ST7789.ST7789(port=0, cs=0, dc=24, rst=19)
display.reset()


def test_unsupported_rotation_320_x_240_90(GPIO, spidev):
import ST7789
with pytest.raises(ValueError):
display = ST7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=90)
del display


def test_unsupported_rotation_320_x_240_270(GPIO, spidev):
import ST7789
with pytest.raises(ValueError):
display = ST7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=270)
del display