painter.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. """Functions that help us work with Quantum Painter's file formats.
  2. """
  3. import math
  4. import re
  5. from string import Template
  6. from PIL import Image, ImageOps
  7. # The list of valid formats Quantum Painter supports
  8. valid_formats = {
  9. 'rgb888': {
  10. 'image_format': 'IMAGE_FORMAT_RGB888',
  11. 'bpp': 24,
  12. 'has_palette': False,
  13. 'num_colors': 16777216,
  14. 'image_format_byte': 0x09, # see qp_internal_formats.h
  15. },
  16. 'rgb565': {
  17. 'image_format': 'IMAGE_FORMAT_RGB565',
  18. 'bpp': 16,
  19. 'has_palette': False,
  20. 'num_colors': 65536,
  21. 'image_format_byte': 0x08, # see qp_internal_formats.h
  22. },
  23. 'pal256': {
  24. 'image_format': 'IMAGE_FORMAT_PALETTE',
  25. 'bpp': 8,
  26. 'has_palette': True,
  27. 'num_colors': 256,
  28. 'image_format_byte': 0x07, # see qp_internal_formats.h
  29. },
  30. 'pal16': {
  31. 'image_format': 'IMAGE_FORMAT_PALETTE',
  32. 'bpp': 4,
  33. 'has_palette': True,
  34. 'num_colors': 16,
  35. 'image_format_byte': 0x06, # see qp_internal_formats.h
  36. },
  37. 'pal4': {
  38. 'image_format': 'IMAGE_FORMAT_PALETTE',
  39. 'bpp': 2,
  40. 'has_palette': True,
  41. 'num_colors': 4,
  42. 'image_format_byte': 0x05, # see qp_internal_formats.h
  43. },
  44. 'pal2': {
  45. 'image_format': 'IMAGE_FORMAT_PALETTE',
  46. 'bpp': 1,
  47. 'has_palette': True,
  48. 'num_colors': 2,
  49. 'image_format_byte': 0x04, # see qp_internal_formats.h
  50. },
  51. 'mono256': {
  52. 'image_format': 'IMAGE_FORMAT_GRAYSCALE',
  53. 'bpp': 8,
  54. 'has_palette': False,
  55. 'num_colors': 256,
  56. 'image_format_byte': 0x03, # see qp_internal_formats.h
  57. },
  58. 'mono16': {
  59. 'image_format': 'IMAGE_FORMAT_GRAYSCALE',
  60. 'bpp': 4,
  61. 'has_palette': False,
  62. 'num_colors': 16,
  63. 'image_format_byte': 0x02, # see qp_internal_formats.h
  64. },
  65. 'mono4': {
  66. 'image_format': 'IMAGE_FORMAT_GRAYSCALE',
  67. 'bpp': 2,
  68. 'has_palette': False,
  69. 'num_colors': 4,
  70. 'image_format_byte': 0x01, # see qp_internal_formats.h
  71. },
  72. 'mono2': {
  73. 'image_format': 'IMAGE_FORMAT_GRAYSCALE',
  74. 'bpp': 1,
  75. 'has_palette': False,
  76. 'num_colors': 2,
  77. 'image_format_byte': 0x00, # see qp_internal_formats.h
  78. }
  79. }
  80. license_template = """\
  81. // Copyright ${year} QMK -- generated source code only, ${generated_type} retains original copyright
  82. // SPDX-License-Identifier: GPL-2.0-or-later
  83. // This file was auto-generated by `${generator_command}`
  84. """
  85. def render_license(subs):
  86. license_txt = Template(license_template)
  87. return license_txt.substitute(subs)
  88. header_file_template = """\
  89. ${license}
  90. #pragma once
  91. #include <qp.h>
  92. extern const uint32_t ${var_prefix}_${sane_name}_length;
  93. extern const uint8_t ${var_prefix}_${sane_name}[${byte_count}];
  94. """
  95. def render_header(subs):
  96. header_txt = Template(header_file_template)
  97. return header_txt.substitute(subs)
  98. source_file_template = """\
  99. ${license}
  100. #include <qp.h>
  101. const uint32_t ${var_prefix}_${sane_name}_length = ${byte_count};
  102. // clang-format off
  103. const uint8_t ${var_prefix}_${sane_name}[${byte_count}] = {
  104. ${bytes_lines}
  105. };
  106. // clang-format on
  107. """
  108. def render_source(subs):
  109. source_txt = Template(source_file_template)
  110. return source_txt.substitute(subs)
  111. def render_bytes(bytes, newline_after=16):
  112. lines = ''
  113. for n in range(len(bytes)):
  114. if n % newline_after == 0 and n > 0 and n != len(bytes):
  115. lines = lines + "\n "
  116. elif n == 0:
  117. lines = lines + " "
  118. lines = lines + " 0x{0:02X},".format(bytes[n])
  119. return lines.rstrip()
  120. def clean_output(str):
  121. str = re.sub(r'\r', '', str)
  122. str = re.sub(r'[\n]{3,}', r'\n\n', str)
  123. return str
  124. def rescale_byte(val, maxval):
  125. """Rescales a byte value to the supplied range, i.e. [0,255] -> [0,maxval].
  126. """
  127. return int(round(val * maxval / 255.0))
  128. def convert_requested_format(im, format):
  129. """Convert an image to the requested format.
  130. """
  131. # Work out the requested format
  132. ncolors = format["num_colors"]
  133. image_format = format["image_format"]
  134. # Work out where we're getting the bytes from
  135. if image_format == 'IMAGE_FORMAT_GRAYSCALE':
  136. # Ensure we have a valid number of colors for the palette
  137. if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
  138. raise ValueError("Number of colors must be 2, 4, 16, or 256.")
  139. # If mono, convert input to grayscale, then to RGB, then grab the raw bytes corresponding to the intensity of the red channel
  140. im = ImageOps.grayscale(im)
  141. im = im.convert("RGB")
  142. elif image_format == 'IMAGE_FORMAT_PALETTE':
  143. # Ensure we have a valid number of colors for the palette
  144. if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
  145. raise ValueError("Number of colors must be 2, 4, 16, or 256.")
  146. # If color, convert input to RGB, palettize based on the supplied number of colors, then get the raw palette bytes
  147. im = im.convert("RGB")
  148. im = im.convert("P", palette=Image.ADAPTIVE, colors=ncolors)
  149. elif image_format == 'IMAGE_FORMAT_RGB565':
  150. # Ensure we have a valid number of colors for the palette
  151. if ncolors != 65536:
  152. raise ValueError("Number of colors must be 65536.")
  153. # If color, convert input to RGB
  154. im = im.convert("RGB")
  155. elif image_format == 'IMAGE_FORMAT_RGB888':
  156. # Ensure we have a valid number of colors for the palette
  157. if ncolors != 1677216:
  158. raise ValueError("Number of colors must be 16777216.")
  159. # If color, convert input to RGB
  160. im = im.convert("RGB")
  161. return im
  162. def convert_image_bytes(im, format):
  163. """Convert the supplied image to the equivalent bytes required by the QMK firmware.
  164. """
  165. # Work out the requested format
  166. ncolors = format["num_colors"]
  167. image_format = format["image_format"]
  168. shifter = int(math.log2(ncolors))
  169. pixels_per_byte = int(8 / math.log2(ncolors))
  170. bytes_per_pixel = math.ceil(math.log2(ncolors) / 8)
  171. (width, height) = im.size
  172. if (pixels_per_byte != 0):
  173. expected_byte_count = ((width * height) + (pixels_per_byte - 1)) // pixels_per_byte
  174. else:
  175. expected_byte_count = width * height * bytes_per_pixel
  176. if image_format == 'IMAGE_FORMAT_GRAYSCALE':
  177. # Take the red channel
  178. image_bytes = im.tobytes("raw", "R")
  179. image_bytes_len = len(image_bytes)
  180. # No palette
  181. palette = None
  182. bytearray = []
  183. for x in range(expected_byte_count):
  184. byte = 0
  185. for n in range(pixels_per_byte):
  186. byte_offset = x * pixels_per_byte + n
  187. if byte_offset < image_bytes_len:
  188. # If mono, each input byte is a grayscale [0,255] pixel -- rescale to the range we want then pack together
  189. byte = byte | (rescale_byte(image_bytes[byte_offset], ncolors - 1) << int(n * shifter))
  190. bytearray.append(byte)
  191. elif image_format == 'IMAGE_FORMAT_PALETTE':
  192. # Convert each pixel to the palette bytes
  193. image_bytes = im.tobytes("raw", "P")
  194. image_bytes_len = len(image_bytes)
  195. # Export the palette
  196. palette = []
  197. pal = im.getpalette()
  198. for n in range(0, ncolors * 3, 3):
  199. palette.append((pal[n + 0], pal[n + 1], pal[n + 2]))
  200. bytearray = []
  201. for x in range(expected_byte_count):
  202. byte = 0
  203. for n in range(pixels_per_byte):
  204. byte_offset = x * pixels_per_byte + n
  205. if byte_offset < image_bytes_len:
  206. # If color, each input byte is the index into the color palette -- pack them together
  207. byte = byte | ((image_bytes[byte_offset] & (ncolors - 1)) << int(n * shifter))
  208. bytearray.append(byte)
  209. if image_format == 'IMAGE_FORMAT_RGB565':
  210. # Take the red, green, and blue channels
  211. image_bytes_red = im.tobytes("raw", "R")
  212. image_bytes_green = im.tobytes("raw", "G")
  213. image_bytes_blue = im.tobytes("raw", "B")
  214. image_pixels_len = len(image_bytes_red)
  215. # No palette
  216. palette = None
  217. bytearray = []
  218. for x in range(image_pixels_len):
  219. # 5 bits of red, 3 MSb of green
  220. byte = ((image_bytes_red[x] >> 3 & 0x1F) << 3) + (image_bytes_green[x] >> 5 & 0x07)
  221. bytearray.append(byte)
  222. # 3 LSb of green, 5 bits of blue
  223. byte = ((image_bytes_green[x] >> 2 & 0x07) << 5) + (image_bytes_blue[x] >> 3 & 0x1F)
  224. bytearray.append(byte)
  225. if image_format == 'IMAGE_FORMAT_RGB888':
  226. # Take the red, green, and blue channels
  227. image_bytes_red = im.tobytes("raw", "R")
  228. image_bytes_green = im.tobytes("raw", "G")
  229. image_bytes_blue = im.tobytes("raw", "B")
  230. image_pixels_len = len(image_bytes_red)
  231. # No palette
  232. palette = None
  233. bytearray = []
  234. for x in range(image_pixels_len):
  235. byte = image_bytes_red[x]
  236. bytearray.append(byte)
  237. byte = image_bytes_green[x]
  238. bytearray.append(byte)
  239. byte = image_bytes_blue[x]
  240. bytearray.append(byte)
  241. if len(bytearray) != expected_byte_count:
  242. raise Exception(f"Wrong byte count, was {len(bytearray)}, expected {expected_byte_count}")
  243. return (palette, bytearray)
  244. def compress_bytes_qmk_rle(bytearray):
  245. debug_dump = False
  246. output = []
  247. temp = []
  248. repeat = False
  249. def append_byte(c):
  250. if debug_dump:
  251. print('Appending byte:', '0x{0:02X}'.format(int(c)), '=', c)
  252. output.append(c)
  253. def append_range(r):
  254. append_byte(127 + len(r))
  255. if debug_dump:
  256. print('Appending {0} byte(s):'.format(len(r)), '[', ', '.join(['{0:02X}'.format(e) for e in r]), ']')
  257. output.extend(r)
  258. for n in range(0, len(bytearray) + 1):
  259. end = True if n == len(bytearray) else False
  260. if not end:
  261. c = bytearray[n]
  262. temp.append(c)
  263. if len(temp) <= 1:
  264. continue
  265. if debug_dump:
  266. print('Temp buffer state {0:3d} bytes:'.format(len(temp)), '[', ', '.join(['{0:02X}'.format(e) for e in temp]), ']')
  267. if repeat:
  268. if temp[-1] != temp[-2]:
  269. repeat = False
  270. if not repeat or len(temp) == 128 or end:
  271. append_byte(len(temp) if end else len(temp) - 1)
  272. append_byte(temp[0])
  273. temp = [temp[-1]]
  274. repeat = False
  275. else:
  276. if len(temp) >= 2 and temp[-1] == temp[-2]:
  277. repeat = True
  278. if len(temp) > 2:
  279. append_range(temp[0:(len(temp) - 2)])
  280. temp = [temp[-1], temp[-1]]
  281. continue
  282. if len(temp) == 128 or end:
  283. append_range(temp)
  284. temp = []
  285. repeat = False
  286. return output