Quantum Painter is the standardised API for graphical displays. It currently includes support for basic drawing primitives, as well as custom images, animations, and fonts.
Due to the complexity, there is no support for Quantum Painter on AVR-based boards.
To enable overall Quantum Painter to be built into your firmware, add the following to `rules.mk`:
```make
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS = ......
```
You will also likely need to select an appropriate driver in `rules.mk`, which is listed below.
!> Quantum Painter is not currently integrated with system-level operations such as disabling displays after a configurable timeout, or when the keyboard goes into suspend. Users will need to handle this manually at the current time.
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:
| Display Panel | Panel Type | Size | Comms Transport | Driver |
| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. |
| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. |
| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. |
| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. |
| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. |
| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. |
| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. |
Drivers have their own set of configurable options, and are described in their respective sections.
## 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.
To use any of the APIs, you need to include `qp.h`:
```c
#include <qp.h>
```
### General Notes :id=quantum-painter-api-general
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.
?> Drawing a horizontal line 8 pixels long, starting from 4 pixels inside the left side of the display, will need `left=4`, `right=11`.
All color data matches the standard QMK HSV triplet definitions:
* Hue is of the range `0...255` and is internally mapped to 0...360 degrees.
* Saturation is of the range `0...255` and is internally mapped to 0...100% saturation.
* Value is of the range `0...255` and is internally mapped to 0...100% brightness.
?> 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
The `qp_init` function is used to initialise a display device after it has been created. This accepts a rotation parameter (`QP_ROTATION_0`, `QP_ROTATION_90`, `QP_ROTATION_180`, `QP_ROTATION_270`), which makes sure that the orientation of what's drawn on the display is correct.
```c
static painter_device_t display;
void keyboard_post_init_kb(void) {
display = qp_make_.......; // Create the display
qp_init(display, QP_ROTATION_0); // Initialise the display
The `qp_power` function instructs the display whether or not the display panel should be on or off.
!> If there is a separate backlight controlled through the normal QMK backlight API, this is not controlled by the `qp_power` function and needs to be manually handled elsewhere.
```c
static uint8_t last_backlight = 255;
void suspend_power_down_user(void) {
if (last_backlight == 255) {
last_backlight = get_backlight_level();
}
backlight_set(0);
rgb_matrix_set_suspend_state(true);
qp_power(display, false);
}
void suspend_wakeup_init_user(void) {
qp_power(display, true);
rgb_matrix_set_suspend_state(false);
if (last_backlight != 255) {
backlight_set(last_backlight);
}
last_backlight = 255;
}
```
#### Display Clear :id=quantum-painter-api-clear
```c
bool qp_clear(painter_device_t device);
```
The `qp_clear` function clears the display's screen.
#### Display Flush :id=quantum-painter-api-flush
```c
bool qp_flush(painter_device_t device);
```
The `qp_flush` function ensures that all drawing operations are "pushed" to the display. This should be done as the last operation whenever a sequence of draws occur, and guarantees that any changes are applied.
!> Some display panels may seem to work even without a call to `qp_flush` -- this may be because the driver cannot queue drawing operations and needs to display them immediately when invoked. In general, calling `qp_flush` at the end is still considered "best practice".
```c
void housekeeping_task_user(void) {
static uint32_t last_draw = 0;
if (timer_elapsed32(last_draw) > 33) { // Throttle to 30fps
The `qp_setpixel` can be used to set a specific pixel on the screen to the supplied color.
?> Using `qp_setpixel` for large amounts of drawing operations is inefficient and should be avoided unless they cannot be achieved with other drawing APIs.
```c
void housekeeping_task_user(void) {
static uint32_t last_draw = 0;
if (timer_elapsed32(last_draw) > 33) { // Throttle to 30fps
last_draw = timer_read32();
// Draw a 240px high vertical rainbow line on X=0:
The `qp_rect` can be used to draw rectangles on the screen with the supplied color, with or without a background fill. If not filled, any pixels inside the rectangle will be left as-is.
```c
void housekeeping_task_user(void) {
static uint32_t last_draw = 0;
if (timer_elapsed32(last_draw) > 33) { // Throttle to 30fps
last_draw = timer_read32();
// Draw 8px-wide rainbow filled rectangles down the left side of the display
for (int i = 0; i <239;i+=8){
qp_rect(display, 0, i, 7, i+7, i, 255, 255, true);
The `qp_circle` can be used to draw circles on the screen with the supplied color, with or without a background fill. If not filled, any pixels inside the circle will be left as-is.
```c
void housekeeping_task_user(void) {
static uint32_t last_draw = 0;
if (timer_elapsed32(last_draw) > 33) { // Throttle to 30fps
last_draw = timer_read32();
// Draw r=4 filled circles down the left side of the display
The `qp_ellipse` can be used to draw ellipses on the screen with the supplied color, with or without a background fill. If not filled, any pixels inside the ellipses will be left as-is.
```c
void housekeeping_task_user(void) {
static uint32_t last_draw = 0;
if (timer_elapsed32(last_draw) > 33) { // Throttle to 30fps
last_draw = timer_read32();
// Draw 16x8 filled ellipses down the left side of the display
for (int i = 0; i <239;i+=8){
qp_ellipse(display, 8, 4+i, 16, 8, i, 255, 255, true);
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.
See the [CLI Commands](quantum_painter.md?id=quantum-painter-cli) for instructions on how to convert images to [QGF](quantum_painter_qgf.md).
?> The total number of images available to load at any one time is controlled by the configurable option `QUANTUM_PAINTER_NUM_IMAGES` in the table above. If more images are required, the number should be increased in `config.h`.
Image information is available through accessing the handle:
The `qp_drawimage` and `qp_drawimage_recolor` functions draw the supplied image to the screen at the supplied location, with the latter function allowing for monochrome-based images to be recolored.
```c
// Draw an image on the bottom-right of the 240x320 display on initialisation
The `qp_animate` and `qp_animate_recolor` functions draw the supplied image to the screen at the supplied location, with the latter function allowing for monochrome-based animations to be recolored. They also set up internal timing such that each frame is rendered at the correct time as per the animated image.
Once an image has been set to animate, it will loop indefinitely until stopped, with no user intervention required.
Both functions return a `deferred_token`, which can then be used to stop the animation, using `qp_stop_animation` below.
```c
// Animate an image on the bottom-right of the 240x320 display on initialisation
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.
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).
?> The total number of fonts available to load at any one time is controlled by the configurable option `QUANTUM_PAINTER_NUM_FONTS` in the table above. If more fonts are required, the number should be increased in `config.h`.
Font information is available through accessing the handle:
| Property | Accessor |
|-------------|----------------------|
| Line Height | `image->line_height` |
#### Unload Font :id=quantum-painter-api-close-font
```c
bool qp_close_font(painter_font_handle_t font);
```
The `qp_close_font` function releases resources related to the loading of the supplied font.
#### Measure Text :id=quantum-painter-api-textwidth
The `qp_drawtext` and `qp_drawtext_recolor` functions draw the supplied string to the screen at the given location using the font supplied, with the latter function allowing for monochrome-based fonts to be recolored.
```c
// Draw a text message on the bottom-right of the 240x320 display on initialisation
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
The `qp_pixdata` function allows raw pixel data to be streamed to the display. It requires a native pixel count rather than the number of bytes to transfer, to ensure display panel data alignment is respected. E.g. for display panels using RGB565 internal format, sending 10 pixels will result in 20 bytes of transfer.
!> 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.
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.