Skip to content

Commit 5f5836d

Browse files
authored
Merge pull request #27 from FoamyGuy/text_slides
Allow text slides
2 parents 9cfed13 + 48c8e8a commit 5f5836d

File tree

4 files changed

+176
-66
lines changed

4 files changed

+176
-66
lines changed

adafruit_slideshow.py

+156-60
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,28 @@
4343
import time
4444
import os
4545
import random
46+
import json
4647
import displayio
4748

49+
try:
50+
# text slides are an optional feature and require adafruit_display_text
51+
from adafruit_display_text import bitmap_label
52+
import terminalio
53+
54+
TEXT_SLIDES_ENABLED = True
55+
except ImportError:
56+
print("Warning: adafruit_display_text not found. No support for text slides.")
57+
TEXT_SLIDES_ENABLED = False
58+
59+
try:
60+
# custom fonts are an optional feature and require adafruit_bitmap_font
61+
from adafruit_bitmap_font import bitmap_font
62+
63+
CUSTOM_FONTS = True
64+
except ImportError:
65+
print("Warning: adafruit_bitmap_font not found. No support for custom fonts.")
66+
CUSTOM_FONTS = False
67+
4868
__version__ = "0.0.0-auto.0"
4969
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git"
5070

@@ -190,12 +210,24 @@ def __init__(
190210
h_align=HorizontalAlignment.LEFT,
191211
v_align=VerticalAlignment.TOP,
192212
):
213+
def _check_json_file(file):
214+
if TEXT_SLIDES_ENABLED:
215+
if file.endswith(".json"):
216+
with open(file) as _file_obj:
217+
try:
218+
json_data = json.loads(_file_obj.read())
219+
if "text" in json_data:
220+
return True
221+
except ValueError:
222+
return False
223+
return False
224+
193225
self.loop = loop
194-
"""Specifies whether to loop through the images continuously or play through the list once.
226+
"""Specifies whether to loop through the slides continuously or play through the list once.
195227
``True`` will continue to loop, ``False`` will play only once."""
196228

197229
self.dwell = dwell
198-
"""The number of seconds each image displays, in seconds."""
230+
"""The number of seconds each slide displays, in seconds."""
199231

200232
self.direction = direction
201233
"""Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be
@@ -205,16 +237,19 @@ def __init__(
205237
"""Enable auto-advance based on dwell time. Set to ``False`` to manually control."""
206238

207239
self.fade_effect = fade_effect
208-
"""Whether to include the fade effect between images. ``True`` tells the code to fade the
209-
backlight up and down between image display transitions. ``False`` maintains max
210-
brightness on the backlight between image transitions."""
240+
"""Whether to include the fade effect between slides. ``True`` tells the code to fade the
241+
backlight up and down between slide display transitions. ``False`` maintains max
242+
brightness on the backlight between slide transitions."""
211243

212244
# Load the image names before setting order so they can be reordered.
213245
self._img_start = None
214246
self._file_list = [
215-
folder + "/" + f
247+
folder + f
216248
for f in os.listdir(folder)
217-
if (f.endswith(".bmp") and not f.startswith("."))
249+
if (
250+
not f.startswith(".")
251+
and (f.endswith(".bmp") or _check_json_file(folder + f))
252+
)
218253
]
219254

220255
self._order = None
@@ -226,11 +261,9 @@ def __init__(
226261
self._h_align = h_align
227262
self._v_align = v_align
228263

229-
self._current_image = -1
230-
self._image_file = None
264+
self._current_slide_index = -1
265+
self._slide_file = None
231266
self._brightness = 0.5
232-
# 4.0.0 Beta 2 replaces Sprite with TileGrid so use either.
233-
self._sprite_class = getattr(displayio, "Sprite", displayio.TileGrid)
234267

235268
# Setup the display
236269
self._group = displayio.Group()
@@ -249,9 +282,9 @@ def __init__(
249282
self.advance()
250283

251284
@property
252-
def current_image_name(self):
285+
def current_slide_name(self):
253286
"""Returns the current image name."""
254-
return self._file_list[self._current_image]
287+
return self._file_list[self._current_slide_index]
255288

256289
@property
257290
def order(self):
@@ -265,9 +298,9 @@ def order(self, order):
265298
raise ValueError("Order must be either 'RANDOM' or 'ALPHABETICAL'")
266299

267300
self._order = order
268-
self._reorder_images()
301+
self._reorder_slides()
269302

270-
def _reorder_images(self):
303+
def _reorder_slides(self):
271304
if self.order == PlayBackOrder.ALPHABETICAL:
272305
self._file_list = sorted(self._file_list)
273306
elif self.order == PlayBackOrder.RANDOM:
@@ -315,73 +348,136 @@ def _fade_down(self):
315348
self._set_backlight(self.brightness * i / steps)
316349
time.sleep(0.01)
317350

351+
def _create_label(self, file):
352+
# pylint: disable=too-many-branches
353+
"""Creates and returns a label from a file object that contains
354+
valid valid json describing the text to use.
355+
See: examples/sample_text_slide.json
356+
"""
357+
json_data = json.loads(file.read())
358+
_scale = 1
359+
if "scale" in json_data:
360+
_scale = int(json_data["scale"])
361+
362+
if CUSTOM_FONTS:
363+
if "font" in json_data:
364+
_font = bitmap_font.load_font(json_data["font"])
365+
else:
366+
_font = terminalio.FONT
367+
else:
368+
_font = terminalio.FONT
369+
370+
label = bitmap_label.Label(_font, text=json_data["text"], scale=_scale)
371+
if "h_align" not in json_data or json_data["h_align"] == "LEFT":
372+
x_anchor_point = 0.0
373+
x_anchored_position = 0
374+
elif json_data["h_align"] == "CENTER":
375+
x_anchor_point = 0.5
376+
x_anchored_position = self._display.width // 2
377+
elif json_data["h_align"] == "RIGHT":
378+
x_anchor_point = 1.0
379+
x_anchored_position = self._display.width - 1
380+
else:
381+
# wrong value for align
382+
x_anchor_point = 0.0
383+
x_anchored_position = 0
384+
385+
if "v_align" not in json_data or json_data["v_align"] == "TOP":
386+
y_anchor_point = 0.0
387+
y_anchored_position = 0
388+
elif json_data["v_align"] == "CENTER":
389+
y_anchor_point = 0.5
390+
y_anchored_position = self._display.height // 2
391+
elif json_data["v_align"] == "BOTTOM":
392+
y_anchor_point = 1.0
393+
y_anchored_position = self._display.height - 1
394+
else:
395+
# wrong value for align
396+
y_anchor_point = 0.0
397+
y_anchored_position = 0
398+
399+
if "background_color" in json_data:
400+
label.background_color = int(json_data["background_color"], 16)
401+
402+
if "color" in json_data:
403+
label.color = int(json_data["color"], 16)
404+
405+
label.anchor_point = (x_anchor_point, y_anchor_point)
406+
label.anchored_position = (x_anchored_position, y_anchored_position)
407+
return label
408+
318409
def update(self):
319410
"""Updates the slideshow to the next image."""
320411
now = time.monotonic()
321412
if not self.auto_advance or now - self._img_start < self.dwell:
322413
return True
323414
return self.advance()
324415

325-
# pylint: disable=too-many-branches
416+
# pylint: disable=too-many-branches, too-many-statements
326417
def advance(self):
327418
"""Displays the next image. Returns True when a new image was displayed, False otherwise."""
328-
if self._image_file:
419+
if self._slide_file:
329420
self._fade_down()
330421
self._group.pop()
331-
self._image_file.close()
332-
self._image_file = None
422+
self._slide_file.close()
423+
self._slide_file = None
333424

334-
self._current_image += self.direction
425+
self._current_slide_index += self.direction
335426

336-
# Try and load an OnDiskBitmap until a valid file is found or we run out of options. This
427+
# Try to load slides until a valid file is found or we run out of options. This
337428
# loop stops because we either set odb or reduce the length of _file_list.
338429
odb = None
339-
while not odb and self._file_list:
340-
if 0 <= self._current_image < len(self._file_list):
430+
lbl = None
431+
while not odb and not lbl and self._file_list:
432+
if 0 <= self._current_slide_index < len(self._file_list):
341433
pass
342434
elif not self.loop:
343435
return False
344436
else:
345-
image_count = len(self._file_list)
346-
if self._current_image < 0:
347-
self._current_image += image_count
348-
elif self._current_image >= image_count:
349-
self._current_image -= image_count
350-
self._reorder_images()
351-
352-
image_name = self._file_list[self._current_image]
353-
self._image_file = open(image_name, "rb")
354-
try:
355-
odb = displayio.OnDiskBitmap(self._image_file)
356-
except ValueError:
357-
self._image_file.close()
358-
self._image_file = None
359-
del self._file_list[self._current_image]
360-
361-
if not odb:
362-
raise RuntimeError("No valid images")
363-
364-
if self._h_align == HorizontalAlignment.RIGHT:
365-
self._group.x = self._display.width - odb.width
366-
elif self._h_align == HorizontalAlignment.CENTER:
367-
self._group.x = round(self._display.width / 2 - odb.width / 2)
368-
else:
369-
self._group.x = 0
437+
slide_count = len(self._file_list)
438+
if self._current_slide_index < 0:
439+
self._current_slide_index += slide_count
440+
elif self._current_slide_index >= slide_count:
441+
self._current_slide_index -= slide_count
442+
self._reorder_slides()
443+
444+
file_name = self._file_list[self._current_slide_index]
445+
self._slide_file = open(file_name, "rb")
446+
if file_name.endswith(".bmp"):
447+
try:
448+
odb = displayio.OnDiskBitmap(self._slide_file)
449+
except ValueError:
450+
self._slide_file.close()
451+
self._slide_file = None
452+
del self._file_list[self._current_slide_index]
453+
elif file_name.endswith(".json"):
454+
lbl = self._create_label(self._slide_file)
455+
456+
if not odb and not lbl:
457+
raise RuntimeError("No valid images or text json files")
458+
459+
if odb:
460+
if self._h_align == HorizontalAlignment.RIGHT:
461+
self._group.x = self._display.width - odb.width
462+
elif self._h_align == HorizontalAlignment.CENTER:
463+
self._group.x = round(self._display.width / 2 - odb.width / 2)
464+
else:
465+
self._group.x = 0
370466

371-
if self._v_align == VerticalAlignment.BOTTOM:
372-
self._group.y = self._display.height - odb.height
373-
elif self._v_align == VerticalAlignment.CENTER:
374-
self._group.y = round(self._display.height / 2 - odb.height / 2)
375-
else:
376-
self._group.y = 0
467+
if self._v_align == VerticalAlignment.BOTTOM:
468+
self._group.y = self._display.height - odb.height
469+
elif self._v_align == VerticalAlignment.CENTER:
470+
self._group.y = round(self._display.height / 2 - odb.height / 2)
471+
else:
472+
self._group.y = 0
377473

378-
try:
379-
sprite = self._sprite_class(odb, pixel_shader=displayio.ColorConverter())
380-
except TypeError:
381-
sprite = self._sprite_class(
382-
odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)
474+
image_tilegrid = displayio.TileGrid(
475+
odb, pixel_shader=displayio.ColorConverter()
383476
)
384-
self._group.append(sprite)
477+
478+
self._group.append(image_tilegrid)
479+
if lbl:
480+
self._group.append(lbl)
385481

386482
if hasattr(self._display, "refresh"):
387483
self._display.refresh()
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"text": "Sample Text\nSlideshow",
3+
"h_align": "CENTER",
4+
"v_align": "CENTER",
5+
"color": "0xFFFFFF",
6+
"background_color": "0x666666",
7+
"scale": 2
8+
}

examples/slideshow_alignment_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@
3939
slideshow.v_align = aligns[i][0]
4040
i += 1
4141

42-
prev_img = slideshow.current_image_name
42+
prev_img = slideshow.current_slide_name
4343
while slideshow.update():
44-
cur_img = slideshow.current_image_name
44+
cur_img = slideshow.current_slide_name
4545
if prev_img != cur_img:
4646
slideshow.h_align = aligns[i][1]
4747
slideshow.v_align = aligns[i][0]

examples/slideshow_simpletest.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1+
"""Basic demonstration script will create a slideshow
2+
object that plays through once alphabetically."""
13
import board
2-
import pulseio
34
from adafruit_slideshow import PlayBackOrder, SlideShow
45

6+
# use built in display (PyPortal, PyGamer, PyBadge, CLUE, etc.)
7+
# see guide for setting up external displays (TFT / OLED breakouts, RGB matrices, etc.)
8+
# https://learn.adafruit.com/circuitpython-display-support-using-displayio/display-and-display-bus
9+
display = board.DISPLAY
10+
511
# pylint: disable=no-member
612

7-
# Create the slideshow object that plays through once alphabetically.
813
slideshow = SlideShow(
914
board.DISPLAY,
10-
pulseio.PWMOut(board.TFT_BACKLIGHT),
11-
folder="/",
15+
None,
16+
folder="/images/",
1217
loop=False,
1318
order=PlayBackOrder.ALPHABETICAL,
19+
dwell=10,
1420
)
1521

1622
while slideshow.update():

0 commit comments

Comments
 (0)