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
|
#include <lcq/qoi.h>
/* encoder */
static void write_u32_be(qoi_encoder *q) {
}
/* decoder */
static u32 read_u32_be(qoi_decoder *q) {
u32 ret = 0;
if (q->cur + 4 >= q->len) return 0xffffffff;
ret |= q->buf[q->cur++] << 24;
ret |= q->buf[q->cur++] << 16;
ret |= q->buf[q->cur++] << 8;
ret |= q->buf[q->cur++];
return ret;
}
typedef enum {
QOI_OP_INVALID = -3,
QOI_OP_RGB = -2,
QOI_OP_RGBA = -1,
QOI_OP_INDEX = 0,
QOI_OP_DIFF = 1,
QOI_OP_LUMA = 2,
QOI_OP_RUN = 3
} qoi_op;
static qoi_op cur_op(qoi_decoder *q) {
u8 v;
if (q->cur >= q->len) return QOI_OP_INVALID;
v = q->buf[q->cur];
if ((i8) v == QOI_OP_RGB) return QOI_OP_RGB;
if ((i8) v == QOI_OP_RGBA) return QOI_OP_RGBA;
return v >> 6;
}
void qoi_decoder_new(qoi_decoder *q, u8 *buf, i64 len) {
i64 i;
q->buf = buf;
q->len = len;
q->cur = 0;
for (i = 0; i < 64; ++i) q->palette[i] = qoi_color_new(0, 0, 0, 0);
}
bool qoi_decode_header(qoi_decoder *q, qoi_header *h) {
if (q->cur + 14 > q->len) return false;
if (read_u32_be(q) != ('q' << 24 | 'o' << 16 | 'i' << 8 | 'f')) {
return false;
}
h->width = read_u32_be(q);
h->height = read_u32_be(q);
h->channels = q->buf[q->cur++];
h->colorspace = q->buf[q->cur++];
return true;
}
bool qoi_decode(qoi_decoder *q, qoi_header *h, qoi_color *pixels) {
i64 len = h->width * h->height;
i64 pidx = 0;
qoi_color c = qoi_color_new(0, 0, 0, 255);
u8 v = 0, w = 0;
u8 padding[8] = {0, 0, 0, 0, 0, 0, 0, 1};
i64 i;
for (i = 0; i < 8; ++i) if (padding[i] != q->buf[q->len - 8 + i]) return false;
while (q->cur < q->len - 8 && pidx < len) {
/* we never need to range-check q->cur mid-loop, since we know the padding exists */
switch (cur_op(q)) {
case QOI_OP_INVALID: return false;
case QOI_OP_RGB:
q->cur++;
c.r = q->buf[q->cur++];
c.g = q->buf[q->cur++];
c.b = q->buf[q->cur++];
pixels[pidx++] = c;
break;
case QOI_OP_RGBA:
q->cur++;
c.r = q->buf[q->cur++];
c.g = q->buf[q->cur++];
c.b = q->buf[q->cur++];
c.a = q->buf[q->cur++];
pixels[pidx++] = c;
break;
case QOI_OP_INDEX:
v = q->buf[q->cur++] & 0x3f /* 0b111111 */;
c = q->palette[v];
pixels[pidx++] = c;
break;
case QOI_OP_DIFF: {
v = q->buf[q->cur++];
c.r += (u8) ((v >> 4 & 0x3) - 2);
c.g += (u8) ((v >> 2 & 0x3) - 2);
c.b += (u8) ((v & 0x3) - 2);
pixels[pidx++] = c;
break;
}
case QOI_OP_LUMA: {
v = (q->buf[q->cur++] & 0x3f) - 32;
w = q->buf[q->cur++];
c.r += (u8) (((w >> 4) & 0xf) - 8 + v);
c.g += v;
c.b += (u8) ((w & 0xf) - 8 + v);
pixels[pidx++] = c;
break;
}
case QOI_OP_RUN:
v = (q->buf[q->cur++] & 0x3f) + 1;
for (; v > 0; v--) {
pixels[pidx++] = c;
}
break;
default: return false;
}
q->palette[qoi_color_hash(c)] = c;
}
return true;
}
|