This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch master
in repository legacy-imlib2.
View the commit online.
commit 5d12f287a8673c99f26cf44698192d5f5641032c
Author: NRK <n...@disroot.org>
AuthorDate: Sat Mar 8 17:55:01 2025 +0000
QOI Saver: add one
---
src/modules/loaders/loader_qoi.c | 190 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 189 insertions(+), 1 deletion(-)
diff --git a/src/modules/loaders/loader_qoi.c b/src/modules/loaders/loader_qoi.c
index 2bc3c38..d421a79 100644
--- a/src/modules/loaders/loader_qoi.c
+++ b/src/modules/loaders/loader_qoi.c
@@ -7,17 +7,22 @@
// decoder code taken from commit 8431e3f7:
// https://codeberg.org/NRK/slashtmp/src/branch/master/compression/qoi-dec.c
+// encoder code taken from commit d81a0c4c:
+// https://codeberg.org/NRK/slashtmp/src/branch/master/compression/qoi-enc.c
//
// simple qoi decoder: https://qoiformat.org/
//
// This is free and unencumbered software released into the public domain.
// For more information, please refer to <https://unlicense.org/>
#define QOIDEC_API static
+#define QOIENC_API static
#define DBG_PFX "LDR-qoi"
#if IMLIB2_DEBUG
#define QOIDEC_ASSERT(X) do { if (!(X)) D("%d: %s\n", __LINE__, #X); } while (0)
+#define QOIENC_ASSERT(X) QOIDEC_ASSERT(X)
#else
#define QOIDEC_ASSERT(X)
+#define QOIENC_ASSERT(X)
#endif
static const char *const _formats[] = { "qoi" };
@@ -40,6 +45,7 @@ typedef enum {
QOIDEC_OK,
QOIDEC_NOT_QOI, QOIDEC_CORRUPTED, QOIDEC_ZERO_DIM, QOIDEC_TOO_LARGE,
} QoiDecStatus;
+
QOIDEC_API QoiDecStatus qoi_dec_init(QoiDecCtx * ctx, const void *buffer,
ptrdiff_t size);
QOIDEC_API QoiDecStatus qoi_dec(QoiDecCtx * ctx);
@@ -188,6 +194,148 @@ qoi_dec(QoiDecCtx *ctx)
return QOIDEC_OK;
}
+//////////// END QoiDec library
+
+typedef struct {
+ uint8_t data[15];
+ int8_t len;
+} QoiEncResult;
+
+typedef struct {
+ uint32_t prev;
+ uint8_t run:7;
+ uint8_t no_alpha:1;
+ uint32_t dict[64];
+} QoiEncCtx;
+
+enum { QOIENC_NO_ALPHA = 1 << 0, QOIENC_IS_SRGB = 1 << 1 };
+
+/// ctx is assumed to be zero initialized by the caller
+QOIENC_API QoiEncResult
+qoi_enc_init(QoiEncCtx *ctx, uint32_t width, uint32_t height, uint32_t flags)
+{
+ QoiEncResult res = {.len = 14 };
+ uint8_t *p = res.data;
+
+ QOIENC_ASSERT((flags & ~UINT32_C(3)) == 0);
+ QOIENC_ASSERT(ctx->run == 0 && ctx->dict[63] == 0x0);
+
+ *p++ = 'q';
+ *p++ = 'o';
+ *p++ = 'i';
+ *p++ = 'f';
+ *p++ = (uint8_t) (width >> 24);
+ *p++ = (uint8_t) (width >> 16);
+ *p++ = (uint8_t) (width >> 8);
+ *p++ = (uint8_t) (width >> 0);
+ *p++ = (uint8_t) (height >> 24);
+ *p++ = (uint8_t) (height >> 16);
+ *p++ = (uint8_t) (height >> 8);
+ *p++ = (uint8_t) (height >> 0);
+ *p++ = (flags & QOIENC_NO_ALPHA) ? 3 : 4;
+ *p++ = (flags & QOIENC_IS_SRGB) ? 0 : 1;
+ ctx->prev = UINT32_C(0xFF) << 24;
+ ctx->no_alpha = !!(flags & QOIENC_NO_ALPHA);
+
+ return res;
+}
+
+QOIENC_API QoiEncResult
+qoi_enc(QoiEncCtx *ctx, uint32_t pixel)
+{
+ enum {
+ OP_RUN = 0xC0, OP_IDX = 0x0, OP_DIFF = 0x40, OP_LUMA = 0x80,
+ OP_RGB = 0xFE, OP_RGBA = 0xFF
+ };
+
+ QoiEncResult res = { 0 };
+ uint8_t *p = res.data;
+
+ if (ctx->no_alpha)
+ pixel |= UINT32_C(0xFF) << 24;
+
+ if (pixel == ctx->prev)
+ {
+ if (++ctx->run == 62)
+ {
+ *p++ = OP_RUN | (ctx->run - 1);
+ ctx->run = 0;
+ }
+ return (res.len = (int8_t) (p - res.data)), res;
+ }
+
+ if (ctx->run > 0)
+ {
+ *p++ = OP_RUN | (ctx->run - 1);
+ ctx->run = 0;
+ }
+ uint32_t prev = ctx->prev;
+ ctx->prev = pixel;
+
+ uint32_t a = ((pixel >> 24) & 0xFF), r = ((pixel >> 16) & 0xFF),
+ g = ((pixel >> 8) & 0xFF), b = ((pixel >> 0) & 0xFF);
+ uint32_t idx = (r * 3 + g * 5 + b * 7 + a * 11) & 63;
+ if (ctx->dict[idx] == pixel)
+ {
+ *p++ = OP_IDX | idx;
+ return (res.len = (int8_t) (p - res.data)), res;
+ }
+ ctx->dict[idx] = pixel;
+
+ int8_t dr = (int8_t) (r - ((prev >> 16) & 0xFF));
+ int8_t dg = (int8_t) (g - ((prev >> 8) & 0xFF));
+ int8_t db = (int8_t) (b - ((prev >> 0) & 0xFF));
+ int8_t dr_g = dr - dg;
+ int8_t db_g = db - dg;
+ if (a != (prev >> 24))
+ {
+ *p++ = OP_RGBA;
+ *p++ = r;
+ *p++ = g;
+ *p++ = b;
+ *p++ = a;
+ }
+ else if (dr >= -2 && dg >= -2 && db >= -2 && dr <= 1 && dg <= 1 && db <= 1)
+ {
+ *p++ = OP_DIFF | (((dr + 2) & 0x3) << 4) |
+ (((dg + 2) & 0x3) << 2) | (((db + 2) & 0x3) << 0);
+ }
+ else if (dg >= -32 && dg < 32 &&
+ dr_g >= -8 && dr_g < 8 && db_g >= -8 && db_g < 8)
+ {
+ *p++ = OP_LUMA | ((dg + 32) & 0x3F);
+ *p++ = ((dr_g + 8) << 4) | (db_g + 8);
+ }
+ else
+ {
+ *p++ = OP_RGB;
+ *p++ = r;
+ *p++ = g;
+ *p++ = b;
+ }
+ return (res.len = (int8_t) (p - res.data)), res;
+}
+
+QOIENC_API QoiEncResult
+qoi_enc_finish(QoiEncCtx *ctx)
+{
+ QoiEncResult res = { 0 };
+ uint8_t *p = res.data;
+ if (ctx->run > 0)
+ *p++ = 0xC0 | (ctx->run - 1);
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x0;
+ *p++ = 0x1;
+ return (res.len = (int8_t) (p - res.data)), res;
+}
+
+//////////// END QoiEnc library
+
static int
_load(ImlibImage *im, int load_data)
{
@@ -216,4 +364,44 @@ _load(ImlibImage *im, int load_data)
return LOAD_SUCCESS;
}
-IMLIB_LOADER(_formats, _load, NULL);
+static int
+_out(FILE *f, QoiEncResult res)
+{
+ return fwrite(res.data, 1, res.len, f) == (size_t)res.len;
+}
+
+static int
+_save(ImlibImage *im)
+{
+ FILE *f = im->fi->fp;
+ QoiEncCtx ctx[1] = { 0 };
+ int flags = 0;
+ int i, j;
+ int w = im->w, h = im->h;
+ uint32_t *imdata = im->data;
+
+ if (!im->has_alpha)
+ flags |= QOIENC_NO_ALPHA;
+
+ if (!_out(f, qoi_enc_init(ctx, w, h, flags)))
+ return LOAD_BADFILE;
+
+ for (i = 0; i < h; ++i)
+ {
+ for (j = 0; j < w; ++j)
+ {
+ if (!_out(f, qoi_enc(ctx, imdata[i * w + j])))
+ return LOAD_BADFILE;
+ }
+
+ if (im->lc && __imlib_LoadProgressRows(im, i, 1))
+ return LOAD_BREAK;
+ }
+
+ if (!_out(f, qoi_enc_finish(ctx)))
+ return LOAD_BADFILE;
+
+ return LOAD_SUCCESS;
+}
+
+IMLIB_LOADER(_formats, _load, _save);
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.