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

Add EYESPI Beret GIF player demo. #115

Merged
merged 1 commit into from
Aug 30, 2023
Merged
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
215 changes: 215 additions & 0 deletions examples/rgb_display_eyespi_beret_animated_gif.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc Williams for Adafruit Industries
# SPDX-License-Identifier: MIT

"""
EYESPI Pi Beret GIF Player Demo

Extracts the frames and other parameters from an animated gif
and then runs the animation on the display.

Save this file as eyespi_beret_gif_player.py to your Raspberry Pi.

Usage:
python3 eyespi_beret_gif_player.py

This example is for use on Raspberry Pi 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
Mike Mallett <[email protected]>
"""
import os
import time
import digitalio
import board
from PIL import Image, ImageOps
import numpy # pylint: disable=unused-import
from adafruit_rgb_display import ili9341
from adafruit_rgb_display import st7789 # pylint: disable=unused-import
from adafruit_rgb_display import hx8357 # pylint: disable=unused-import
from adafruit_rgb_display import st7735 # pylint: disable=unused-import
from adafruit_rgb_display import ssd1351 # pylint: disable=unused-import
from adafruit_rgb_display import ssd1331 # pylint: disable=unused-import

# Button pins for EYESPI Pi Beret
BUTTON_NEXT = board.D5
BUTTON_PREVIOUS = board.D6

# CS and DC pins for EYEPSPI Pi Beret:
cs_pin = digitalio.DigitalInOut(board.CE0)
dc_pin = digitalio.DigitalInOut(board.D25)

# Reset pin for EYESPI Pi Beret
reset_pin = digitalio.DigitalInOut(board.D27)

# Backlight pin for Pi Beret
backlight = digitalio.DigitalInOut(board.D18)
backlight.switch_to_output()
backlight.value = True

# Config for display baudrate (default max is 64mhz):
BAUDRATE = 64000000

# Setup SPI bus using hardware SPI:
spi = board.SPI()

# pylint: disable=line-too-long
# fmt: off
# Create the display.
disp = ili9341.ILI9341(spi, rotation=90, # 2.2", 2.4", 2.8", 3.2" ILI9341
# disp = st7789.ST7789(spi, rotation=90, # 2.0" ST7789
# disp = st7789.ST7789(spi, height=240, y_offset=80, rotation=180, # 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 = st7789.ST7789(spi, rotation=90, width=172, height=320, x_offset=34, # 1.47" ST7789
# disp = st7789.ST7789(spi, rotation=270, width=170, height=320, x_offset=35, # 1.9" 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, width=80, # 0.96" MiniTFT Rev A ST7735R
# disp = st7735.ST7735R(spi, rotation=90, invert=True, width=80, x_offset=26, y_offset=1, # 0.96" MiniTFT Rev B 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
cs=cs_pin,
dc=dc_pin,
rst=reset_pin,
baudrate=BAUDRATE,
)
# fmt: on
# pylint: enable=line-too-long


def init_button(pin):
button = digitalio.DigitalInOut(pin)
button.switch_to_input()
button.pull = digitalio.Pull.UP
return button


class Frame: # pylint: disable=too-few-public-methods
def __init__(self, duration=0):
self.duration = duration
self.image = None


class AnimatedGif:
def __init__(self, display, width=None, height=None, folder=None):
self._frame_count = 0
self._loop = 0
self._index = 0
self._duration = 0
self._gif_files = []
self._frames = []

if width is not None:
self._width = width
else:
self._width = display.width
if height is not None:
self._height = height
else:
self._height = display.height
self.display = display
self.advance_button = init_button(BUTTON_NEXT)
self.back_button = init_button(BUTTON_PREVIOUS)
if folder is not None:
self.load_files(folder)
self.run()

def advance(self):
self._index = (self._index + 1) % len(self._gif_files)

def back(self):
self._index = (self._index - 1 + len(self._gif_files)) % len(self._gif_files)

def load_files(self, folder):
gif_files = [f for f in os.listdir(folder) if f.endswith(".gif")]
for gif_file in gif_files:
gif_file = os.path.join(folder, gif_file)
image = Image.open(gif_file)
# Only add animated Gifs
if image.is_animated:
self._gif_files.append(gif_file)

print("Found", self._gif_files)
if not self._gif_files:
print("No Gif files found in current folder")
exit() # pylint: disable=consider-using-sys-exit

def preload(self):
image = Image.open(self._gif_files[self._index])
print("Loading {}...".format(self._gif_files[self._index]))
if "duration" in image.info:
self._duration = image.info["duration"]
else:
self._duration = 0
if "loop" in image.info:
self._loop = image.info["loop"]
else:
self._loop = 1
self._frame_count = image.n_frames
self._frames.clear()
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_object = Frame(duration=self._duration)
if "duration" in image.info:
frame_object.duration = image.info["duration"]
frame_object.image = ImageOps.pad( # pylint: disable=no-member
image.convert("RGB"),
(self._width, self._height),
method=Image.NEAREST,
color=(0, 0, 0),
centering=(0.5, 0.5),
)
self._frames.append(frame_object)

def play(self):
self.preload()

_prev_advance_btn_val = self.advance_button.value
_prev_back_btn_val = self.back_button.value
# Check if we have loaded any files first
if not self._gif_files:
print("There are no Gif Images loaded to Play")
return False
while True:
for frame_object in self._frames:
start_time = time.monotonic()
self.display.image(frame_object.image)
_cur_advance_btn_val = self.advance_button.value
_cur_back_btn_val = self.back_button.value
if not _cur_advance_btn_val and _prev_advance_btn_val:
self.advance()
return False
if not _cur_back_btn_val and _prev_back_btn_val:
self.back()
return False

_prev_back_btn_val = _cur_back_btn_val
_prev_advance_btn_val = _cur_advance_btn_val
while time.monotonic() < (start_time + frame_object.duration / 1000):
pass

if self._loop == 1:
return True
if self._loop > 0:
self._loop -= 1

def run(self):
while True:
auto_advance = self.play()
if auto_advance:
self.advance()


if disp.rotation % 180 == 90:
disp_height = disp.width # we swap height/width to rotate it to landscape!
disp_width = disp.height
else:
disp_width = disp.width
disp_height = disp.height

gif_player = AnimatedGif(disp, width=disp_width, height=disp_height, folder=".")