import { _ as _export_sfc, c as createElementBlock, o as openBlock, a8 as createStaticVNode } from "./chunks/framework.B9AX-CPi.js"; const __pageData = JSON.parse('{"title":"QMK Graphics Format","description":"","frontmatter":{},"headers":[],"relativePath":"quantum_painter_qgf.md","filePath":"quantum_painter_qgf.md"}'); const _sfc_main = { name: "quantum_painter_qgf.md" }; const _hoisted_1 = /* @__PURE__ */ createStaticVNode('

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:

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

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 {\n    uint8_t type_id;      // See each respective block type\n    uint8_t neg_type_id;  // Negated type ID, used for detecting parsing errors\n    uint24_t length;      // 24-bit blob length, allowing for block sizes of a maximum of 16MB\n} qgf_block_header_v1_t;\n// _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

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

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 {\n    qgf_block_header_v1_t header;    // = { .type_id = 0x01, .neg_type_id = (~0x01), .length = (N * sizeof(uint32_t)) }\n    uint32_t              offset[N]; // where 'N' is the number of frames in the file\n} qgf_frame_offsets_v1_t;

Frame descriptor block

This block denotes the start of a frame.

Block format:

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

Frame flags is a bitmask with the following format:

bit 7bit 6bit 5bit 4bit 3bit 2bit 1bit 0
------DeltaTransparency

Compression scheme possible values:

Frame palette block

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

Frame delta block

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 {\n    qgf_block_header_v1_t header;  // = { .type_id = 0x04, .neg_type_id = (~0x04), .length = 8 }\n    uint16_t left;                 // The left pixel location to draw the delta image\n    uint16_t top;                  // The top pixel location to draw the delta image\n    uint16_t right;                // The right pixel location to to draw the delta image\n    uint16_t bottom;               // The bottom pixel location to to draw the delta image\n} qgf_delta_v1_t;\n// _Static_assert(sizeof(qgf_delta_v1_t) == 13, "qgf_delta_v1_t must be 13 bytes in v1 of QGF");

Frame data block

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 {\n    qgf_block_header_v1_t header;   // = { .type_id = 0x05, .neg_type_id = (~0x05), .length = N }\n    uint8_t               data[N];  // N data octets\n} qgf_data_v1_t;
', 50); const _hoisted_51 = [ _hoisted_1 ]; function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", null, _hoisted_51); } const quantum_painter_qgf = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); export { __pageData, quantum_painter_qgf as default };