diff options
Diffstat (limited to 'mons_image/src/qoi.c')
-rw-r--r-- | mons_image/src/qoi.c | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/mons_image/src/qoi.c b/mons_image/src/qoi.c new file mode 100644 index 0000000..c290c4e --- /dev/null +++ b/mons_image/src/qoi.c @@ -0,0 +1,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; +} |