From 151ba1783c66c708609318f9521c7404661f1f9a Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Fri, 15 Nov 2019 20:20:16 +0100 Subject: [PATCH 1/4] Allow image drawing in subscreen areas This extends the `image` method with two additional arguments for the image origin on the screen. The previous exact size check was adapted to ensure that the image drawn at the supplied origin does not exceed the display dimensions. The changes allow to update a smaller part of the screen with an image which is especially benefitial on lower end hardware. Some example test results from a Rasberry Pi Zero: - Draw fullscreen image (240x320): ~0.3s - Draw half-screen image (240x160): ~0.2s - Draw single line of text (240x22): ~0.06s Fixes: #48 --- adafruit_rgb_display/rgb.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/adafruit_rgb_display/rgb.py b/adafruit_rgb_display/rgb.py index b2822e5..d64dfa9 100644 --- a/adafruit_rgb_display/rgb.py +++ b/adafruit_rgb_display/rgb.py @@ -175,9 +175,10 @@ def pixel(self, x, y, color=None): self._block(x, y, x, y, self._encode_pixel(color)) return None - def image(self, img, rotation=None): - """Set buffer to value of Python Imaging Library image. The image should - be in 1 bit mode and a size equal to the display size.""" + def image(self, img, rotation=None, x=0, y=0): + """Set buffer to value of Python Imaging Library image. The image should + be in 1 bit mode and a size not exceeding the display size when drawn at + the supplied origin.""" if rotation is None: rotation = self.rotation if not img.mode in ('RGB', 'RGBA'): @@ -187,20 +188,20 @@ def image(self, img, rotation=None): if rotation != 0: img = img.rotate(rotation, expand=True) imwidth, imheight = img.size - if imwidth != self.width or imheight != self.height: - raise ValueError('Image must be same dimensions as display ({0}x{1}).' \ + if x + imwidth > self.width or y + imheight > self.height: + raise ValueError('Image must not exceed dimensions of display ({0}x{1}).' \ .format(self.width, self.height)) if numpy: pixels = list(image_to_data(img)) else: # Slower but doesn't require numpy - pixels = bytearray(self.width * self.height * 2) - for x in range(self.width): - for y in range(self.height): - pix = color565(img.getpixel((x, y))) - pixels[2*(y * self.width + x)] = pix >> 8 - pixels[2*(y * self.width + x) + 1] = pix & 0xFF - self._block(0, 0, self.width - 1, self.height - 1, pixels) + pixels = bytearray(imwidth * imheight * 2) + for i in range(imwidth): + for j in range(imheight): + pix = color565(img.getpixel((i, j))) + pixels[2*(j * imwidth + i)] = pix >> 8 + pixels[2*(j * imwidth + i) + 1] = pix & 0xFF + self._block(x, y, x + imwidth - 1, y + imheight - 1, pixels) #pylint: disable-msg=too-many-arguments def fill_rectangle(self, x, y, width, height, color): From a4083f64e03604f5d3c0624bea45b1b7bd093d4f Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Wed, 20 Nov 2019 11:06:43 -0800 Subject: [PATCH 2/4] Updated Pillow Example Pins to match PiTFT --- examples/rgb_display_pillow_demo.py | 4 ++-- examples/rgb_display_pillow_image.py | 4 ++-- examples/rgb_display_pillow_stats.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/rgb_display_pillow_demo.py b/examples/rgb_display_pillow_demo.py index 08578dc..ef12e15 100644 --- a/examples/rgb_display_pillow_demo.py +++ b/examples/rgb_display_pillow_demo.py @@ -14,8 +14,8 @@ # Configuration for CS and DC pins (these are PiTFT defaults): cs_pin = digitalio.DigitalInOut(board.CE0) -dc_pin = digitalio.DigitalInOut(board.D24) -reset_pin = digitalio.DigitalInOut(board.D25) +dc_pin = digitalio.DigitalInOut(board.D25) +reset_pin = digitalio.DigitalInOut(board.D24) # Config for display baudrate (default max is 24mhz): BAUDRATE = 24000000 diff --git a/examples/rgb_display_pillow_image.py b/examples/rgb_display_pillow_image.py index 2fce392..d9860b2 100644 --- a/examples/rgb_display_pillow_image.py +++ b/examples/rgb_display_pillow_image.py @@ -10,8 +10,8 @@ # Configuration for CS and DC pins (these are PiTFT defaults): cs_pin = digitalio.DigitalInOut(board.CE0) -dc_pin = digitalio.DigitalInOut(board.D24) -reset_pin = digitalio.DigitalInOut(board.D25) +dc_pin = digitalio.DigitalInOut(board.D25) +reset_pin = digitalio.DigitalInOut(board.D24) # Config for display baudrate (default max is 24mhz): BAUDRATE = 24000000 diff --git a/examples/rgb_display_pillow_stats.py b/examples/rgb_display_pillow_stats.py index c9ee508..069a832 100644 --- a/examples/rgb_display_pillow_stats.py +++ b/examples/rgb_display_pillow_stats.py @@ -12,8 +12,8 @@ # Configuration for CS and DC pins (these are PiTFT defaults): cs_pin = digitalio.DigitalInOut(board.CE0) -dc_pin = digitalio.DigitalInOut(board.D24) -reset_pin = digitalio.DigitalInOut(board.D25) +dc_pin = digitalio.DigitalInOut(board.D25) +reset_pin = digitalio.DigitalInOut(board.D24) # Config for display baudrate (default max is 24mhz): BAUDRATE = 24000000 From ae915b776107d4b9e81864284b6040cc9d885e73 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Fri, 3 Jan 2020 11:26:16 -0800 Subject: [PATCH 3/4] Initial commit of PiTFT Animated Gif Player --- examples/rgb_display_pillow_animated_gif.py | 144 ++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 examples/rgb_display_pillow_animated_gif.py diff --git a/examples/rgb_display_pillow_animated_gif.py b/examples/rgb_display_pillow_animated_gif.py new file mode 100644 index 0000000..28ec0d5 --- /dev/null +++ b/examples/rgb_display_pillow_animated_gif.py @@ -0,0 +1,144 @@ +""" +Example to extract the frames and other parameters from an animated gif +and then run the animation on the display. + +Usage: +python3 rgb_display_pillow_animated_gif.py + +This example is for use on (Linux) computers that are using CPython with +Adafruit Blinka to support CircuitPython libraries. CircuitPython does +not support PIL/pillow (python imaging library)! + +Author(s): Melissa LeBlanc-Williams for Adafruit Industries +""" +import os +import time +import digitalio +import board +from PIL import Image +import adafruit_rgb_display.ili9341 as ili9341 +import adafruit_rgb_display.st7789 as st7789 # pylint: disable=unused-import +import adafruit_rgb_display.hx8357 as hx8357 # pylint: disable=unused-import +import adafruit_rgb_display.st7735 as st7735 # pylint: disable=unused-import +import adafruit_rgb_display.ssd1351 as ssd1351 # pylint: disable=unused-import +import adafruit_rgb_display.ssd1331 as ssd1331 # pylint: disable=unused-import + +# Configuration for CS and DC pins (these are PiTFT defaults): +cs_pin = digitalio.DigitalInOut(board.CE0) +dc_pin = digitalio.DigitalInOut(board.D25) +reset_pin = digitalio.DigitalInOut(board.D24) + +def init_button(pin): + button = digitalio.DigitalInOut(pin) + button.switch_to_input() + button.pull = digitalio.Pull.UP + return button + +class AnimatedGif: + def __init__(self, display, folder=None): + self._frame_count = 0 + self._loop = 0 + self._index = 0 + self._delay = 0 + self._gif_files = [] + self._frames = [] + self.display = display + self.advance_button = init_button(board.D17) + self.back_button = init_button(board.D22) + if folder is not None: + self.load_files(folder) + self.run() + + def advance(self, loop=False): + if self._index < len(self._gif_files) - 1: + self._index += 1 + elif loop and self._index == len(self._gif_files) - 1: + self._index = 0 + + def back(self, loop=False): + if self._index > 0: + self._index -= 1 + elif loop and self._index == 0: + self._index = len(self._gif_files) - 1 + + def load_files(self, folder): + self._gif_files = [f for f in os.listdir(folder) if f[-4:] == '.gif'] + print("Found", self._gif_files) + if len(self._gif_files) == 0: + print("No Gif files found in current folder") + exit() + + def preload(self): + image = Image.open(self._gif_files[self._index]) + print("Loading {}...".format(self._gif_files[self._index])) + self._delay = image.info['duration'] + if "loop" in image.info: + self._loop = image.info['loop'] + else: + self._loop = 1 + self._frame_count = image.n_frames + + for frame in range(self._frame_count): + image.seek(frame) + # Create blank image for drawing. + # Make sure to create image with mode 'RGB' for full color. + frame_image = Image.new('RGB', (width, height)) + frame_image.paste(image, (width // 2 - image.width // 2, + height // 2 - image.height // 2)) + self._frames.append(frame_image) + + def play(self): + self.preload() + + # Check if we have loaded any files first + if len(self._gif_files) == 0: + print("There are no Gif Images to Play") + + for frame_image in self._frames: + self.display.image(frame_image) + if not self.advance_button.value: + self.advance() + elif not self.back_button.value: + self.back() + time.sleep(self._delay / 1000) + + if self._loop == 1: + return + if self._loop > 0: + self._loop -= 1 + + def run(self): + while True: + self.play() + self.advance(True) + +# Config for display baudrate (default max is 24mhz): +BAUDRATE = 24000000 + +# Setup SPI bus using hardware SPI: +spi = board.SPI() + +# pylint: disable=line-too-long +# Create the display: +#disp = st7789.ST7789(spi, rotation=90 # 2.0" ST7789 +#disp = st7789.ST7789(spi, height=240, y_offset=80, rotation=90 # 1.3", 1.54" ST7789 +#disp = st7789.ST7789(spi, rotation=90, width=135, height=240, x_offset=53, y_offset=40, # 1.14" ST7789 +#disp = hx8357.HX8357(spi, rotation=180, # 3.5" HX8357 +#disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R +#disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R +#disp = st7735.ST7735R(spi, rotation=90, bgr=True, # 0.96" MiniTFT ST7735R +#disp = ssd1351.SSD1351(spi, rotation=180, # 1.5" SSD1351 +#disp = ssd1351.SSD1351(spi, height=96, y_offset=32, rotation=180, # 1.27" SSD1351 +#disp = ssd1331.SSD1331(spi, rotation=180, # 0.96" SSD1331 +disp = ili9341.ILI9341(spi, rotation=90, # 2.2", 2.4", 2.8", 3.2" ILI9341 + cs=cs_pin, dc=dc_pin, rst=reset_pin, baudrate=BAUDRATE) +# pylint: enable=line-too-long + +if disp.rotation % 180 == 90: + height = disp.width # we swap height/width to rotate it to landscape! + width = disp.height +else: + width = disp.width # we swap height/width to rotate it to landscape! + height = disp.height + +gif_player = AnimatedGif(disp, ".") From 495a2684bfcf0f1aeca515f5614ccc1712505394 Mon Sep 17 00:00:00 2001 From: Melissa LeBlanc-Williams Date: Fri, 3 Jan 2020 11:37:42 -0800 Subject: [PATCH 4/4] Bumping to 64MHz and linting --- examples/rgb_display_pillow_animated_gif.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/rgb_display_pillow_animated_gif.py b/examples/rgb_display_pillow_animated_gif.py index 28ec0d5..c695d63 100644 --- a/examples/rgb_display_pillow_animated_gif.py +++ b/examples/rgb_display_pillow_animated_gif.py @@ -64,7 +64,7 @@ def back(self, loop=False): def load_files(self, folder): self._gif_files = [f for f in os.listdir(folder) if f[-4:] == '.gif'] print("Found", self._gif_files) - if len(self._gif_files) == 0: + if not self._gif_files: print("No Gif files found in current folder") exit() @@ -89,9 +89,9 @@ def preload(self): def play(self): self.preload() - + # Check if we have loaded any files first - if len(self._gif_files) == 0: + if not self._gif_files: print("There are no Gif Images to Play") for frame_image in self._frames: @@ -113,7 +113,7 @@ def run(self): self.advance(True) # Config for display baudrate (default max is 24mhz): -BAUDRATE = 24000000 +BAUDRATE = 64000000 # Setup SPI bus using hardware SPI: spi = board.SPI()