Skip to content

Commit bfb827a

Browse files
committed
Added auto_advance and backlight_level control
Added ability to choose whether slideshow will automatically cycle through the images or whether you would like to specify an input to manually cycle through the slideshow. Added backlight brightness level control. Added ability to cycle forwards or backwards through image list. Added ability to turn off the fade in/out effects during image transition - this means the redraw effect of the image changing is visible, but it also means you can advance more quickly through the list if you choose to. Documentation updated. Various linting fixes.
1 parent cce8983 commit bfb827a

File tree

1 file changed

+185
-70
lines changed

1 file changed

+185
-70
lines changed

adafruit_slideshow.py

Lines changed: 185 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@
2626
CircuitPython helper library for displaying a slideshow of images on a board with a built-in
2727
display.
2828
29-
* Author(s): Kattni Rembor, Carter Nelson
29+
* Author(s): Kattni Rembor, Carter Nelson, Roy Hooper
3030
3131
Implementation Notes
3232
--------------------
3333
3434
**Hardware:**
3535
36-
* `Adafruit Hollowing M0 Express <https://www.adafruit.com/product/3900>`_"
36+
* `Adafruit Hollowing M0 Express <https://www.adafruit.com/product/3900>`_
3737
3838
**Software and Dependencies:**
3939
@@ -52,9 +52,33 @@
5252
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git"
5353

5454

55-
class SlideShow():
56-
"""Class for displaying a slideshow of .bmp images on boards with built-in
57-
displays.
55+
_FADE_IN = 0
56+
_SHOW_IMG = 1
57+
_FADE_OUT = 2
58+
_LOAD_IMG = 3
59+
_WAIT = 4
60+
61+
62+
class PlayBackMode:
63+
"""Helper class for handling playback mode."""
64+
# pylint: disable=too-few-public-methods
65+
ALPHA = 0
66+
RANDOM = 1
67+
# pylint: enable=too-few-public-methods
68+
69+
70+
class PlayBackDirection:
71+
"""Helper class for handling playback direction."""
72+
# pylint: disable=too-few-public-methods
73+
BACKWARD = -1
74+
FORWARD = 1
75+
# pylint: enable=too-few-public-methods
76+
77+
78+
class SlideShow:
79+
# pylint: disable=too-many-instance-attributes
80+
"""
81+
Class for displaying a slideshow of .bmp images on boards with built-in displays.
5882
5983
:param str folder: Specify the folder containing the image files, in quotes. Default is
6084
the root directory, ``"/"``.
@@ -68,6 +92,15 @@ class SlideShow():
6892
6993
:param int dwell: The number of seconds each image displays, in seconds. Default is 3.
7094
95+
:param fade_effect: Specify whether to include the fade effect between images. ``True``
96+
tells the code to fade the backlight up and down between image display
97+
transitions. ``False`` maintains max brightness on the backlight between
98+
image transitions. Default is ``True``.
99+
100+
:param auto_advance: Specify whether to automatically advance after dwell seconds. ``True``
101+
if slideshow should auto play, ``False`` if you want to control advancement
102+
manually. Default is ``True``.
103+
71104
Example code for Hollowing Express. With this example, the slideshow will play through once
72105
in alphabetical order:
73106
@@ -77,44 +110,76 @@ class SlideShow():
77110
78111
slideshow = adafruit_slideshow.SlideShow()
79112
slideshow.loop = False
80-
slideshow.order = slideshow.ALPHA
113+
slideshow.order = PlayBackMode.ALPHA
81114
82115
while slideshow.update():
83116
pass
84-
"""
85-
MAX_BRIGHTNESS = 2 ** 15
86117
87-
ALPHA = 0
88-
RANDOM = 1
118+
Example code for Hollowing Express. Sets ``dwell`` to 0 seconds, turns ``auto_advance`` off,
119+
and uses capacitive touch to advance backwards and forwards through the images and to control
120+
the brightness level of the backlight:
121+
122+
.. code-block:: python
123+
124+
from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection
125+
import touchio
126+
import board
127+
128+
forward_button = touchio.TouchIn(board.TOUCH4)
129+
back_button = touchio.TouchIn(board.TOUCH1)
130+
131+
brightness_up = touchio.TouchIn(board.TOUCH3)
132+
brightness_down = touchio.TouchIn(board.TOUCH2)
89133
90-
FADE_IN = 0
91-
SHOW_IMG = 1
92-
FADE_OUT = 2
93-
LOAD_IMG = 3
134+
slideshow = SlideShow()
135+
slideshow.auto_advance = False
136+
slideshow.dwell = 0
94137
95-
def __init__(self, folder="/", order=RANDOM, loop=True, dwell=3):
138+
while True:
139+
if forward_button.value:
140+
slideshow.advance()
141+
if back_button.value:
142+
slideshow.advance(direction=PlayBackDirection.BACKWARD)
143+
144+
if brightness_up.value:
145+
slideshow.backlight_level_up()
146+
elif brightness_down.value:
147+
slideshow.backlight_level_down()
148+
slideshow.update()
149+
"""
150+
151+
_max_brightness = 2 ** 15
152+
153+
# pylint: disable=too-many-arguments
154+
def __init__(self, folder="/", order=PlayBackMode.RANDOM, loop=True, dwell=3, fade_effect=True,
155+
auto_advance=True):
96156
self._group = displayio.Group()
97157
board.DISPLAY.show(self._group)
98158
self._backlight = pulseio.PWMOut(board.TFT_BACKLIGHT)
99-
self._folder = folder
100-
self._order = order
101-
self._loop = loop
102-
self._dwell = dwell
103-
self._current_state = self.LOAD_IMG
159+
self.folder = folder
160+
"""Specifies the folder containing the image files. Default is the root directory, ``"/"``.
161+
"""
162+
self.loop = loop
163+
"""Specifies whether to loop through the images continuously or play through the list once.
164+
``True`` will continue to loop, ``False`` will play only once. Default is `True`."""
165+
self.dwell = dwell
166+
"""The number of seconds each image displays, in seconds. Default is 3."""
167+
self.fade_effect = fade_effect
168+
self._current_state = _LOAD_IMG
104169
self._img_start = None
105170
self._file_list = None
106171
self._images = None
107172
self._load_images()
108-
109-
@property
110-
def folder(self):
111-
"""Specifies the folder containing the image files. Default is the root directory, ``"/"``.
112-
"""
113-
return self._folder
114-
115-
@folder.setter
116-
def folder(self, folder):
117-
self._folder = folder
173+
self._order = None
174+
self.order = order
175+
self.direction = PlayBackDirection.FORWARD
176+
"""Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be
177+
``PlayBackDirection.BACKWARD``."""
178+
self.auto_advance = auto_advance
179+
"""Enable auto-advance based on dwell time. Set to ``False`` to manually control."""
180+
self._update_order()
181+
self._current_backlight_level = self._max_brightness
182+
# pylint: enable=too-many-arguments
118183

119184
@property
120185
def order(self):
@@ -124,63 +189,107 @@ def order(self):
124189

125190
@order.setter
126191
def order(self, order):
192+
if order not in [PlayBackMode.ALPHA, PlayBackMode.RANDOM]:
193+
raise ValueError("Order must be either 'RANDOM' or 'ALPHA'")
194+
if order == self._order:
195+
return
127196
self._order = order
197+
self._update_order()
128198

129-
@property
130-
def loop(self):
131-
"""Specifies whether to loop through the images continuously or play through the list once.
132-
``True`` will continue to loop, ``False`` will play only once. Default is `True`."""
133-
return self._loop
199+
def _update_order(self):
200+
if self.order == PlayBackMode.ALPHA:
201+
self._file_list = sorted(self._file_list)
202+
if self.order == PlayBackMode.RANDOM:
203+
self._file_list = sorted(self._file_list, key=lambda x: random.random())
134204

135-
@loop.setter
136-
def loop(self, loop):
137-
self._loop = loop
205+
def backlight_level_up(self, step=16):
206+
"""Increases the backlight brightness level.
138207
139-
@property
140-
def dwell(self):
141-
"""The number of seconds each image displays, in seconds. Default is 3."""
142-
return self._dwell
208+
:param step: Specify the number of steps by which current backlight level will be increased.
209+
Default is 16.
210+
"""
211+
self._max_brightness += step
212+
if self._max_brightness >= 2 ** 16:
213+
self._max_brightness = 2 ** 16 - 1
214+
self._current_backlight_level = self._max_brightness
215+
return self._current_backlight_level
216+
217+
def backlight_level_down(self, step=16):
218+
"""Decreases the backlight brightness level.
143219
144-
@dwell.setter
145-
def dwell(self, dwell):
146-
self._dwell = dwell
220+
:param step: Specify the number of steps by which current backlight level will be decreased.
221+
Default is 16.
222+
"""
223+
self._max_brightness -= step
224+
if self._max_brightness < 0:
225+
self._max_brightness = 0
226+
self._current_backlight_level = self._max_brightness
227+
return self._current_backlight_level
228+
229+
def _fade_up(self):
230+
steps = 100
231+
for b in range(steps):
232+
self._backlight.duty_cycle = b * self._current_backlight_level // steps
233+
time.sleep(0.01)
234+
235+
def _fade_down(self):
236+
steps = 100
237+
for b in range(steps, -1, -1):
238+
self._backlight.duty_cycle = b * self._current_backlight_level // steps
239+
time.sleep(0.01)
147240

148241
def update(self):
149242
"""Updates the slideshow to the next image."""
150243
now = time.monotonic()
151-
if self._current_state == self.FADE_IN:
152-
steps = 100
153-
for b in range(steps):
154-
self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps
155-
time.sleep(0.01)
156-
self._current_state = self.SHOW_IMG
244+
if self._current_state == _FADE_IN:
245+
if self.fade_effect:
246+
self._fade_up()
247+
else:
248+
self._backlight.duty_cycle = self._current_backlight_level
249+
self._current_state = _SHOW_IMG
157250
self._img_start = time.monotonic()
158251

159-
if self._current_state == self.SHOW_IMG:
160-
if now - self._img_start > self._dwell:
161-
self._current_state = self.FADE_OUT
252+
if self._current_state == _SHOW_IMG:
253+
self._backlight.duty_cycle = self._current_backlight_level
254+
if now - self._img_start > self.dwell:
255+
self._current_state = _FADE_OUT if self.auto_advance else _WAIT
162256

163-
if self._current_state == self.FADE_OUT:
164-
steps = 100
165-
for b in range(steps, -1, -1):
166-
self._backlight.duty_cycle = b * SlideShow.MAX_BRIGHTNESS // steps
167-
time.sleep(0.01)
257+
if self._current_state == _WAIT:
258+
self._backlight.duty_cycle = self._current_backlight_level
259+
260+
if self._current_state == _FADE_OUT:
261+
if self.fade_effect:
262+
self._fade_down()
263+
else:
264+
self._backlight.duty_cycle = self._current_backlight_level
168265
self._group.pop()
169-
self._current_state = self.LOAD_IMG
266+
self._current_state = _LOAD_IMG
170267

171-
if self._current_state == self.LOAD_IMG:
268+
if self._current_state == _LOAD_IMG:
172269
try:
173270
imagename = next(self._images)
174271
except StopIteration:
175272
return False
176273
try:
177274
self._show_bmp(imagename)
178-
self._current_state = self.FADE_IN
275+
self._current_state = _FADE_IN
179276
except ValueError as error:
180277
print("Incompatible image:", imagename, str(error))
181278

182279
return True
183280

281+
def advance(self, direction=None):
282+
"""Displays the next image when `auto_advance` is False.
283+
284+
Does not advance the image until the current image change is over.
285+
286+
:param int direction: Change the playback direction when advancing to the next image.
287+
"""
288+
if direction:
289+
self.direction = direction
290+
if self._current_state == _WAIT:
291+
self._current_state = _FADE_OUT
292+
184293
def _show_bmp(self, imagename):
185294
"""Opens and loads the image onto the display."""
186295
with open(imagename, "rb") as image:
@@ -191,19 +300,25 @@ def _show_bmp(self, imagename):
191300

192301
def _get_next_image(self):
193302
"""Cycles through the list of images."""
303+
index = -1 if self.direction == PlayBackDirection.FORWARD else len(self._file_list)
194304
while True:
195-
for image in self._file_list:
196-
yield image
197-
if not self._loop:
305+
wrapped = False
306+
index += self.direction
307+
if index < 0:
308+
index = len(self._file_list) - 1
309+
wrapped = True
310+
elif index >= len(self._file_list):
311+
index = 0
312+
wrapped = True
313+
yield self._file_list[index]
314+
if wrapped and not self.loop:
198315
return
199316

200317
def _load_images(self):
201-
"""Loads the list of images to be displayed in alphabetical or random order."""
318+
"""Loads the list of images to be displayed."""
202319
self._file_list = self._get_filenames()
203-
if self._order == SlideShow.RANDOM:
204-
self._file_list = sorted(self._file_list, key=lambda x: random.random())
205-
self._images = self._get_next_image()
320+
self._images = self._get_next_image()
206321

207322
def _get_filenames(self, extension="bmp"):
208323
"""Creates a list of available image files ending with .bmp in the specified folder."""
209-
return list(filter(lambda x: x.endswith(extension), os.listdir(self._folder)))
324+
return list(filter(lambda x: x.endswith(extension), os.listdir(self.folder)))

0 commit comments

Comments
 (0)