From 45851a10f66119ceff3baadde27f68e287eff481 Mon Sep 17 00:00:00 2001 From: David Hoelscher Date: Sat, 14 Jan 2023 04:24:54 -0600 Subject: Add RGB565 and RGB888 color support to Quantum Painter (#19382) --- lib/python/qmk/painter.py | 80 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 5 deletions(-) (limited to 'lib/python/qmk') 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}") -- cgit v1.2.3