summaryrefslogtreecommitdiff
path: root/docs/quantum_painter_qgf.md
blob: caf6731e652b9773f13fa5782f4fbb46f79f1f0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# QMK Graphics Format :id=qmk-graphics-format

QMK uses a graphics format _("Quantum Graphics Format" - QGF)_ specifically for resource-constrained systems.

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.

All integer values are in little-endian format.

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.

The general structure of the file is:

* _Graphics descriptor block_
* _Frame offset block_
* Repeating list of frames:
    * _Frame descriptor block_
    * _Frame palette block_ (optional, depending on frame format)
    * _Frame delta block_ (optional, depending on delta flag)
    * _Frame data block_

Different frames within the file should be considered "isolated" and may have their own image format and/or palette.

## Block Header :id=qgf-block-header

This block header is present for all blocks, including the graphics descriptor.

_Block header_ format:

```c
typedef struct __attribute__((packed)) qgf_block_header_v1_t {
    uint8_t type_id;      // See each respective block type
    uint8_t neg_type_id;  // Negated type ID, used for detecting parsing errors
    uint24_t length;      // 24-bit blob length, allowing for block sizes of a maximum of 16MB
} qgf_block_header_v1_t;
// _Static_assert(sizeof(qgf_block_header_v1_t) == 5, "qgf_block_header_v1_t must be 5 bytes in v1 of QGF");
```
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.

## Graphics descriptor block :id=qgf-graphics-descriptor

* _typeid_ = 0x00
* _length_ = 18

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_.

_Block_ format:

```c
typedef struct __attribute__((packed)) qgf_graphics_descriptor_v1_t {
    qgf_block_header_v1_t header;               // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 18 }
    uint24_t              magic;                // constant, equal to 0x464751 ("QGF")
    uint8_t               qgf_version;          // constant, equal to 0x01
    uint32_t              total_file_size;      // total size of the entire file, starting at offset zero
    uint32_t              neg_total_file_size;  // negated value of total_file_size, used for detecting parsing errors
    uint16_t              image_width;          // in pixels
    uint16_t              image_height;         // in pixels
    uint16_t              frame_count;          // minimum of 1
} qgf_graphics_descriptor_v1_t;
// _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");
```

## Frame offset block :id=qgf-frame-offset-descriptor

* _typeid_ = 0x01
* _length_ = variable

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.

Duplicate frame offsets in this block are allowed, if a certain frame is to be shown multiple times during animation.

_Block_ format:

```c
typedef struct __attribute__((packed)) qgf_frame_offsets_v1_t {
    qgf_block_header_v1_t header;    // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = (N * sizeof(uint32_t)) }
    uint32_t              offset[N]; // where 'N' is the number of frames in the file
} qgf_frame_offsets_v1_t;
```

## Frame descriptor block :id=qgf-frame-descriptor

* _typeid_ = 0x02
* _length_ = 5

This block denotes the start of a frame.

_Block_ format:

```c
typedef struct __attribute__((packed)) qgf_frame_v1_t {
    qgf_block_header_v1_t header;              // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = 5 }
    uint8_t               format;              // Frame format, see below.
    uint8_t               flags;               // Frame flags, see below.
    uint8_t               compression_scheme;  // Compression scheme, see below.
    uint8_t               transparency_index;  // palette index used for transparent pixels (not yet implemented)
    uint16_t              delay;               // frame delay time for animations (in units of milliseconds)
} qgf_frame_v1_t;
// _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");
```

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_.

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_.

Frame format possible values:

* `0x00`: 1bpp grayscale, no palette, `0` = black, `1` = white, LSb first pixel
* `0x01`: 2bpp grayscale, no palette, `0` = black, `3` = white, linear interpolation of brightness, LSb first pixel
* `0x02`: 4bpp grayscale, no palette, `0` = black, `15` = white, linear interpolation of brightness, LSb first pixel
* `0x03`: 8bpp grayscale, no palette, `0` = black, `255` = white, linear interpolation of brightness, LSb first pixel
* `0x04`: 1bpp indexed palette, 2 colors, LSb first pixel
* `0x05`: 2bpp indexed palette, 4 colors, LSb first pixel
* `0x06`: 4bpp indexed palette, 16 colors, LSb first pixel
* `0x07`: 8bpp indexed palette, 256 colors, LSb first pixel

Frame flags is a bitmask with the following format:

| `bit 7` | `bit 6` | `bit 5` | `bit 4` | `bit 3` | `bit 2` | `bit 1` | `bit 0`      |
|---------|---------|---------|---------|---------|---------|---------|--------------|
| -       | -       | -       | -       | -       | -       | Delta   | Transparency |

* `[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_.
* `[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.

Compression scheme possible values:

* `0x00`: No compression
* `0x01`: [QMK RLE](quantum_painter_rle.md)

## Frame palette block :id=qgf-frame-palette-descriptor

* _typeid_ = 0x03
* _length_ = variable

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:

```c
typedef struct __attribute__((packed)) qgf_palette_v1_t {
    qgf_block_header_v1_t header;     // = { .type_id = 0x03, .neg_type_id = (~0x03), .length = (N * 3 * sizeof(uint8_t)) }
    struct {  // container for a single HSV palette entry
        uint8_t h;                    // hue component: `[0,360)` degrees is mapped to `[0,255]` uint8_t.
        uint8_t s;                    // saturation component: `[0,1]` is mapped to `[0,255]` uint8_t.
        uint8_t v;                    // value component: `[0,1]` is mapped to `[0,255]` uint8_t.
    } hsv[N];                         // N * hsv, where N is the number of palette entries depending on the frame format in the descriptor
} qgf_palette_v1_t;
```

## Frame delta block :id=qgf-frame-delta-descriptor

* _typeid_ = 0x04
* _length_ = 8

This block describes where the delta frame should be drawn, with respect to the top left location of the image.

```c
typedef struct __attribute__((packed)) qgf_delta_v1_t {
    qgf_block_header_v1_t header;  // = { .type_id = 0x04, .neg_type_id = (~0x04), .length = 8 }
    uint16_t left;                 // The left pixel location to draw the delta image
    uint16_t top;                  // The top pixel location to draw the delta image
    uint16_t right;                // The right pixel location to to draw the delta image
    uint16_t bottom;               // The bottom pixel location to to draw the delta image
} qgf_delta_v1_t;
// _Static_assert(sizeof(qgf_delta_v1_t) == 13, "qgf_delta_v1_t must be 13 bytes in v1 of QGF");
```

## Frame data block :id=qgf-frame-data-descriptor

* _typeid_ = 0x05
* _length_ = variable

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:

```c
typedef struct __attribute__((packed)) qgf_data_v1_t {
    qgf_block_header_v1_t header;   // = { .type_id = 0x05, .neg_type_id = (~0x05), .length = N }
    uint8_t               data[N];  // N data octets
} qgf_data_v1_t;
```