/ examples / rgb_display_pillow_animated_gif.py
rgb_display_pillow_animated_gif.py
  1  """
  2  Example to extract the frames and other parameters from an animated gif
  3  and then run the animation on the display.
  4  
  5  Usage:
  6  python3 rgb_display_pillow_animated_gif.py
  7  
  8  This example is for use on (Linux) computers that are using CPython with
  9  Adafruit Blinka to support CircuitPython libraries. CircuitPython does
 10  not support PIL/pillow (python imaging library)!
 11  
 12  Author(s): Melissa LeBlanc-Williams for Adafruit Industries
 13  """
 14  import os
 15  import time
 16  import digitalio
 17  import board
 18  from PIL import Image, ImageOps
 19  import numpy  # pylint: disable=unused-import
 20  import adafruit_rgb_display.ili9341 as ili9341
 21  import adafruit_rgb_display.st7789 as st7789  # pylint: disable=unused-import
 22  import adafruit_rgb_display.hx8357 as hx8357  # pylint: disable=unused-import
 23  import adafruit_rgb_display.st7735 as st7735  # pylint: disable=unused-import
 24  import adafruit_rgb_display.ssd1351 as ssd1351  # pylint: disable=unused-import
 25  import adafruit_rgb_display.ssd1331 as ssd1331  # pylint: disable=unused-import
 26  
 27  # Change to match your display
 28  BUTTON_NEXT = board.D17
 29  BUTTON_PREVIOUS = board.D22
 30  
 31  # Configuration for CS and DC pins (these are PiTFT defaults):
 32  cs_pin = digitalio.DigitalInOut(board.CE0)
 33  dc_pin = digitalio.DigitalInOut(board.D25)
 34  
 35  # Set this to None on the Mini PiTFT
 36  reset_pin = digitalio.DigitalInOut(board.D24)
 37  
 38  
 39  def init_button(pin):
 40      button = digitalio.DigitalInOut(pin)
 41      button.switch_to_input()
 42      button.pull = digitalio.Pull.UP
 43      return button
 44  
 45  
 46  # pylint: disable=too-few-public-methods
 47  class Frame:
 48      def __init__(self, duration=0):
 49          self.duration = duration
 50          self.image = None
 51  
 52  
 53  # pylint: enable=too-few-public-methods
 54  
 55  
 56  class AnimatedGif:
 57      def __init__(self, display, width=None, height=None, folder=None):
 58          self._frame_count = 0
 59          self._loop = 0
 60          self._index = 0
 61          self._duration = 0
 62          self._gif_files = []
 63          self._frames = []
 64  
 65          if width is not None:
 66              self._width = width
 67          else:
 68              self._width = display.width
 69          if height is not None:
 70              self._height = height
 71          else:
 72              self._height = display.height
 73          self.display = display
 74          self.advance_button = init_button(BUTTON_NEXT)
 75          self.back_button = init_button(BUTTON_PREVIOUS)
 76          if folder is not None:
 77              self.load_files(folder)
 78              self.run()
 79  
 80      def advance(self):
 81          self._index = (self._index + 1) % len(self._gif_files)
 82  
 83      def back(self):
 84          self._index = (self._index - 1 + len(self._gif_files)) % len(self._gif_files)
 85  
 86      def load_files(self, folder):
 87          gif_files = [f for f in os.listdir(folder) if f.endswith(".gif")]
 88          for gif_file in gif_files:
 89              image = Image.open(gif_file)
 90              # Only add animated Gifs
 91              if image.is_animated:
 92                  self._gif_files.append(gif_file)
 93  
 94          print("Found", self._gif_files)
 95          if not self._gif_files:
 96              print("No Gif files found in current folder")
 97              exit()  # pylint: disable=consider-using-sys-exit
 98  
 99      def preload(self):
100          image = Image.open(self._gif_files[self._index])
101          print("Loading {}...".format(self._gif_files[self._index]))
102          if "duration" in image.info:
103              self._duration = image.info["duration"]
104          else:
105              self._duration = 0
106          if "loop" in image.info:
107              self._loop = image.info["loop"]
108          else:
109              self._loop = 1
110          self._frame_count = image.n_frames
111          self._frames.clear()
112          for frame in range(self._frame_count):
113              image.seek(frame)
114              # Create blank image for drawing.
115              # Make sure to create image with mode 'RGB' for full color.
116              frame_object = Frame(duration=self._duration)
117              if "duration" in image.info:
118                  frame_object.duration = image.info["duration"]
119              frame_object.image = ImageOps.pad(  # pylint: disable=no-member
120                  image.convert("RGB"),
121                  (self._width, self._height),
122                  method=Image.NEAREST,
123                  color=(0, 0, 0),
124                  centering=(0.5, 0.5),
125              )
126              self._frames.append(frame_object)
127  
128      def play(self):
129          self.preload()
130  
131          # Check if we have loaded any files first
132          if not self._gif_files:
133              print("There are no Gif Images loaded to Play")
134              return False
135          while True:
136              for frame_object in self._frames:
137                  start_time = time.monotonic()
138                  self.display.image(frame_object.image)
139                  if not self.advance_button.value:
140                      self.advance()
141                      return False
142                  if not self.back_button.value:
143                      self.back()
144                      return False
145                  while time.monotonic() < (start_time + frame_object.duration / 1000):
146                      pass
147  
148              if self._loop == 1:
149                  return True
150              if self._loop > 0:
151                  self._loop -= 1
152  
153      def run(self):
154          while True:
155              auto_advance = self.play()
156              if auto_advance:
157                  self.advance()
158  
159  
160  # Config for display baudrate (default max is 64mhz):
161  BAUDRATE = 64000000
162  
163  # Setup SPI bus using hardware SPI:
164  spi = board.SPI()
165  
166  # pylint: disable=line-too-long
167  # Create the display:
168  # disp = st7789.ST7789(spi, rotation=90,                            # 2.0" ST7789
169  # disp = st7789.ST7789(spi, height=240, y_offset=80, rotation=180,  # 1.3", 1.54" ST7789
170  # disp = st7789.ST7789(spi, rotation=90, width=135, height=240, x_offset=53, y_offset=40, # 1.14" ST7789
171  # disp = hx8357.HX8357(spi, rotation=180,                           # 3.5" HX8357
172  # disp = st7735.ST7735R(spi, rotation=90,                           # 1.8" ST7735R
173  # disp = st7735.ST7735R(spi, rotation=270, height=128, x_offset=2, y_offset=3,   # 1.44" ST7735R
174  # disp = st7735.ST7735R(spi, rotation=90, bgr=True,                 # 0.96" MiniTFT ST7735R
175  # disp = ssd1351.SSD1351(spi, rotation=180,                         # 1.5" SSD1351
176  # disp = ssd1351.SSD1351(spi, height=96, y_offset=32, rotation=180, # 1.27" SSD1351
177  # disp = ssd1331.SSD1331(spi, rotation=180,                         # 0.96" SSD1331
178  disp = ili9341.ILI9341(
179      spi,
180      rotation=90,  # 2.2", 2.4", 2.8", 3.2" ILI9341
181      cs=cs_pin,
182      dc=dc_pin,
183      rst=reset_pin,
184      baudrate=BAUDRATE,
185  )
186  # pylint: enable=line-too-long
187  
188  if disp.rotation % 180 == 90:
189      disp_height = disp.width  # we swap height/width to rotate it to landscape!
190      disp_width = disp.height
191  else:
192      disp_width = disp.width
193      disp_height = disp.height
194  
195  gif_player = AnimatedGif(disp, width=disp_width, height=disp_height, folder=".")