Support the QOI lossless image format by lifting a header from the
reference implementation.
https://qoiformat.org/
---
AUTHORS | 1 +
README.md | 1 +
meson.build | 1 +
src/backend_qoi.c | 84 ++++++++++++++
src/backend_qoi.h | 272 ++++++++++++++++++++++++++++++++++++++++++++++
src/main.c | 3 +
6 files changed, 362 insertions(+)
create mode 100644 src/backend_qoi.c
create mode 100644 src/backend_qoi.h
diff --git a/AUTHORS b/AUTHORS
index 2e13cf5..459bb59 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,6 +8,7 @@ People who have contributed to imv:
* Jose Diez
* Kenneth Hanley
* Julian Heinzel
+ * Emery Hemingway
* Hannes Körber
* Aleksandra Kosiacka
* Michal Koutenský
diff --git a/README.md b/README.md
index a0c4a03..81e97a6 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,7 @@ Features
* TIFF
* Various RAW formats
* Photoshop PSD files
+ * QOI
* Configurable key bindings and behaviour
* Highly scriptable with IPC via imv-msg
diff --git a/meson.build b/meson.build
index 67ea830..500465d 100644
--- a/meson.build
+++ b/meson.build
@@ -66,6 +66,7 @@ deps_for_imv = [
]
files_common = files(
+ 'src/backend_qoi.c',
'src/binds.c',
'src/bitmap.c',
'src/canvas.c',
diff --git a/src/backend_qoi.c b/src/backend_qoi.c
new file mode 100644
index 0000000..5b5f779
--- /dev/null
+++ b/src/backend_qoi.c
@@ -0,0 +1,84 @@
+#include "backend.h"
+#include "bitmap.h"
+#include "image.h"
+#include "source_private.h"
+
+#define QOI_IMPLEMENTATION
+#include "backend_qoi.h"
+
+
+struct private {
+ void *pixels;
+ qoi_desc desc;
+};
+
+static void free_private(void *raw_private)
+{
+ if (!raw_private) {
+ return;
+ }
+ struct private *private = raw_private;
+ if (private->pixels != NULL) {
+ free(private->pixels);
+ }
+ free(private);
+}
+
+static void load_image(void *raw_private, struct imv_image **image, int *frametime)
+{
+ *image = NULL;
+ *frametime = 0;
+
+ struct private *private = raw_private;
+
+ struct imv_bitmap *bmp = malloc(sizeof *bmp);
+ bmp->width = private->desc.width,
+ bmp->height = private->desc.height,
+ bmp->format = IMV_ABGR;
+ bmp->data = private->pixels;
+ private->pixels = NULL;
+
+ *image = imv_image_create_from_bitmap(bmp);
+}
+
+static const struct imv_source_vtable vtable = {
+ .load_first_frame = load_image,
+ .free = free_private,
+};
+
+static enum backend_result open_path(const char *path, struct imv_source **src)
+{
+ struct private *private = malloc(sizeof *private);
+
+ private->pixels = qoi_read(path, &private->desc, 4);
+ if (private->pixels == NULL) {
+ free(private);
+ return BACKEND_UNSUPPORTED;
+ }
+
+ *src = imv_source_create(&vtable, private);
+ return BACKEND_SUCCESS;
+}
+
+static enum backend_result open_memory(void *data, size_t len, struct imv_source **src)
+{
+ struct private *private = malloc(sizeof *private);
+
+ private->pixels = qoi_decode(data, len, &private->desc, 4);
+ if (private->pixels == NULL) {
+ free(private);
+ return BACKEND_UNSUPPORTED;
+ }
+
+ *src = imv_source_create(&vtable, private);
+ return BACKEND_SUCCESS;
+}
+
+const struct imv_backend imv_backend_qoi = {
+ .name = "qoi",
+ .description = "QOI reference implementation",
+ .website = "https://qoiformat.org/",
+ .license = "MIT",
+ .open_path = &open_path,
+ .open_memory = &open_memory,
+};
diff --git a/src/backend_qoi.h b/src/backend_qoi.h
new file mode 100644
index 0000000..c1368bf
--- /dev/null
+++ b/src/backend_qoi.h
@@ -0,0 +1,272 @@
+/*
+
+Copyright (c) 2021, Dominic Szablewski - https://phoboslab.org
+SPDX-License-Identifier: MIT
+
+
+QOI - The "Quite OK Image" format for fast, lossless image compression
+
+-- About
+
+QOI decodes images in a lossless format. Compared to stb_image and
+stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
+20% better compression.
+
+*/
+
+
+/* -----------------------------------------------------------------------------
+Header - Public functions */
+
+#ifndef QOI_H
+#define QOI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
+It is filled with the description read from the file header for qoi_read and
+qoi_decode.
+
+The colorspace in this qoi_desc is an enum where
+ 0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
+ 1 = all channels are linear
+You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
+informative. It will be saved to the file header, but does not affect
+how chunks are en-/decoded. */
+
+#define QOI_SRGB 0
+#define QOI_LINEAR 1
+
+typedef struct {
+ unsigned int width;
+ unsigned int height;
+ unsigned char channels;
+ unsigned char colorspace;
+} qoi_desc;
+
+#ifndef QOI_NO_STDIO
+
+/* Read and decode a QOI image from the file system. If channels is 0, the
+number of channels from the file header is used. If channels is 3 or 4 the
+output format will be forced into this number of channels.
+
+The function either returns NULL on failure (invalid data, or malloc or fopen
+failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
+will be filled with the description from the file header.
+
+The returned pixel data should be free()d after use. */
+
+void *qoi_read(const char *filename, qoi_desc *desc, int channels);
+
+#endif /* QOI_NO_STDIO */
+
+/* Decode a QOI image from memory.
+
+The function either returns NULL on failure (invalid parameters or malloc
+failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
+is filled with the description from the file header.
+
+The returned pixel data should be free()d after use. */
+
+void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* QOI_H */
+
+
+/* -----------------------------------------------------------------------------
+Implementation */
+
+#ifdef QOI_IMPLEMENTATION
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef QOI_MALLOC
+ #define QOI_MALLOC(sz) malloc(sz)
+ #define QOI_FREE(p) free(p)
+#endif
+#ifndef QOI_ZEROARR
+ #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
+#endif
+
+#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
+#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
+#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
+#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
+#define QOI_OP_RGB 0xfe /* 11111110 */
+#define QOI_OP_RGBA 0xff /* 11111111 */
+
+#define QOI_MASK_2 0xc0 /* 11000000 */
+
+#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
+#define QOI_MAGIC \
+ (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
+ ((unsigned int)'i') << 8 | ((unsigned int)'f'))
+#define QOI_HEADER_SIZE 14
+
+/* 2GB is the max file size that this implementation can safely handle. We guard
+against anything larger than that, assuming the worst case with 5 bytes per
+pixel, rounded down to a nice clean value. 400 million pixels ought to be
+enough for anybody. */
+#define QOI_PIXELS_MAX ((unsigned int)400000000)
+
+typedef union {
+ struct { unsigned char r, g, b, a; } rgba;
+ unsigned int v;
+} qoi_rgba_t;
+
+static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
+
+static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
+ unsigned int a = bytes[(*p)++];
+ unsigned int b = bytes[(*p)++];
+ unsigned int c = bytes[(*p)++];
+ unsigned int d = bytes[(*p)++];
+ return a << 24 | b << 16 | c << 8 | d;
+}
+
+void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
+ const unsigned char *bytes;
+ unsigned int header_magic;
+ unsigned char *pixels;
+ qoi_rgba_t index[64];
+ qoi_rgba_t px;
+ int px_len, chunks_len, px_pos;
+ int p = 0, run = 0;
+
+ if (
+ data == NULL || desc == NULL ||
+ (channels != 0 && channels != 3 && channels != 4) ||
+ size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
+ ) {
+ return NULL;
+ }
+
+ bytes = (const unsigned char *)data;
+
+ header_magic = qoi_read_32(bytes, &p);
+ desc->width = qoi_read_32(bytes, &p);
+ desc->height = qoi_read_32(bytes, &p);
+ desc->channels = bytes[p++];
+ desc->colorspace = bytes[p++];
+
+ if (
+ desc->width == 0 || desc->height == 0 ||
+ desc->channels < 3 || desc->channels > 4 ||
+ desc->colorspace > 1 ||
+ header_magic != QOI_MAGIC ||
+ desc->height >= QOI_PIXELS_MAX / desc->width
+ ) {
+ return NULL;
+ }
+
+ if (channels == 0) {
+ channels = desc->channels;
+ }
+
+ px_len = desc->width * desc->height * channels;
+ pixels = (unsigned char *) QOI_MALLOC(px_len);
+ if (!pixels) {
+ return NULL;
+ }
+
+ QOI_ZEROARR(index);
+ px.rgba.r = 0;
+ px.rgba.g = 0;
+ px.rgba.b = 0;
+ px.rgba.a = 255;
+
+ chunks_len = size - (int)sizeof(qoi_padding);
+ for (px_pos = 0; px_pos < px_len; px_pos += channels) {
+ if (run > 0) {
+ run--;
+ }
+ else if (p < chunks_len) {
+ int b1 = bytes[p++];
+
+ if (b1 == QOI_OP_RGB) {
+ px.rgba.r = bytes[p++];
+ px.rgba.g = bytes[p++];
+ px.rgba.b = bytes[p++];
+ }
+ else if (b1 == QOI_OP_RGBA) {
+ px.rgba.r = bytes[p++];
+ px.rgba.g = bytes[p++];
+ px.rgba.b = bytes[p++];
+ px.rgba.a = bytes[p++];
+ }
+ else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
+ px = index[b1];
+ }
+ else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
+ px.rgba.r += ((b1 >> 4) & 0x03) - 2;
+ px.rgba.g += ((b1 >> 2) & 0x03) - 2;
+ px.rgba.b += ( b1 & 0x03) - 2;
+ }
+ else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
+ int b2 = bytes[p++];
+ int vg = (b1 & 0x3f) - 32;
+ px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
+ px.rgba.g += vg;
+ px.rgba.b += vg - 8 + (b2 & 0x0f);
+ }
+ else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
+ run = (b1 & 0x3f);
+ }
+
+ index[QOI_COLOR_HASH(px) % 64] = px;
+ }
+
+ pixels[px_pos + 0] = px.rgba.r;
+ pixels[px_pos + 1] = px.rgba.g;
+ pixels[px_pos + 2] = px.rgba.b;
+
+ if (channels == 4) {
+ pixels[px_pos + 3] = px.rgba.a;
+ }
+ }
+
+ return pixels;
+}
+
+#ifndef QOI_NO_STDIO
+#include <stdio.h>
+
+void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
+ FILE *f = fopen(filename, "rb");
+ int size, bytes_read;
+ void *pixels, *data;
+
+ if (!f) {
+ return NULL;
+ }
+
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ if (size <= 0) {
+ fclose(f);
+ return NULL;
+ }
+ fseek(f, 0, SEEK_SET);
+
+ data = QOI_MALLOC(size);
+ if (!data) {
+ fclose(f);
+ return NULL;
+ }
+
+ bytes_read = fread(data, 1, size, f);
+ fclose(f);
+
+ pixels = qoi_decode(data, bytes_read, desc, channels);
+ QOI_FREE(data);
+ return pixels;
+}
+
+#endif /* QOI_NO_STDIO */
+#endif /* QOI_IMPLEMENTATION */
diff --git a/src/main.c b/src/main.c
index 06bd005..bfdd4bf 100644
--- a/src/main.c
+++ b/src/main.c
@@ -9,6 +9,7 @@ extern const struct imv_backend imv_backend_libtiff;
extern const struct imv_backend imv_backend_libjpeg;
extern const struct imv_backend imv_backend_libnsgif;
extern const struct imv_backend imv_backend_libheif;
+extern const struct imv_backend imv_backend_qoi;
int main(int argc, char **argv)
{
@@ -46,6 +47,8 @@ int main(int argc, char **argv)
imv_install_backend(imv, &imv_backend_libheif);
#endif
+ imv_install_backend(imv, &imv_backend_qoi);
+
if (!imv_load_config(imv)) {
imv_free(imv);
return 1;
--
2.40.1