diff options
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. + |