From 45851a10f66119ceff3baadde27f68e287eff481 Mon Sep 17 00:00:00 2001 From: David Hoelscher Date: Sat, 14 Jan 2023 04:24:54 -0600 Subject: [PATCH] Add RGB565 and RGB888 color support to Quantum Painter (#19382) --- docs/quantum_painter.md | 59 +++++++++------ drivers/painter/gc9a01/qp_gc9a01.c | 1 + drivers/painter/generic/qp_rgb565_surface.c | 7 ++ drivers/painter/ili9xxx/qp_ili9163.c | 1 + drivers/painter/ili9xxx/qp_ili9341.c | 1 + drivers/painter/ili9xxx/qp_ili9488.c | 1 + drivers/painter/ssd1351/qp_ssd1351.c | 1 + drivers/painter/st77xx/qp_st7735.c | 1 + drivers/painter/st77xx/qp_st7789.c | 1 + drivers/painter/tft_panel/qp_tft_panel.c | 5 ++ drivers/painter/tft_panel/qp_tft_panel.h | 2 + lib/python/qmk/painter.py | 80 +++++++++++++++++++-- quantum/painter/qgf.c | 4 +- quantum/painter/qp.c | 2 +- quantum/painter/qp.h | 8 +++ quantum/painter/qp_draw.h | 10 +++ quantum/painter/qp_draw_codec.c | 44 ++++++++++-- quantum/painter/qp_draw_image.c | 37 +++++++--- quantum/painter/qp_draw_text.c | 2 +- quantum/painter/qp_internal_driver.h | 2 + quantum/painter/qp_internal_formats.h | 2 + 21 files changed, 227 insertions(+), 44 deletions(-) diff --git a/docs/quantum_painter.md b/docs/quantum_painter.md index 781b467a452d..ac37053c79c0 100644 --- a/docs/quantum_painter.md +++ b/docs/quantum_painter.md @@ -32,15 +32,16 @@ Supported devices: ## Quantum Painter Configuration :id=quantum-painter-config -| Option | Default | Purpose | -|-----------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------| -| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. | -| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. | -| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. | -| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. | -| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. | -| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. | -| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. | +| Option | Default | Purpose | +|------------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------| +| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. | +| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. | +| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. | +| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. | +| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. | +| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. | +| `QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS` | `FALSE` | If native color range is supported. Requires significantly more RAM on the MCU. | +| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. | Drivers have their own set of configurable options, and are described in their respective sections. @@ -63,7 +64,7 @@ options: -d, --no-deltas Disables the use of delta frames when encoding animations. -r, --no-rle Disables the use of RLE when encoding images. -f FORMAT, --format FORMAT - Output format, valid types: pal256, pal16, pal4, pal2, mono256, mono16, mono4, mono2 + Output format, valid types: rgb888, rgb565, pal256, pal16, pal4, pal2, mono256, mono16, mono4, mono2 -o OUTPUT, --output OUTPUT Specify output directory. Defaults to same directory as input. -i INPUT, --input INPUT @@ -77,16 +78,18 @@ The `OUTPUT` argument needs to be a directory, and will default to the same dire The `FORMAT` argument can be any of the following: -| Format | Meaning | -|-----------|-----------------------------------------------------------------------| -| `pal256` | 256-color palette (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) | -| `pal16` | 16-color palette | -| `pal4` | 4-color palette | -| `pal2` | 2-color palette | -| `mono256` | 256-shade grayscale (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) | -| `mono16` | 16-shade grayscale | -| `mono4` | 4-shade grayscale | -| `mono2` | 2-shade grayscale | +| Format | Meaning | +|-----------|-------------------------------------------------------------------------------------------| +| `rgb888` | 16,777,216 colors in 8-8-8 RGB format (requires `QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS`) | +| `rgb565` | 65,536 colors in 5-6-5 RGB format (requires `QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS`) | +| `pal256` | 256-color palette (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) | +| `pal16` | 16-color palette | +| `pal4` | 4-color palette | +| `pal2` | 2-color palette | +| `mono256` | 256-shade grayscale (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) | +| `mono16` | 16-shade grayscale | +| `mono4` | 4-shade grayscale | +| `mono2` | 2-shade grayscale | **Examples**: @@ -154,7 +157,7 @@ options: -w, --raw Writes out the QFF file as raw data instead of c/h combo. -r, --no-rle Disable the use of RLE to minimise converted image size. -f FORMAT, --format FORMAT - Output format, valid types: pal256, pal16, pal4, pal2, mono256, mono16, mono4, mono2 + Output format, valid types: rgb565, pal256, pal16, pal4, pal2, mono256, mono16, mono4, mono2 -u UNICODE_GLYPHS, --unicode-glyphs UNICODE_GLYPHS Also generate the specified unicode glyphs. -n, --no-ascii Disables output of the full ASCII character set (0x20..0x7E), exporting only the glyphs specified. @@ -215,6 +218,8 @@ The maximum number of displays can be configured by changing the following in yo #define GC9A01_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with GC9A01 + #### ** ILI9163 ** Enabling support for the ILI9163 in Quantum Painter is done by adding the following to `rules.mk`: @@ -239,6 +244,8 @@ The maximum number of displays can be configured by changing the following in yo #define ILI9163_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with ILI9163 + #### ** ILI9341 ** Enabling support for the ILI9341 in Quantum Painter is done by adding the following to `rules.mk`: @@ -263,6 +270,8 @@ The maximum number of displays can be configured by changing the following in yo #define ILI9341_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with ILI9341 + #### ** ILI9488 ** Enabling support for the ILI9488 in Quantum Painter is done by adding the following to `rules.mk`: @@ -287,6 +296,8 @@ The maximum number of displays can be configured by changing the following in yo #define ILI9488_NUM_DEVICES 3 ``` +Native color format rgb888 is compatible with ILI9488 + #### ** SSD1351 ** Enabling support for the SSD1351 in Quantum Painter is done by adding the following to `rules.mk`: @@ -311,6 +322,8 @@ The maximum number of displays can be configured by changing the following in yo #define SSD1351_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with SSD1351 + #### ** ST7735 ** Enabling support for the ST7735 in Quantum Painter is done by adding the following to `rules.mk`: @@ -335,6 +348,8 @@ The maximum number of displays can be configured by changing the following in yo #define ST7735_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with ST7735 + !> Some ST7735 devices are known to have different drawing offsets -- despite being a 132x162 pixel display controller internally, some display panels are only 80x160, or smaller. These may require an offset to be applied; see `qp_set_viewport_offsets` above for information on how to override the offsets if they aren't correctly rendered. #### ** ST7789 ** @@ -361,6 +376,8 @@ The maximum number of displays can be configured by changing the following in yo #define ST7789_NUM_DEVICES 3 ``` +Native color format rgb565 is compatible with ST7789 + !> Some ST7789 devices are known to have different drawing offsets -- despite being a 240x320 pixel display controller internally, some display panels are only 240x240, or smaller. These may require an offset to be applied; see `qp_set_viewport_offsets` above for information on how to override the offsets if they aren't correctly rendered. diff --git a/drivers/painter/gc9a01/qp_gc9a01.c b/drivers/painter/gc9a01/qp_gc9a01.c index 5bdab1e52077..5d079435c60f 100644 --- a/drivers/painter/gc9a01/qp_gc9a01.c +++ b/drivers/painter/gc9a01/qp_gc9a01.c @@ -104,6 +104,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t gc9a01_driver_vtable = { .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/generic/qp_rgb565_surface.c b/drivers/painter/generic/qp_rgb565_surface.c index c4de336535c6..474c86feec32 100644 --- a/drivers/painter/generic/qp_rgb565_surface.c +++ b/drivers/painter/generic/qp_rgb565_surface.c @@ -164,6 +164,12 @@ static bool qp_rgb565_surface_append_pixels_rgb565(painter_device_t device, uint return true; } +// Append data to the target location +static bool qp_rgb565_surface_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { + target_buffer[pixdata_offset] = pixdata_byte; + return true; +} + const struct painter_driver_vtable_t rgb565_surface_driver_vtable = { .init = qp_rgb565_surface_init, .power = qp_rgb565_surface_power, @@ -173,6 +179,7 @@ const struct painter_driver_vtable_t rgb565_surface_driver_vtable = { .viewport = qp_rgb565_surface_viewport, .palette_convert = qp_rgb565_surface_palette_convert_rgb565_swapped, .append_pixels = qp_rgb565_surface_append_pixels_rgb565, + .append_pixdata = qp_rgb565_surface_append_pixdata, }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/drivers/painter/ili9xxx/qp_ili9163.c b/drivers/painter/ili9xxx/qp_ili9163.c index 8bb01d12e0b1..af37686631fe 100644 --- a/drivers/painter/ili9xxx/qp_ili9163.c +++ b/drivers/painter/ili9xxx/qp_ili9163.c @@ -69,6 +69,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t ili9163_driver_vtable = .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/ili9xxx/qp_ili9341.c b/drivers/painter/ili9xxx/qp_ili9341.c index 2a9738831633..aca380991225 100644 --- a/drivers/painter/ili9xxx/qp_ili9341.c +++ b/drivers/painter/ili9xxx/qp_ili9341.c @@ -76,6 +76,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t ili9341_driver_vtable = .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/ili9xxx/qp_ili9488.c b/drivers/painter/ili9xxx/qp_ili9488.c index cda9a9be0026..e51f0e1d5133 100644 --- a/drivers/painter/ili9xxx/qp_ili9488.c +++ b/drivers/painter/ili9xxx/qp_ili9488.c @@ -69,6 +69,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t ili9488_driver_vtable = .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb888, .append_pixels = qp_tft_panel_append_pixels_rgb888, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/ssd1351/qp_ssd1351.c b/drivers/painter/ssd1351/qp_ssd1351.c index 85146490a03a..548785a1bd00 100644 --- a/drivers/painter/ssd1351/qp_ssd1351.c +++ b/drivers/painter/ssd1351/qp_ssd1351.c @@ -73,6 +73,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t ssd1351_driver_vtable = .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 1, .swap_window_coords = true, diff --git a/drivers/painter/st77xx/qp_st7735.c b/drivers/painter/st77xx/qp_st7735.c index 74145e0e4e9d..7ee5a6b562ca 100644 --- a/drivers/painter/st77xx/qp_st7735.c +++ b/drivers/painter/st77xx/qp_st7735.c @@ -93,6 +93,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t st7735_driver_vtable = { .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/st77xx/qp_st7789.c b/drivers/painter/st77xx/qp_st7789.c index 905f6bb27033..9f474369d6b0 100644 --- a/drivers/painter/st77xx/qp_st7789.c +++ b/drivers/painter/st77xx/qp_st7789.c @@ -92,6 +92,7 @@ const struct tft_panel_dc_reset_painter_driver_vtable_t st7789_driver_vtable = { .viewport = qp_tft_panel_viewport, .palette_convert = qp_tft_panel_palette_convert_rgb565_swapped, .append_pixels = qp_tft_panel_append_pixels_rgb565, + .append_pixdata = qp_tft_panel_append_pixdata, }, .num_window_bytes = 2, .swap_window_coords = false, diff --git a/drivers/painter/tft_panel/qp_tft_panel.c b/drivers/painter/tft_panel/qp_tft_panel.c index e7c744ab34e0..4a24cf9953d7 100644 --- a/drivers/painter/tft_panel/qp_tft_panel.c +++ b/drivers/painter/tft_panel/qp_tft_panel.c @@ -126,3 +126,8 @@ bool qp_tft_panel_append_pixels_rgb888(painter_device_t device, uint8_t *target_ } return true; } + +bool qp_tft_panel_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte) { + target_buffer[pixdata_offset] = pixdata_byte; + return true; +} diff --git a/drivers/painter/tft_panel/qp_tft_panel.h b/drivers/painter/tft_panel/qp_tft_panel.h index 3cb015891bdd..83b8dd54068c 100644 --- a/drivers/painter/tft_panel/qp_tft_panel.h +++ b/drivers/painter/tft_panel/qp_tft_panel.h @@ -59,3 +59,5 @@ bool qp_tft_panel_palette_convert_rgb888(painter_device_t device, int16_t palett bool qp_tft_panel_append_pixels_rgb565(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices); bool qp_tft_panel_append_pixels_rgb888(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices); + +bool qp_tft_panel_append_pixdata(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte); diff --git a/lib/python/qmk/painter.py b/lib/python/qmk/painter.py index d0cc1dddec55..7ecdc55404a6 100644 --- a/lib/python/qmk/painter.py +++ b/lib/python/qmk/painter.py @@ -7,6 +7,20 @@ # 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}") diff --git a/quantum/painter/qgf.c b/quantum/painter/qgf.c index 834837105bac..6a4af07001ec 100644 --- a/quantum/painter/qgf.c +++ b/quantum/painter/qgf.c @@ -38,11 +38,13 @@ bool qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette) [PALETTE_2BPP] = {.bpp = 2, .has_palette = true}, [PALETTE_4BPP] = {.bpp = 4, .has_palette = true}, [PALETTE_8BPP] = {.bpp = 8, .has_palette = true}, + [RGB565_16BPP] = {.bpp = 16, .has_palette = false}, + [RGB888_24BPP] = {.bpp = 24, .has_palette = false}, }; // clang-format on // Copy out the required info - if (format > PALETTE_8BPP) { + if (format > RGB888_24BPP) { qp_dprintf("Failed to parse frame_descriptor, invalid format 0x%02X\n", (int)format); return false; } diff --git a/quantum/painter/qp.c b/quantum/painter/qp.c index e292ff64973c..de36dee2c10c 100644 --- a/quantum/painter/qp.c +++ b/quantum/painter/qp.c @@ -12,7 +12,7 @@ // Internal driver validation static bool validate_driver_vtable(struct painter_driver_t *driver) { - return (driver->driver_vtable && driver->driver_vtable->init && driver->driver_vtable->power && driver->driver_vtable->clear && driver->driver_vtable->viewport && driver->driver_vtable->pixdata && driver->driver_vtable->palette_convert && driver->driver_vtable->append_pixels) ? true : false; + return (driver->driver_vtable && driver->driver_vtable->init && driver->driver_vtable->power && driver->driver_vtable->clear && driver->driver_vtable->viewport && driver->driver_vtable->pixdata && driver->driver_vtable->palette_convert && driver->driver_vtable->append_pixels && driver->driver_vtable->append_pixdata) ? true : false; } static bool validate_comms_vtable(struct painter_driver_t *driver) { diff --git a/quantum/painter/qp.h b/quantum/painter/qp.h index e5f595d71d5a..00f5d7931af8 100644 --- a/quantum/painter/qp.h +++ b/quantum/painter/qp.h @@ -64,6 +64,14 @@ # define QUANTUM_PAINTER_SUPPORTS_256_PALETTE FALSE #endif +#ifndef QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS +/** + * @def This controls whether the native color range is supported. This avoids the use of palettes but each image + * requires more storage space. + */ +# define QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS FALSE +#endif + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Quantum Painter types diff --git a/quantum/painter/qp_draw.h b/quantum/painter/qp_draw.h index 7094d80eaa68..84b1946ca7a3 100644 --- a/quantum/painter/qp_draw.h +++ b/quantum/painter/qp_draw.h @@ -30,9 +30,11 @@ bool qp_internal_fillrect_helper_impl(painter_device_t device, uint16_t l, uint1 // Convert from input pixel data + palette to equivalent pixels typedef int16_t (*qp_internal_byte_input_callback)(void* cb_arg); typedef bool (*qp_internal_pixel_output_callback)(qp_pixel_t* palette, uint8_t index, void* cb_arg); +typedef bool (*qp_internal_byte_output_callback)(uint8_t byte, void* cb_arg); bool qp_internal_decode_palette(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t* palette, qp_internal_pixel_output_callback output_callback, void* output_arg); bool qp_internal_decode_grayscale(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_internal_pixel_output_callback output_callback, void* output_arg); bool qp_internal_decode_recolor(painter_device_t device, uint32_t pixel_count, uint8_t bits_per_pixel, qp_internal_byte_input_callback input_callback, void* input_arg, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, qp_internal_pixel_output_callback output_callback, void* output_arg); +bool qp_internal_send_bytes(painter_device_t device, uint32_t byte_count, qp_internal_byte_input_callback input_callback, void* input_arg, qp_internal_byte_output_callback output_callback, void* output_arg); // Global variable used for interpolated pixel lookup table. #if QUANTUM_PAINTER_SUPPORTS_256_PALETTE @@ -82,4 +84,12 @@ struct qp_internal_pixel_output_state { bool qp_internal_pixel_appender(qp_pixel_t* palette, uint8_t index, void* cb_arg); +struct qp_internal_byte_output_state { + painter_device_t device; + uint32_t byte_write_pos; + uint32_t max_bytes; +}; + +bool qp_internal_byte_appender(uint8_t byteval, void* cb_arg); + qp_internal_byte_input_callback qp_internal_prepare_input_state(struct qp_internal_byte_input_state* input_state, painter_compression_t compression); diff --git a/quantum/painter/qp_draw_codec.c b/quantum/painter/qp_draw_codec.c index 438dce399460..5d1cf7c52e99 100644 --- a/quantum/painter/qp_draw_codec.c +++ b/quantum/painter/qp_draw_codec.c @@ -12,18 +12,19 @@ static const qp_pixel_t qp_pixel_white = {.hsv888 = {.h = 0, .s = 0, .v = 255}}; static const qp_pixel_t qp_pixel_black = {.hsv888 = {.h = 0, .s = 0, .v = 0}}; bool qp_internal_bpp_capable(uint8_t bits_per_pixel) { -#if !(QUANTUM_PAINTER_SUPPORTS_256_PALETTE) +#if !(QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS) +# if !(QUANTUM_PAINTER_SUPPORTS_256_PALETTE) if (bits_per_pixel > 4) { qp_dprintf("qp_internal_decode_palette: image bpp greater than 4\n"); return false; } -#endif +# endif if (bits_per_pixel > 8) { qp_dprintf("qp_internal_decode_palette: image bpp greater than 8\n"); return false; } - +#endif return true; } @@ -32,7 +33,7 @@ bool qp_internal_decode_palette(painter_device_t device, uint32_t pixel_count, u const uint8_t pixels_per_byte = 8 / bits_per_pixel; uint32_t remaining_pixels = pixel_count; // don't try to derive from byte_count, we may not use an entire byte while (remaining_pixels > 0) { - uint8_t byteval = input_callback(input_arg); + int16_t byteval = input_callback(input_arg); if (byteval < 0) { return false; } @@ -64,6 +65,21 @@ bool qp_internal_decode_recolor(painter_device_t device, uint32_t pixel_count, u return qp_internal_decode_palette(device, pixel_count, bits_per_pixel, input_callback, input_arg, qp_internal_global_pixel_lookup_table, output_callback, output_arg); } +bool qp_internal_send_bytes(painter_device_t device, uint32_t byte_count, qp_internal_byte_input_callback input_callback, void* input_arg, qp_internal_byte_output_callback output_callback, void* output_arg) { + uint32_t remaining_bytes = byte_count; + while (remaining_bytes > 0) { + int16_t byteval = input_callback(input_arg); + if (byteval < 0) { + return false; + } + if (!output_callback(byteval, output_arg)) { + return false; + } + remaining_bytes -= 1; + } + return true; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Progressive pull of bytes, push of pixels @@ -128,6 +144,26 @@ bool qp_internal_pixel_appender(qp_pixel_t* palette, uint8_t index, void* cb_arg return true; } +bool qp_internal_byte_appender(uint8_t byteval, void* cb_arg) { + struct qp_internal_byte_output_state* state = (struct qp_internal_byte_output_state*)cb_arg; + struct painter_driver_t* driver = (struct painter_driver_t*)state->device; + + if (!driver->driver_vtable->append_pixdata(state->device, qp_internal_global_pixdata_buffer, state->byte_write_pos++, byteval)) { + return false; + } + + // If we've hit the transmit limit, send out the entire buffer and reset the write position + if (state->byte_write_pos == state->max_bytes) { + struct painter_driver_t* driver = (struct painter_driver_t*)state->device; + if (!driver->driver_vtable->pixdata(state->device, qp_internal_global_pixdata_buffer, state->byte_write_pos * 8 / driver->native_bits_per_pixel)) { + return false; + } + state->byte_write_pos = 0; + } + + return true; +} + qp_internal_byte_input_callback qp_internal_prepare_input_state(struct qp_internal_byte_input_state* input_state, painter_compression_t compression) { switch (compression) { case IMAGE_UNCOMPRESSED: diff --git a/quantum/painter/qp_draw_image.c b/quantum/painter/qp_draw_image.c index 943cbfef5be8..fa806172420b 100644 --- a/quantum/painter/qp_draw_image.c +++ b/quantum/painter/qp_draw_image.c @@ -151,7 +151,7 @@ static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device, qp_internal_invalidate_palette(); if (!qp_internal_bpp_capable(info->bpp)) { - qp_dprintf("qp_drawimage_recolor: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE)\n", (int)info->bpp); + qp_dprintf("qp_drawimage_recolor: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE or QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS)\n", (int)info->bpp); qp_comms_stop(device); return false; } @@ -167,8 +167,10 @@ static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device, needs_pixconvert = true; } else { - // Interpolate from fg/bg - needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries); + if (info->bpp <= 8) { + // Interpolate from fg/bg + needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries); + } } if (needs_pixconvert) { @@ -260,15 +262,28 @@ static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint1 return false; } - // Set up the output state - struct qp_internal_pixel_output_state output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)}; - - // Decode the pixel data and stream to the display - bool ret = qp_internal_decode_palette(device, pixel_count, frame_info->bpp, input_callback, &input_state, qp_internal_global_pixel_lookup_table, qp_internal_pixel_appender, &output_state); + bool ret = false; + if (frame_info->bpp <= 8) { + // Set up the output state + struct qp_internal_pixel_output_state output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)}; - // Any leftovers need transmission as well. - if (ret && output_state.pixel_write_pos > 0) { - ret &= driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, output_state.pixel_write_pos); + // Decode the pixel data and stream to the display + ret = qp_internal_decode_palette(device, pixel_count, frame_info->bpp, input_callback, &input_state, qp_internal_global_pixel_lookup_table, qp_internal_pixel_appender, &output_state); + // Any leftovers need transmission as well. + if (ret && output_state.pixel_write_pos > 0) { + ret &= driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, output_state.pixel_write_pos); + } + } else { + // Set up the output state + struct qp_internal_byte_output_state output_state = {.device = device, .byte_write_pos = 0, .max_bytes = qp_internal_num_pixels_in_buffer(device) * driver->native_bits_per_pixel / 8}; + + // Stream the raw pixel data to the display + uint32_t byte_count = pixel_count * frame_info->bpp / 8; + ret = qp_internal_send_bytes(device, byte_count, input_callback, &input_state, qp_internal_byte_appender, &output_state); + // Any leftovers need transmission as well. + if (ret && output_state.byte_write_pos > 0) { + ret &= driver->driver_vtable->pixdata(device, qp_internal_global_pixdata_buffer, output_state.byte_write_pos * 8 / driver->native_bits_per_pixel); + } } qp_dprintf("qp_drawimage_recolor: %s\n", ret ? "ok" : "fail"); diff --git a/quantum/painter/qp_draw_text.c b/quantum/painter/qp_draw_text.c index 0f5473abd001..f9fb2bf08f6a 100644 --- a/quantum/painter/qp_draw_text.c +++ b/quantum/painter/qp_draw_text.c @@ -100,7 +100,7 @@ static painter_font_handle_t qp_load_font_internal(bool (*stream_factory)(qff_fo qff_read_font_descriptor(&font->stream, &font->base.line_height, &font->has_ascii_table, &font->num_unicode_glyphs, &font->bpp, &font->has_palette, &font->compression_scheme, NULL); if (!qp_internal_bpp_capable(font->bpp)) { - qp_dprintf("qp_load_font: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE)\n", (int)font->bpp); + qp_dprintf("qp_load_font: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE or QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS)\n", (int)font->bpp); qp_close_font((painter_font_handle_t)font); return NULL; } diff --git a/quantum/painter/qp_internal_driver.h b/quantum/painter/qp_internal_driver.h index 9e9d6bc8482e..82a0178a73b1 100644 --- a/quantum/painter/qp_internal_driver.h +++ b/quantum/painter/qp_internal_driver.h @@ -16,6 +16,7 @@ typedef bool (*painter_driver_viewport_func)(painter_device_t device, uint16_t l typedef bool (*painter_driver_pixdata_func)(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count); typedef bool (*painter_driver_convert_palette_func)(painter_device_t device, int16_t palette_size, qp_pixel_t *palette); typedef bool (*painter_driver_append_pixels)(painter_device_t device, uint8_t *target_buffer, qp_pixel_t *palette, uint32_t pixel_offset, uint32_t pixel_count, uint8_t *palette_indices); +typedef bool (*painter_driver_append_pixdata)(painter_device_t device, uint8_t *target_buffer, uint32_t pixdata_offset, uint8_t pixdata_byte); // Driver vtable definition struct painter_driver_vtable_t { @@ -27,6 +28,7 @@ struct painter_driver_vtable_t { painter_driver_pixdata_func pixdata; painter_driver_convert_palette_func palette_convert; painter_driver_append_pixels append_pixels; + painter_driver_append_pixdata append_pixdata; }; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/quantum/painter/qp_internal_formats.h b/quantum/painter/qp_internal_formats.h index a4a86f03454d..194f82b31a88 100644 --- a/quantum/painter/qp_internal_formats.h +++ b/quantum/painter/qp_internal_formats.h @@ -44,6 +44,8 @@ typedef enum qp_image_format_t { PALETTE_2BPP = 0x05, PALETTE_4BPP = 0x06, PALETTE_8BPP = 0x07, + RGB565_16BPP = 0x08, + RGB888_24BPP = 0x09, } qp_image_format_t; typedef enum painter_compression_t { IMAGE_UNCOMPRESSED, IMAGE_COMPRESSED_RLE } painter_compression_t;