/ CircuitPython_Painter / code.py
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)