[QP] Add RGB565 surface. Docs clarification, cleanup, tabsification, and reordering. (#18396)

master
Nick Brassel 2022-09-19 07:30:08 +10:00 committed by GitHub
parent e9bdc4eeb1
commit 1849897444
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 782 additions and 294 deletions

View File

@ -8,7 +8,7 @@ To enable overall Quantum Painter to be built into your firmware, add the follow
```make ```make
QUANTUM_PAINTER_ENABLE = yes QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ...... QUANTUM_PAINTER_DRIVERS += ......
``` ```
You will also likely need to select an appropriate driver in `rules.mk`, which is listed below. You will also likely need to select an appropriate driver in `rules.mk`, which is listed below.
@ -17,17 +17,18 @@ You will also likely need to select an appropriate driver in `rules.mk`, which i
The QMK CLI can be used to convert from normal images such as PNG files or animated GIFs, as well as fonts from TTF files. The QMK CLI can be used to convert from normal images such as PNG files or animated GIFs, as well as fonts from TTF files.
Hardware supported: Supported devices:
| Display Panel | Panel Type | Size | Comms Transport | Driver | | Display Panel | Panel Type | Size | Comms Transport | Driver |
|---------------|--------------------|------------------|-----------------|-----------------------------------------| |----------------|--------------------|------------------|-----------------|---------------------------------------------|
| GC9A01 | RGB LCD (circular) | 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = gc9a01_spi` | | GC9A01 | RGB LCD (circular) | 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += gc9a01_spi` |
| ILI9163 | RGB LCD | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = ili9163_spi` | | ILI9163 | RGB LCD | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9163_spi` |
| ILI9341 | RGB LCD | 240x320 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = ili9341_spi` | | ILI9341 | RGB LCD | 240x320 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9341_spi` |
| ILI9488 | RGB LCD | 320x480 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = ili9488_spi` | | ILI9488 | RGB LCD | 320x480 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ili9488_spi` |
| SSD1351 | RGB OLED | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = ssd1351_spi` | | SSD1351 | RGB OLED | 128x128 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += ssd1351_spi` |
| ST7789 | RGB LCD | 240x320, 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = st7789_spi` | | ST7735 | RGB LCD | 132x162, 80x160 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7735_spi` |
| ST7735 | RGB LCD | 132x162, 80x160 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = st7735_spi` | | ST7789 | RGB LCD | 240x320, 240x240 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS += st7789_spi` |
| RGB565 Surface | Virtual | User-defined | None | `QUANTUM_PAINTER_DRIVERS += rgb565_surface` |
## Quantum Painter Configuration :id=quantum-painter-config ## Quantum Painter Configuration :id=quantum-painter-config
@ -45,7 +46,9 @@ Drivers have their own set of configurable options, and are described in their r
## Quantum Painter CLI Commands :id=quantum-painter-cli ## Quantum Painter CLI Commands :id=quantum-painter-cli
### `qmk painter-convert-graphics` <!-- tabs:start -->
### ** `qmk painter-convert-graphics` **
This command converts images to a format usable by QMK, i.e. the QGF File Format. This command converts images to a format usable by QMK, i.e. the QGF File Format.
@ -93,7 +96,7 @@ Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/my_image.qgf.h...
Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/my_image.qgf.c... Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/my_image.qgf.c...
``` ```
### `qmk painter-make-font-image` ### ** `qmk painter-make-font-image` **
This command converts a TTF font to an intermediate format for editing, before converting to the QFF File Format. This command converts a TTF font to an intermediate format for editing, before converting to the QFF File Format.
@ -126,7 +129,7 @@ The `UNICODE_GLYPHS` argument allows for specifying extra unicode glyphs to gene
$ qmk painter-make-font-image --font NotoSans-ExtraCondensedBold.ttf --size 11 -o noto11.png --unicode-glyphs "ĄȽɂɻɣɈʣ" $ qmk painter-make-font-image --font NotoSans-ExtraCondensedBold.ttf --size 11 -o noto11.png --unicode-glyphs "ĄȽɂɻɣɈʣ"
``` ```
### `qmk painter-convert-font-image` ### ** `qmk painter-convert-font-image` **
This command converts an intermediate font image to the QFF File Format. This command converts an intermediate font image to the QFF File Format.
@ -170,6 +173,255 @@ Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.h...
Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.c... Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.c...
``` ```
<!-- tabs:end -->
## Quantum Painter Display Drivers :id=quantum-painter-drivers
<!-- tabs:start -->
### ** Common: Standard TFT (SPI + D/C + RST) **
Most TFT display panels use a 5-pin interface -- SPI SCK, SPI MOSI, SPI CS, D/C, and RST pins.
For these displays, QMK's `spi_master` must already be correctly configured for the platform you're building for.
The pin assignments for SPI CS, D/C, and RST are specified during device construction.
<!-- tabs:start -->
#### ** GC9A01 **
Enabling support for the GC9A01 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += gc9a01_spi
```
Creating a GC9A01 device in firmware can then be done with the following API:
```c
painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_gc9a01_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define GC9A01_NUM_DEVICES 3
```
#### ** ILI9163 **
Enabling support for the ILI9163 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += ili9163_spi
```
Creating a ILI9163 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9163_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9163_NUM_DEVICES 3
```
#### ** ILI9341 **
Enabling support for the ILI9341 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += ili9341_spi
```
Creating a ILI9341 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9341_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9341_NUM_DEVICES 3
```
#### ** ILI9488 **
Enabling support for the ILI9488 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += ili9488_spi
```
Creating a ILI9488 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9488_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9488_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9488_NUM_DEVICES 3
```
#### ** SSD1351 **
Enabling support for the SSD1351 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += ssd1351_spi
```
Creating a SSD1351 device in firmware can then be done with the following API:
```c
painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ssd1351_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define SSD1351_NUM_DEVICES 3
```
#### ** ST7735 **
Enabling support for the ST7735 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += st7735_spi
```
Creating a ST7735 device in firmware can then be done with the following API:
```c
painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_st7735_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ST7735_NUM_DEVICES 3
```
!> 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 **
Enabling support for the ST7789 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += st7789_spi
```
Creating a ST7789 device in firmware can then be done with the following API:
```c
painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_st7789_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ST7789_NUM_DEVICES 3
```
!> 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 -->
### ** Common: Surfaces **
Quantum Painter has surface drivers which are able to target a buffer in RAM. In general, surfaces keep track of the "dirty" region -- the area that has been drawn to since the last flush -- so that when transferring to the display they can transfer the minimal amount of data to achieve the end result.
!> These generally require significant amounts of RAM, so at large sizes and/or higher bit depths, they may not be usable on all MCUs.
<!-- tabs:start -->
#### ** RGB565 Surface **
Enabling support for RGB565 surfaces in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += rgb565_surface
```
Creating a RGB565 surface in firmware can then be done with the following API:
```c
painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer);
```
The `buffer` is a user-supplied area of memory, and is assumed to be of the size `sizeof(uint16_t) * panel_width * panel_height`.
The device handle returned from the `qp_rgb565_make_surface` function can be used to perform all other drawing operations.
Example:
```c
static painter_device_t my_surface;
static uint16_t my_framebuffer[320 * 240]; // Allocate a buffer for a 320x240 RGB565 display
void keyboard_post_init_kb(void) {
my_surface = qp_rgb565_make_surface(320, 240, my_framebuffer);
qp_init(my_surface, QP_ROTATION_0);
}
```
The maximum number of RGB565 surfaces can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 surfaces:
#define RGB565_SURFACE_NUM_DEVICES 3
```
To transfer the contents of the RGB565 surface to another display, the following API can be invoked:
```c
bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y);
```
The `surface` is the surface to copy out from. The `display` is the target display to draw into. `x` and `y` are the target location to draw the surface pixel data. Under normal circumstances, the location should be consistent, as the dirty region is calculated with respect to the `x` and `y` coordinates -- changing those will result in partial, overlapping draws.
?> Calling `qp_flush()` on the surface resets its dirty region. Copying the surface contents to the display also automatically resets the dirty region.
<!-- tabs:end -->
<!-- tabs:end -->
## Quantum Painter Drawing API :id=quantum-painter-api ## Quantum Painter Drawing API :id=quantum-painter-api
All APIs require a `painter_device_t` object as their first parameter -- this object comes from the specific device initialisation, and instructions on creating it can be found in each driver's respective section. All APIs require a `painter_device_t` object as their first parameter -- this object comes from the specific device initialisation, and instructions on creating it can be found in each driver's respective section.
@ -179,7 +431,9 @@ To use any of the APIs, you need to include `qp.h`:
#include <qp.h> #include <qp.h>
``` ```
### General Notes :id=quantum-painter-api-general <!-- tabs:start -->
### ** General Notes **
The coordinate system used in Quantum Painter generally accepts `left`, `top`, `right`, and `bottom` instead of x/y/width/height, and each coordinate is inclusive of where pixels should be drawn. This is required as some datatypes used by display panels have a maximum value of `255` -- for any value or geometry extent that matches `256`, this would be represented as a `0`, instead. The coordinate system used in Quantum Painter generally accepts `left`, `top`, `right`, and `bottom` instead of x/y/width/height, and each coordinate is inclusive of where pixels should be drawn. This is required as some datatypes used by display panels have a maximum value of `255` -- for any value or geometry extent that matches `256`, this would be represented as a `0`, instead.
@ -193,9 +447,11 @@ All color data matches the standard QMK HSV triplet definitions:
?> Colors used in Quantum Painter are not subject to the RGB lighting CIE curve, if it is enabled. ?> Colors used in Quantum Painter are not subject to the RGB lighting CIE curve, if it is enabled.
### Device Control :id=quantum-painter-api-device-control ### ** Device Control **
#### Display Initialisation :id=quantum-painter-api-init <!-- tabs:start -->
#### ** Display Initialisation **
```c ```c
bool qp_init(painter_device_t device, painter_rotation_t rotation); bool qp_init(painter_device_t device, painter_rotation_t rotation);
@ -211,7 +467,7 @@ void keyboard_post_init_kb(void) {
} }
``` ```
#### Display Power :id=quantum-painter-api-power #### ** Display Power **
```c ```c
bool qp_power(painter_device_t device, bool power_on); bool qp_power(painter_device_t device, bool power_on);
@ -242,7 +498,7 @@ void suspend_wakeup_init_user(void) {
} }
``` ```
#### Display Clear :id=quantum-painter-api-clear #### ** Display Clear **
```c ```c
bool qp_clear(painter_device_t device); bool qp_clear(painter_device_t device);
@ -250,7 +506,7 @@ bool qp_clear(painter_device_t device);
The `qp_clear` function clears the display's screen. The `qp_clear` function clears the display's screen.
#### Display Flush :id=quantum-painter-api-flush #### ** Display Flush **
```c ```c
bool qp_flush(painter_device_t device); bool qp_flush(painter_device_t device);
@ -272,9 +528,13 @@ void housekeeping_task_user(void) {
} }
``` ```
### Drawing Primitives :id=quantum-painter-api-primitives <!-- tabs:end -->
#### Set Pixel :id=quantum-painter-api-setpixel ### ** Drawing Primitives **
<!-- tabs:start -->
#### ** Set Pixel **
```c ```c
bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val); bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val);
@ -298,7 +558,7 @@ void housekeeping_task_user(void) {
} }
``` ```
#### Draw Line :id=quantum-painter-api-line #### ** Draw Line **
```c ```c
bool qp_line(painter_device_t device, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t hue, uint8_t sat, uint8_t val); bool qp_line(painter_device_t device, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint8_t hue, uint8_t sat, uint8_t val);
@ -320,7 +580,7 @@ void housekeeping_task_user(void) {
} }
``` ```
#### Draw Rect :id=quantum-painter-api-rect #### ** Draw Rect **
```c ```c
bool qp_rect(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint8_t hue, uint8_t sat, uint8_t val, bool filled); bool qp_rect(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
@ -342,7 +602,7 @@ void housekeeping_task_user(void) {
} }
``` ```
#### Draw Circle :id=quantum-painter-api-circle #### ** Draw Circle **
```c ```c
bool qp_circle(painter_device_t device, uint16_t x, uint16_t y, uint16_t radius, uint8_t hue, uint8_t sat, uint8_t val, bool filled); bool qp_circle(painter_device_t device, uint16_t x, uint16_t y, uint16_t radius, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
@ -364,7 +624,7 @@ void housekeeping_task_user(void) {
} }
``` ```
#### Draw Ellipse :id=quantum-painter-api-ellipse #### ** Draw Ellipse **
```c ```c
bool qp_ellipse(painter_device_t device, uint16_t x, uint16_t y, uint16_t sizex, uint16_t sizey, uint8_t hue, uint8_t sat, uint8_t val, bool filled); bool qp_ellipse(painter_device_t device, uint16_t x, uint16_t y, uint16_t sizex, uint16_t sizey, uint8_t hue, uint8_t sat, uint8_t val, bool filled);
@ -386,9 +646,24 @@ void housekeeping_task_user(void) {
} }
``` ```
### Image Functions :id=quantum-painter-api-images <!-- tabs:end -->
#### Load Image :id=quantum-painter-api-load-image ### ** Image Functions **
Making an image available for use requires compiling it into your firmware. To do so, assuming you've created `my_image.qgf.c` and `my_image.qgf.h` as per the CLI examples above, you'd add the following to your `rules.mk`:
```make
SRC += my_image.qgf.c
```
...and in your `keymap.c`, you'd add to the top of the file:
```c
#include "my_image.qgf.h"
```
<!-- tabs:start -->
#### ** Load Image **
```c ```c
painter_image_handle_t qp_load_image_mem(const void *buffer); painter_image_handle_t qp_load_image_mem(const void *buffer);
@ -396,7 +671,7 @@ painter_image_handle_t qp_load_image_mem(const void *buffer);
The `qp_load_image_mem` function loads a QGF image from memory or flash. The `qp_load_image_mem` function loads a QGF image from memory or flash.
`qp_load_image_mem` returns a handle to the loaded image, which can then be used to draw to the screen using `qp_drawimage`, `qp_drawimage_recolor`, `qp_animate`, or `qp_animate_recolor`. If an image is no longer required, it can be unloaded by calling `qp_close_image` below. `qp_load_image_mem` returns a handle to the loaded image, which can then be used to draw to the screen using `qp_drawimage`, `qp_drawimage_recolor`, `qp_animate`, or `qp_animate_recolor`. If an image is no longer required, it can be unloaded by calling `qp_close_image` below.
See the [CLI Commands](quantum_painter.md?id=quantum-painter-cli) for instructions on how to convert images to [QGF](quantum_painter_qgf.md). See the [CLI Commands](quantum_painter.md?id=quantum-painter-cli) for instructions on how to convert images to [QGF](quantum_painter_qgf.md).
@ -410,7 +685,7 @@ Image information is available through accessing the handle:
| Height | `image->height` | | Height | `image->height` |
| Frame Count | `image->frame_count` | | Frame Count | `image->frame_count` |
#### Unload Image :id=quantum-painter-api-close-image #### ** Unload Image **
```c ```c
bool qp_close_image(painter_image_handle_t image); bool qp_close_image(painter_image_handle_t image);
@ -418,7 +693,7 @@ bool qp_close_image(painter_image_handle_t image);
The `qp_close_image` function releases resources related to the loading of the supplied image. The `qp_close_image` function releases resources related to the loading of the supplied image.
#### Draw image :id=quantum-painter-api-draw-image #### ** Draw image **
```c ```c
bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image); bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
@ -438,7 +713,7 @@ void keyboard_post_init_kb(void) {
} }
``` ```
#### Animate Image :id=quantum-painter-api-animate-image #### ** Animate Image **
```c ```c
deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image); deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
@ -463,7 +738,7 @@ void keyboard_post_init_kb(void) {
} }
``` ```
#### Stop Animation :id=quantum-painter-api-stop-animation #### ** Stop Animation **
```c ```c
void qp_stop_animation(deferred_token anim_token); void qp_stop_animation(deferred_token anim_token);
@ -478,9 +753,24 @@ void housekeeping_task_user(void) {
} }
``` ```
### Font Functions :id=quantum-painter-api-fonts <!-- tabs:end -->
#### Load Font :id=quantum-painter-api-load-font ### ** Font Functions **
Making a font available for use requires compiling it into your firmware. To do so, assuming you've created `my_font.qff.c` and `my_font.qff.h` as per the CLI examples above, you'd add the following to your `rules.mk`:
```make
SRC += noto11.qff.c
```
...and in your `keymap.c`, you'd add to the top of the file:
```c
#include "noto11.qff.h"
```
<!-- tabs: start -->
#### ** Load Font **
```c ```c
painter_font_handle_t qp_load_font_mem(const void *buffer); painter_font_handle_t qp_load_font_mem(const void *buffer);
@ -488,7 +778,7 @@ painter_font_handle_t qp_load_font_mem(const void *buffer);
The `qp_load_font_mem` function loads a QFF font from memory or flash. The `qp_load_font_mem` function loads a QFF font from memory or flash.
`qp_load_font_mem` returns a handle to the loaded font, which can then be measured using `qp_textwidth`, or drawn to the screen using `qp_drawtext`, or `qp_drawtext_recolor`. If a font is no longer required, it can be unloaded by calling `qp_close_font` below. `qp_load_font_mem` returns a handle to the loaded font, which can then be measured using `qp_textwidth`, or drawn to the screen using `qp_drawtext`, or `qp_drawtext_recolor`. If a font is no longer required, it can be unloaded by calling `qp_close_font` below.
See the [CLI Commands](quantum_painter.md?id=quantum-painter-cli) for instructions on how to convert TTF fonts to [QFF](quantum_painter_qff.md). See the [CLI Commands](quantum_painter.md?id=quantum-painter-cli) for instructions on how to convert TTF fonts to [QFF](quantum_painter_qff.md).
@ -500,7 +790,7 @@ Font information is available through accessing the handle:
|-------------|----------------------| |-------------|----------------------|
| Line Height | `image->line_height` | | Line Height | `image->line_height` |
#### Unload Font :id=quantum-painter-api-close-font #### ** Unload Font **
```c ```c
bool qp_close_font(painter_font_handle_t font); bool qp_close_font(painter_font_handle_t font);
@ -508,7 +798,7 @@ bool qp_close_font(painter_font_handle_t font);
The `qp_close_font` function releases resources related to the loading of the supplied font. The `qp_close_font` function releases resources related to the loading of the supplied font.
#### Measure Text :id=quantum-painter-api-textwidth #### ** Measure Text **
```c ```c
int16_t qp_textwidth(painter_font_handle_t font, const char *str); int16_t qp_textwidth(painter_font_handle_t font, const char *str);
@ -516,7 +806,7 @@ int16_t qp_textwidth(painter_font_handle_t font, const char *str);
The `qp_textwidth` function allows measurement of how many pixels wide the supplied string would result in, for the given font. The `qp_textwidth` function allows measurement of how many pixels wide the supplied string would result in, for the given font.
#### Draw Text :id=quantum-painter-api-drawtext #### ** Draw Text **
```c ```c
int16_t qp_drawtext(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str); int16_t qp_drawtext(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str);
@ -529,7 +819,7 @@ The `qp_drawtext` and `qp_drawtext_recolor` functions draw the supplied string t
// Draw a text message on the bottom-right of the 240x320 display on initialisation // Draw a text message on the bottom-right of the 240x320 display on initialisation
static painter_font_handle_t my_font; static painter_font_handle_t my_font;
void keyboard_post_init_kb(void) { void keyboard_post_init_kb(void) {
my_font = qp_load_font_mem(font_opensans); my_font = qp_load_font_mem(font_noto11);
if (my_font != NULL) { if (my_font != NULL) {
static const char *text = "Hello from QMK!"; static const char *text = "Hello from QMK!";
int16_t width = qp_textwidth(my_font, text); int16_t width = qp_textwidth(my_font, text);
@ -538,9 +828,13 @@ void keyboard_post_init_kb(void) {
} }
``` ```
### Advanced Functions :id=quantum-painter-api-advanced <!-- tabs:end -->
#### Get Geometry :id=quantum-painter-api-get-geometry ### ** Advanced Functions **
<!-- tabs:start -->
#### ** Get Geometry **
```c ```c
void qp_get_geometry(painter_device_t device, uint16_t *width, uint16_t *height, painter_rotation_t *rotation, uint16_t *offset_x, uint16_t *offset_y); void qp_get_geometry(painter_device_t device, uint16_t *width, uint16_t *height, painter_rotation_t *rotation, uint16_t *offset_x, uint16_t *offset_y);
@ -548,7 +842,7 @@ void qp_get_geometry(painter_device_t device, uint16_t *width, uint16_t *height,
The `qp_get_geometry` function allows external code to retrieve the current width, height, rotation, and drawing offsets. The `qp_get_geometry` function allows external code to retrieve the current width, height, rotation, and drawing offsets.
#### Set Viewport Offsets :id=quantum-painter-api-set-viewport #### ** Set Viewport Offsets **
```c ```c
void qp_set_viewport_offsets(painter_device_t device, uint16_t offset_x, uint16_t offset_y); void qp_set_viewport_offsets(painter_device_t device, uint16_t offset_x, uint16_t offset_y);
@ -556,7 +850,7 @@ void qp_set_viewport_offsets(painter_device_t device, uint16_t offset_x, uint16_
The `qp_set_viewport_offsets` function can be used to offset all subsequent drawing operations. For example, if a display controller is internally 240x320, but the display panel is 240x240 and has a Y offset of 80 pixels, you could invoke `qp_set_viewport_offsets(display, 0, 80);` and the drawing positioning would be corrected. The `qp_set_viewport_offsets` function can be used to offset all subsequent drawing operations. For example, if a display controller is internally 240x320, but the display panel is 240x240 and has a Y offset of 80 pixels, you could invoke `qp_set_viewport_offsets(display, 0, 80);` and the drawing positioning would be corrected.
#### Set Viewport :id=quantum-painter-api-viewport #### ** Set Viewport **
```c ```c
bool qp_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom); bool qp_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom);
@ -564,7 +858,7 @@ bool qp_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t
The `qp_viewport` function controls where raw pixel data is written to. The `qp_viewport` function controls where raw pixel data is written to.
#### Stream Pixel Data :id=quantum-painter-api-pixdata #### ** Stream Pixel Data **
```c ```c
bool qp_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count); bool qp_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count);
@ -574,184 +868,6 @@ The `qp_pixdata` function allows raw pixel data to be streamed to the display. I
!> Under normal circumstances, users will not need to manually call either `qp_viewport` or `qp_pixdata`. These allow for writing of raw pixel information, in the display panel's native format, to the area defined by the viewport. !> Under normal circumstances, users will not need to manually call either `qp_viewport` or `qp_pixdata`. These allow for writing of raw pixel information, in the display panel's native format, to the area defined by the viewport.
## Quantum Painter Display Drivers :id=quantum-painter-drivers <!-- tabs:end -->
### Common: Standard TFT (SPI + D/C + RST) <!-- tabs:end -->
Most TFT display panels use a 5-pin interface -- SPI SCK, SPI MOSI, SPI CS, D/C, and RST pins.
For these displays, QMK's `spi_master` must already be correctly configured for the platform you're building for.
The pin assignments for SPI CS, D/C, and RST are specified during device construction.
### GC9A01 :id=qp-driver-gc9a01
Enabling support for the GC9A01 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = gc9a01_spi
```
Creating a GC9A01 device in firmware can then be done with the following API:
```c
painter_device_t qp_gc9a01_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_gc9a01_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define GC9A01_NUM_DEVICES 3
```
### ILI9163 :id=qp-driver-ili9163
Enabling support for the ILI9163 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ili9163_spi
```
Creating a ILI9163 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9163_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9163_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9163_NUM_DEVICES 3
```
### ILI9341 :id=qp-driver-ili9341
Enabling support for the ILI9341 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ili9341_spi
```
Creating a ILI9341 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9341_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9341_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9341_NUM_DEVICES 3
```
### ILI9488 :id=qp-driver-ili9488
Enabling support for the ILI9488 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ili9488_spi
```
Creating a ILI9488 device in firmware can then be done with the following API:
```c
painter_device_t qp_ili9488_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ili9488_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ILI9488_NUM_DEVICES 3
```
### SSD1351 :id=qp-driver-ssd1351
Enabling support for the SSD1351 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ssd1351_spi
```
Creating a SSD1351 device in firmware can then be done with the following API:
```c
painter_device_t qp_ssd1351_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_ssd1351_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define SSD1351_NUM_DEVICES 3
```
### ST7789 :id=qp-driver-st7789
Enabling support for the ST7789 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = st7789_spi
```
Creating a ST7789 device in firmware can then be done with the following API:
```c
painter_device_t qp_st7789_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_st7789_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ST7789_NUM_DEVICES 3
```
!> 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.
### ST7735 :id=qp-driver-st7735
Enabling support for the ST7735 in Quantum Painter is done by adding the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = st7735_spi
```
Creating a ST7735 device in firmware can then be done with the following API:
```c
painter_device_t qp_st7735_make_spi_device(uint16_t panel_width, uint16_t panel_height, pin_t chip_select_pin, pin_t dc_pin, pin_t reset_pin, uint16_t spi_divisor, int spi_mode);
```
The device handle returned from the `qp_st7735_make_spi_device` function can be used to perform all other drawing operations.
The maximum number of displays can be configured by changing the following in your `config.h` (default is 1):
```c
// 3 displays:
#define ST7735_NUM_DEVICES 3
```
!> 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.

View File

@ -0,0 +1,277 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "color.h"
#include "qp_rgb565_surface.h"
#include "qp_draw.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Common
// Device definition
typedef struct rgb565_surface_painter_device_t {
struct painter_driver_t base; // must be first, so it can be cast to/from the painter_device_t* type
// The target buffer
uint16_t *buffer;
// Manually manage the viewport for streaming pixel data to the display
uint16_t viewport_l;
uint16_t viewport_t;
uint16_t viewport_r;
uint16_t viewport_b;
// Current write location to the display when streaming pixel data
uint16_t pixdata_x;
uint16_t pixdata_y;
// Maintain a dirty region so we can stream only what we need
bool is_dirty;
uint16_t dirty_l;
uint16_t dirty_t;
uint16_t dirty_r;
uint16_t dirty_b;
} rgb565_surface_painter_device_t;
// Driver storage
rgb565_surface_painter_device_t surface_drivers[RGB565_SURFACE_NUM_DEVICES] = {0};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
static inline void increment_pixdata_location(rgb565_surface_painter_device_t *surface) {
// Increment the X-position
surface->pixdata_x++;
// If the x-coord has gone past the right-side edge, loop it back around and increment the y-coord
if (surface->pixdata_x > surface->viewport_r) {
surface->pixdata_x = surface->viewport_l;
surface->pixdata_y++;
}
// If the y-coord has gone past the bottom, loop it back to the top
if (surface->pixdata_y > surface->viewport_b) {
surface->pixdata_y = surface->viewport_t;
}
}
static inline void setpixel(rgb565_surface_painter_device_t *surface, uint16_t x, uint16_t y, uint16_t rgb565) {
// Skip messing with the dirty info if the original value already matches
if (surface->buffer[y * surface->base.panel_width + x] != rgb565) {
// Maintain dirty region
if (surface->dirty_l > x) {
surface->dirty_l = x;
}
if (surface->dirty_r < x) {
surface->dirty_r = x;
}
if (surface->dirty_t > y) {
surface->dirty_t = y;
}
if (surface->dirty_b < y) {
surface->dirty_b = y;
}
// Always dirty after a setpixel
surface->is_dirty = true;
// Update the pixel data in the buffer
surface->buffer[y * surface->base.panel_width + x] = rgb565;
}
}
static inline void append_pixel(rgb565_surface_painter_device_t *surface, uint16_t rgb565) {
setpixel(surface, surface->pixdata_x, surface->pixdata_y, rgb565);
increment_pixdata_location(surface);
}
static inline void stream_pixdata(rgb565_surface_painter_device_t *surface, const uint16_t *data, uint32_t native_pixel_count) {
for (uint32_t pixel_counter = 0; pixel_counter < native_pixel_count; ++pixel_counter) {
append_pixel(surface, data[pixel_counter]);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Driver vtable
static bool qp_rgb565_surface_init(painter_device_t device, painter_rotation_t rotation) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver;
memset(surface->buffer, 0, driver->panel_width * driver->panel_height * driver->native_bits_per_pixel / 8);
return true;
}
static bool qp_rgb565_surface_power(painter_device_t device, bool power_on) {
// No-op.
return true;
}
static bool qp_rgb565_surface_clear(painter_device_t device) {
struct painter_driver_t *driver = (struct painter_driver_t *)device;
driver->driver_vtable->init(device, driver->rotation); // Re-init the surface
return true;
}
static bool qp_rgb565_surface_flush(painter_device_t device) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver;
surface->dirty_l = surface->dirty_t = UINT16_MAX;
surface->dirty_r = surface->dirty_b = 0;
surface->is_dirty = false;
return true;
}
static bool qp_rgb565_surface_viewport(painter_device_t device, uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver;
// Set the viewport locations
surface->viewport_l = left;
surface->viewport_t = top;
surface->viewport_r = right;
surface->viewport_b = bottom;
// Reset the write location to the top left
surface->pixdata_x = left;
surface->pixdata_y = top;
return true;
}
// Stream pixel data to the current write position in GRAM
static bool qp_rgb565_surface_pixdata(painter_device_t device, const void *pixel_data, uint32_t native_pixel_count) {
struct painter_driver_t * driver = (struct painter_driver_t *)device;
rgb565_surface_painter_device_t *surface = (rgb565_surface_painter_device_t *)driver;
stream_pixdata(surface, (const uint16_t *)pixel_data, native_pixel_count);
return true;
}
// Pixel colour conversion
static bool qp_rgb565_surface_palette_convert_rgb565_swapped(painter_device_t device, int16_t palette_size, qp_pixel_t *palette) {
for (int16_t i = 0; i < palette_size; ++i) {
RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v});
uint16_t rgb565 = (((uint16_t)rgb.r) >> 3) << 11 | (((uint16_t)rgb.g) >> 2) << 5 | (((uint16_t)rgb.b) >> 3);
palette[i].rgb565 = __builtin_bswap16(rgb565);
}
return true;
}
// Append pixels to the target location, keyed by the pixel index
static bool qp_rgb565_surface_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) {
uint16_t *buf = (uint16_t *)target_buffer;
for (uint32_t i = 0; i < pixel_count; ++i) {
buf[pixel_offset + i] = palette[palette_indices[i]].rgb565;
}
return true;
}
const struct painter_driver_vtable_t rgb565_surface_driver_vtable = {
.init = qp_rgb565_surface_init,
.power = qp_rgb565_surface_power,
.clear = qp_rgb565_surface_clear,
.flush = qp_rgb565_surface_flush,
.pixdata = qp_rgb565_surface_pixdata,
.viewport = qp_rgb565_surface_viewport,
.palette_convert = qp_rgb565_surface_palette_convert_rgb565_swapped,
.append_pixels = qp_rgb565_surface_append_pixels_rgb565,
};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Comms vtable
static bool qp_rgb565_surface_comms_init(painter_device_t device) {
// No-op.
return true;
}
static bool qp_rgb565_surface_comms_start(painter_device_t device) {
// No-op.
return true;
}
static void qp_rgb565_surface_comms_stop(painter_device_t device) {
// No-op.
}
uint32_t qp_rgb565_surface_comms_send(painter_device_t device, const void *data, uint32_t byte_count) {
// No-op.
return byte_count;
}
struct painter_comms_vtable_t rgb565_surface_driver_comms_vtable = {
// These are all effective no-op's because they're not actually needed.
.comms_init = qp_rgb565_surface_comms_init,
.comms_start = qp_rgb565_surface_comms_start,
.comms_stop = qp_rgb565_surface_comms_stop,
.comms_send = qp_rgb565_surface_comms_send};
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Factory function for creating a handle to an rgb565 surface
painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer) {
for (uint32_t i = 0; i < RGB565_SURFACE_NUM_DEVICES; ++i) {
rgb565_surface_painter_device_t *driver = &surface_drivers[i];
if (!driver->base.driver_vtable) {
driver->base.driver_vtable = &rgb565_surface_driver_vtable;
driver->base.comms_vtable = &rgb565_surface_driver_comms_vtable;
driver->base.native_bits_per_pixel = 16; // RGB565
driver->base.panel_width = panel_width;
driver->base.panel_height = panel_height;
driver->base.rotation = QP_ROTATION_0;
driver->base.offset_x = 0;
driver->base.offset_y = 0;
driver->buffer = (uint16_t *)buffer;
return (painter_device_t)driver;
}
}
return NULL;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Drawing routine to copy out the dirty region and send it to another device
bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y) {
struct painter_driver_t * surface_driver = (struct painter_driver_t *)surface;
rgb565_surface_painter_device_t *surface_handle = (rgb565_surface_painter_device_t *)surface_driver;
// If we're not dirty... we're done.
if (!surface_handle->is_dirty) {
return true;
}
// Set the target drawing area
bool ok = qp_viewport(display, x + surface_handle->dirty_l, y + surface_handle->dirty_t, x + surface_handle->dirty_r, y + surface_handle->dirty_b);
if (!ok) {
return false;
}
// Housekeeping of the amount of pixels to transfer
uint32_t total_pixel_count = QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE / sizeof(uint16_t);
uint32_t pixel_counter = 0;
uint16_t *target_buffer = (uint16_t *)qp_internal_global_pixdata_buffer;
// Fill the global pixdata area so that we can start transferring to the panel
for (uint16_t y = surface_handle->dirty_t; y <= surface_handle->dirty_b; ++y) {
for (uint16_t x = surface_handle->dirty_l; x <= surface_handle->dirty_r; ++x) {
// Update the target buffer
target_buffer[pixel_counter++] = surface_handle->buffer[y * surface_handle->base.panel_width + x];
// If we've accumulated enough data, send it
if (pixel_counter == total_pixel_count) {
ok = qp_pixdata(display, qp_internal_global_pixdata_buffer, pixel_counter);
if (!ok) {
return false;
}
// Reset the counter
pixel_counter = 0;
}
}
}
// If there's any leftover data, send it
if (pixel_counter > 0) {
ok = qp_pixdata(display, qp_internal_global_pixdata_buffer, pixel_counter);
if (!ok) {
return false;
}
}
// Clear the dirty info for the surface
return qp_flush(surface);
}

View File

@ -0,0 +1,42 @@
// Copyright 2022 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later
#include "qp_internal.h"
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter RGB565 surface configurables (add to your keyboard's config.h)
#ifndef RGB565_SURFACE_NUM_DEVICES
/**
* @def This controls the maximum number of surface devices that Quantum Painter can use at any one time.
* Increasing this number allows for multiple framebuffers to be used. Each requires its own RAM allocation.
*/
# define RGB565_SURFACE_NUM_DEVICES 1
#endif
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Forward declarations
#ifdef QUANTUM_PAINTER_RGB565_SURFACE_ENABLE
/**
* Factory method for an RGB565 surface (aka framebuffer).
*
* @param panel_width[in] the width of the display panel
* @param panel_height[in] the height of the display panel
* @param buffer[in] pointer to a preallocated buffer of size `(sizeof(uint16_t) * panel_width * panel_height)`
* @return the device handle used with all drawing routines in Quantum Painter
*/
painter_device_t qp_rgb565_make_surface(uint16_t panel_width, uint16_t panel_height, void *buffer);
/**
* Helper method to draw the dirty contents of the framebuffer to the target device.
*
* After successful completion, the dirty area is reset.
*
* @param surface[in] the surface to copy from
* @param display[in] the display to copy into
* @param x[in] the x-location of the original position of the framebuffer
* @param y[in] the y-location of the original position of the framebuffer
* @return whether the draw operation completed successfully
*/
bool qp_rgb565_surface_draw(painter_device_t surface, painter_device_t display, uint16_t x, uint16_t y);
#endif // QUANTUM_PAINTER_RGB565_SURFACE_ENABLE

View File

@ -7,8 +7,6 @@
#include "qp_draw.h" #include "qp_draw.h"
#include "qp_tft_panel.h" #include "qp_tft_panel.h"
#define BYTE_SWAP(x) (((((uint16_t)(x)) >> 8) & 0x00FF) | ((((uint16_t)(x)) << 8) & 0xFF00))
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter API implementations // Quantum Painter API implementations
@ -94,7 +92,7 @@ bool qp_tft_panel_palette_convert_rgb565_swapped(painter_device_t device, int16_
for (int16_t i = 0; i < palette_size; ++i) { for (int16_t i = 0; i < palette_size; ++i) {
RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v}); RGB rgb = hsv_to_rgb_nocie((HSV){palette[i].hsv888.h, palette[i].hsv888.s, palette[i].hsv888.v});
uint16_t rgb565 = (((uint16_t)rgb.r) >> 3) << 11 | (((uint16_t)rgb.g) >> 2) << 5 | (((uint16_t)rgb.b) >> 3); uint16_t rgb565 = (((uint16_t)rgb.r) >> 3) << 11 | (((uint16_t)rgb.g) >> 2) << 5 | (((uint16_t)rgb.b) >> 3);
palette[i].rgb565 = BYTE_SWAP(rgb565); palette[i].rgb565 = __builtin_bswap16(rgb565);
} }
return true; return true;
} }

View File

@ -432,6 +432,10 @@ int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, pai
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter Drivers // Quantum Painter Drivers
#ifdef QUANTUM_PAINTER_RGB565_SURFACE_ENABLE
# include "qp_rgb565_surface.h"
#endif // QUANTUM_PAINTER_RGB565_SURFACE_ENABLE
#ifdef QUANTUM_PAINTER_ILI9163_ENABLE #ifdef QUANTUM_PAINTER_ILI9163_ENABLE
# include "qp_ili9163.h" # include "qp_ili9163.h"
#endif // QUANTUM_PAINTER_ILI9163_ENABLE #endif // QUANTUM_PAINTER_ILI9163_ENABLE

View File

@ -25,10 +25,10 @@ typedef struct qgf_image_handle_t {
static qgf_image_handle_t image_descriptors[QUANTUM_PAINTER_NUM_IMAGES] = {0}; static qgf_image_handle_t image_descriptors[QUANTUM_PAINTER_NUM_IMAGES] = {0};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_load_image_mem // Helper: load image from stream
painter_image_handle_t qp_load_image_mem(const void *buffer) { static painter_image_handle_t qp_load_image_internal(bool (*stream_factory)(qgf_image_handle_t *image, void *arg), void *arg) {
qp_dprintf("qp_load_image_mem: entry\n"); qp_dprintf("qp_load_image: entry\n");
qgf_image_handle_t *image = NULL; qgf_image_handle_t *image = NULL;
// Find a free slot // Find a free slot
@ -41,20 +41,18 @@ painter_image_handle_t qp_load_image_mem(const void *buffer) {
// Drop out if not found // Drop out if not found
if (!image) { if (!image) {
qp_dprintf("qp_load_image_mem: fail (no free slot)\n"); qp_dprintf("qp_load_image: fail (no free slot)\n");
return NULL; return NULL;
} }
// Assume we can read the graphics descriptor if (!stream_factory(image, arg)) {
image->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qgf_graphics_descriptor_v1_t)); qp_dprintf("qp_load_image: fail (could not create stream)\n");
return NULL;
// Update the length of the stream to match, and rewind to the start }
image->mem_stream.length = qgf_get_total_size(&image->stream);
image->mem_stream.position = 0;
// Now that we know the length, validate the input data // Now that we know the length, validate the input data
if (!qgf_validate_stream(&image->stream)) { if (!qgf_validate_stream(&image->stream)) {
qp_dprintf("qp_load_image_mem: fail (failed validation)\n"); qp_dprintf("qp_load_image: fail (failed validation)\n");
return NULL; return NULL;
} }
@ -63,10 +61,30 @@ painter_image_handle_t qp_load_image_mem(const void *buffer) {
// Validation success, we can return the handle // Validation success, we can return the handle
image->validate_ok = true; image->validate_ok = true;
qp_dprintf("qp_load_image_mem: ok\n"); qp_dprintf("qp_load_image: ok\n");
return (painter_image_handle_t)image; return (painter_image_handle_t)image;
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_load_image_mem
static inline bool image_mem_stream_factory(qgf_image_handle_t *image, void *arg) {
void *buffer = arg;
// Assume we can read the graphics descriptor
image->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qgf_graphics_descriptor_v1_t));
// Update the length of the stream to match, and rewind to the start
image->mem_stream.length = qgf_get_total_size(&image->stream);
image->mem_stream.position = 0;
return true;
}
painter_image_handle_t qp_load_image_mem(const void *buffer) {
return qp_load_image_internal(image_mem_stream_factory, (void *)buffer);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_close_image // Quantum Painter External API: qp_close_image
@ -79,6 +97,7 @@ bool qp_close_image(painter_image_handle_t image) {
// Free up this image for use elsewhere. // Free up this image for use elsewhere.
qgf_image->validate_ok = false; qgf_image->validate_ok = false;
qp_stream_close(&qgf_image->stream);
return true; return true;
} }

View File

@ -36,10 +36,10 @@ typedef struct qff_font_handle_t {
static qff_font_handle_t font_descriptors[QUANTUM_PAINTER_NUM_FONTS] = {0}; static qff_font_handle_t font_descriptors[QUANTUM_PAINTER_NUM_FONTS] = {0};
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_load_font_mem // Helper: load font from stream
painter_font_handle_t qp_load_font_mem(const void *buffer) { static painter_font_handle_t qp_load_font_internal(bool (*stream_factory)(qff_font_handle_t *font, void *arg), void *arg) {
qp_dprintf("qp_load_font_mem: entry\n"); qp_dprintf("qp_load_font: entry\n");
qff_font_handle_t *font = NULL; qff_font_handle_t *font = NULL;
// Find a free slot // Find a free slot
@ -52,20 +52,18 @@ painter_font_handle_t qp_load_font_mem(const void *buffer) {
// Drop out if not found // Drop out if not found
if (!font) { if (!font) {
qp_dprintf("qp_load_font_mem: fail (no free slot)\n"); qp_dprintf("qp_load_font: fail (no free slot)\n");
return NULL; return NULL;
} }
// Assume we can read the graphics descriptor if (!stream_factory(font, arg)) {
font->mem_stream = qp_make_memory_stream((void *)buffer, sizeof(qff_font_descriptor_v1_t)); qp_dprintf("qp_load_font: fail (could not create stream)\n");
return NULL;
// Update the length of the stream to match, and rewind to the start }
font->mem_stream.length = qff_get_total_size(&font->stream);
font->mem_stream.position = 0;
// Now that we know the length, validate the input data // Now that we know the length, validate the input data
if (!qff_validate_stream(&font->stream)) { if (!qff_validate_stream(&font->stream)) {
qp_dprintf("qp_load_font_mem: fail (failed validation)\n"); qp_dprintf("qp_load_font: fail (failed validation)\n");
return NULL; return NULL;
} }
@ -76,12 +74,12 @@ painter_font_handle_t qp_load_font_mem(const void *buffer) {
void *ram_buffer = malloc(font->mem_stream.length); void *ram_buffer = malloc(font->mem_stream.length);
if (ram_buffer == NULL) { if (ram_buffer == NULL) {
qp_dprintf("qp_load_font_mem: could not allocate enough RAM for font, falling back to original\n"); qp_dprintf("qp_load_font: could not allocate enough RAM for font, falling back to original\n");
} else { } else {
do { do {
// Copy the data into RAM // Copy the data into RAM
if (qp_stream_read(ram_buffer, 1, font->mem_stream.length, &font->mem_stream) != font->mem_stream.length) { if (qp_stream_read(ram_buffer, 1, font->mem_stream.length, &font->mem_stream) != font->mem_stream.length) {
qp_dprintf("qp_load_font_mem: could not copy from flash to RAM, falling back to original\n"); qp_dprintf("qp_load_font: could not copy from flash to RAM, falling back to original\n");
break; break;
} }
@ -102,17 +100,37 @@ painter_font_handle_t qp_load_font_mem(const void *buffer) {
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); 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)) { if (!qp_internal_bpp_capable(font->bpp)) {
qp_dprintf("qp_load_font_mem: 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)\n", (int)font->bpp);
qp_close_font((painter_font_handle_t)font); qp_close_font((painter_font_handle_t)font);
return NULL; return NULL;
} }
// Validation success, we can return the handle // Validation success, we can return the handle
font->validate_ok = true; font->validate_ok = true;
qp_dprintf("qp_load_font_mem: ok\n"); qp_dprintf("qp_load_font: ok\n");
return (painter_font_handle_t)font; return (painter_font_handle_t)font;
} }
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_load_font_mem
static inline bool font_mem_stream_factory(qff_font_handle_t *font, void *arg) {
void *buffer = arg;
// Assume we can read the graphics descriptor
font->mem_stream = qp_make_memory_stream(buffer, sizeof(qff_font_descriptor_v1_t));
// Update the length of the stream to match, and rewind to the start
font->mem_stream.length = qff_get_total_size(&font->stream);
font->mem_stream.position = 0;
return true;
}
painter_font_handle_t qp_load_font_mem(const void *buffer) {
return qp_load_font_internal(font_mem_stream_factory, (void *)buffer);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Quantum Painter External API: qp_close_font // Quantum Painter External API: qp_close_font
@ -133,6 +151,7 @@ bool qp_close_font(painter_font_handle_t font) {
#endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM #endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
// Free up this font for use elsewhere. // Free up this font for use elsewhere.
qp_stream_close(&qff_font->stream);
qff_font->validate_ok = false; qff_font->validate_ok = false;
return true; return true;
} }

View File

@ -38,7 +38,7 @@ uint32_t qp_stream_write_impl(const void *input_buf, uint32_t member_size, uint3
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Memory streams // Memory streams
int16_t mem_get(qp_stream_t *stream) { static inline int16_t mem_get(qp_stream_t *stream) {
qp_memory_stream_t *s = (qp_memory_stream_t *)stream; qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
if (s->position >= s->length) { if (s->position >= s->length) {
s->is_eof = true; s->is_eof = true;
@ -47,7 +47,7 @@ int16_t mem_get(qp_stream_t *stream) {
return s->buffer[s->position++]; return s->buffer[s->position++];
} }
bool mem_put(qp_stream_t *stream, uint8_t c) { static inline bool mem_put(qp_stream_t *stream, uint8_t c) {
qp_memory_stream_t *s = (qp_memory_stream_t *)stream; qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
if (s->position >= s->length) { if (s->position >= s->length) {
s->is_eof = true; s->is_eof = true;
@ -57,7 +57,7 @@ bool mem_put(qp_stream_t *stream, uint8_t c) {
return true; return true;
} }
int mem_seek(qp_stream_t *stream, int32_t offset, int origin) { static inline int mem_seek(qp_stream_t *stream, int32_t offset, int origin) {
qp_memory_stream_t *s = (qp_memory_stream_t *)stream; qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
// Handle as per fseek // Handle as per fseek
@ -95,26 +95,23 @@ int mem_seek(qp_stream_t *stream, int32_t offset, int origin) {
return 0; return 0;
} }
int32_t mem_tell(qp_stream_t *stream) { static inline int32_t mem_tell(qp_stream_t *stream) {
qp_memory_stream_t *s = (qp_memory_stream_t *)stream; qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
return s->position; return s->position;
} }
bool mem_is_eof(qp_stream_t *stream) { static inline bool mem_is_eof(qp_stream_t *stream) {
qp_memory_stream_t *s = (qp_memory_stream_t *)stream; qp_memory_stream_t *s = (qp_memory_stream_t *)stream;
return s->is_eof; return s->is_eof;
} }
static inline void mem_close(qp_stream_t *stream) {
// No-op.
}
qp_memory_stream_t qp_make_memory_stream(void *buffer, int32_t length) { qp_memory_stream_t qp_make_memory_stream(void *buffer, int32_t length) {
qp_memory_stream_t stream = { qp_memory_stream_t stream = {
.base = .base = {.get = mem_get, .put = mem_put, .seek = mem_seek, .tell = mem_tell, .is_eof = mem_is_eof, .close = mem_close},
{
.get = mem_get,
.put = mem_put,
.seek = mem_seek,
.tell = mem_tell,
.is_eof = mem_is_eof,
},
.buffer = (uint8_t *)buffer, .buffer = (uint8_t *)buffer,
.length = length, .length = length,
.position = 0, .position = 0,
@ -127,43 +124,41 @@ qp_memory_stream_t qp_make_memory_stream(void *buffer, int32_t length) {
#ifdef QP_STREAM_HAS_FILE_IO #ifdef QP_STREAM_HAS_FILE_IO
int16_t file_get(qp_stream_t *stream) { static inline int16_t file_get(qp_stream_t *stream) {
qp_file_stream_t *s = (qp_file_stream_t *)stream; qp_file_stream_t *s = (qp_file_stream_t *)stream;
int c = fgetc(s->file); int c = fgetc(s->file);
if (c < 0 || feof(s->file)) return STREAM_EOF; if (c < 0 || feof(s->file)) return STREAM_EOF;
return (uint16_t)c; return (uint16_t)c;
} }
bool file_put(qp_stream_t *stream, uint8_t c) { static inline bool file_put(qp_stream_t *stream, uint8_t c) {
qp_file_stream_t *s = (qp_file_stream_t *)stream; qp_file_stream_t *s = (qp_file_stream_t *)stream;
return fputc(c, s->file) == c; return fputc(c, s->file) == c;
} }
int file_seek(qp_stream_t *stream, int32_t offset, int origin) { static inline int file_seek(qp_stream_t *stream, int32_t offset, int origin) {
qp_file_stream_t *s = (qp_file_stream_t *)stream; qp_file_stream_t *s = (qp_file_stream_t *)stream;
return fseek(s->file, offset, origin); return fseek(s->file, offset, origin);
} }
int32_t file_tell(qp_stream_t *stream) { static inline int32_t file_tell(qp_stream_t *stream) {
qp_file_stream_t *s = (qp_file_stream_t *)stream; qp_file_stream_t *s = (qp_file_stream_t *)stream;
return (int32_t)ftell(s->file); return (int32_t)ftell(s->file);
} }
bool file_is_eof(qp_stream_t *stream) { static inline bool file_is_eof(qp_stream_t *stream) {
qp_file_stream_t *s = (qp_file_stream_t *)stream; qp_file_stream_t *s = (qp_file_stream_t *)stream;
return (bool)feof(s->file); return (bool)feof(s->file);
} }
static inline void file_close(qp_stream_t *stream) {
qp_file_stream_t *s = (qp_file_stream_t *)stream;
fclose(s->file);
}
qp_file_stream_t qp_make_file_stream(FILE *f) { qp_file_stream_t qp_make_file_stream(FILE *f) {
qp_file_stream_t stream = { qp_file_stream_t stream = {
.base = .base = {.get = file_get, .put = file_put, .seek = file_seek, .tell = file_tell, .is_eof = file_is_eof, .close = file_close},
{
.get = file_get,
.put = file_put,
.seek = file_seek,
.tell = file_tell,
.is_eof = file_is_eof,
},
.file = f, .file = f,
}; };
return stream; return stream;

View File

@ -41,6 +41,8 @@ typedef struct qp_stream_t qp_stream_t;
uint32_t qp_stream_read_impl(void *output_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream); uint32_t qp_stream_read_impl(void *output_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream);
uint32_t qp_stream_write_impl(const void *input_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream); uint32_t qp_stream_write_impl(const void *input_buf, uint32_t member_size, uint32_t num_members, qp_stream_t *stream);
#define qp_stream_close(stream_ptr) (((qp_stream_t *)(stream_ptr))->close((qp_stream_t *)(stream_ptr)))
#define STREAM_EOF ((int16_t)(-1)) #define STREAM_EOF ((int16_t)(-1))
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -52,6 +54,7 @@ struct qp_stream_t {
int (*seek)(qp_stream_t *stream, int32_t offset, int origin); int (*seek)(qp_stream_t *stream, int32_t offset, int origin);
int32_t (*tell)(qp_stream_t *stream); int32_t (*tell)(qp_stream_t *stream);
bool (*is_eof)(qp_stream_t *stream); bool (*is_eof)(qp_stream_t *stream);
void (*close)(qp_stream_t *stream);
}; };
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -77,6 +80,6 @@ typedef struct qp_file_stream_t {
FILE * file; FILE * file;
} qp_file_stream_t; } qp_file_stream_t;
qp_file_stream_t qo_make_file_stream(FILE *f); qp_file_stream_t qp_make_file_stream(FILE *f);
#endif // QP_STREAM_HAS_FILE_IO #endif // QP_STREAM_HAS_FILE_IO

View File

@ -3,7 +3,15 @@ QUANTUM_PAINTER_DRIVERS ?=
QUANTUM_PAINTER_ANIMATIONS_ENABLE ?= yes QUANTUM_PAINTER_ANIMATIONS_ENABLE ?= yes
# The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS # The list of permissible drivers that can be listed in QUANTUM_PAINTER_DRIVERS
VALID_QUANTUM_PAINTER_DRIVERS := ili9163_spi ili9341_spi ili9488_spi st7789_spi st7735_spi gc9a01_spi ssd1351_spi VALID_QUANTUM_PAINTER_DRIVERS := \
rgb565_surface \
ili9163_spi \
ili9341_spi \
ili9488_spi \
st7735_spi \
st7789_spi \
gc9a01_spi \
ssd1351_spi
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
@ -40,6 +48,13 @@ define handle_quantum_painter_driver
ifeq ($$(filter $$(strip $$(CURRENT_PAINTER_DRIVER)),$$(VALID_QUANTUM_PAINTER_DRIVERS)),) ifeq ($$(filter $$(strip $$(CURRENT_PAINTER_DRIVER)),$$(VALID_QUANTUM_PAINTER_DRIVERS)),)
$$(error "$$(CURRENT_PAINTER_DRIVER)" is not a valid Quantum Painter driver) $$(error "$$(CURRENT_PAINTER_DRIVER)" is not a valid Quantum Painter driver)
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),rgb565_surface)
OPT_DEFS += -DQUANTUM_PAINTER_RGB565_SURFACE_ENABLE
COMMON_VPATH += \
$(DRIVER_PATH)/painter/generic
SRC += \
$(DRIVER_PATH)/painter/generic/qp_rgb565_surface.c \
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9163_spi) else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),ili9163_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
@ -73,17 +88,6 @@ define handle_quantum_painter_driver
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \ $(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/ili9xxx/qp_ili9488.c \ $(DRIVER_PATH)/painter/ili9xxx/qp_ili9488.c \
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7789_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
OPT_DEFS += -DQUANTUM_PAINTER_ST7789_ENABLE -DQUANTUM_PAINTER_ST7789_SPI_ENABLE
COMMON_VPATH += \
$(DRIVER_PATH)/painter/tft_panel \
$(DRIVER_PATH)/painter/st77xx
SRC += \
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/st77xx/qp_st7789.c
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7735_spi) else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7735_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
@ -95,6 +99,17 @@ define handle_quantum_painter_driver
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \ $(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/st77xx/qp_st7735.c $(DRIVER_PATH)/painter/st77xx/qp_st7735.c
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),st7789_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes
OPT_DEFS += -DQUANTUM_PAINTER_ST7789_ENABLE -DQUANTUM_PAINTER_ST7789_SPI_ENABLE
COMMON_VPATH += \
$(DRIVER_PATH)/painter/tft_panel \
$(DRIVER_PATH)/painter/st77xx
SRC += \
$(DRIVER_PATH)/painter/tft_panel/qp_tft_panel.c \
$(DRIVER_PATH)/painter/st77xx/qp_st7789.c
else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),gc9a01_spi) else ifeq ($$(strip $$(CURRENT_PAINTER_DRIVER)),gc9a01_spi)
QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI := yes
QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes QUANTUM_PAINTER_NEEDS_COMMS_SPI_DC_RESET := yes