summaryrefslogtreecommitdiff
path: root/lib/python/qmk/painter.py
diff options
context:
space:
mode:
authorDavid Hoelscher <infinityis@users.noreply.github.com>2023-01-14 04:24:54 -0600
committerGitHub <noreply@github.com>2023-01-14 21:24:54 +1100
commit45851a10f66119ceff3baadde27f68e287eff481 (patch)
treefe482cc85723cbf6a626ddb2f346d916f60ce29b /lib/python/qmk/painter.py
parent5873fbe5690a008548c258840b273d0712495ed4 (diff)
Add RGB565 and RGB888 color support to Quantum Painter (#19382)
Diffstat (limited to 'lib/python/qmk/painter.py')
-rw-r--r--lib/python/qmk/painter.py80
1 files changed, 75 insertions, 5 deletions
diff --git a/lib/python/qmk/painter.py b/lib/python/qmk/painter.py
index d0cc1dddec..7ecdc55404 100644
--- a/lib/python/qmk/painter.py
+++ b/lib/python/qmk/painter.py
@@ -7,6 +7,20 @@ from PIL import Image, ImageOps
# The list of valid formats Quantum Painter supports
valid_formats = {
+ 'rgb888': {
+ 'image_format': 'IMAGE_FORMAT_RGB888',
+ 'bpp': 24,
+ 'has_palette': False,
+ 'num_colors': 16777216,
+ 'image_format_byte': 0x09, # see qp_internal_formats.h
+ },
+ 'rgb565': {
+ 'image_format': 'IMAGE_FORMAT_RGB565',
+ 'bpp': 16,
+ 'has_palette': False,
+ 'num_colors': 65536,
+ 'image_format_byte': 0x08, # see qp_internal_formats.h
+ },
'pal256': {
'image_format': 'IMAGE_FORMAT_PALETTE',
'bpp': 8,
@@ -144,19 +158,33 @@ def convert_requested_format(im, format):
ncolors = format["num_colors"]
image_format = format["image_format"]
- # Ensure we have a valid number of colors for the palette
- if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
- raise ValueError("Number of colors must be 2, 4, 16, or 256.")
-
# Work out where we're getting the bytes from
if image_format == 'IMAGE_FORMAT_GRAYSCALE':
+ # Ensure we have a valid number of colors for the palette
+ if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
+ raise ValueError("Number of colors must be 2, 4, 16, or 256.")
# If mono, convert input to grayscale, then to RGB, then grab the raw bytes corresponding to the intensity of the red channel
im = ImageOps.grayscale(im)
im = im.convert("RGB")
elif image_format == 'IMAGE_FORMAT_PALETTE':
+ # Ensure we have a valid number of colors for the palette
+ if ncolors <= 0 or ncolors > 256 or (ncolors & (ncolors - 1) != 0):
+ raise ValueError("Number of colors must be 2, 4, 16, or 256.")
# If color, convert input to RGB, palettize based on the supplied number of colors, then get the raw palette bytes
im = im.convert("RGB")
im = im.convert("P", palette=Image.ADAPTIVE, colors=ncolors)
+ elif image_format == 'IMAGE_FORMAT_RGB565':
+ # Ensure we have a valid number of colors for the palette
+ if ncolors != 65536:
+ raise ValueError("Number of colors must be 65536.")
+ # If color, convert input to RGB
+ im = im.convert("RGB")
+ elif image_format == 'IMAGE_FORMAT_RGB888':
+ # Ensure we have a valid number of colors for the palette
+ if ncolors != 1677216:
+ raise ValueError("Number of colors must be 16777216.")
+ # If color, convert input to RGB
+ im = im.convert("RGB")
return im
@@ -170,8 +198,12 @@ def convert_image_bytes(im, format):
image_format = format["image_format"]
shifter = int(math.log2(ncolors))
pixels_per_byte = int(8 / math.log2(ncolors))
+ bytes_per_pixel = math.ceil(math.log2(ncolors) / 8)
(width, height) = im.size
- expected_byte_count = ((width * height) + (pixels_per_byte - 1)) // pixels_per_byte
+ if (pixels_per_byte != 0):
+ expected_byte_count = ((width * height) + (pixels_per_byte - 1)) // pixels_per_byte
+ else:
+ expected_byte_count = width * height * bytes_per_pixel
if image_format == 'IMAGE_FORMAT_GRAYSCALE':
# Take the red channel
@@ -212,6 +244,44 @@ def convert_image_bytes(im, format):
byte = byte | ((image_bytes[byte_offset] & (ncolors - 1)) << int(n * shifter))
bytearray.append(byte)
+ if image_format == 'IMAGE_FORMAT_RGB565':
+ # Take the red, green, and blue channels
+ image_bytes_red = im.tobytes("raw", "R")
+ image_bytes_green = im.tobytes("raw", "G")
+ image_bytes_blue = im.tobytes("raw", "B")
+ image_pixels_len = len(image_bytes_red)
+
+ # No palette
+ palette = None
+
+ bytearray = []
+ for x in range(image_pixels_len):
+ # 5 bits of red, 3 MSb of green
+ byte = ((image_bytes_red[x] >> 3 & 0x1F) << 3) + (image_bytes_green[x] >> 5 & 0x07)
+ bytearray.append(byte)
+ # 3 LSb of green, 5 bits of blue
+ byte = ((image_bytes_green[x] >> 2 & 0x07) << 5) + (image_bytes_blue[x] >> 3 & 0x1F)
+ bytearray.append(byte)
+
+ if image_format == 'IMAGE_FORMAT_RGB888':
+ # Take the red, green, and blue channels
+ image_bytes_red = im.tobytes("raw", "R")
+ image_bytes_green = im.tobytes("raw", "G")
+ image_bytes_blue = im.tobytes("raw", "B")
+ image_pixels_len = len(image_bytes_red)
+
+ # No palette
+ palette = None
+
+ bytearray = []
+ for x in range(image_pixels_len):
+ byte = image_bytes_red[x]
+ bytearray.append(byte)
+ byte = image_bytes_green[x]
+ bytearray.append(byte)
+ byte = image_bytes_blue[x]
+ bytearray.append(byte)
+
if len(bytearray) != expected_byte_count:
raise Exception(f"Wrong byte count, was {len(bytearray)}, expected {expected_byte_count}")