/ adafruit_displayio_color_picker / wheel_maker.py
wheel_maker.py
1 # SPDY-FileCopyrightText: 2012 jacksongabbard 2 # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries 3 # SPDX-FileCopyrightText: 2021 Kevin Matocha, Jose David M. 4 # 5 # SPDX-License-Identifier: MIT 6 # SPDY-License-Identifier: Unlicense 7 8 """ 9 `wheel_maker` 10 ================================================================================ 11 12 Save a displayio.Bitmap (and associated displayio.Palette) in a BMP file. 13 This script is adapted in the works from Dave Astels on the ``adafruit_bitmapsaver`` 14 and the works of Jackson Glabbard 15 https://jg.gg/2012/05/28/generating-a-color-picker-style-color-wheel-in-python/ 16 https://github.com/jacksongabbard/Python-Color-Gamut-Generator/blob/master/color-wheel-generator.py 17 and Kevin Matocha on the ``switch_round`` for the ``_color_to_tuple`` function 18 19 * Author(s): Dave Astels, Jackson Glabbard, Kevin Matocha, Jose David M. 20 21 Implementation Notes 22 -------------------- 23 24 **Hardware:** 25 26 27 **Software and Dependencies:** 28 29 * Adafruit CircuitPython firmware for the supported boards: 30 https://github.com/adafruit/circuitpython/releases 31 32 """ 33 34 import math 35 import struct 36 import gc 37 import board 38 import digitalio 39 import busio 40 41 try: 42 import adafruit_sdcard 43 import storage 44 except ImportError: 45 pass 46 47 # pylint: disable=invalid-name, no-member, too-many-locals 48 49 50 def _write_bmp_header(output_file, filesize): 51 output_file.write(bytes("BM", "ascii")) 52 output_file.write(struct.pack("<I", filesize)) 53 output_file.write(b"\00\x00") 54 output_file.write(b"\00\x00") 55 output_file.write(struct.pack("<I", 54)) 56 57 58 def _bytes_per_row(source_width): 59 pixel_bytes = 3 * source_width 60 padding_bytes = (4 - (pixel_bytes % 4)) % 4 61 return pixel_bytes + padding_bytes 62 63 64 def _write_dib_header(output_file, width, height): 65 output_file.write(struct.pack("<I", 40)) 66 output_file.write(struct.pack("<I", width)) 67 output_file.write(struct.pack("<I", height)) 68 output_file.write(struct.pack("<H", 1)) 69 output_file.write(struct.pack("<H", 24)) 70 for _ in range(24): 71 output_file.write(b"\x00") 72 73 74 def make_color(base, adj, ratio, shade): 75 """ 76 Go through each bit of the colors adjusting blue with blue, red with red, 77 green with green, etc. 78 """ 79 output = 0x0 80 bit = 0 81 for pos in range(3): 82 base_chan = color_wheel[base][pos] 83 adj_chan = color_wheel[adj][pos] 84 new_chan = int(round(base_chan * (1 - ratio) + adj_chan * ratio)) 85 86 # now alter the channel by the shade 87 if shade < 1: 88 new_chan = new_chan * shade 89 elif shade > 1: 90 shade_ratio = shade - 1 91 new_chan = (0xFF * shade_ratio) + (new_chan * (1 - shade_ratio)) 92 93 output = output + (int(new_chan) << bit) 94 bit = bit + 8 95 return output 96 97 98 def color_to_tuple(value): 99 """Converts a color from a 24-bit integer to a tuple. 100 :param value: RGB LED desired value - can be a RGB tuple or a 24-bit integer. 101 """ 102 if isinstance(value, tuple): 103 return value 104 if isinstance(value, int): 105 if value >> 24: 106 raise ValueError("Only bits 0->23 valid for integer input") 107 r = value >> 16 108 g = (value >> 8) & 0xFF 109 b = value & 0xFF 110 return [r, g, b] 111 112 raise ValueError("Color must be a tuple or 24-bit integer value.") 113 114 115 color_wheel = [ 116 [0xFF, 0x00, 0xFF], 117 [0xFF, 0x00, 0x00], 118 [0xFF, 0xFF, 0x00], 119 [0x00, 0xFF, 0x00], 120 [0x00, 0xFF, 0xFF], 121 [0x00, 0x00, 0xFF], 122 [0xFF, 0x00, 0xFF], 123 ] 124 125 126 def make_wheel(image_name, img_size, bg_color=0x000000): 127 """ 128 :param image_name: Name of the ouput bitmap image 129 :param img_size: size of the bitmap image in pixels height=width 130 :param bg_color: color of the background in 24 bit format. Defaults 0x000000 131 :return: color 132 133 134 **Quickstart: Importing and using the make_wheel** 135 136 Here is one way of importing the ``make_wheel`` module so you can use: 137 138 .. code-block:: python 139 140 from adafruit_displayio_color_picker import wheel_maker 141 142 Now you can create a wheel of 200 pixels with a black background using: 143 144 .. code-block:: python 145 146 make_wheel("wheel200.bmp", 200 , 0x000000) 147 148 149 """ 150 img_size_width = img_size 151 img_size_height = img_size 152 img_half = img_size / 2 153 outer_radius = img_size // 2 154 background_color = color_to_tuple(bg_color) 155 row_buffer = bytearray(_bytes_per_row(img_size_width)) 156 result_buffer = bytearray(2048) 157 158 spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) 159 cs = digitalio.DigitalInOut(board.SD_CS) 160 sdcard = adafruit_sdcard.SDCard(spi, cs) 161 vfs = storage.VfsFat(sdcard) 162 storage.mount(vfs, "/sd") 163 file_path = "/sd" + image_name 164 print("saving starts") 165 output_file = open(file_path, "wb") 166 filesize = 54 + img_size_height * _bytes_per_row(img_size_width) 167 _write_bmp_header(output_file, filesize) 168 _write_dib_header(output_file, img_size_width, img_size_height) 169 170 for y in range(img_size, 0, -1): 171 buffer_index = 0 172 for x in range(img_size): 173 dist = abs(math.sqrt((x - img_half) ** 2 + (y - img_half) ** 2)) 174 shade = 1 * dist / outer_radius 175 if x - img_half == 0: 176 angle = -90 177 if y > img_half: 178 angle = 90 179 else: 180 angle = math.atan2((y - img_half), (x - img_half)) * 180 / math.pi 181 182 angle = (angle - 30) % 360 183 184 idx = angle / 60 185 if idx < 0: 186 idx = 6 + idx 187 base = int(round(idx)) 188 189 adj = (6 + base + (-1 if base > idx else 1)) % 6 190 ratio = max(idx, base) - min(idx, base) 191 color = make_color(base, adj, ratio, shade) 192 193 if dist > outer_radius: 194 color_rgb = background_color 195 else: 196 color_rgb = color_to_tuple(color) 197 198 for b in color_rgb: 199 row_buffer[buffer_index] = b & 0xFF 200 buffer_index += 1 201 output_file.write(row_buffer) 202 for i in range(img_size_width * 2): 203 result_buffer[i] = 0 204 gc.collect() 205 206 output_file.close() 207 print("saving done")