4343import time
4444import os
4545import random
46+ import json
4647import 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 ()
0 commit comments