:: limine / common / lib / qoi.c 3.8 KB raw

1
#include <stdint.h>
2
#include <stddef.h>
3
#include <lib/qoi.h>
4
#include <lib/misc.h>
5
#include <mm/pmm.h>
6
7
#define QOI_OP_INDEX 0x00
8
#define QOI_OP_DIFF  0x40
9
#define QOI_OP_LUMA  0x80
10
#define QOI_OP_RUN   0xc0
11
#define QOI_OP_RGB   0xfe
12
#define QOI_OP_RGBA  0xff
13
#define QOI_OP_MASK  0xc0
14
#define QOI_HEADER_SIZE  14
15
#define QOI_PADDING_SIZE  8
16
#define QOI_MAX_DIM    65536u
17
#define QOI_MAX_PIXELS ((size_t)400000000)
18
#define QOI_HASH(r, g, b, a) \
19
    ((((unsigned)(r) * 3u) + ((unsigned)(g) * 5u) + \
20
        ((unsigned)(b) * 7u) + ((unsigned)(a) * 11u)) & 63u)
21
22
static uint32_t qoi_be32(const uint8_t *p) {
23
    return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
24
           ((uint32_t)p[2] <<  8) |  (uint32_t)p[3];
25
}
26
27
/*  The decoded buffer is laid out as [total_bytes][padding to 16][pixels...].
28
    The 16-byte header lets qoi_free() recover the original allocation size
29
    without the caller having to track it.  */
30
static uint32_t *qoi_alloc_xrgb(size_t pixels) {
31
    size_t bytes = CHECKED_MUL(pixels, (size_t)4, return NULL);
32
    size_t total = CHECKED_ADD(bytes, (size_t)16, return NULL);
33
    void *raw = ext_mem_alloc(total);
34
    *(size_t *) raw = total;
35
    return (uint32_t *)((uint8_t *) raw + 16);
36
}
37
38
void qoi_free(uint8_t *buf) {
39
    if (buf) { uint8_t *raw = buf - 16;  pmm_free(raw, *(size_t *) raw); }
40
}
41
42
uint8_t *qoi_decode(const void *src, size_t src_size,
43
            int *out_w, int *out_h) {
44
    if (!src || src_size < QOI_HEADER_SIZE + QOI_PADDING_SIZE) return NULL;
45
    const uint8_t * p = src;
46
    if (p[0] != 'q' || p[1] != 'o' || p[2] != 'i' || p[3] != 'f')
47
        return NULL;
48
    uint32_t w = qoi_be32(p + 4), h = qoi_be32(p + 8);
49
    uint8_t channels = p[12];  /* p[13] is the colorspace tag, ignored. */
50
    if (w == 0 || h == 0 || w > QOI_MAX_DIM || h > QOI_MAX_DIM) return NULL;
51
    if (channels != 3 && channels != 4) return NULL;
52
    size_t pixels = CHECKED_MUL((size_t)w, (size_t)h, return NULL);
53
    if (pixels > QOI_MAX_PIXELS) return NULL;
54
    uint32_t *out = qoi_alloc_xrgb(pixels);
55
    if (out == NULL) return NULL;
56
    uint32_t index[64] = { 0 }, v;  int run = 0, dg;
57
    uint8_t r = 0, g = 0, b = 0, a = 0xff, b2;
58
    size_t pos = QOI_HEADER_SIZE, end = src_size - QOI_PADDING_SIZE;
59
    for (size_t px = 0; px < pixels; px++) {
60
        if (run > 0) run--; else {
61
            if (pos >= end) goto fail;
62
            uint8_t op = p[pos++];
63
            if (op == QOI_OP_RGB) {
64
                if (end - pos < 3) goto fail;
65
                r = p[pos++]; g = p[pos++]; b = p[pos++];
66
            } else if (op == QOI_OP_RGBA) {
67
                if (end - pos < 4) goto fail;
68
                r = p[pos++]; g = p[pos++]; b = p[pos++]; a = p[pos++];
69
            } else switch (op & QOI_OP_MASK) {
70
                case QOI_OP_INDEX:
71
                    v = index[op & 0x3f];
72
                    r = v;  g = v >> 8;  b = v >> 16;  a = v >> 24;
73
                    break;
74
                case QOI_OP_DIFF:
75
                    r += (int)((op >> 4) & 3u) - 2;
76
                    g += (int)((op >> 2) & 3u) - 2;
77
                    b += (int)( op       & 3u) - 2;
78
                    break;
79
                case QOI_OP_LUMA:
80
                    if (pos >= end) goto fail;
81
                    b2 = p[pos++];
82
                    dg = (int)(op & 0x3f) - 32;
83
                    r += dg + (int)((b2 >> 4) & 0xf) - 8;
84
                    g += dg;
85
                    b += dg + (int)( b2       & 0xf) - 8;
86
                    break;
87
                case QOI_OP_RUN: run = op & 0x3f; break;
88
            }
89
            index[QOI_HASH(r, g, b, a)] =
90
              (uint32_t)r        | ((uint32_t)g << 8) |
91
             ((uint32_t)b << 16) | ((uint32_t)a << 24);
92
        }
93
        out[px] = ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b;
94
    }
95
    *out_w = (int)w;  *out_h = (int)h;  return (uint8_t *) out;
96
fail:
97
    qoi_free((uint8_t *) out);  return NULL;
98
}
tab: 248 wrap: offon