summaryrefslogtreecommitdiff
path: root/quantum/painter/qgf.h
blob: 54585edd0415091264121328a8a6ad1731bdc270 (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
// Copyright 2021 Nick Brassel (@tzarc)
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

// Quantum Graphics File "QGF" File Format.
// See https://docs.qmk.fm/#/quantum_painter_qgf for more information.

#include <stdint.h>
#include <stdbool.h>

#include "qp_stream.h"
#include "qp_internal.h"

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// QGF structures

/////////////////////////////////////////
// Common block header

typedef struct QP_PACKED qgf_block_header_v1_t {
    uint8_t  type_id;     // See each respective block type below.
    uint8_t  neg_type_id; // Negated type ID, used for detecting parsing errors.
    uint32_t length : 24; // 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");

/////////////////////////////////////////
// Graphics descriptor

#define QGF_GRAPHICS_DESCRIPTOR_TYPEID 0x00

typedef struct QP_PACKED qgf_graphics_descriptor_v1_t {
    qgf_block_header_v1_t header;              // = { .type_id = 0x00, .neg_type_id = (~0x00), .length = 18 }
    uint32_t              magic : 24;          // 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
    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");

#define QGF_MAGIC 0x464751

/////////////////////////////////////////
// Frame offset descriptor

#define QGF_FRAME_OFFSET_DESCRIPTOR_TYPEID 0x01

typedef struct QP_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[0]; // '0' signifies that this struct is immediately followed by the frame offsets
} qgf_frame_offsets_v1_t;

_Static_assert(sizeof(qgf_frame_offsets_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_frame_offsets_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");

/////////////////////////////////////////
// Frame descriptor

#define QGF_FRAME_DESCRIPTOR_TYPEID 0x02

typedef struct QP_PACKED qgf_frame_v1_t {
    qgf_block_header_v1_t header;                 // = { .type_id = 0x02, .neg_type_id = (~0x02), .length = 6 }
    qp_image_format_t     format : 8;             // Frame format, see qp.h.
    uint8_t               flags;                  // Frame flags, see below.
    painter_compression_t compression_scheme : 8; // Compression scheme, see qp.h.
    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");

#define QGF_FRAME_FLAG_DELTA 0x02
#define QGF_FRAME_FLAG_TRANSPARENT 0x01

/////////////////////////////////////////
// Frame palette descriptor

#define QGF_FRAME_PALETTE_DESCRIPTOR_TYPEID 0x03

typedef struct QP_PACKED qgf_palette_entry_v1_t {
    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.
} qgf_palette_entry_v1_t;

_Static_assert(sizeof(qgf_palette_entry_v1_t) == 3, "Palette entry is not 3 bytes in size");

typedef struct QP_PACKED qgf_palette_v1_t {
    qgf_block_header_v1_t  header; // = { .type_id = 0x03, .neg_type_id = (~0x03), .length = (N * 3 * sizeof(uint8_t)) }
    qgf_palette_entry_v1_t hsv[0]; // N * hsv, where N is the number of palette entries depending on the frame format in the descriptor
} qgf_palette_v1_t;

_Static_assert(sizeof(qgf_palette_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_palette_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");

/////////////////////////////////////////
// Frame delta descriptor

#define QGF_FRAME_DELTA_DESCRIPTOR_TYPEID 0x04

typedef struct QP_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) == (sizeof(qgf_block_header_v1_t) + 8), "qgf_delta_v1_t must be 13 bytes in v1 of QGF");

/////////////////////////////////////////
// Frame data descriptor

#define QGF_FRAME_DATA_DESCRIPTOR_TYPEID 0x05

typedef struct QP_PACKED qgf_data_v1_t {
    qgf_block_header_v1_t header;  // = { .type_id = 0x05, .neg_type_id = (~0x05), .length = N }
    uint8_t               data[0]; // 0 signifies that this struct is immediately followed by the length of data specified in the header
} qgf_data_v1_t;

_Static_assert(sizeof(qgf_data_v1_t) == sizeof(qgf_block_header_v1_t), "qgf_data_v1_t must only contain qgf_block_header_v1_t in v1 of QGF");

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// QGF API

uint32_t qgf_get_total_size(qp_stream_t *stream);
bool     qgf_validate_stream(qp_stream_t *stream);
bool     qgf_validate_block_header(qgf_block_header_v1_t *desc, uint8_t expected_typeid, int32_t expected_length);
bool     qgf_read_graphics_descriptor(qp_stream_t *stream, uint16_t *image_width, uint16_t *image_height, uint16_t *frame_count, uint32_t *total_bytes);
bool     qgf_parse_format(qp_image_format_t format, uint8_t *bpp, bool *has_palette);
void     qgf_seek_to_frame_descriptor(qp_stream_t *stream, uint16_t frame_number);
bool     qgf_parse_frame_descriptor(qgf_frame_v1_t *frame_descriptor, uint8_t *bpp, bool *has_palette, bool *is_delta, painter_compression_t *compression_scheme, uint16_t *delay);