forked from mirrors/qmk_userspace
		
	Add RGB565 and RGB888 color support to Quantum Painter (#19382)
This commit is contained in:
		
					parent
					
						
							
								5873fbe569
							
						
					
				
			
			
				commit
				
					
						45851a10f6
					
				
			
		
					 21 changed files with 226 additions and 43 deletions
				
			
		| 
						 | 
				
			
			@ -33,13 +33,14 @@ 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_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
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +79,9 @@ 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                                                                                   |
 | 
			
		||||
|-----------|-----------------------------------------------------------------------|
 | 
			
		||||
|-----------|-------------------------------------------------------------------------------------------|
 | 
			
		||||
| `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                                                                           |
 | 
			
		||||
| 
						 | 
				
			
			@ -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.
 | 
			
		||||
 | 
			
		||||
<!-- tabs:end -->
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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"]
 | 
			
		||||
 | 
			
		||||
    # 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.")
 | 
			
		||||
 | 
			
		||||
    # Work out where we're getting the bytes from
 | 
			
		||||
    if image_format == 'IMAGE_FORMAT_GRAYSCALE':
 | 
			
		||||
        # 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
 | 
			
		||||
    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}")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,6 +12,7 @@ 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_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");
 | 
			
		||||
| 
						 | 
				
			
			@ -23,7 +24,7 @@ bool qp_internal_bpp_capable(uint8_t bits_per_pixel) {
 | 
			
		|||
        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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,9 +167,11 @@ static bool qp_drawimage_prepare_frame_for_stream_read(painter_device_t device,
 | 
			
		|||
 | 
			
		||||
        needs_pixconvert = true;
 | 
			
		||||
    } else {
 | 
			
		||||
        if (info->bpp <= 8) {
 | 
			
		||||
            // Interpolate from fg/bg
 | 
			
		||||
            needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (needs_pixconvert) {
 | 
			
		||||
        // Convert the palette to native format
 | 
			
		||||
| 
						 | 
				
			
			@ -260,16 +262,29 @@ static bool qp_drawimage_recolor_impl(painter_device_t device, uint16_t x, uint1
 | 
			
		|||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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)};
 | 
			
		||||
 | 
			
		||||
        // 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);
 | 
			
		||||
 | 
			
		||||
        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");
 | 
			
		||||
    qp_comms_stop(device);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue