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