The OpenD Programming Language

1 module gamut.codecs.qoi;
2 
3 /*
4 
5 QOI - The "Quite OK Image" format for fast, lossless image compression
6 
7 Dominic Szablewski - https://phoboslab.org
8 
9 
10 -- LICENSE: The MIT License(MIT)
11 
12 Copyright(c) 2021 Dominic Szablewski
13 Copyright(c) 2022 Guillaume Piolat (D translation)
14 
15 Permission is hereby granted, free of charge, to any person obtaining a copy of
16 this software and associated documentation files(the "Software"), to deal in
17 the Software without restriction, including without limitation the rights to
18 use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
19 of the Software, and to permit persons to whom the Software is furnished to do
20 so, subject to the following conditions :
21 The above copyright notice and this permission notice shall be included in all
22 copies or substantial portions of the Software.
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30 
31 
32 -- About
33 
34 QOI encodes and decodes images in a lossless format. Compared to stb_image and
35 stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
36 20% better compression.
37 
38 
39 -- Documentation
40 
41 This library provides the following functions;
42 - qoi_decode  -- decode the raw bytes of a QOI image from memory
43 - qoi_encode  -- encode an rgba buffer into a QOI image in memory
44 
45 
46 -- Data Format
47 
48 A QOI file has a 14 byte header, followed by any number of data "chunks" and an
49 8-byte end marker.
50 
51 struct qoi_header_t {
52     char     magic[4];   // magic bytes "qoif"
53     uint32_t width;      // image width in pixels (BE)
54     uint32_t height;     // image height in pixels (BE)
55     uint8_t  channels;   // 3 = RGB, 4 = RGBA
56     uint8_t  colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
57 };
58 
59 Images are encoded row by row, left to right, top to bottom. The decoder and
60 encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
61 image is complete when all pixels specified by width * height have been covered.
62 
63 Pixels are encoded as
64  - a run of the previous pixel
65  - an index into an array of previously seen pixels
66  - a difference to the previous pixel value in r,g,b
67  - full r,g,b or r,g,b,a values
68 
69 The color channels are assumed to not be premultiplied with the alpha channel
70 ("un-premultiplied alpha").
71 
72 A running array[64] (zero-initialized) of previously seen pixel values is
73 maintained by the encoder and decoder. Each pixel that is seen by the encoder
74 and decoder is put into this array at the position formed by a hash function of
75 the color value. In the encoder, if the pixel value at the index matches the
76 current pixel, this index position is written to the stream as QOI_OP_INDEX.
77 The hash function for the index is:
78 
79     index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
80 
81 Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
82 bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
83 values encoded in these data bits have the most significant bit on the left.
84 
85 The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
86 presence of an 8-bit tag first.
87 
88 The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
89 
90 
91 The possible chunks are:
92 
93 
94 .- QOI_OP_INDEX ----------.
95 |         Byte[0]         |
96 |  7  6  5  4  3  2  1  0 |
97 |-------+-----------------|
98 |  0  0 |     index       |
99 `-------------------------`
100 2-bit tag b00
101 6-bit index into the color index array: 0..63
102 
103 A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
104 same index. QOI_OP_RUN should be used instead.
105 
106 
107 .- QOI_OP_DIFF -----------.
108 |         Byte[0]         |
109 |  7  6  5  4  3  2  1  0 |
110 |-------+-----+-----+-----|
111 |  0  1 |  dr |  dg |  db |
112 `-------------------------`
113 2-bit tag b01
114 2-bit   red channel difference from the previous pixel between -2..1
115 2-bit green channel difference from the previous pixel between -2..1
116 2-bit  blue channel difference from the previous pixel between -2..1
117 
118 The difference to the current channel values are using a wraparound operation,
119 so "1 - 2" will result in 255, while "255 + 1" will result in 0.
120 
121 Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
122 0 (b00). 1 is stored as 3 (b11).
123 
124 The alpha value remains unchanged from the previous pixel.
125 
126 
127 .- QOI_OP_LUMA -------------------------------------.
128 |         Byte[0]         |         Byte[1]         |
129 |  7  6  5  4  3  2  1  0 |  7  6  5  4  3  2  1  0 |
130 |-------+-----------------+-------------+-----------|
131 |  1  0 |  green diff     |   dr - dg   |  db - dg  |
132 `---------------------------------------------------`
133 2-bit tag b10
134 6-bit green channel difference from the previous pixel -32..31
135 4-bit   red channel difference minus green channel difference -8..7
136 4-bit  blue channel difference minus green channel difference -8..7
137 
138 The green channel is used to indicate the general direction of change and is
139 encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
140 of the green channel difference and are encoded in 4 bits. I.e.:
141     dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
142     db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
143 
144 The difference to the current channel values are using a wraparound operation,
145 so "10 - 13" will result in 253, while "250 + 7" will result in 1.
146 
147 Values are stored as unsigned integers with a bias of 32 for the green channel
148 and a bias of 8 for the red and blue channel.
149 
150 The alpha value remains unchanged from the previous pixel.
151 
152 
153 .- QOI_OP_RUN ------------.
154 |         Byte[0]         |
155 |  7  6  5  4  3  2  1  0 |
156 |-------+-----------------|
157 |  1  1 |       run       |
158 `-------------------------`
159 2-bit tag b11
160 6-bit run-length repeating the previous pixel: 1..62
161 
162 The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
163 (b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
164 QOI_OP_RGBA tags.
165 
166 
167 .- QOI_OP_RGB ------------------------------------------.
168 |         Byte[0]         | Byte[1] | Byte[2] | Byte[3] |
169 |  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  |
170 |-------------------------+---------+---------+---------|
171 |  1  1  1  1  1  1  1  0 |   red   |  green  |  blue   |
172 `-------------------------------------------------------`
173 8-bit tag b11111110
174 8-bit   red channel value
175 8-bit green channel value
176 8-bit  blue channel value
177 
178 The alpha value remains unchanged from the previous pixel.
179 
180 
181 .- QOI_OP_RGBA ---------------------------------------------------.
182 |         Byte[0]         | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
183 |  7  6  5  4  3  2  1  0 | 7 .. 0  | 7 .. 0  | 7 .. 0  | 7 .. 0  |
184 |-------------------------+---------+---------+---------+---------|
185 |  1  1  1  1  1  1  1  1 |   red   |  green  |  blue   |  alpha  |
186 `-----------------------------------------------------------------`
187 8-bit tag b11111111
188 8-bit   red channel value
189 8-bit green channel value
190 8-bit  blue channel value
191 8-bit alpha channel value
192 
193 */
194 
195 
196 /* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
197 It describes either the input format (for qoi_write and qoi_encode), or is
198 filled with the description read from the file header (for qoi_read and
199 qoi_decode).
200 
201 The colorspace in this qoi_desc is an enum where
202     0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
203     1 = all channels are linear
204 You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
205 informative. It will be saved to the file header, but does not affect
206 how chunks are en-/decoded. */
207 
208 import core.stdc.string: memset;
209 import core.stdc.stdlib: malloc, free;
210 
211 nothrow @nogc:
212 
213 enum QOI_SRGB = 0;
214 enum QOI_LINEAR = 1;
215 
216 struct qoi_desc
217 {
218     uint width;
219     uint height;
220     int pitchBytes; // number of bytes between start of lines.
221     ubyte channels;
222     ubyte colorspace;
223 }
224 
225 
226 
227 alias QOI_MALLOC = malloc;
228 alias QOI_FREE = free;
229 
230 enum int QOI_OP_INDEX = 0x00; /* 00xxxxxx */
231 enum int QOI_OP_DIFF  = 0x40; /* 01xxxxxx */
232 enum int QOI_OP_LUMA  = 0x80; /* 10xxxxxx */
233 enum int QOI_OP_RUN   = 0xc0; /* 11xxxxxx */
234 enum int QOI_OP_RGB   = 0xfe; /* 11111110 */
235 enum int QOI_OP_RGBA  = 0xff; /* 11111111 */
236 
237 enum int QOI_MASK_2   = 0xc0; /* 11000000 */
238 
239 int  QOI_COLOR_HASH(qoi_rgba_t C)
240 {
241     return (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11);
242 }
243 
244 enum uint QOI_MAGIC = 0x716F6966; // "qoif"
245 enum QOI_HEADER_SIZE = 14;
246 
247 /* 2GB is the max file size that this implementation can safely handle. We guard
248 against anything larger than that, assuming the worst case with 5 bytes per
249 pixel, rounded down to a nice clean value. 400 million pixels ought to be
250 enough for anybody. */
251 enum uint QOI_PIXELS_MAX = 400000000;
252 
253 struct RGBA
254 {
255     ubyte r, g, b, a;
256 }
257 static assert(RGBA.sizeof == 4);
258 
259 struct qoi_rgba_t 
260 {   
261     union
262     {
263         RGBA rgba;
264         uint v;
265     }
266 }
267 
268 static immutable ubyte[8] qoi_padding = [0,0,0,0,0,0,0,1];
269 
270 void qoi_write_32(ubyte* bytes, int *p, uint v) 
271 {
272     bytes[(*p)++] = (0xff000000 & v) >> 24;
273     bytes[(*p)++] = (0x00ff0000 & v) >> 16;
274     bytes[(*p)++] = (0x0000ff00 & v) >> 8;
275     bytes[(*p)++] = (0x000000ff & v);
276 }
277 
278 uint qoi_read_32(const(ubyte)* bytes, int *p) 
279 {
280     uint a = bytes[(*p)++];
281     uint b = bytes[(*p)++];
282     uint c = bytes[(*p)++];
283     uint d = bytes[(*p)++];
284     return a << 24 | b << 16 | c << 8 | d;
285 }
286 
287 /* Encode raw RGB or RGBA pixels into a QOI image in memory.
288 
289 The function either returns null on failure (invalid parameters or malloc
290 failed) or a pointer to the encoded data on success. On success the out_len
291 is set to the size in bytes of the encoded data.
292 
293 The returned qoi data should be free()d after use. */
294 version(encodeQOI)
295 void *qoi_encode(const(ubyte)* data, const(qoi_desc)* desc, int *out_len) 
296 {
297     int i, max_size, p, run;
298     int px_len, px_end, px_pos, channels;
299     ubyte *bytes;
300     qoi_rgba_t[64] index;
301     qoi_rgba_t px, px_prev;
302 
303     if (
304         data == null || out_len == null || desc == null ||
305         desc.width == 0 || desc.height == 0 ||
306         desc.channels < 3 || desc.channels > 4 ||
307         desc.colorspace > 1 ||
308         desc.height >= QOI_PIXELS_MAX / desc.width
309     ) {
310         return null;
311     }
312 
313     max_size =
314         desc.width * desc.height * (desc.channels + 1) +
315         QOI_HEADER_SIZE + cast(int)qoi_padding.sizeof;
316 
317     p = 0;
318     bytes = cast(ubyte *) QOI_MALLOC(max_size);
319     if (!bytes) 
320     {
321         return null;
322     }
323 
324     qoi_write_32(bytes, &p, QOI_MAGIC);
325     qoi_write_32(bytes, &p, desc.width);
326     qoi_write_32(bytes, &p, desc.height);
327     bytes[p++] = desc.channels;
328     bytes[p++] = desc.colorspace;
329 
330     memset(index.ptr, 0, 64 * qoi_rgba_t.sizeof);
331 
332     run = 0;
333     px_prev.rgba.r = 0;
334     px_prev.rgba.g = 0;
335     px_prev.rgba.b = 0;
336     px_prev.rgba.a = 255;
337     px = px_prev;
338 
339     px_len = desc.width * desc.height * desc.channels;
340     px_end = px_len - desc.channels;
341     channels = desc.channels;
342 
343     for (int posy = 0; posy < desc.height; ++posy)
344     {
345         const(ubyte)* line = data + desc.pitchBytes * posy;
346 
347         for (int posx = 0; posx < desc.width; ++posx)
348         {
349             if (channels == 4) 
350             {
351                 px = *cast(qoi_rgba_t *)(&line[posx * 4]);
352             }
353             else {
354                 px.rgba.r = line[posx * 3 + 0];
355                 px.rgba.g = line[posx * 3 + 1];
356                 px.rgba.b = line[posx * 3 + 2];
357             }
358 
359             if (px.v == px_prev.v) {
360                 run++;
361                 if (run == 62 || px_pos == px_end) {
362                     bytes[p++] = cast(ubyte)(QOI_OP_RUN | (run - 1));
363                     run = 0;
364                 }
365             }
366             else {
367                 int index_pos;
368 
369                 if (run > 0) {
370                     bytes[p++] = cast(ubyte)(QOI_OP_RUN | (run - 1));
371                     run = 0;
372                 }
373 
374                 index_pos = QOI_COLOR_HASH(px) % 64;
375 
376                 if (index[index_pos].v == px.v) {
377                     bytes[p++] = cast(ubyte)(QOI_OP_INDEX | index_pos);
378                 }
379                 else {
380                     index[index_pos] = px;
381 
382                     if (px.rgba.a == px_prev.rgba.a) {
383                         byte vr = cast(byte)(px.rgba.r - px_prev.rgba.r);
384                         byte vg = cast(byte)(px.rgba.g - px_prev.rgba.g);
385                         byte vb = cast(byte)(px.rgba.b - px_prev.rgba.b);
386                         byte vg_r = cast(byte)(vr - vg);
387                         byte vg_b = cast(byte)(vb - vg);
388 
389                         if (
390                             vr > -3 && vr < 2 &&
391                             vg > -3 && vg < 2 &&
392                             vb > -3 && vb < 2
393                         ) {
394                             bytes[p++] = cast(ubyte)(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2));
395                         }
396                         else if (
397                             vg_r >  -9 && vg_r <  8 &&
398                             vg   > -33 && vg   < 32 &&
399                             vg_b >  -9 && vg_b <  8
400                         ) {
401                             bytes[p++] = cast(ubyte)(QOI_OP_LUMA     | (vg   + 32));
402                             bytes[p++] = cast(ubyte)( (vg_r + 8) << 4 | (vg_b +  8) );
403                         }
404                         else {
405                             bytes[p++] = QOI_OP_RGB;
406                             bytes[p++] = px.rgba.r;
407                             bytes[p++] = px.rgba.g;
408                             bytes[p++] = px.rgba.b;
409                         }
410                     }
411                     else {
412                         bytes[p++] = QOI_OP_RGBA;
413                         bytes[p++] = px.rgba.r;
414                         bytes[p++] = px.rgba.g;
415                         bytes[p++] = px.rgba.b;
416                         bytes[p++] = px.rgba.a;
417                     }
418                 }
419             }
420             px_prev = px;
421             px_pos += channels;
422         }
423     }
424 
425     assert(px_pos == px_len);
426 
427     for (i = 0; i < cast(int)(qoi_padding.length); i++) 
428     {
429         bytes[p++] = qoi_padding[i];
430     }
431 
432     // PERF, realloc here to take less memory
433     // Check encoding speed reg in QOIX example
434 
435     *out_len = p;
436     return bytes;
437 }
438 
439 
440 /** Decode a QOI image from memory.
441 
442 The function either returns null on failure (invalid parameters or malloc
443 failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
444 is filled with the description from the file header.
445 
446 The returned pixel data should be free()d after use. */
447 version(decodeQOI)
448 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) 
449 {
450     const(ubyte)* bytes;
451     uint header_magic;
452     ubyte* pixels;
453     qoi_rgba_t[64] index;
454     qoi_rgba_t px;
455     int px_len, chunks_len, px_pos;
456     int p = 0, run = 0;
457 
458     if ((channels != 0 && channels != 3 && channels != 4) ||
459         size < QOI_HEADER_SIZE + cast(int)(qoi_padding.sizeof)
460     ) {
461         return null;
462     }
463 
464     bytes = cast(const(ubyte)*)data;
465 
466     header_magic = qoi_read_32(bytes, &p);
467     desc.width = qoi_read_32(bytes, &p);
468     desc.height = qoi_read_32(bytes, &p);
469     desc.channels = bytes[p++]; // Return original number of channels.
470     desc.colorspace = bytes[p++];
471 
472     if (
473         desc.width == 0 || desc.height == 0 ||
474         desc.channels < 3 || desc.channels > 4 ||
475         desc.colorspace > 1 ||
476         header_magic != QOI_MAGIC ||
477         desc.height >= QOI_PIXELS_MAX / desc.width
478     ) {
479         return null;
480     }
481 
482     if (channels == 0) {
483         channels = desc.channels;
484     }
485 
486     px_len = desc.width * desc.height * channels;
487     pixels = cast(ubyte*) QOI_MALLOC(px_len);
488     if (!pixels) {
489         return null;
490     }
491 
492     memset(index.ptr, 0, 64 * qoi_rgba_t.sizeof);
493     px.rgba.r = 0;
494     px.rgba.g = 0;
495     px.rgba.b = 0;
496     px.rgba.a = 255;
497 
498     chunks_len = size - cast(int)(qoi_padding.length);
499     for (px_pos = 0; px_pos < px_len; px_pos += channels) {
500         if (run > 0) {
501             run--;
502         }
503         else if (p < chunks_len) {
504             int b1 = bytes[p++];
505 
506             if (b1 == QOI_OP_RGB) {
507                 px.rgba.r = bytes[p++];
508                 px.rgba.g = bytes[p++];
509                 px.rgba.b = bytes[p++];
510             }
511             else if (b1 == QOI_OP_RGBA) {
512                 px.rgba.r = bytes[p++];
513                 px.rgba.g = bytes[p++];
514                 px.rgba.b = bytes[p++];
515                 px.rgba.a = bytes[p++];
516             }
517             else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
518                 px = index[b1];
519             }
520             else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
521                 px.rgba.r += ((b1 >> 4) & 0x03) - 2;
522                 px.rgba.g += ((b1 >> 2) & 0x03) - 2;
523                 px.rgba.b += ( b1       & 0x03) - 2;
524             }
525             else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
526                 int b2 = bytes[p++];
527                 int vg = (b1 & 0x3f) - 32;
528                 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
529                 px.rgba.g += vg;
530                 px.rgba.b += vg - 8 +  (b2       & 0x0f);
531             }
532             else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
533                 run = (b1 & 0x3f);
534             }
535 
536             index[QOI_COLOR_HASH(px) % 64] = px;
537         }
538 
539         if (channels == 4) {
540             *cast(qoi_rgba_t*)(pixels + px_pos) = px;
541         }
542         else {
543             pixels[px_pos + 0] = px.rgba.r;
544             pixels[px_pos + 1] = px.rgba.g;
545             pixels[px_pos + 2] = px.rgba.b;
546         }
547     }
548 
549     return pixels;
550 }