summaryrefslogtreecommitdiff
path: root/qoi
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2026-07-09 23:51:55 -0400
committerLLLL Colonq <llll@colonq>2026-07-09 23:51:55 -0400
commit2bdcaf319b1d74ffbaccf08a58336f804761beab (patch)
treec21de6df74ec79b5574ad2b89fcc275d10847802 /qoi
Refactor into monorepo
Diffstat (limited to 'qoi')
-rw-r--r--qoi/.envrc1
-rw-r--r--qoi/.gitignore5
-rw-r--r--qoi/Makefile53
-rw-r--r--qoi/bjarne.qoibin0 -> 705778 bytes
-rw-r--r--qoi/include/lcq/qoi.h49
-rw-r--r--qoi/mrgreen.qoibin0 -> 614 bytes
-rw-r--r--qoi/packages.nix15
-rw-r--r--qoi/src/main.c58
-rw-r--r--qoi/src/qoi.c112
9 files changed, 293 insertions, 0 deletions
diff --git a/qoi/.envrc b/qoi/.envrc
new file mode 100644
index 0000000..c4b17d7
--- /dev/null
+++ b/qoi/.envrc
@@ -0,0 +1 @@
+use_flake
diff --git a/qoi/.gitignore b/qoi/.gitignore
new file mode 100644
index 0000000..df2b66d
--- /dev/null
+++ b/qoi/.gitignore
@@ -0,0 +1,5 @@
+/build
+/.direnv
+/TAGS
+/qoi
+/*.a \ No newline at end of file
diff --git a/qoi/Makefile b/qoi/Makefile
new file mode 100644
index 0000000..0a75636
--- /dev/null
+++ b/qoi/Makefile
@@ -0,0 +1,53 @@
+CC ?= gcc
+AR ?= ar
+override CPPFLAGS += -MMD -MP
+override CFLAGS += --std=c99 -g -Ideps/ -Isrc/ -Iinclude/ -Wall -Wextra -Wpedantic -Wconversion -Wformat-security -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Wnull-dereference -Wfloat-equal -Wundef -Wpointer-arith -Wbad-function-cast -Wlogical-op -Wmissing-braces -Wcast-align -Wstrict-overflow=5 -ftrapv
+override LDFLAGS += -g -lraylib
+
+BUILD = build_$(CC)
+
+SRCS := src/qoi.c
+CHK_SOURCES ?= $(SRCS)
+OBJECTS := $(SRCS:src/%.c=$(BUILD)/%.o)
+EXE := qoi
+LIB := libcolonq-qoi.a
+
+prefix ?= /usr/local
+exec_prefix ?= $(prefix)
+bindir ?= $(exec_prefix)/bin
+includedir ?= $(prefix)/include
+libdir ?= $(exec_prefix)/lib
+
+.PHONY: all clean install check-syntax
+
+all: $(EXE) $(LIB)
+
+$(EXE): $(BUILD)/main.o $(LIB)
+ $(CC) -o $@ $^ $(LDFLAGS)
+
+$(LIB): $(OBJECTS)
+ ar rcs $@ $^
+
+$(BUILD):
+ mkdir $(BUILD)/
+
+$(BUILD)/%.o: src/%.c | $(BUILD)
+ $(CC) $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
+
+clean:
+ -rm $(EXE)
+ -rm -r $(BUILD)/
+
+TAGS: $(SRCS)
+ etags $^
+
+install: $(EXE) $(LIB)
+ mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(libdir) $(DESTDIR)$(includedir)
+ install $(EXE) $(DESTDIR)$(bindir)/$(EXE)
+ install $(LIB) $(DESTDIR)$(libdir)/$(LIB)
+ cp -r include/* $(DESTDIR)$(includedir)
+
+check-syntax: TAGS
+ gcc $(CFLAGS) -fsyntax-only $(CHK_SOURCES)
+
+-include $(OBJECTS:.o=.d)
diff --git a/qoi/bjarne.qoi b/qoi/bjarne.qoi
new file mode 100644
index 0000000..89c5b33
--- /dev/null
+++ b/qoi/bjarne.qoi
Binary files differ
diff --git a/qoi/include/lcq/qoi.h b/qoi/include/lcq/qoi.h
new file mode 100644
index 0000000..b90c4b2
--- /dev/null
+++ b/qoi/include/lcq/qoi.h
@@ -0,0 +1,49 @@
+#ifndef LCOLONQ_QOI_H
+#define LCOLONQ_QOI_H
+
+#include <lcq/prelude.h>
+
+typedef struct {
+ u8 r, g, b, a;
+} qoi_color;
+static inline qoi_color qoi_color_new(u8 r, u8 g, u8 b, u8 a) {
+ qoi_color ret;
+ ret.r = r; ret.g = g; ret.b = b; ret.a = a;
+ return ret;
+}
+static inline u8 qoi_color_hash(qoi_color c) {
+ return (u8) (c.r * 3 + c.g * 5 + c.b * 7 + c.a * 11) % 64;
+}
+
+typedef enum {
+ QOI_CHANNELS_RGB = 3,
+ QOI_CHANNELS_RGBA = 4
+} qoi_channels;
+typedef enum {
+ QOI_COLORSPACE_SRGB = 0,
+ QOI_COLORSPACE_LINEAR = 1
+} qoi_colorspace;
+typedef struct {
+ u32 width, height;
+ u8 channels, colorspace;
+} qoi_header;
+
+typedef struct {
+ u8 *buf;
+ i64 len;
+ i64 cur;
+ qoi_color palette[64];
+} qoi_encoder;
+
+typedef struct {
+ u8 *buf;
+ i64 len;
+ i64 cur;
+ qoi_color palette[64];
+} qoi_decoder;
+
+void qoi_decoder_new(qoi_decoder *q, u8 *buf, i64 len);
+bool qoi_decode_header(qoi_decoder *q, qoi_header *h);
+bool qoi_decode(qoi_decoder *q, qoi_header *h, qoi_color *pixels);
+
+#endif
diff --git a/qoi/mrgreen.qoi b/qoi/mrgreen.qoi
new file mode 100644
index 0000000..3960a67
--- /dev/null
+++ b/qoi/mrgreen.qoi
Binary files differ
diff --git a/qoi/packages.nix b/qoi/packages.nix
new file mode 100644
index 0000000..d66e00b
--- /dev/null
+++ b/qoi/packages.nix
@@ -0,0 +1,15 @@
+pkgs: lcq: {
+ native = pkgs.stdenv.mkDerivation {
+ pname = "libcolonq-qoi";
+ version = "git";
+ src = ./.;
+ buildInputs = [
+ lcq.prelude.native
+ pkgs.raylib
+ ];
+ hardeningDisable = ["all"];
+ installPhase = ''
+ make prefix=$out install
+ '';
+ };
+}
diff --git a/qoi/src/main.c b/qoi/src/main.c
new file mode 100644
index 0000000..b44c682
--- /dev/null
+++ b/qoi/src/main.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+
+#include <raylib.h>
+
+#include <lcq/qoi.h>
+
+int main(int argc, char **argv) {
+ FILE *f;
+ u8 *buf;
+ i64 len;
+ qoi_decoder q;
+ qoi_header h;
+ qoi_color *pixels;
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s PATH\n", argv[0]);
+ return 1;
+ }
+ if (!(f = fopen(argv[1], "r"))) {
+ fprintf(stderr, "failed to open file: %s\n", argv[1]);
+ return 1;
+ }
+ fseek(f, 0, SEEK_END);
+ len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ buf = calloc((size_t) len, sizeof(u8));
+ fread(buf, sizeof(u8), (size_t) len, f);
+ qoi_decoder_new(&q, buf, len);
+ if (!qoi_decode_header(&q, &h)) {
+ fprintf(stderr, "failed to decode header\n");
+ return 1;
+ }
+ pixels = calloc(h.width * h.height, sizeof(qoi_color));
+ qoi_decode(&q, &h, pixels);
+
+ InitWindow(800, 450, "qoi test");
+ {
+ Image im;
+ Texture2D tex;
+ im.data = pixels;
+ im.width = (int) h.width;
+ im.height = (int) h.height;
+ im.format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;
+ im.mipmaps = 1;
+ tex = LoadTextureFromImage(im);
+ while (!WindowShouldClose()) {
+ BeginDrawing();
+ ClearBackground(RAYWHITE);
+ DrawText("hi", 0, 0, 100, BLACK);
+ DrawTexture(tex, 10, 10, WHITE);
+ EndDrawing();
+ }
+ CloseWindow();
+ }
+
+ return 0;
+}
diff --git a/qoi/src/qoi.c b/qoi/src/qoi.c
new file mode 100644
index 0000000..52545c9
--- /dev/null
+++ b/qoi/src/qoi.c
@@ -0,0 +1,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;
+}