summaryrefslogtreecommitdiff
path: root/qoi/src/qoi.c
blob: 52545c960faef0e362935ab60642015f6543a9bb (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
#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;
}