summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/unit_test.yml2
-rw-r--r--builddefs/common_features.mk8
-rw-r--r--docs/_summary.md1
-rw-r--r--docs/cli_commands.md12
-rw-r--r--docs/quantum_painter.md705
-rw-r--r--docs/quantum_painter_qff.md103
-rw-r--r--docs/quantum_painter_qgf.md178
-rw-r--r--docs/quantum_painter_rle.md29
-rw-r--r--drivers/painter/comms/qp_comms_spi.c137
-rw-r--r--drivers/painter/comms/qp_comms_spi.h51
-rw-r--r--drivers/painter/gc9a01/qp_gc9a01.c150
-rw-r--r--drivers/painter/gc9a01/qp_gc9a01.h37
-rw-r--r--drivers/painter/gc9a01/qp_gc9a01_opcodes.h78
-rw-r--r--drivers/painter/ili9xxx/qp_ili9163.c121
-rw-r--r--drivers/painter/ili9xxx/qp_ili9163.h37
-rw-r--r--drivers/painter/ili9xxx/qp_ili9341.c128
-rw-r--r--drivers/painter/ili9xxx/qp_ili9341.h37
-rw-r--r--drivers/painter/ili9xxx/qp_ili9xxx_opcodes.h100
-rw-r--r--drivers/painter/ssd1351/qp_ssd1351.c125
-rw-r--r--drivers/painter/ssd1351/qp_ssd1351.h37
-rw-r--r--drivers/painter/ssd1351/qp_ssd1351_opcodes.h48
-rw-r--r--drivers/painter/st77xx/qp_st7789.c144
-rw-r--r--drivers/painter/st77xx/qp_st7789.h44
-rw-r--r--drivers/painter/st77xx/qp_st7789_opcodes.h64
-rw-r--r--drivers/painter/st77xx/qp_st77xx_opcodes.h51
-rw-r--r--drivers/painter/tft_panel/qp_tft_panel.c130
-rw-r--r--drivers/painter/tft_panel/qp_tft_panel.h67
-rw-r--r--lib/python/qmk/cli/__init__.py4
-rw-r--r--lib/python/qmk/cli/painter/__init__.py2
-rw-r--r--lib/python/qmk/cli/painter/convert_graphics.py86
-rw-r--r--lib/python/qmk/cli/painter/make_font.py87
-rw-r--r--lib/python/qmk/painter.py268
-rw-r--r--lib/python/qmk/painter_qff.py401
-rw-r--r--lib/python/qmk/painter_qgf.py408
-rw-r--r--quantum/main.c11
-rw-r--r--quantum/painter/qff.c137
-rw-r--r--quantum/painter/qff.h88
-rw-r--r--quantum/painter/qgf.c292
-rw-r--r--quantum/painter/qgf.h136
-rw-r--r--quantum/painter/qp.c228
-rw-r--r--quantum/painter/qp.h453
-rw-r--r--quantum/painter/qp_comms.c72
-rw-r--r--quantum/painter/qp_comms.h25
-rw-r--r--quantum/painter/qp_draw.h85
-rw-r--r--quantum/painter/qp_draw_circle.c172
-rw-r--r--quantum/painter/qp_draw_codec.c142
-rw-r--r--quantum/painter/qp_draw_core.c294
-rw-r--r--quantum/painter/qp_draw_ellipse.c116
-rw-r--r--quantum/painter/qp_draw_image.c382
-rw-r--r--quantum/painter/qp_draw_text.c444
-rw-r--r--quantum/painter/qp_internal.h33
-rw-r--r--quantum/painter/qp_internal_driver.h82
-rw-r--r--quantum/painter/qp_internal_formats.h49
-rw-r--r--quantum/painter/qp_stream.c171
-rw-r--r--quantum/painter/qp_stream.h82
-rw-r--r--quantum/painter/rules.mk116
-rw-r--r--quantum/process_keycode/process_unicode_common.c30
-rw-r--r--quantum/quantum.h4
-rw-r--r--quantum/utf8.c46
-rw-r--r--quantum/utf8.h21
-rw-r--r--requirements.txt1
-rw-r--r--setup.cfg4
62 files changed, 7561 insertions, 35 deletions
diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml
index 26bcb2f511..726ce19f0c 100644
--- a/.github/workflows/unit_test.yml
+++ b/.github/workflows/unit_test.yml
@@ -26,5 +26,7 @@ jobs:
- uses: actions/checkout@v2
with:
submodules: recursive
+ - name: Install dependencies
+ run: pip3 install -r requirements-dev.txt
- name: Run tests
run: make test:all
diff --git a/builddefs/common_features.mk b/builddefs/common_features.mk
index a1793f91a5..c976b8296d 100644
--- a/builddefs/common_features.mk
+++ b/builddefs/common_features.mk
@@ -149,6 +149,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
endif
endif
+QUANTUM_PAINTER_ENABLE ?= no
+ifeq ($(strip $(QUANTUM_PAINTER_ENABLE)), yes)
+ include $(QUANTUM_DIR)/painter/rules.mk
+endif
+
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi
EEPROM_DRIVER ?= vendor
ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)
@@ -696,7 +701,8 @@ endif
ifeq ($(strip $(UNICODE_COMMON)), yes)
OPT_DEFS += -DUNICODE_COMMON_ENABLE
- SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c
+ SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c \
+ $(QUANTUM_DIR)/utf8.c
endif
MAGIC_ENABLE ?= yes
diff --git a/docs/_summary.md b/docs/_summary.md
index 249bfcd9ed..786685eba4 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -94,6 +94,7 @@
* Hardware Features
* Displays
+ * [Quantum Painter](quantum_painter.md)
* [HD44780 LCD Driver](feature_hd44780.md)
* [ST7565 LCD Driver](feature_st7565.md)
* [OLED Driver](feature_oled_driver.md)
diff --git a/docs/cli_commands.md b/docs/cli_commands.md
index 463abcef12..a380d3eb2f 100644
--- a/docs/cli_commands.md
+++ b/docs/cli_commands.md
@@ -515,3 +515,15 @@ Run single test:
qmk pytest -t qmk.tests.test_cli_commands.test_c2json
qmk pytest -t qmk.tests.test_qmk_path
+
+## `qmk painter-convert-graphics`
+
+This command converts images to a format usable by QMK, i.e. the QGF File Format. See the [Quantum Painter](quantum_painter.md?id=quantum-painter-cli) documentation for more information on this command.
+
+## `qmk painter-make-font-image`
+
+This command converts a TTF font to an intermediate format for editing, before converting to the QFF File Format. See the [Quantum Painter](quantum_painter.md?id=quantum-painter-cli) documentation for more information on this command.
+
+## `qmk painter-convert-font-image`
+
+This command converts an intermediate font image to the QFF File Format. See the [Quantum Painter](quantum_painter.md?id=quantum-painter-cli) documentation for more information on this command.
diff --git a/docs/quantum_painter.md b/docs/quantum_painter.md
new file mode 100644
index 0000000000..a3705b62ce
--- /dev/null
+++ b/docs/quantum_painter.md
@@ -0,0 +1,705 @@
+# Quantum Painter :id=quantum-painter
+
+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 |
+|---------------|--------------------|------------------|-----------------|-----------------------------------------|
+| 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` |
+| ILI9341 | RGB LCD | 240x320 | SPI + D/C + RST | `QUANTUM_PAINTER_DRIVERS = ili9341_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` |
+
+## Quantum Painter Configuration :id=quantum-painter-config
+
+| Option | Default | Purpose |
+|-----------------------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------|
+| `QUANTUM_PAINTER_NUM_IMAGES` | `8` | The maximum number of images/animations that can be loaded at any one time. |
+| `QUANTUM_PAINTER_NUM_FONTS` | `4` | The maximum number of fonts that can be loaded at any one time. |
+| `QUANTUM_PAINTER_CONCURRENT_ANIMATIONS` | `4` | The maximum number of animations that can be executed at the same time. |
+| `QUANTUM_PAINTER_LOAD_FONTS_TO_RAM` | `FALSE` | Whether or not fonts should be loaded to RAM. Relevant for fonts stored in off-chip persistent storage, such as external flash. |
+| `QUANTUM_PAINTER_PIXDATA_BUFFER_SIZE` | `32` | The limit of the amount of pixel data that can be transmitted in one transaction to the display. Higher values require more RAM on the MCU. |
+| `QUANTUM_PAINTER_SUPPORTS_256_PALETTE` | `FALSE` | If 256-color palettes are supported. Requires significantly more RAM on the MCU. |
+| `QUANTUM_PAINTER_DEBUG` | _unset_ | Prints out significant amounts of debugging information to CONSOLE output. Significant performance degradation, use only for debugging. |
+
+Drivers have their own set of configurable options, and are described in their respective sections.
+
+## Quantum Painter CLI Commands :id=quantum-painter-cli
+
+### `qmk painter-convert-graphics`
+
+This command converts images to a format usable by QMK, i.e. the QGF File Format.
+
+**Usage**:
+
+```
+usage: qmk painter-convert-graphics [-h] [-d] [-r] -f FORMAT [-o OUTPUT] -i INPUT [-v]
+
+optional arguments:
+ -h, --help show this help message and exit
+ -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
+ -o OUTPUT, --output OUTPUT
+ Specify output directory. Defaults to same directory as input.
+ -i INPUT, --input INPUT
+ Specify input graphic file.
+ -v, --verbose Turns on verbose output.
+```
+
+The `INPUT` argument can be any image file loadable by Python's Pillow module. Common formats include PNG, or Animated GIF.
+
+The `OUTPUT` argument needs to be a directory, and will default to the same directory as the input argument.
+
+The `FORMAT` argument can be any of the following:
+
+| Format | Meaning |
+|-----------|-----------------------------------------------------------------------|
+| `pal256` | 256-color palette (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) |
+| `pal16` | 16-color palette |
+| `pal4` | 4-color palette |
+| `pal2` | 2-color palette |
+| `mono256` | 256-shade grayscale (requires `QUANTUM_PAINTER_SUPPORTS_256_PALETTE`) |
+| `mono16` | 16-shade grayscale |
+| `mono4` | 4-shade grayscale |
+| `mono2` | 2-shade grayscale |
+
+**Examples**:
+
+```
+$ cd /home/qmk/qmk_firmware/keyboards/my_keeb
+$ qmk painter-convert-graphics -f mono16 -i my_image.gif -o ./generated/
+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...
+```
+
+### `qmk painter-make-font-image`
+
+This command converts a TTF font to an intermediate format for editing, before converting to the QFF File Format.
+
+**Usage**:
+
+```
+usage: qmk painter-make-font-image [-h] [-a] [-u UNICODE_GLYPHS] [-n] [-s SIZE] -o OUTPUT -f FONT
+
+optional arguments:
+ -h, --help show this help message and exit
+ -a, --no-aa Disable anti-aliasing on fonts.
+ -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.
+ -s SIZE, --size SIZE Specify font size. Default 12.
+ -o OUTPUT, --output OUTPUT
+ Specify output image path.
+ -f FONT, --font FONT Specify input font file.
+```
+
+The `FONT` argument is generally a TrueType Font file (TTF).
+
+The `OUTPUT` argument is the output image to generate, generally something like `my_font.png`.
+
+The `UNICODE_GLYPHS` argument allows for specifying extra unicode glyphs to generate, and accepts a string.
+
+**Examples**:
+
+```
+$ qmk painter-make-font-image --font NotoSans-ExtraCondensedBold.ttf --size 11 -o noto11.png --unicode-glyphs "ĄȽɂɻɣɈʣ"
+```
+
+### `qmk painter-convert-font-image`
+
+This command converts an intermediate font image to the QFF File Format.
+
+This command expects an image that conforms to the following format:
+
+* Top-left pixel (at `0,0`) is the "delimiter" color:
+ * Each glyph in the font starts when a pixel of this color is found on the first row
+ * The first row is discarded when converting to the QFF format
+* The number of delimited glyphs must match the supplied arguments to the command:
+ * The full ASCII set `0x20..0x7E` (if `--no-ascii` was not specified)
+ * The corresponding number of unicode glyphs if any were specified with `--unicode-glyphs`
+* The order of the glyphs matches the ASCII set, if any, followed by the Unicode glyph set, if any.
+
+**Usage**:
+
+```
+usage: qmk painter-convert-font-image [-h] [-r] -f FORMAT [-u UNICODE_GLYPHS] [-n] [-o OUTPUT] [-i INPUT]
+
+optional arguments:
+ -h, --help show this help message and exit
+ -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
+ -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.
+ -o OUTPUT, --output OUTPUT
+ Specify output directory. Defaults to same directory as input.
+ -i INPUT, --input INPUT
+ Specify input graphic file.
+```
+
+The same arguments for `--no-ascii` and `--unicode-glyphs` need to be specified, as per `qmk painter-make-font-image`.
+
+**Examples**:
+
+```
+$ cd /home/qmk/qmk_firmware/keyboards/my_keeb
+$ qmk painter-convert-font-image --input noto11.png -f mono4 --unicode-glyphs "ĄȽɂɻɣɈʣ"
+Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.h...
+Writing /home/qmk/qmk_firmware/keyboards/my_keeb/generated/noto11.qff.c...
+```
+
+## 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
+
+#### Display Initialisation :id=quantum-painter-api-init
+
+```c
+bool qp_init(painter_device_t device, painter_rotation_t rotation);
+```
+
+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
+}
+```
+
+#### Display Power :id=quantum-painter-api-power
+
+```c
+bool qp_power(painter_device_t device, bool power_on);
+```
+
+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
+ last_draw = timer_read32();
+ // Draw a rect based off the current RGB color
+ qp_rect(display, 0, 7, 0, 239, rgb_matrix_get_hue(), 255, 255);
+ qp_flush(display);
+ }
+}
+```
+
+### Drawing Primitives :id=quantum-painter-api-primitives
+
+#### Set Pixel :id=quantum-painter-api-setpixel
+
+```c
+bool qp_setpixel(painter_device_t device, uint16_t x, uint16_t y, uint8_t hue, uint8_t sat, uint8_t val);
+```
+
+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:
+ for (int i = 0; i < 239; ++i) {
+ qp_setpixel(display, 0, i, i, 255, 255);
+ }
+ qp_flush(display);
+ }
+}
+```
+
+#### Draw Line :id=quantum-painter-api-line
+
+```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);
+```
+
+The `qp_line` can be used to draw lines on the screen with the supplied color.
+
+```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 down the left side of the display
+ for (int i = 0; i < 239; ++i) {
+ qp_line(display, 0, i, 7, i, i, 255, 255);
+ }
+ qp_flush(display);
+ }
+}
+```
+
+#### Draw Rect :id=quantum-painter-api-rect
+
+```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);
+```
+
+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);
+ }
+ qp_flush(display);
+ }
+}
+```
+
+#### Draw Circle :id=quantum-painter-api-circle
+
+```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);
+```
+
+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
+ for (int i = 0; i < 239; i+=8) {
+ qp_circle(display, 4, 4+i, 4, i, 255, 255, true);
+ }
+ qp_flush(display);
+ }
+}
+```
+
+#### Draw Ellipse :id=quantum-painter-api-ellipse
+
+```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);
+```
+
+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);
+ }
+ qp_flush(display);
+ }
+}
+```
+
+### Image Functions :id=quantum-painter-api-images
+
+#### Load Image :id=quantum-painter-api-load-image
+
+```c
+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.
+
+`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:
+
+| Property | Accessor |
+|-------------|----------------------|
+| Width | `image->width` |
+| Height | `image->height` |
+| Frame Count | `image->frame_count` |
+
+#### Unload Image :id=quantum-painter-api-close-image
+
+```c
+bool qp_close_image(painter_image_handle_t image);
+```
+
+The `qp_close_image` function releases resources related to the loading of the supplied image.
+
+#### Draw image :id=quantum-painter-api-draw-image
+
+```c
+bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
+bool qp_drawimage_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg);
+```
+
+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
+static painter_image_handle_t my_image;
+void keyboard_post_init_kb(void) {
+ my_image = qp_load_image_mem(gfx_my_image);
+ if (my_image != NULL) {
+ qp_drawimage(display, (239 - my_image->width), (319 - my_image->height), my_image);
+ }
+}
+```
+
+#### Animate Image :id=quantum-painter-api-animate-image
+
+```c
+deferred_token qp_animate(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image);
+deferred_token qp_animate_recolor(painter_device_t device, uint16_t x, uint16_t y,