From 00ef90f51c7e05ba5794af8ded0ffd5ec2344ee0 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 9 Oct 2018 16:56:19 -0700 Subject: [PATCH 1/3] Simplify the internals and brightness. --- README.rst | 10 +- adafruit_slideshow.py | 310 +++++++++++++++---------------- examples/slideshow_simpletest.py | 15 +- examples/slideshow_touch.py | 20 +- 4 files changed, 167 insertions(+), 188 deletions(-) diff --git a/README.rst b/README.rst index 3164d5b..423ad88 100644 --- a/README.rst +++ b/README.rst @@ -31,11 +31,13 @@ Usage Example .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow + from adafruit_slideshow import PlayBackOrder, SlideShow + import board + import pulseio - slideshow = SlideShow() - slideshow.loop = False - slideshow.order = PlayBackMode.ALPHA + # Create the slideshow object that plays through once alphabetically. + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 0a83f1c..0edf60b 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -23,8 +23,7 @@ """ `adafruit_slideshow` ==================================================== -CircuitPython helper library for displaying a slideshow of images on a board with a built-in -display. +CircuitPython helper library for displaying a slideshow of images on a display. * Author(s): Kattni Rembor, Carter Nelson, Roy Hooper @@ -44,73 +43,72 @@ import time import os import random -import board import displayio -import pulseio __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Slideshow.git" - -_FADE_IN = 0 -_SHOW_IMG = 1 -_FADE_OUT = 2 -_LOAD_IMG = 3 -_WAIT = 4 - - -class PlayBackMode: - """Helper class for handling playback mode.""" +class PlayBackOrder: + """Defines possible slideshow playback orders.""" # pylint: disable=too-few-public-methods - ALPHA = 0 + ALPHABETICAL = 0 + """Orders by alphabetical sort of filenames""" + RANDOM = 1 + """Randomly shuffles the images""" # pylint: enable=too-few-public-methods class PlayBackDirection: - """Helper class for handling playback direction.""" + """Defines possible slideshow playback directions.""" # pylint: disable=too-few-public-methods BACKWARD = -1 + """The next image is before the current image. When alphabetically sorted, this is towards A.""" + FORWARD = 1 + """The next image is after the current image. When alphabetically sorted, this is towards Z.""" # pylint: enable=too-few-public-methods class SlideShow: # pylint: disable=too-many-instance-attributes """ - Class for displaying a slideshow of .bmp images on boards with built-in displays. + Class for displaying a slideshow of .bmp images on displays. :param str folder: Specify the folder containing the image files, in quotes. Default is the root directory, ``"/"``. - :param order: The order in which the images display. You can choose random (``RANDOM``) or - alphabetical (``ALPHA``). Default is ``RANDOM``. + :param PlayBackOrder order: The order in which the images display. You can choose random (``RANDOM``) or + alphabetical (``ALPHABETICAL``). Default is ``ALPHABETICAL``. - :param loop: Specify whether to loop the images or play through the list once. `True` + :param bool loop: Specify whether to loop the images or play through the list once. `True` if slideshow will continue to loop, ``False`` if it will play only once. Default is ``True``. :param int dwell: The number of seconds each image displays, in seconds. Default is 3. - :param fade_effect: Specify whether to include the fade effect between images. ``True`` + :param bool fade_effect: Specify whether to include the fade effect between images. ``True`` tells the code to fade the backlight up and down between image display transitions. ``False`` maintains max brightness on the backlight between image transitions. Default is ``True``. - :param auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` + :param bool auto_advance: Specify whether to automatically advance after dwell seconds. ``True`` if slideshow should auto play, ``False`` if you want to control advancement manually. Default is ``True``. + :param PlayBackDirection direction: The playback direction. + Example code for Hallowing Express. With this example, the slideshow will play through once in alphabetical order: .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow + from adafruit_slideshow import PlayBackOrder, SlideShow + import board + import pulseio - slideshow = SlideShow() - slideshow.loop = False - slideshow.order = PlayBackMode.ALPHA + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass @@ -121,9 +119,10 @@ class SlideShow: .. code-block:: python - from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection + from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection import touchio import board + import pulseio forward_button = touchio.TouchIn(board.TOUCH4) back_button = touchio.TouchIn(board.TOUCH1) @@ -131,194 +130,175 @@ class SlideShow: brightness_up = touchio.TouchIn(board.TOUCH3) brightness_down = touchio.TouchIn(board.TOUCH2) - slideshow = SlideShow() - slideshow.auto_advance = False - slideshow.dwell = 0 + slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + auto_advance=False, dwell=0) while True: if forward_button.value: + slideshow.direction = PlayBackDirection.FORWARD slideshow.advance() if back_button.value: - slideshow.advance(direction=PlayBackDirection.BACKWARD) + slideshow.direction = PlayBackDirection.BACKWARD + slideshow.advance() if brightness_up.value: - slideshow.backlight_level_up() + slideshow.brightness += 0.001 elif brightness_down.value: - slideshow.backlight_level_down() - slideshow.update() + slideshow.brightness -= 0.001 """ - _max_brightness = 2 ** 15 - - # pylint: disable=too-many-arguments - def __init__(self, folder="/", order=PlayBackMode.RANDOM, loop=True, dwell=3, fade_effect=True, - auto_advance=True): - self._group = displayio.Group() - board.DISPLAY.show(self._group) - self._backlight = pulseio.PWMOut(board.TFT_BACKLIGHT) - self.folder = folder - """Specifies the folder containing the image files. Default is the root directory, ``"/"``. - """ + def __init__(self, display, backlight_pwm, *, folder="/", order=PlayBackOrder.ALPHABETICAL, loop=True, + dwell=3, fade_effect=True, auto_advance=True, direction=PlayBackDirection.FORWARD): self.loop = loop """Specifies whether to loop through the images continuously or play through the list once. - ``True`` will continue to loop, ``False`` will play only once. Default is `True`.""" + ``True`` will continue to loop, ``False`` will play only once.""" + self.dwell = dwell - """The number of seconds each image displays, in seconds. Default is 3.""" - self.fade_effect = fade_effect - self._current_state = _LOAD_IMG - self._img_start = None - self._file_list = None - self._images = None - self._load_images() - self._order = None - self.order = order - self.direction = PlayBackDirection.FORWARD + """The number of seconds each image displays, in seconds.""" + + self.direction = direction """Specify the playback direction. Default is ``PlayBackDirection.FORWARD``. Can also be ``PlayBackDirection.BACKWARD``.""" + self.auto_advance = auto_advance """Enable auto-advance based on dwell time. Set to ``False`` to manually control.""" - self._update_order() - self._current_backlight_level = self._max_brightness - # pylint: enable=too-many-arguments + + self.fade_effect = fade_effect + """Whether to include the fade effect between images. ``True`` tells the code to fade the + backlight up and down between image display transitions. ``False`` maintains max + brightness on the backlight between image transitions.""" + + # Load the image names before setting order so they can be reordered. + self._img_start = None + self._file_list = list(filter(lambda x: x.endswith("bmp"), os.listdir(folder))) + + self.order = order + """The order in which the images display. You can choose random (``RANDOM``) or + alphabetical (``ALPHA``).""" + + self._current_image = -1 + self._image_file = None + self._brightness = 0.5 + + # Setup the display + self._group = displayio.Group() + self._display = display + display.show(self._group) + + self._backlight_pwm = backlight_pwm + + # Show the first image + self.advance() @property def order(self): """Specifies the order in which the images are displayed. Options are random (``RANDOM``) or - alphabetical (``ALPHA``). Default is ``RANDOM``.""" + alphabetical (``ALPHABETICAL``). Default is ``RANDOM``.""" return self._order @order.setter def order(self, order): - if order not in [PlayBackMode.ALPHA, PlayBackMode.RANDOM]: - raise ValueError("Order must be either 'RANDOM' or 'ALPHA'") - if order == self._order: - return + if order not in [PlayBackOrder.ALPHABETICAL, PlayBackOrder.RANDOM]: + raise ValueError("Order must be either 'RANDOM' or 'ALPHABETICAL'") + self._order = order - self._update_order() + self._reorder_images() - def _update_order(self): - if self.order == PlayBackMode.ALPHA: + def _reorder_images(self): + if self.order == PlayBackOrder.ALPHABETICAL: self._file_list = sorted(self._file_list) - if self.order == PlayBackMode.RANDOM: + elif self.order == PlayBackOrder.RANDOM: self._file_list = sorted(self._file_list, key=lambda x: random.random()) - def backlight_level_up(self, step=16): - """Increases the backlight brightness level. + def _set_backlight(self, brightness): + full_brightness = 2 ** 16 - 1 + self._backlight_pwm.duty_cycle = int(full_brightness * brightness) - :param step: Specify the number of steps by which current backlight level will be increased. - Default is 16. - """ - self._max_brightness += step - if self._max_brightness >= 2 ** 16: - self._max_brightness = 2 ** 16 - 1 - self._current_backlight_level = self._max_brightness - return self._current_backlight_level - - def backlight_level_down(self, step=16): - """Decreases the backlight brightness level. - - :param step: Specify the number of steps by which current backlight level will be decreased. - Default is 16. - """ - self._max_brightness -= step - if self._max_brightness < 0: - self._max_brightness = 0 - self._current_backlight_level = self._max_brightness - return self._current_backlight_level + @property + def brightness(self): + """Brightness of the backlight when an image is displaying. Clamps to 0 to 1.0""" + return self._brightness + + @brightness.setter + def brightness(self, brightness): + if brightness < 0: + brightness = 0 + elif brightness > 1.0: + brightness = 1.0 + self._brightness = brightness + self._set_backlight(brightness) def _fade_up(self): + if not self.fade_effect: + self._set_backlight(self.brightness) + return steps = 100 - for b in range(steps): - self._backlight.duty_cycle = b * self._current_backlight_level // steps + for i in range(steps): + self._set_backlight(self.brightness * i / steps) time.sleep(0.01) def _fade_down(self): + if not self.fade_effect: + self._set_backlight(self.brightness) + return steps = 100 - for b in range(steps, -1, -1): - self._backlight.duty_cycle = b * self._current_backlight_level // steps + for i in range(steps, -1, -1): + self._set_backlight(self.brightness * i / steps) time.sleep(0.01) def update(self): """Updates the slideshow to the next image.""" now = time.monotonic() - if self._current_state == _FADE_IN: - if self.fade_effect: - self._fade_up() - else: - self._backlight.duty_cycle = self._current_backlight_level - self._current_state = _SHOW_IMG - self._img_start = time.monotonic() - - if self._current_state == _SHOW_IMG: - self._backlight.duty_cycle = self._current_backlight_level - if now - self._img_start > self.dwell: - self._current_state = _FADE_OUT if self.auto_advance else _WAIT + if not self.auto_advance or now - self._img_start < self.dwell: + return True - if self._current_state == _WAIT: - self._backlight.duty_cycle = self._current_backlight_level + return self.advance() - if self._current_state == _FADE_OUT: - if self.fade_effect: - self._fade_down() - else: - self._backlight.duty_cycle = self._current_backlight_level + def advance(self): + """Displays the next image. Returns True when a new image was displayed, False otherwise. + """ + if self._image_file: + self._fade_down() self._group.pop() - self._current_state = _LOAD_IMG - - if self._current_state == _LOAD_IMG: - try: - imagename = next(self._images) - except StopIteration: + self._image_file.close() + self._image_file = None + + self._current_image += self.direction + + # Try and load an OnDiskBitmap until a valid file is found or we run out of options. This + # loop stops because we either set odb or reduce the length of _file_list. + odb = None + while not odb and self._file_list: + if 0 <= self._current_image < len(self._file_list): + pass + elif not self.loop: return False + else: + image_count = len(self._file_list) - 1 + if self._current_image < 0: + self._current_image += image_count + elif self._current_image > image_count: + self._current_image -= image_count + self._reorder_images() + + imagename = self._file_list[self._current_image] + self._image_file = open(imagename, "rb") try: - self._show_bmp(imagename) - self._current_state = _FADE_IN + odb = displayio.OnDiskBitmap(self._image_file) except ValueError as error: - print("Incompatible image:", imagename, str(error)) + self._image_file.close() + self._image_file = None + print("remove", imagename) + del self._file_list[self._current_image] - return True + if not odb: + raise RuntimeError("No valid images") - def advance(self, direction=None): - """Displays the next image when `auto_advance` is False. + sprite = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)) + self._group.append(sprite) + self._display.wait_for_frame() - Does not advance the image until the current image change is over. + self._fade_up() + self._img_start = time.monotonic() - :param int direction: Change the playback direction when advancing to the next image. - """ - if direction: - self.direction = direction - if self._current_state == _WAIT: - self._current_state = _FADE_OUT - - def _show_bmp(self, imagename): - """Opens and loads the image onto the display.""" - with open(imagename, "rb") as image: - odb = displayio.OnDiskBitmap(image) - face = displayio.Sprite(odb, pixel_shader=displayio.ColorConverter(), position=(0, 0)) - self._group.append(face) - board.DISPLAY.wait_for_frame() - - def _get_next_image(self): - """Cycles through the list of images.""" - index = -1 if self.direction == PlayBackDirection.FORWARD else len(self._file_list) - while True: - wrapped = False - index += self.direction - if index < 0: - index = len(self._file_list) - 1 - wrapped = True - elif index >= len(self._file_list): - index = 0 - wrapped = True - yield self._file_list[index] - if wrapped and not self.loop: - return - - def _load_images(self): - """Loads the list of images to be displayed.""" - self._file_list = self._get_filenames() - self._images = self._get_next_image() - - def _get_filenames(self, extension="bmp"): - """Creates a list of available image files ending with .bmp in the specified folder.""" - return list(filter(lambda x: x.endswith(extension), os.listdir(self.folder))) + return True diff --git a/examples/slideshow_simpletest.py b/examples/slideshow_simpletest.py index 8ee4139..8b15c75 100644 --- a/examples/slideshow_simpletest.py +++ b/examples/slideshow_simpletest.py @@ -1,13 +1,10 @@ -from adafruit_slideshow import PlayBackMode, SlideShow +from adafruit_slideshow import PlayBackOrder, SlideShow +import board +import pulseio -# Create the slideshow object -slideshow = SlideShow() - -# Set it to play through only once. -slideshow.loop = False - -# Set the order to alphabetical. -slideshow.order = PlayBackMode.ALPHA +# Create the slideshow object that plays through once alphabetically. +slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + loop=False, order=PlayBackOrder.ALPHABETICAL) while slideshow.update(): pass diff --git a/examples/slideshow_touch.py b/examples/slideshow_touch.py index 4934aff..b0e0e92 100644 --- a/examples/slideshow_touch.py +++ b/examples/slideshow_touch.py @@ -1,6 +1,7 @@ -import board -from adafruit_slideshow import PlayBackMode, SlideShow, PlayBackDirection +from adafruit_slideshow import PlayBackOrder, SlideShow, PlayBackDirection import touchio +import board +import pulseio forward_button = touchio.TouchIn(board.TOUCH4) back_button = touchio.TouchIn(board.TOUCH1) @@ -8,19 +9,18 @@ brightness_up = touchio.TouchIn(board.TOUCH3) brightness_down = touchio.TouchIn(board.TOUCH2) -slideshow = SlideShow() -slideshow.order = PlayBackMode.ALPHA -slideshow.auto_advance = False -slideshow.dwell = 0 +slideshow = SlideShow(board.DISPLAY, pulseio.PWMOut(board.TFT_BACKLIGHT), folder="/", + auto_advance=False, dwell=0) while True: if forward_button.value: + slideshow.direction = PlayBackDirection.FORWARD slideshow.advance() if back_button.value: - slideshow.advance(direction=PlayBackDirection.BACKWARD) + slideshow.direction = PlayBackDirection.BACKWARD + slideshow.advance() if brightness_up.value: - slideshow.backlight_level_up() + slideshow.brightness += 0.001 elif brightness_down.value: - slideshow.backlight_level_down() - slideshow.update() + slideshow.brightness -= 0.001 From 20ccb320df4a6351de883a8c8f9d9ff5c33277e1 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Tue, 9 Oct 2018 16:58:20 -0700 Subject: [PATCH 2/3] Remove builtin display requirement --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 423ad88..70582cf 100644 --- a/README.rst +++ b/README.rst @@ -13,8 +13,7 @@ Introduction :target: https://travis-ci.org/adafruit/adafruit_CircuitPython_Slideshow :alt: Build Status -CircuitPython helper library for displaying a slideshow of images on a board with a built-in -display. +CircuitPython helper library for displaying a slideshow of images on a display. Dependencies ============= From 084a9d574e97c4ac645f9268f69a0256300bb745 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 10 Oct 2018 14:16:32 -0700 Subject: [PATCH 3/3] Remove print --- adafruit_slideshow.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_slideshow.py b/adafruit_slideshow.py index 0edf60b..6d4c057 100755 --- a/adafruit_slideshow.py +++ b/adafruit_slideshow.py @@ -288,7 +288,6 @@ def advance(self): except ValueError as error: self._image_file.close() self._image_file = None - print("remove", imagename) del self._file_list[self._current_image] if not odb: