~exec64/imv-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH] Add QOI support

Details
Message ID
<20230529185334.455213-1-ehmry@posteo.net>
DKIM signature
missing
Download raw message
Patch: +362 -0
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
Reply to thread Export thread (mbox)