|
@@ -28,8 +28,8 @@ SOFTWARE.
|
|
|
|
|
|
-- About
|
|
|
|
|
|
-QOI encodes and 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
|
|
|
+QOI encodes and 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.
|
|
|
|
|
|
|
|
@@ -45,7 +45,7 @@ stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
|
|
|
// the input pixel data.
|
|
|
qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
|
|
|
.width = 1920,
|
|
|
- .height = 1080,
|
|
|
+ .height = 1080,
|
|
|
.channels = 4,
|
|
|
.colorspace = QOI_SRGB
|
|
|
});
|
|
@@ -74,7 +74,7 @@ QOI_NO_STDIO before including this library.
|
|
|
This library uses malloc() and free(). To supply your own malloc implementation
|
|
|
you can define QOI_MALLOC and QOI_FREE before including this library.
|
|
|
|
|
|
-This library uses memset() to zero-initialize the index. To supply your own
|
|
|
+This library uses memset() to zero-initialize the index. To supply your own
|
|
|
implementation you can define QOI_ZEROARR before including this library.
|
|
|
|
|
|
|
|
@@ -91,9 +91,9 @@ struct qoi_header_t {
|
|
|
uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
|
|
|
};
|
|
|
|
|
|
-Images are encoded from top to bottom, left to right. The decoder and encoder
|
|
|
-start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An image is
|
|
|
-complete when all pixels specified by width * height have been covered.
|
|
|
+Images are encoded row by row, left to right, top to bottom. The decoder and
|
|
|
+encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
|
|
|
+image is complete when all pixels specified by width * height have been covered.
|
|
|
|
|
|
Pixels are encoded as
|
|
|
- a run of the previous pixel
|
|
@@ -101,20 +101,20 @@ Pixels are encoded as
|
|
|
- a difference to the previous pixel value in r,g,b
|
|
|
- full r,g,b or r,g,b,a values
|
|
|
|
|
|
-The color channels are assumed to not be premultiplied with the alpha channel
|
|
|
+The color channels are assumed to not be premultiplied with the alpha channel
|
|
|
("un-premultiplied alpha").
|
|
|
|
|
|
-A running array[64] (zero-initialized) of previously seen pixel values is
|
|
|
+A running array[64] (zero-initialized) of previously seen pixel values is
|
|
|
maintained by the encoder and decoder. Each pixel that is seen by the encoder
|
|
|
and decoder is put into this array at the position formed by a hash function of
|
|
|
the color value. In the encoder, if the pixel value at the index matches the
|
|
|
-current pixel, this index position is written to the stream as QOI_OP_INDEX.
|
|
|
+current pixel, this index position is written to the stream as QOI_OP_INDEX.
|
|
|
The hash function for the index is:
|
|
|
|
|
|
index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
|
|
|
|
|
|
-Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
|
|
|
-bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
|
|
|
+Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
|
|
|
+bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
|
|
|
values encoded in these data bits have the most significant bit on the left.
|
|
|
|
|
|
The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
|
|
@@ -135,11 +135,11 @@ The possible chunks are:
|
|
|
2-bit tag b00
|
|
|
6-bit index into the color index array: 0..63
|
|
|
|
|
|
-A valid encoder must not issue 7 or more consecutive QOI_OP_INDEX chunks to the
|
|
|
-index 0, to avoid confusion with the 8 byte end marker.
|
|
|
+A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
|
|
|
+same index. QOI_OP_RUN should be used instead.
|
|
|
|
|
|
|
|
|
-.- QOI_OP_DIFF -----------.
|
|
|
+.- QOI_OP_DIFF -----------.
|
|
|
| Byte[0] |
|
|
|
| 7 6 5 4 3 2 1 0 |
|
|
|
|-------+-----+-----+-----|
|
|
@@ -150,14 +150,16 @@ index 0, to avoid confusion with the 8 byte end marker.
|
|
|
2-bit green channel difference from the previous pixel between -2..1
|
|
|
2-bit blue channel difference from the previous pixel between -2..1
|
|
|
|
|
|
-The difference to the current channel values are using a wraparound operation,
|
|
|
+The difference to the current channel values are using a wraparound operation,
|
|
|
so "1 - 2" will result in 255, while "255 + 1" will result in 0.
|
|
|
|
|
|
-Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
|
|
|
+Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
|
|
|
0 (b00). 1 is stored as 3 (b11).
|
|
|
|
|
|
+The alpha value remains unchanged from the previous pixel.
|
|
|
|
|
|
-.- QOI_OP_LUMA -------------------------------------.
|
|
|
+
|
|
|
+.- QOI_OP_LUMA -------------------------------------.
|
|
|
| Byte[0] | Byte[1] |
|
|
|
| 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
|
|
|
|-------+-----------------+-------------+-----------|
|
|
@@ -168,18 +170,20 @@ Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
|
|
|
4-bit red channel difference minus green channel difference -8..7
|
|
|
4-bit blue channel difference minus green channel difference -8..7
|
|
|
|
|
|
-The green channel is used to indicate the general direction of change and is
|
|
|
+The green channel is used to indicate the general direction of change and is
|
|
|
encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
|
|
|
of the green channel difference and are encoded in 4 bits. I.e.:
|
|
|
- dr_dg = (last_px.r - cur_px.r) - (last_px.g - cur_px.g)
|
|
|
- db_dg = (last_px.b - cur_px.b) - (last_px.g - cur_px.g)
|
|
|
+ dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
|
|
|
+ db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
|
|
|
|
|
|
-The difference to the current channel values are using a wraparound operation,
|
|
|
+The difference to the current channel values are using a wraparound operation,
|
|
|
so "10 - 13" will result in 253, while "250 + 7" will result in 1.
|
|
|
|
|
|
-Values are stored as unsigned integers with a bias of 32 for the green channel
|
|
|
+Values are stored as unsigned integers with a bias of 32 for the green channel
|
|
|
and a bias of 8 for the red and blue channel.
|
|
|
|
|
|
+The alpha value remains unchanged from the previous pixel.
|
|
|
+
|
|
|
|
|
|
.- QOI_OP_RUN ------------.
|
|
|
| Byte[0] |
|
|
@@ -190,8 +194,8 @@ and a bias of 8 for the red and blue channel.
|
|
|
2-bit tag b11
|
|
|
6-bit run-length repeating the previous pixel: 1..62
|
|
|
|
|
|
-The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
|
|
|
-(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
|
|
|
+The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
|
|
|
+(b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
|
|
|
QOI_OP_RGBA tags.
|
|
|
|
|
|
|
|
@@ -206,6 +210,8 @@ QOI_OP_RGBA tags.
|
|
|
8-bit green channel value
|
|
|
8-bit blue channel value
|
|
|
|
|
|
+The alpha value remains unchanged from the previous pixel.
|
|
|
+
|
|
|
|
|
|
.- QOI_OP_RGBA ---------------------------------------------------.
|
|
|
| Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
|
|
@@ -232,17 +238,17 @@ Header - Public functions */
|
|
|
extern "C" {
|
|
|
#endif
|
|
|
|
|
|
-/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
|
|
|
-It describes either the input format (for qoi_write and qoi_encode), or is
|
|
|
+/* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
|
|
|
+It describes either the input format (for qoi_write and qoi_encode), or 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
|
|
|
+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
|
|
|
+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
|
|
|
-en-/decoding in any way. */
|
|
|
+how chunks are en-/decoded. */
|
|
|
|
|
|
#define QOI_SRGB 0
|
|
|
#define QOI_LINEAR 1
|
|
@@ -256,11 +262,11 @@ typedef struct {
|
|
|
|
|
|
#ifndef QOI_NO_STDIO
|
|
|
|
|
|
-/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
|
|
|
-system. The qoi_desc struct must be filled with the image width, height,
|
|
|
-number of channels (3 = RGB, 4 = RGBA) and the colorspace.
|
|
|
+/* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
|
|
|
+system. The qoi_desc struct must be filled with the image width, height,
|
|
|
+number of channels (3 = RGB, 4 = RGBA) and the colorspace.
|
|
|
|
|
|
-The function returns 0 on failure (invalid parameters, or fopen or malloc
|
|
|
+The function returns 0 on failure (invalid parameters, or fopen or malloc
|
|
|
failed) or the number of bytes written on success. */
|
|
|
|
|
|
int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
|
|
@@ -271,7 +277,7 @@ 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
|
|
|
+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. */
|
|
@@ -283,8 +289,8 @@ void *qoi_read(const char *filename, qoi_desc *desc, int channels);
|
|
|
|
|
|
/* Encode raw RGB or RGBA pixels into a QOI image in memory.
|
|
|
|
|
|
-The function either returns NULL on failure (invalid parameters or malloc
|
|
|
-failed) or a pointer to the encoded data on success. On success the out_len
|
|
|
+The function either returns NULL on failure (invalid parameters or malloc
|
|
|
+failed) or a pointer to the encoded data on success. On success the out_len
|
|
|
is set to the size in bytes of the encoded data.
|
|
|
|
|
|
The returned qoi data should be free()d after use. */
|
|
@@ -294,8 +300,8 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
|
|
|
|
|
|
/* 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
|
|
|
+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. */
|
|
@@ -340,8 +346,8 @@ Implementation */
|
|
|
#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
|
|
|
+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)
|
|
|
|
|
@@ -352,14 +358,14 @@ typedef union {
|
|
|
|
|
|
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
|
|
|
|
|
|
-void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
|
|
|
+static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
|
|
|
bytes[(*p)++] = (0xff000000 & v) >> 24;
|
|
|
bytes[(*p)++] = (0x00ff0000 & v) >> 16;
|
|
|
bytes[(*p)++] = (0x0000ff00 & v) >> 8;
|
|
|
bytes[(*p)++] = (0x000000ff & v);
|
|
|
}
|
|
|
|
|
|
-unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
|
|
|
+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)++];
|
|
@@ -385,8 +391,8 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
|
|
|
return NULL;
|
|
|
}
|
|
|
|
|
|
- max_size =
|
|
|
- desc->width * desc->height * (desc->channels + 1) +
|
|
|
+ max_size =
|
|
|
+ desc->width * desc->height * (desc->channels + 1) +
|
|
|
QOI_HEADER_SIZE + sizeof(qoi_padding);
|
|
|
|
|
|
p = 0;
|
|
@@ -412,7 +418,7 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
|
|
|
px_prev.rgba.b = 0;
|
|
|
px_prev.rgba.a = 255;
|
|
|
px = px_prev;
|
|
|
-
|
|
|
+
|
|
|
px_len = desc->width * desc->height * desc->channels;
|
|
|
px_end = px_len - desc->channels;
|
|
|
channels = desc->channels;
|
|
@@ -460,14 +466,14 @@ void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
|
|
|
|
|
|
if (
|
|
|
vr > -3 && vr < 2 &&
|
|
|
- vg > -3 && vg < 2 &&
|
|
|
+ vg > -3 && vg < 2 &&
|
|
|
vb > -3 && vb < 2
|
|
|
) {
|
|
|
bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
|
|
|
}
|
|
|
else if (
|
|
|
- vg_r > -9 && vg_r < 8 &&
|
|
|
- vg > -33 && vg < 32 &&
|
|
|
+ vg_r > -9 && vg_r < 8 &&
|
|
|
+ vg > -33 && vg < 32 &&
|
|
|
vg_b > -9 && vg_b < 8
|
|
|
) {
|
|
|
bytes[p++] = QOI_OP_LUMA | (vg + 32);
|
|
@@ -526,7 +532,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
|
|
|
desc->colorspace = bytes[p++];
|
|
|
|
|
|
if (
|
|
|
- desc->width == 0 || desc->height == 0 ||
|
|
|
+ desc->width == 0 || desc->height == 0 ||
|
|
|
desc->channels < 3 || desc->channels > 4 ||
|
|
|
desc->colorspace > 1 ||
|
|
|
header_magic != QOI_MAGIC ||
|
|
@@ -592,7 +598,7 @@ void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
|
|
|
index[QOI_COLOR_HASH(px) % 64] = px;
|
|
|
}
|
|
|
|
|
|
- if (channels == 4) {
|
|
|
+ if (channels == 4) {
|
|
|
*(qoi_rgba_t*)(pixels + px_pos) = px;
|
|
|
}
|
|
|
else {
|
|
@@ -621,11 +627,11 @@ int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
|
|
|
if (!encoded) {
|
|
|
fclose(f);
|
|
|
return 0;
|
|
|
- }
|
|
|
-
|
|
|
+ }
|
|
|
+
|
|
|
fwrite(encoded, 1, size, f);
|
|
|
fclose(f);
|
|
|
-
|
|
|
+
|
|
|
QOI_FREE(encoded);
|
|
|
return size;
|
|
|
}
|