Skip to content

Commit f203261

Browse files
authored
Merge pull request #1840 from makermelissa/main
Add Rotary Gif Players
2 parents 74dc60c + c39010a commit f203261

File tree

4 files changed

+324
-0
lines changed

4 files changed

+324
-0
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import os
2+
import time
3+
from PIL import Image, ImageOps
4+
5+
# pylint: disable=too-few-public-methods
6+
class Frame:
7+
def __init__(self, duration=0):
8+
self.duration = duration
9+
self.image = None
10+
11+
12+
# pylint: enable=too-few-public-methods
13+
14+
15+
class AnimatedGif:
16+
def __init__(self, display, include_delays=True, folder=None):
17+
self._frame_count = 0
18+
self._loop = 0
19+
self._index = 0
20+
self._duration = 0
21+
self._gif_files = []
22+
self._frames = []
23+
self._running = True
24+
self.display = display
25+
self.include_delays = include_delays
26+
if folder is not None:
27+
self.load_files(folder)
28+
self.run()
29+
30+
def advance(self):
31+
self._index = (self._index + 1) % len(self._gif_files)
32+
33+
def back(self):
34+
self._index = (self._index - 1 + len(self._gif_files)) % len(self._gif_files)
35+
36+
def load_files(self, folder):
37+
gif_files = [f for f in os.listdir(folder) if f.endswith(".gif")]
38+
gif_folder = folder
39+
if gif_folder[:-1] != "/":
40+
gif_folder += "/"
41+
for gif_file in gif_files:
42+
image = Image.open(gif_folder + gif_file)
43+
# Only add animated Gifs
44+
if image.is_animated:
45+
self._gif_files.append(gif_folder + gif_file)
46+
47+
print("Found", self._gif_files)
48+
if not self._gif_files:
49+
print("No Gif files found in current folder")
50+
exit() # pylint: disable=consider-using-sys-exit
51+
52+
def preload(self):
53+
image = Image.open(self._gif_files[self._index])
54+
print("Loading {}...".format(self._gif_files[self._index]))
55+
if "duration" in image.info:
56+
self._duration = image.info["duration"]
57+
else:
58+
self._duration = 0
59+
if "loop" in image.info:
60+
self._loop = image.info["loop"]
61+
else:
62+
self._loop = 1
63+
self._frame_count = image.n_frames
64+
self._frames.clear()
65+
for frame in range(self._frame_count):
66+
image.seek(frame)
67+
# Create blank image for drawing.
68+
# Make sure to create image with mode 'RGB' for full color.
69+
frame_object = Frame(duration=self._duration)
70+
if "duration" in image.info:
71+
frame_object.duration = image.info["duration"]
72+
frame_object.image = ImageOps.pad( # pylint: disable=no-member
73+
image.convert("RGB"),
74+
(self._width, self._height),
75+
method=Image.NEAREST,
76+
color=(0, 0, 0),
77+
centering=(0.5, 0.5),
78+
)
79+
self._frames.append(frame_object)
80+
81+
def play(self):
82+
self.preload()
83+
current_frame = 0
84+
last_action = None
85+
# Check if we have loaded any files first
86+
if not self._gif_files:
87+
print("There are no Gif Images loaded to Play")
88+
return False
89+
self.update_display(self._frames[current_frame].image)
90+
while self._running:
91+
action = self.get_next_value()
92+
if action:
93+
if not last_action:
94+
last_action = action
95+
if action == "click":
96+
self.advance()
97+
return False
98+
elif int(action) < int(last_action):
99+
current_frame -= 1
100+
else:
101+
current_frame += 1
102+
current_frame %= self._frame_count
103+
frame_object = self._frames[current_frame]
104+
start_time = time.monotonic()
105+
self.update_display(frame_object.image)
106+
if self.include_delays:
107+
remaining_delay = frame_object.duration / 1000 - (
108+
time.monotonic() - start_time
109+
)
110+
if remaining_delay > 0:
111+
time.sleep(remaining_delay)
112+
last_action = action
113+
if self._loop == 1:
114+
return True
115+
if self._loop > 0:
116+
self._loop -= 1
117+
118+
def run(self):
119+
while self._running:
120+
auto_advance = self.play()
121+
if auto_advance:
122+
self.advance()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import usb_cdc
2+
import rotaryio
3+
import board
4+
import digitalio
5+
6+
serial = usb_cdc.data
7+
encoder = rotaryio.IncrementalEncoder(board.ROTA, board.ROTB)
8+
button = digitalio.DigitalInOut(board.SWITCH)
9+
button.switch_to_input(pull=digitalio.Pull.UP)
10+
11+
last_position = None
12+
button_state = False
13+
14+
while True:
15+
position = encoder.position
16+
if last_position is None or position != last_position:
17+
serial.write(bytes(str(position) + ",", "utf-8"))
18+
last_position = position
19+
print(button.value)
20+
if not button.value and not button_state:
21+
button_state = True
22+
if button.value and button_state:
23+
serial.write(bytes("click,", "utf-8"))
24+
button_state = False
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
"""
5+
This example is for use on (Linux) computers that are using CPython with
6+
Adafruit Blinka to support CircuitPython libraries. CircuitPython does
7+
not support PIL/pillow (python imaging library)!
8+
9+
Author(s): Melissa LeBlanc-Williams for Adafruit Industries
10+
"""
11+
import digitalio
12+
import board
13+
from animatedgif import AnimatedGif
14+
import numpy # pylint: disable=unused-import
15+
from adafruit_seesaw import seesaw, rotaryio, digitalio as ss_digitalio
16+
from adafruit_rgb_display import st7789
17+
18+
seesaw = seesaw.Seesaw(board.I2C(), addr=0x36)
19+
20+
seesaw_product = (seesaw.get_version() >> 16) & 0xFFFF
21+
print("Found product {}".format(seesaw_product))
22+
if seesaw_product != 4991:
23+
print("Wrong firmware loaded? Make sure you have a rotary encoder connected.")
24+
25+
seesaw.pin_mode(24, seesaw.INPUT_PULLUP)
26+
button = ss_digitalio.DigitalIO(seesaw, 24)
27+
encoder = rotaryio.IncrementalEncoder(seesaw)
28+
29+
# Change to match your display
30+
BUTTON_UP = board.D23
31+
BUTTON_DOWN = board.D24
32+
33+
# Configuration for CS and DC pins (these are PiTFT defaults):
34+
cs_pin = digitalio.DigitalInOut(board.CE0)
35+
dc_pin = digitalio.DigitalInOut(board.D25)
36+
37+
38+
def init_button(pin):
39+
digital_button = digitalio.DigitalInOut(pin)
40+
digital_button.switch_to_input(pull=digitalio.Pull.UP)
41+
return digital_button
42+
43+
44+
class TFTAnimatedGif(AnimatedGif):
45+
def __init__(self, display, include_delays=True, folder=None):
46+
self._width = display.width
47+
self._height = display.height
48+
self.up_button = init_button(BUTTON_UP)
49+
self.down_button = init_button(BUTTON_DOWN)
50+
self._last_position = None
51+
self._button_state = False
52+
super().__init__(display, include_delays=include_delays, folder=folder)
53+
54+
def get_next_value(self):
55+
while self._running:
56+
position = -encoder.position
57+
if position != self._last_position:
58+
self._last_position = position
59+
return str(position)
60+
elif not button.value and not self._button_state:
61+
self._button_state = True
62+
return str("click")
63+
elif button.value and self._button_state:
64+
self._button_state = False
65+
if not self.up_button.value or not self.down_button.value:
66+
self._running = False
67+
return None
68+
69+
def update_display(self, image):
70+
self.display.image(image)
71+
72+
73+
# Config for display baudrate (default max is 64mhz):
74+
BAUDRATE = 64000000
75+
76+
# Setup SPI bus using hardware SPI:
77+
spi = board.SPI()
78+
79+
# Create the display:
80+
disp = st7789.ST7789(
81+
spi,
82+
height=240,
83+
y_offset=80,
84+
rotation=180,
85+
cs=cs_pin,
86+
dc=dc_pin,
87+
rst=None,
88+
baudrate=BAUDRATE,
89+
)
90+
91+
gif_player = TFTAnimatedGif(disp, include_delays=False, folder="images")
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
This example is for use on (Linux) computers that are using CPython with
3+
Adafruit Blinka to support CircuitPython libraries. CircuitPython does
4+
not support PIL/pillow (python imaging library)!
5+
6+
Author(s): Melissa LeBlanc-Williams for Adafruit Industries
7+
"""
8+
import serial
9+
import adafruit_board_toolkit.circuitpython_serial
10+
from animatedgif import AnimatedGif
11+
import pygame
12+
13+
port = None
14+
15+
16+
def detect_port():
17+
"""
18+
Detect the port automatically
19+
"""
20+
comports = adafruit_board_toolkit.circuitpython_serial.data_comports()
21+
ports = [comport.device for comport in comports]
22+
if len(ports) >= 1:
23+
if len(ports) > 1:
24+
print("Multiple devices detected, using the first detected port.")
25+
return ports[0]
26+
raise RuntimeError(
27+
"Unable to find any CircuitPython Devices with the CDC data port enabled."
28+
)
29+
30+
31+
port = serial.Serial(
32+
detect_port(),
33+
9600,
34+
parity="N",
35+
rtscts=False,
36+
xonxoff=False,
37+
exclusive=True,
38+
)
39+
40+
41+
class PyGameAnimatedGif(AnimatedGif):
42+
def __init__(self, display, include_delays=True, folder=None):
43+
self._width, self._height = pygame.display.get_surface().get_size()
44+
self._incoming_packet = b""
45+
super().__init__(display, include_delays=include_delays, folder=folder)
46+
47+
def get_next_value(self):
48+
if not port:
49+
return None
50+
while port.in_waiting:
51+
self._incoming_packet += port.read(port.in_waiting)
52+
while (
53+
self._running
54+
and not len(self._incoming_packet)
55+
and self._incoming_packet.decode().find(",")
56+
):
57+
self._incoming_packet += port.read(port.in_waiting)
58+
self.check_pygame_events()
59+
60+
all_values = self._incoming_packet.decode().split(",")
61+
value = all_values.pop(0)
62+
self._incoming_packet = ",".join(all_values).encode("utf-8")
63+
return value
64+
65+
def check_pygame_events(self):
66+
for event in pygame.event.get():
67+
if event.type == pygame.QUIT:
68+
self._running = False
69+
elif event.type == pygame.KEYDOWN:
70+
if event.key == pygame.K_ESCAPE:
71+
self._running = False
72+
73+
def update_display(self, image):
74+
pilImage = image
75+
self.display.blit(
76+
pygame.image.fromstring(pilImage.tobytes(), pilImage.size, pilImage.mode),
77+
(0, 0),
78+
)
79+
pygame.display.flip()
80+
81+
82+
pygame.init()
83+
pygame.mouse.set_visible(False)
84+
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
85+
gif_player = PyGameAnimatedGif(screen, include_delays=False, folder="images")
86+
pygame.mouse.set_visible(True)
87+
port.close()

0 commit comments

Comments
 (0)