Skip to content

Commit 011c7d7

Browse files
authored
Merge pull request #115 from kattni/eyespi-beret
Add EYESPI Beret GIF player demo.
2 parents 94b0a9a + 07e240f commit 011c7d7

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# SPDX-FileCopyrightText: 2021 Melissa LeBlanc Williams for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
"""
5+
EYESPI Pi Beret GIF Player Demo
6+
7+
Extracts the frames and other parameters from an animated gif
8+
and then runs the animation on the display.
9+
10+
Save this file as eyespi_beret_gif_player.py to your Raspberry Pi.
11+
12+
Usage:
13+
python3 eyespi_beret_gif_player.py
14+
15+
This example is for use on Raspberry Pi that are using CPython with
16+
Adafruit Blinka to support CircuitPython libraries. CircuitPython does
17+
not support PIL/pillow (python imaging library)!
18+
19+
Author(s): Melissa LeBlanc-Williams for Adafruit Industries
20+
Mike Mallett <mike@nerdcore.net>
21+
"""
22+
import os
23+
import time
24+
import digitalio
25+
import board
26+
from PIL import Image, ImageOps
27+
import numpy # pylint: disable=unused-import
28+
from adafruit_rgb_display import ili9341
29+
from adafruit_rgb_display import st7789 # pylint: disable=unused-import
30+
from adafruit_rgb_display import hx8357 # pylint: disable=unused-import
31+
from adafruit_rgb_display import st7735 # pylint: disable=unused-import
32+
from adafruit_rgb_display import ssd1351 # pylint: disable=unused-import
33+
from adafruit_rgb_display import ssd1331 # pylint: disable=unused-import
34+
35+
# Button pins for EYESPI Pi Beret
36+
BUTTON_NEXT = board.D5
37+
BUTTON_PREVIOUS = board.D6
38+
39+
# CS and DC pins for EYEPSPI Pi Beret:
40+
cs_pin = digitalio.DigitalInOut(board.CE0)
41+
dc_pin = digitalio.DigitalInOut(board.D25)
42+
43+
# Reset pin for EYESPI Pi Beret
44+
reset_pin = digitalio.DigitalInOut(board.D27)
45+
46+
# Backlight pin for Pi Beret
47+
backlight = digitalio.DigitalInOut(board.D18)
48+
backlight.switch_to_output()
49+
backlight.value = True
50+
51+
# Config for display baudrate (default max is 64mhz):
52+
BAUDRATE = 64000000
53+
54+
# Setup SPI bus using hardware SPI:
55+
spi = board.SPI()
56+
57+
# pylint: disable=line-too-long
58+
# fmt: off
59+
# Create the display.
60+
disp = ili9341.ILI9341(spi, rotation=90, # 2.2", 2.4", 2.8", 3.2" ILI9341
61+
# disp = st7789.ST7789(spi, rotation=90, # 2.0" ST7789
62+
# disp = st7789.ST7789(spi, height=240, y_offset=80, rotation=180, # 1.3", 1.54" ST7789
63+
# disp = st7789.ST7789(spi, rotation=90, width=135, height=240, x_offset=53, y_offset=40, # 1.14" ST7789
64+
# disp = st7789.ST7789(spi, rotation=90, width=172, height=320, x_offset=34, # 1.47" ST7789
65+
# disp = st7789.ST7789(spi, rotation=270, width=170, height=320, x_offset=35, # 1.9" ST7789
66+
# disp = hx8357.HX8357(spi, rotation=180, # 3.5" HX8357
67+
# disp = st7735.ST7735R(spi, rotation=90, # 1.8" ST7735R
68+
# disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3, # 1.44" ST7735R
69+
# disp = st7735.ST7735R(spi, rotation=90, bgr=True, width=80, # 0.96" MiniTFT Rev A ST7735R
70+
# disp = st7735.ST7735R(spi, rotation=90, invert=True, width=80, x_offset=26, y_offset=1, # 0.96" MiniTFT Rev B ST7735R
71+
# disp = ssd1351.SSD1351(spi, rotation=180, # 1.5" SSD1351
72+
# disp = ssd1351.SSD1351(spi, height=96, y_offset=32, rotation=180, # 1.27" SSD1351
73+
# disp = ssd1331.SSD1331(spi, rotation=180, # 0.96" SSD1331
74+
cs=cs_pin,
75+
dc=dc_pin,
76+
rst=reset_pin,
77+
baudrate=BAUDRATE,
78+
)
79+
# fmt: on
80+
# pylint: enable=line-too-long
81+
82+
83+
def init_button(pin):
84+
button = digitalio.DigitalInOut(pin)
85+
button.switch_to_input()
86+
button.pull = digitalio.Pull.UP
87+
return button
88+
89+
90+
class Frame: # pylint: disable=too-few-public-methods
91+
def __init__(self, duration=0):
92+
self.duration = duration
93+
self.image = None
94+
95+
96+
class AnimatedGif:
97+
def __init__(self, display, width=None, height=None, folder=None):
98+
self._frame_count = 0
99+
self._loop = 0
100+
self._index = 0
101+
self._duration = 0
102+
self._gif_files = []
103+
self._frames = []
104+
105+
if width is not None:
106+
self._width = width
107+
else:
108+
self._width = display.width
109+
if height is not None:
110+
self._height = height
111+
else:
112+
self._height = display.height
113+
self.display = display
114+
self.advance_button = init_button(BUTTON_NEXT)
115+
self.back_button = init_button(BUTTON_PREVIOUS)
116+
if folder is not None:
117+
self.load_files(folder)
118+
self.run()
119+
120+
def advance(self):
121+
self._index = (self._index + 1) % len(self._gif_files)
122+
123+
def back(self):
124+
self._index = (self._index - 1 + len(self._gif_files)) % len(self._gif_files)
125+
126+
def load_files(self, folder):
127+
gif_files = [f for f in os.listdir(folder) if f.endswith(".gif")]
128+
for gif_file in gif_files:
129+
gif_file = os.path.join(folder, gif_file)
130+
image = Image.open(gif_file)
131+
# Only add animated Gifs
132+
if image.is_animated:
133+
self._gif_files.append(gif_file)
134+
135+
print("Found", self._gif_files)
136+
if not self._gif_files:
137+
print("No Gif files found in current folder")
138+
exit() # pylint: disable=consider-using-sys-exit
139+
140+
def preload(self):
141+
image = Image.open(self._gif_files[self._index])
142+
print("Loading {}...".format(self._gif_files[self._index]))
143+
if "duration" in image.info:
144+
self._duration = image.info["duration"]
145+
else:
146+
self._duration = 0
147+
if "loop" in image.info:
148+
self._loop = image.info["loop"]
149+
else:
150+
self._loop = 1
151+
self._frame_count = image.n_frames
152+
self._frames.clear()
153+
for frame in range(self._frame_count):
154+
image.seek(frame)
155+
# Create blank image for drawing.
156+
# Make sure to create image with mode 'RGB' for full color.
157+
frame_object = Frame(duration=self._duration)
158+
if "duration" in image.info:
159+
frame_object.duration = image.info["duration"]
160+
frame_object.image = ImageOps.pad( # pylint: disable=no-member
161+
image.convert("RGB"),
162+
(self._width, self._height),
163+
method=Image.NEAREST,
164+
color=(0, 0, 0),
165+
centering=(0.5, 0.5),
166+
)
167+
self._frames.append(frame_object)
168+
169+
def play(self):
170+
self.preload()
171+
172+
_prev_advance_btn_val = self.advance_button.value
173+
_prev_back_btn_val = self.back_button.value
174+
# Check if we have loaded any files first
175+
if not self._gif_files:
176+
print("There are no Gif Images loaded to Play")
177+
return False
178+
while True:
179+
for frame_object in self._frames:
180+
start_time = time.monotonic()
181+
self.display.image(frame_object.image)
182+
_cur_advance_btn_val = self.advance_button.value
183+
_cur_back_btn_val = self.back_button.value
184+
if not _cur_advance_btn_val and _prev_advance_btn_val:
185+
self.advance()
186+
return False
187+
if not _cur_back_btn_val and _prev_back_btn_val:
188+
self.back()
189+
return False
190+
191+
_prev_back_btn_val = _cur_back_btn_val
192+
_prev_advance_btn_val = _cur_advance_btn_val
193+
while time.monotonic() < (start_time + frame_object.duration / 1000):
194+
pass
195+
196+
if self._loop == 1:
197+
return True
198+
if self._loop > 0:
199+
self._loop -= 1
200+
201+
def run(self):
202+
while True:
203+
auto_advance = self.play()
204+
if auto_advance:
205+
self.advance()
206+
207+
208+
if disp.rotation % 180 == 90:
209+
disp_height = disp.width # we swap height/width to rotate it to landscape!
210+
disp_width = disp.height
211+
else:
212+
disp_width = disp.width
213+
disp_height = disp.height
214+
215+
gif_player = AnimatedGif(disp, width=disp_width, height=disp_height, folder=".")

0 commit comments

Comments
 (0)