aboutsummaryrefslogtreecommitdiff
path: root/mons_image/src/qoi.c
blob: c290c4efed3859860d1cf23358a585cbc8b2a1d6 (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
137
138
139
140
#include <stdlib.h>
#include <stdio.h>
#include <byteswap.h>
#include <inttypes.h>
#include "qoi.h"
#include "image.h"

#define MONS_QOI_RGB 3
#define MONS_QOI_RGBA 4
#define MONS_QOI_SRGB_LINEAR_ALPHA 0
#define MONS_QOI_LINEAR 1
#define MONS_QOI_HEADER_LEN 14

#define MONS_QOI_OP_RGB 0b11111110
#define MONS_QOI_OP_RGBA 0b11111111
#define MONS_QOI_OP_INDEX 0b00000000
#define MONS_QOI_OP_DIFF 0b01000000
#define MONS_QOI_OP_LUMA 0b10000000
#define MONS_QOI_OP_RUN 0b11000000

typedef struct mons_qoi_header {
    char magic[4];
    uint32_t width;
    uint32_t height;
    uint8_t channels;
    uint8_t colorspace;
} mons_qoi_header;

unsigned char pixel_index_hash(mons_pixel pixel) {
    return (pixel.r * 3 + pixel.g * 5 + pixel.b * 7 + pixel.a * 11) % 64;
}

mons_image mons_load_qoi(FILE *stream) {
    mons_qoi_header header;
    if (fread(&header, MONS_QOI_HEADER_LEN, 1, stream) != 1) {
        fprintf(stderr, "Error reading QOI header");
        return (mons_image){0};
    }
    header.width = __bswap_32(header.width);
    header.height = __bswap_32(header.height);
    mons_pixel previous = {0, 0, 0, 255};
    mons_pixel previously_seen[64] = {0};
    mons_image output = {
        .width = header.width,
        .height = header.height,
        .data = calloc(4, header.width * header.height),
    };
    for (size_t i = 0; i < header.width * header.height; i++) {
        unsigned char op;
        if (fread(&op, 1, 1, stream) != 1) {
            fprintf(stderr, "Failed reading operation byte!");
            return (mons_image){0};
        }
        switch (op) {
        case MONS_QOI_OP_RGB: {
            unsigned char rgb[3];
            if (fread(&rgb, 1, 3, stream) != 3) {
                fprintf(stderr, "Failed reading RGB data!");
                return (mons_image){0};
            }
            output.data[i] = (mons_pixel){
                .r = rgb[0],
                .g = rgb[1],
                .b = rgb[2],
                .a = previous.a,
            };
            break;
        }
        case MONS_QOI_OP_RGBA: {
            unsigned char rgba[4];
            if (fread(&rgba, 1, 4, stream) != 4) {
                fprintf(stderr, "Failed reading RGBA data!");
                return (mons_image){0};
            }
            output.data[i] = (mons_pixel){
                .r = rgba[0],
                .g = rgba[1],
                .b = rgba[2],
                .a = rgba[3],
            };
            break;
        }
        default:
            switch (op & 0b11000000) {
            case MONS_QOI_OP_INDEX: {
                unsigned char index = op & 0b00111111;
                output.data[i] = previously_seen[index];
                break;
            }
            case MONS_QOI_OP_DIFF: {
                char r_diff = ((op & 0b00110000) >> 4) - 2;
                char g_diff = ((op & 0b00001100) >> 2) - 2;
                char b_diff = (op & 0b00000011) - 2;
                output.data[i] = (mons_pixel){
                    .r = previous.r + r_diff,
                    .g = previous.g + g_diff,
                    .b = previous.b + b_diff,
                    .a = previous.a,
                };
                break;
            }
            case MONS_QOI_OP_LUMA: {
                int g_diff = (op & 0b00111111) - 32;
                unsigned char second_byte;
                if (fread(&second_byte, 1, 1, stream) != 1) {
                    fprintf(stderr,
                            "Failed to read second byte of luma operation!");
                    return (mons_image){0};
                }
                int r_diff = g_diff - 8 + (((second_byte & 0b11110000) >> 4));
                int b_diff = g_diff - 8 + (second_byte & 0b00001111);
                output.data[i] = (mons_pixel){
                    .r = previous.r + r_diff,
                    .g = previous.g + g_diff,
                    .b = previous.b + b_diff,
                    .a = previous.a,
                };
                break;
            }
            case MONS_QOI_OP_RUN: {
                unsigned char run = (op & 0b00111111) + 1;
                for (int j = 0; j < run; j++) {
                    output.data[i++] = previous;
                }
                i--;
                break;
            }
            default:
                break;
            }
            break;
        }
        previous = output.data[i];
        unsigned int hash = pixel_index_hash(output.data[i]);
        previously_seen[hash] = output.data[i];
    }
    fclose(stream);

    return output;
}