Skip to content

Commit 1f2b1de

Browse files
authored
Quantum Painter (#10174)
* Install dependencies before executing unit tests. * Split out UTF-8 decoder. * Fixup python formatting rules. * Add documentation for QGF/QFF and the RLE format used. * Add CLI commands for converting images and fonts. * Add stub rules.mk for QP. * Add stream type. * Add base driver and comms interfaces. * Add support for SPI, SPI+D/C comms drivers. * Include <qp.h> when enabled. * Add base support for SPI+D/C+RST panels, as well as concrete implementation of ST7789. * Add support for GC9A01. * Add support for ILI9341. * Add support for ILI9163. * Add support for SSD1351. * Implement qp_setpixel, including pixdata buffer management. * Implement qp_line. * Implement qp_rect. * Implement qp_circle. * Implement qp_ellipse. * Implement palette interpolation. * Allow for streams to work with either flash or RAM. * Image loading. * Font loading. * QGF palette loading. * Progressive decoder of pixel data supporting Raw+RLE, 1-,2-,4-,8-bpp monochrome and palette-based images. * Image drawing. * Animations. * Font rendering. * Check against 256 colours, dump out the loaded palette if debugging enabled. * Fix build. * AVR is not the intended audience. * `qmk format-c` * Generation fix. * First batch of docs. * More docs and examples. * Review comments. * Public API documentation.
1 parent 1dbbd2b commit 1f2b1de

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+7561
-35
lines changed

.github/workflows/unit_test.yml

+2
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ jobs:
2626
- uses: actions/checkout@v2
2727
with:
2828
submodules: recursive
29+
- name: Install dependencies
30+
run: pip3 install -r requirements-dev.txt
2931
- name: Run tests
3032
run: make test:all

builddefs/common_features.mk

+7-1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ ifeq ($(strip $(POINTING_DEVICE_ENABLE)), yes)
149149
endif
150150
endif
151151

152+
QUANTUM_PAINTER_ENABLE ?= no
153+
ifeq ($(strip $(QUANTUM_PAINTER_ENABLE)), yes)
154+
include $(QUANTUM_DIR)/painter/rules.mk
155+
endif
156+
152157
VALID_EEPROM_DRIVER_TYPES := vendor custom transient i2c spi
153158
EEPROM_DRIVER ?= vendor
154159
ifeq ($(filter $(EEPROM_DRIVER),$(VALID_EEPROM_DRIVER_TYPES)),)
@@ -696,7 +701,8 @@ endif
696701

697702
ifeq ($(strip $(UNICODE_COMMON)), yes)
698703
OPT_DEFS += -DUNICODE_COMMON_ENABLE
699-
SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c
704+
SRC += $(QUANTUM_DIR)/process_keycode/process_unicode_common.c \
705+
$(QUANTUM_DIR)/utf8.c
700706
endif
701707

702708
MAGIC_ENABLE ?= yes

docs/_summary.md

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494

9595
* Hardware Features
9696
* Displays
97+
* [Quantum Painter](quantum_painter.md)
9798
* [HD44780 LCD Driver](feature_hd44780.md)
9899
* [ST7565 LCD Driver](feature_st7565.md)
99100
* [OLED Driver](feature_oled_driver.md)

docs/cli_commands.md

+12
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,15 @@ Run single test:
515515

516516
qmk pytest -t qmk.tests.test_cli_commands.test_c2json
517517
qmk pytest -t qmk.tests.test_qmk_path
518+
519+
## `qmk painter-convert-graphics`
520+
521+
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.
522+
523+
## `qmk painter-make-font-image`
524+
525+
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.
526+
527+
## `qmk painter-convert-font-image`
528+
529+
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.

docs/quantum_painter.md

+705
Large diffs are not rendered by default.

docs/quantum_painter_qff.md

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# QMK Font Format :id=qmk-font-format
2+
3+
QMK uses a font format _("Quantum Font Format" - QFF)_ specifically for resource-constrained systems.
4+
5+
This format is capable of encoding 1-, 2-, 4-, and 8-bit-per-pixel greyscale- and palette-based images into a font. It also includes RLE for pixel data for some basic compression.
6+
7+
All integer values are in little-endian format.
8+
9+
The QFF is defined in terms of _blocks_ -- each _block_ contains a _header_ and an optional _blob_ of data. The _header_ contains the block's _typeid_, and the length of the _blob_ that follows. Each block type is denoted by a different _typeid_ has its own block definition below. All blocks are defined as packed structs, containing zero padding between fields.
10+
11+
The general structure of the file is:
12+
13+
* _Font descriptor block_
14+
* _ASCII glyph block_ (optional, only if ASCII glyphs are included)
15+
* _Unicode glyph block_ (optional, only if Unicode glyphs are included)
16+
* _Font palette block_ (optional, depending on frame format)
17+
* _Font data block_
18+
19+
## Block Header :id=qff-block-header
20+
21+
The block header is identical to [QGF's block header](quantum_painter_qgf.md#qgf-block-header), and is present for all blocks, including the font descriptor.
22+
23+
## Font descriptor block :id=qff-font-descriptor
24+
25+
* _typeid_ = 0x00
26+
* _length_ = 20
27+
28+
This block must be located at the start of the file contents, and can exist a maximum of once in an entire QGF file. It is always followed by either the _ASCII glyph table_ or the _Unicode glyph table_, depending on which glyphs are included in the font.
29+
30+
_Block_ format:
31+
32+
```c
33+
typedef struct __attribute__((packed)) qff_font_descriptor_v1_t {
34+
qgf_block_header_v1_t header; // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 20 }
35+
uint24_t magic; // constant, equal to 0x464651 ("QFF")
36+
uint8_t qff_version; // constant, equal to 0x01
37+
uint32_t total_file_size; // total size of the entire file, starting at offset zero
38+
uint32_t neg_total_file_size; // negated value of total_file_size, used for detecting parsing errors
39+
uint8_t line_height; // glyph height in pixels
40+
bool has_ascii_table; // whether the font has an ascii table of glyphs (0x20...0x7E)
41+
uint16_t num_unicode_glyphs; // the number of glyphs in the unicode table -- no table specified if zero
42+
uint8_t format; // frame format, see below.
43+
uint8_t flags; // frame flags, see below.
44+
uint8_t compression_scheme; // compression scheme, see below.
45+
uint8_t transparency_index; // palette index used for transparent pixels (not yet implemented)
46+
} qff_font_descriptor_v1_t;
47+
// _Static_assert(sizeof(qff_font_descriptor_v1_t) == (sizeof(qgf_block_header_v1_t) + 20), "qff_font_descriptor_v1_t must be 25 bytes in v1 of QFF");
48+
```
49+
50+
The values for `format`, `flags`, `compression_scheme`, and `transparency_index` match [QGF's frame descriptor block](quantum_painter_qgf.md#qgf-frame-descriptor), with the exception that the `delta` flag is ignored by QFF.
51+
52+
## ASCII glyph table :id=qff-ascii-table
53+
54+
* _typeid_ = 0x01
55+
* _length_ = 290
56+
57+
If the font contains ascii characters, the _ASCII glyph block_ must be located directly after the _font descriptor block_.
58+
59+
```c
60+
#define QFF_GLYPH_WIDTH_BITS 6
61+
#define QFF_GLYPH_WIDTH_MASK ((1<<QFF_GLYPH_WIDTH_BITS)-1)
62+
#define QFF_GLYPH_OFFSET_BITS 18
63+
#define QFF_GLYPH_OFFSET_MASK (((1<<QFF_GLYPH_OFFSET_BITS)-1) << QFF_GLYPH_WIDTH_BITS)
64+
65+
typedef struct __attribute__((packed)) qff_ascii_glyph_table_v1_t {
66+
qgf_block_header_v1_t header; // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = 285 }
67+
uint24_t glyph[95]; // 95 glyphs, 0x20..0x7E, see bits/masks above for values
68+
} qff_ascii_glyph_table_v1_t;
69+
// _Static_assert(sizeof(qff_ascii_glyph_table_v1_t) == (sizeof(qgf_block_header_v1_t) + 285), "qff_ascii_glyph_table_v1_t must be 290 bytes in v1 of QFF");
70+
```
71+
72+
## Unicode glyph table :id=qff-unicode-table
73+
74+
* _typeid_ = 0x02
75+
* _length_ = variable
76+
77+
If this font contains unicode characters, the _unicode glyph block_ must be located directly after the _ASCII glyph table block_, or the _font descriptor block_ if the font does not contain ASCII characters.
78+
79+
```c
80+
typedef struct __attribute__((packed)) qff_unicode_glyph_table_v1_t {
81+
qgf_block_header_v1_t header; // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = (N * 6) }
82+
struct __attribute__((packed)) { // container for a single unicode glyph
83+
uint24_t code_point; // the unicode code point
84+
uint24_t glyph; // the glyph information, as per ASCII glyphs above
85+
} glyph[N]; // N glyphs worth of data
86+
} qff_unicode_glyph_table_v1_t;
87+
```
88+
89+
## Font palette block :id=qff-palette-descriptor
90+
91+
* _typeid_ = 0x03
92+
* _length_ = variable
93+
94+
The _font palette block_ is identical to [QGF's frame palette block](quantum_painter_qgf.md#qgf-frame-palette-descriptor), retaining the same _typeid_ of 0x03.
95+
96+
It is only specified in the QFF if the font is palette-based, and follows the _unicode glyph block_ if the font contains any Unicode glyphs, or the _ASCII glyph block_ if the font contains only ASCII glyphs.
97+
98+
## Font data block :id=qff-data-descriptor
99+
100+
* _typeid_ = 0x04
101+
* _length_ = variable
102+
103+
The _font data block_ is the last block in the file and is identical to [QGF's frame data block](quantum_painter_qgf.md#qgf-frame-data-descriptor), however has a different _typeid_ of 0x04 in QFF.

docs/quantum_painter_qgf.md

+178
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# QMK Graphics Format :id=qmk-graphics-format
2+
3+
QMK uses a graphics format _("Quantum Graphics Format" - QGF)_ specifically for resource-constrained systems.
4+
5+
This format is capable of encoding 1-, 2-, 4-, and 8-bit-per-pixel greyscale- and palette-based images. It also includes RLE for pixel data for some basic compression.
6+
7+
All integer values are in little-endian format.
8+
9+
The QGF is defined in terms of _blocks_ -- each _block_ contains a _header_ and an optional _blob_ of data. The _header_ contains the block's _typeid_, and the length of the _blob_ that follows. Each block type is denoted by a different _typeid_ has its own block definition below. All blocks are defined as packed structs, containing zero padding between fields.
10+
11+
The general structure of the file is:
12+
13+
* _Graphics descriptor block_
14+
* _Frame offset block_
15+
* Repeating list of frames:
16+
* _Frame descriptor block_
17+
* _Frame palette block_ (optional, depending on frame format)
18+
* _Frame delta block_ (optional, depending on delta flag)
19+
* _Frame data block_
20+
21+
Different frames within the file should be considered "isolated" and may have their own image format and/or palette.
22+
23+
## Block Header :id=qgf-block-header
24+
25+
This block header is present for all blocks, including the graphics descriptor.
26+
27+
_Block header_ format:
28+
29+
```c
30+
typedef struct __attribute__((packed)) qgf_block_header_v1_t {
31+
uint8_t type_id; // See each respective block type
32+
uint8_t neg_type_id; // Negated type ID, used for detecting parsing errors
33+
uint24_t length; // 24-bit blob length, allowing for block sizes of a maximum of 16MB
34+
} qgf_block_header_v1_t;
35+
// _Static_assert(sizeof(qgf_block_header_v1_t) == 5, "qgf_block_header_v1_t must be 5 bytes in v1 of QGF");
36+
```
37+
The _length_ describes the number of octets in the data following the block header -- a block header may specify a _length_ of `0` if no blob is specified.
38+
39+
## Graphics descriptor block :id=qgf-graphics-descriptor
40+
41+
* _typeid_ = 0x00
42+
* _length_ = 18
43+
44+
This block must be located at the start of the file contents, and can exist a maximum of once in an entire QGF file. It is always followed by the _frame offset block_.
45+
46+
_Block_ format:
47+
48+
```c
49+
typedef struct __attribute__((packed)) qgf_graphics_descriptor_v1_t {
50+
qgf_block_header_v1_t header; // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 18 }
51+
uint24_t magic; // constant, equal to 0x464751 ("QGF")
52+
uint8_t qgf_version; // constant, equal to 0x01
53+
uint32_t total_file_size; // total size of the entire file, starting at offset zero
54+
uint32_t neg_total_file_size; // negated value of total_file_size, used for detecting parsing errors
55+
uint16_t image_width; // in pixels
56+
uint16_t image_height; // in pixels
57+
uint16_t frame_count; // minimum of 1
58+
} qgf_graphics_descriptor_v1_t;
59+
// _Static_assert(sizeof(qgf_graphics_descriptor_v1_t) == (sizeof(qgf_block_header_v1_t) + 18), "qgf_graphics_descriptor_v1_t must be 23 bytes in v1 of QGF");
60+
```
61+
62+
## Frame offset block :id=qgf-frame-offset-descriptor
63+
64+
* _typeid_ = 0x01
65+
* _length_ = variable
66+
67+
This block denotes the offsets within the file to each frame's _frame descriptor block_, relative to the start of the file. The _frame offset block_ always immediately follows the _graphics descriptor block_. The contents of this block are an array of U32's, with one entry for each frame.
68+
69+
Duplicate frame offsets in this block are allowed, if a certain frame is to be shown multiple times during animation.
70+
71+
_Block_ format:
72+
73+
```c
74+
typedef struct __attribute__((packed)) qgf_frame_offsets_v1_t {
75+
qgf_block_header_v1_t header; // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = (N * sizeof(uint32_t)) }
76+
uint32_t offset[N]; // where 'N' is the number of frames in the file
77+
} qgf_frame_offsets_v1_t;
78+
```
79+
80+
## Frame descriptor block :id=qgf-frame-descriptor
81+
82+
* _typeid_ = 0x02
83+
* _length_ = 5
84+
85+
This block denotes the start of a frame.
86+
87+
_Block_ format:
88+
89+
```c
90+
typedef struct __attribute__((packed)) qgf_frame_v1_t {
91+
qgf_block_header_v1_t header; // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = 5 }
92+
uint8_t format; // Frame format, see below.
93+
uint8_t flags; // Frame flags, see below.
94+
uint8_t compression_scheme; // Compression scheme, see below.
95+
uint8_t transparency_index; // palette index used for transparent pixels (not yet implemented)
96+
uint16_t delay; // frame delay time for animations (in units of milliseconds)
97+
} qgf_frame_v1_t;
98+
// _Static_assert(sizeof(qgf_frame_v1_t) == (sizeof(qgf_block_header_v1_t) + 6), "qgf_frame_v1_t must be 11 bytes in v1 of QGF");
99+
```
100+
101+
If this frame is grayscale, the _frame descriptor block_ (or _frame delta block_ if flags denote a delta frame) is immediately followed by this frame's corresponding _frame data block_.
102+
103+
If the frame uses an indexed palette, the _frame descriptor block_ (or _frame delta block_ if flags denote a delta frame) is immediately followed by this frame's corresponding _frame palette block_.
104+
105+
Frame format possible values:
106+
107+
* `0x00`: 1bpp grayscale, no palette, `0` = black, `1` = white, LSb first pixel
108+
* `0x01`: 2bpp grayscale, no palette, `0` = black, `3` = white, linear interpolation of brightness, LSb first pixel
109+
* `0x02`: 4bpp grayscale, no palette, `0` = black, `15` = white, linear interpolation of brightness, LSb first pixel
110+
* `0x03`: 8bpp grayscale, no palette, `0` = black, `255` = white, linear interpolation of brightness, LSb first pixel
111+
* `0x04`: 1bpp indexed palette, 2 colors, LSb first pixel
112+
* `0x05`: 2bpp indexed palette, 4 colors, LSb first pixel
113+
* `0x06`: 4bpp indexed palette, 16 colors, LSb first pixel
114+
* `0x07`: 8bpp indexed palette, 256 colors, LSb first pixel
115+
116+
Frame flags is a bitmask with the following format:
117+
118+
| `bit 7` | `bit 6` | `bit 5` | `bit 4` | `bit 3` | `bit 2` | `bit 1` | `bit 0` |
119+
|---------|---------|---------|---------|---------|---------|---------|--------------|
120+
| - | - | - | - | - | - | Delta | Transparency |
121+
122+
* `[1]` -- Delta: Signifies that the current frame is a delta frame, which specifies only a sub-image. The _frame delta block_ follows the _frame palette block_ if the image format specifies a palette, otherwise it directly follows the _frame descriptor block_.
123+
* `[0]` -- Transparency: The transparent palette index in the _blob_ is considered valid and should be used when considering which pixels should be transparent during rendering this frame, if possible.
124+
125+
Compression scheme possible values:
126+
127+
* `0x00`: No compression
128+
* `0x01`: [QMK RLE](quantum_painter_rle.md)
129+
130+
## Frame palette block :id=qgf-frame-palette-descriptor
131+
132+
* _typeid_ = 0x03
133+
* _length_ = variable
134+
135+
This block describes the palette used for the frame. The _blob_ contains an array of palette entries -- one palette entry is present for each color used -- each palette entry is in QMK HSV888 format:
136+
137+
```c
138+
typedef struct __attribute__((packed)) qgf_palette_v1_t {
139+
qgf_block_header_v1_t header; // = { .type_id = 0x03, .neg_type_id = (~0x03), .length = (N * 3 * sizeof(uint8_t)) }
140+
struct { // container for a single HSV palette entry
141+
uint8_t h; // hue component: `[0,360)` degrees is mapped to `[0,255]` uint8_t.
142+
uint8_t s; // saturation component: `[0,1]` is mapped to `[0,255]` uint8_t.
143+
uint8_t v; // value component: `[0,1]` is mapped to `[0,255]` uint8_t.
144+
} hsv[N]; // N * hsv, where N is the number of palette entries depending on the frame format in the descriptor
145+
} qgf_palette_v1_t;
146+
```
147+
148+
## Frame delta block :id=qgf-frame-delta-descriptor
149+
150+
* _typeid_ = 0x04
151+
* _length_ = 8
152+
153+
This block describes where the delta frame should be drawn, with respect to the top left location of the image.
154+
155+
```c
156+
typedef struct __attribute__((packed)) qgf_delta_v1_t {
157+
qgf_block_header_v1_t header; // = { .type_id = 0x04, .neg_type_id = (~0x04), .length = 8 }
158+
uint16_t left; // The left pixel location to draw the delta image
159+
uint16_t top; // The top pixel location to draw the delta image
160+
uint16_t right; // The right pixel location to to draw the delta image
161+
uint16_t bottom; // The bottom pixel location to to draw the delta image
162+
} qgf_delta_v1_t;
163+
// _Static_assert(sizeof(qgf_delta_v1_t) == 13, "qgf_delta_v1_t must be 13 bytes in v1 of QGF");
164+
```
165+
166+
## Frame data block :id=qgf-frame-data-descriptor
167+
168+
* _typeid_ = 0x05
169+
* _length_ = variable
170+
171+
This block describes the data associated with the frame. The _blob_ contains an array of bytes containing the data corresponding to the frame's image format:
172+
173+
```c
174+
typedef struct __attribute__((packed)) qgf_data_v1_t {
175+
qgf_block_header_v1_t header; // = { .type_id = 0x05, .neg_type_id = (~0x05), .length = N }
176+
uint8_t data[N]; // N data octets
177+
} qgf_data_v1_t;
178+
```

docs/quantum_painter_rle.md

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# QMK QGF/QFF RLE data schema :id=qmk-qp-rle-schema
2+
3+
There are two "modes" to the RLE algorithm used in both [QGF](quantum_painter_qgf.md)/[QFF](quantum_painter_qff.md):
4+
5+
* Non-repeating sections of octets, with associated length of up to `128` octets
6+
* `length` = `marker - 128`
7+
* A corresponding `length` number of octets follow directly after the marker octet
8+
* Repeated octet with associated length, with associated length of up to `128`
9+
* `length` = `marker`
10+
* A single octet follows the marker that should be repeated `length` times.
11+
12+
Decoder pseudocode:
13+
```
14+
while !EOF
15+
marker = READ_OCTET()
16+
17+
if marker >= 128
18+
length = marker - 128
19+
for i = 0 ... length-1
20+
c = READ_OCTET()
21+
WRITE_OCTET(c)
22+
23+
else
24+
length = marker
25+
c = READ_OCTET()
26+
for i = 0 ... length-1
27+
WRITE_OCTET(c)
28+
29+
```

0 commit comments

Comments
 (0)