220#define QOI_MALLOC(sz) gdMalloc(sz)
221#define QOI_FREE(p) gdFree(p)
222#define qoi_encode gdQoiEncode
223#define qoi_decode gdQoiDecode
247 unsigned char channels;
248 unsigned char colorspace;
260int qoi_write(
const char *filename,
const void *data,
const qoi_desc *desc);
272void *qoi_read(
const char *filename, qoi_desc *desc,
int channels);
284void *qoi_encode(
const void *data,
const qoi_desc *desc,
int *out_len);
294void *qoi_decode(
const void *data,
int size, qoi_desc *desc,
int channels);
304#ifdef QOI_IMPLEMENTATION
309#define QOI_MALLOC(sz) gdMalloc(sz)
310#define QOI_FREE(p) gdFree(p)
313#define QOI_ZEROARR(a) memset((a), 0, sizeof(a))
316#define QOI_OP_INDEX 0x00
317#define QOI_OP_DIFF 0x40
318#define QOI_OP_LUMA 0x80
319#define QOI_OP_RUN 0xc0
320#define QOI_OP_RGB 0xfe
321#define QOI_OP_RGBA 0xff
323#define QOI_MASK_2 0xc0
325#define QOI_COLOR_HASH(C) (C.rgba.r * 3 + C.rgba.g * 5 + C.rgba.b * 7 + C.rgba.a * 11)
327 (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | ((unsigned int)'i') << 8 | \
329#define QOI_HEADER_SIZE 14
335#define QOI_PIXELS_MAX ((unsigned int)400000000)
339 unsigned char r, g, b, a;
344static const unsigned char qoi_padding[8] = {0, 0, 0, 0, 0, 0, 0, 1};
346static void qoi_write_32(
unsigned char *bytes,
int *p,
unsigned int v)
348 bytes[(*p)++] = (0xff000000 & v) >> 24;
349 bytes[(*p)++] = (0x00ff0000 & v) >> 16;
350 bytes[(*p)++] = (0x0000ff00 & v) >> 8;
351 bytes[(*p)++] = (0x000000ff & v);
354static unsigned int qoi_read_32(
const unsigned char *bytes,
int *p)
356 unsigned int a = bytes[(*p)++];
357 unsigned int b = bytes[(*p)++];
358 unsigned int c = bytes[(*p)++];
359 unsigned int d = bytes[(*p)++];
360 return a << 24 | b << 16 | c << 8 | d;
363void *qoi_encode(
const void *data,
const qoi_desc *desc,
int *out_len)
365 int i, max_size, p, run;
366 int px_len, px_end, px_pos, channels;
367 unsigned char *bytes;
368 const unsigned char *pixels;
369 qoi_rgba_t index[64];
370 qoi_rgba_t px, px_prev;
372 if (data == NULL || out_len == NULL || desc == NULL || desc->width == 0 || desc->height == 0 ||
373 desc->channels < 3 || desc->channels > 4 || desc->colorspace > 1 ||
374 desc->height >= QOI_PIXELS_MAX / desc->width) {
379 desc->width * desc->height * (desc->channels + 1) + QOI_HEADER_SIZE +
sizeof(qoi_padding);
382 bytes = (
unsigned char *)QOI_MALLOC(max_size);
387 qoi_write_32(bytes, &p, QOI_MAGIC);
388 qoi_write_32(bytes, &p, desc->width);
389 qoi_write_32(bytes, &p, desc->height);
390 bytes[p++] = desc->channels;
391 bytes[p++] = desc->colorspace;
393 pixels = (
const unsigned char *)data;
401 px_prev.rgba.a = 255;
404 px_len = desc->width * desc->height * desc->channels;
405 px_end = px_len - desc->channels;
406 channels = desc->channels;
408 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
409 px.rgba.r = pixels[px_pos + 0];
410 px.rgba.g = pixels[px_pos + 1];
411 px.rgba.b = pixels[px_pos + 2];
414 px.rgba.a = pixels[px_pos + 3];
417 if (px.v == px_prev.v) {
419 if (run == 62 || px_pos == px_end) {
420 bytes[p++] = QOI_OP_RUN | (run - 1);
427 bytes[p++] = QOI_OP_RUN | (run - 1);
431 index_pos = QOI_COLOR_HASH(px) & (64 - 1);
433 if (index[index_pos].v == px.v) {
434 bytes[p++] = QOI_OP_INDEX | index_pos;
436 index[index_pos] = px;
438 if (px.rgba.a == px_prev.rgba.a) {
439 signed char vr = px.rgba.r - px_prev.rgba.r;
440 signed char vg = px.rgba.g - px_prev.rgba.g;
441 signed char vb = px.rgba.b - px_prev.rgba.b;
443 signed char vg_r = vr - vg;
444 signed char vg_b = vb - vg;
446 if (vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2) {
447 bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
448 }
else if (vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 &&
450 bytes[p++] = QOI_OP_LUMA | (vg + 32);
451 bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
453 bytes[p++] = QOI_OP_RGB;
454 bytes[p++] = px.rgba.r;
455 bytes[p++] = px.rgba.g;
456 bytes[p++] = px.rgba.b;
459 bytes[p++] = QOI_OP_RGBA;
460 bytes[p++] = px.rgba.r;
461 bytes[p++] = px.rgba.g;
462 bytes[p++] = px.rgba.b;
463 bytes[p++] = px.rgba.a;
470 for (i = 0; i < (int)
sizeof(qoi_padding); i++) {
471 bytes[p++] = qoi_padding[i];
478void *qoi_decode(
const void *data,
int size, qoi_desc *desc,
int channels)
480 const unsigned char *bytes;
481 unsigned int header_magic;
482 unsigned char *pixels;
483 qoi_rgba_t index[64];
485 int px_len, chunks_len, px_pos;
488 if (data == NULL || desc == NULL || (channels != 0 && channels != 3 && channels != 4) ||
489 size < QOI_HEADER_SIZE + (
int)
sizeof(qoi_padding)) {
493 bytes = (
const unsigned char *)data;
495 if (memcmp(bytes + size -
sizeof(qoi_padding), qoi_padding,
sizeof(qoi_padding)) != 0) {
499 header_magic = qoi_read_32(bytes, &p);
500 desc->width = qoi_read_32(bytes, &p);
501 desc->height = qoi_read_32(bytes, &p);
502 desc->channels = bytes[p++];
503 desc->colorspace = bytes[p++];
505 if (desc->width == 0 || desc->height == 0 || desc->channels < 3 || desc->channels > 4 ||
506 desc->colorspace > 1 || header_magic != QOI_MAGIC ||
507 desc->height >= QOI_PIXELS_MAX / desc->width) {
512 channels = desc->channels;
515 px_len = desc->width * desc->height * channels;
516 pixels = (
unsigned char *)QOI_MALLOC(px_len);
527 chunks_len = size - (int)
sizeof(qoi_padding);
528 for (px_pos = 0; px_pos < px_len; px_pos += channels) {
531 }
else if (p < chunks_len) {
534 if (b1 == QOI_OP_RGB) {
535 if (p + 3 > chunks_len) {
539 px.rgba.r = bytes[p++];
540 px.rgba.g = bytes[p++];
541 px.rgba.b = bytes[p++];
542 }
else if (b1 == QOI_OP_RGBA) {
543 if (p + 4 > chunks_len) {
547 px.rgba.r = bytes[p++];
548 px.rgba.g = bytes[p++];
549 px.rgba.b = bytes[p++];
550 px.rgba.a = bytes[p++];
551 }
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
553 }
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
554 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
555 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
556 px.rgba.b += (b1 & 0x03) - 2;
557 }
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
558 if (p >= chunks_len) {
563 int vg = (b1 & 0x3f) - 32;
564 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
566 px.rgba.b += vg - 8 + (b2 & 0x0f);
567 }
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
571 index[QOI_COLOR_HASH(px) & (64 - 1)] = px;
577 pixels[px_pos + 0] = px.rgba.r;
578 pixels[px_pos + 1] = px.rgba.g;
579 pixels[px_pos + 2] = px.rgba.b;
582 pixels[px_pos + 3] = px.rgba.a;
592int qoi_write(
const char *filename,
const void *data,
const qoi_desc *desc)
594 FILE *f = fopen(filename,
"wb");
602 encoded = qoi_encode(data, desc, &size);
608 fwrite(encoded, 1, size, f);
614 return err ? 0 : size;
617void *qoi_read(
const char *filename, qoi_desc *desc,
int channels)
619 FILE *f = fopen(filename,
"rb");
620 int size, bytes_read;
627 fseek(f, 0, SEEK_END);
629 if (size <= 0 || fseek(f, 0, SEEK_SET) != 0) {
634 data = QOI_MALLOC(size);
640 bytes_read = fread(data, 1, size, f);
642 pixels = (bytes_read != size) ? NULL : qoi_decode(data, bytes_read, desc, channels);