code.py
  1  # SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  # Dotstar painter! Can handle up to ~2300 pixel size image (e.g. 36 x 64)
  6  
  7  import gc
  8  import time
  9  
 10  import board
 11  import busio
 12  import digitalio
 13  
 14  FILENAME = "blinka.bmp"
 15  IMAGE_DELAY = 0.2
 16  REPEAT = True
 17  BRIGHTNESS = 0.3
 18  PIXEL_DELAY = 0.001
 19  
 20  dotstar = busio.SPI(board.SCK, board.MOSI)
 21  while not dotstar.try_lock():
 22      pass
 23  dotstar.configure(baudrate=12000000)
 24  
 25  # we'll resize this later
 26  databuf = bytearray(0)
 27  
 28  led = digitalio.DigitalInOut(board.D13)
 29  led.switch_to_output()
 30  
 31  
 32  def read_le(s):
 33      # as of this writting, int.from_bytes does not have LE support, DIY!
 34      result = 0
 35      shift = 0
 36      for byte in bytearray(s):
 37          result += byte << shift
 38          shift += 8
 39      return result
 40  
 41  
 42  class BMPError(Exception):
 43      pass
 44  
 45  
 46  try:
 47      with open("/" + FILENAME, "rb") as f:
 48          print("File opened")
 49          if f.read(2) != b'BM':  # check signature
 50              raise BMPError("Not BitMap file")
 51  
 52          bmpFileSize = read_le(f.read(4))
 53          f.read(4)  # Read & ignore creator bytes
 54  
 55          bmpImageoffset = read_le(f.read(4))  # Start of image data
 56          headerSize = read_le(f.read(4))
 57          bmpWidth = read_le(f.read(4))
 58          bmpHeight = read_le(f.read(4))
 59          flip = True
 60  
 61          print("Size: %d\nImage offset: %d\nHeader size: %d" %
 62                (bmpFileSize, bmpImageoffset, headerSize))
 63          print("Width: %d\nHeight: %d" % (bmpWidth, bmpHeight))
 64  
 65          if read_le(f.read(2)) != 1:
 66              raise BMPError("Not singleplane")
 67          bmpDepth = read_le(f.read(2))  # bits per pixel
 68          print("Bit depth: %d" % (bmpDepth))
 69          if bmpDepth != 24:
 70              raise BMPError("Not 24-bit")
 71          if read_le(f.read(2)) != 0:
 72              raise BMPError("Compressed file")
 73  
 74          print("Image OK!")
 75  
 76          rowSize = (bmpWidth * 3 + 3) & ~3  # 32-bit line boundary
 77  
 78          # its huge! but its also fast :)
 79          databuf = bytearray(bmpWidth * bmpHeight * 4)
 80  
 81          for row in range(bmpHeight):  # For each scanline...
 82              if flip:  # Bitmap is stored bottom-to-top order (normal BMP)
 83                  pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize
 84              else:  # Bitmap is stored top-to-bottom
 85                  pos = bmpImageoffset + row * rowSize
 86  
 87              # print ("seek to %d" % pos)
 88              f.seek(pos)
 89              for col in range(bmpWidth):
 90                  b, g, r = bytearray(f.read(3))  # BMP files store RGB in BGR
 91                  # front load brightness, gamma and reordering here!
 92                  order = [b, g, r]
 93                  idx = (col * bmpHeight + (bmpHeight - row - 1)) * 4
 94                  databuf[idx] = 0xFF  # first byte is 'brightness'
 95                  idx += 1
 96                  for color in order:
 97                      databuf[idx] = int(
 98                          pow((color * BRIGHTNESS) / 255, 2.7) * 255 + 0.5)
 99                      idx += 1
100  
101  except OSError as e:
102      if e.args[0] == 28:
103          raise OSError("OS Error 28 0.25")
104      else:
105          raise OSError("OS Error 0.5")
106  except BMPError as e:
107      print("Failed to parse BMP: " + e.args[0])
108  
109  gc.collect()
110  print(gc.mem_free())
111  print("Ready to go!")
112  while True:
113      print("Draw!")
114      index = 0
115  
116      for col in range(bmpWidth):
117          row = databuf[index:index + bmpHeight * 4]
118          dotstar.write(bytearray([0x00, 0x00, 0x00, 0x00]))
119          dotstar.write(row)
120          dotstar.write(bytearray([0x00, 0x00, 0x00, 0x00]))
121          index += bmpHeight * 4
122          time.sleep(PIXEL_DELAY)
123  
124      # clear it out
125      dotstar.write(bytearray([0x00, 0x00, 0x00, 0x00]))
126      for r in range(bmpHeight * 5):
127          dotstar.write(bytearray([0xFF, 0x00, 0x00, 0x00]))
128      dotstar.write(bytearray([0xff, 0xff, 0xff, 0xff]))
129      gc.collect()
130  
131      if not REPEAT:
132          break
133  
134      time.sleep(IMAGE_DELAY)