File Coverage

qoi.h
Criterion Covered Total %
statement 141 188 75.0
branch 79 122 64.7
condition n/a
subroutine n/a
pod n/a
total 220 310 70.9


line stmt bran cond sub pod time code
1             /*
2              
3             QOI - The "Quite OK Image" format for fast, lossless image compression
4              
5             Dominic Szablewski - https://phoboslab.org
6              
7              
8             -- LICENSE: The MIT License(MIT)
9              
10             Copyright(c) 2021 Dominic Szablewski
11              
12             Permission is hereby granted, free of charge, to any person obtaining a copy of
13             this software and associated documentation files(the "Software"), to deal in
14             the Software without restriction, including without limitation the rights to
15             use, copy, modify, merge, publish, distribute, sublicense, and / or sell copies
16             of the Software, and to permit persons to whom the Software is furnished to do
17             so, subject to the following conditions :
18             The above copyright notice and this permission notice shall be included in all
19             copies or substantial portions of the Software.
20             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21             IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22             FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
23             AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24             LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25             OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26             SOFTWARE.
27              
28              
29             -- About
30              
31             QOI encodes and decodes images in a lossless format. Compared to stb_image and
32             stb_image_write QOI offers 20x-50x faster encoding, 3x-4x faster decoding and
33             20% better compression.
34              
35              
36             -- Synopsis
37              
38             // Define `QOI_IMPLEMENTATION` in *one* C/C++ file before including this
39             // library to create the implementation.
40              
41             #define QOI_IMPLEMENTATION
42             #include "qoi.h"
43              
44             // Encode and store an RGBA buffer to the file system. The qoi_desc describes
45             // the input pixel data.
46             qoi_write("image_new.qoi", rgba_pixels, &(qoi_desc){
47             .width = 1920,
48             .height = 1080,
49             .channels = 4,
50             .colorspace = QOI_SRGB
51             });
52              
53             // Load and decode a QOI image from the file system into a 32bbp RGBA buffer.
54             // The qoi_desc struct will be filled with the width, height, number of channels
55             // and colorspace read from the file header.
56             qoi_desc desc;
57             void *rgba_pixels = qoi_read("image.qoi", &desc, 4);
58              
59              
60              
61             -- Documentation
62              
63             This library provides the following functions;
64             - qoi_read -- read and decode a QOI file
65             - qoi_decode -- decode the raw bytes of a QOI image from memory
66             - qoi_write -- encode and write a QOI file
67             - qoi_encode -- encode an rgba buffer into a QOI image in memory
68              
69             See the function declaration below for the signature and more information.
70              
71             If you don't want/need the qoi_read and qoi_write functions, you can define
72             QOI_NO_STDIO before including this library.
73              
74             This library uses malloc() and free(). To supply your own malloc implementation
75             you can define QOI_MALLOC and QOI_FREE before including this library.
76              
77             This library uses memset() to zero-initialize the index. To supply your own
78             implementation you can define QOI_ZEROARR before including this library.
79              
80              
81             -- Data Format
82              
83             A QOI file has a 14 byte header, followed by any number of data "chunks" and an
84             8-byte end marker.
85              
86             struct qoi_header_t {
87             char magic[4]; // magic bytes "qoif"
88             uint32_t width; // image width in pixels (BE)
89             uint32_t height; // image height in pixels (BE)
90             uint8_t channels; // 3 = RGB, 4 = RGBA
91             uint8_t colorspace; // 0 = sRGB with linear alpha, 1 = all channels linear
92             };
93              
94             Images are encoded row by row, left to right, top to bottom. The decoder and
95             encoder start with {r: 0, g: 0, b: 0, a: 255} as the previous pixel value. An
96             image is complete when all pixels specified by width * height have been covered.
97              
98             Pixels are encoded as
99             - a run of the previous pixel
100             - an index into an array of previously seen pixels
101             - a difference to the previous pixel value in r,g,b
102             - full r,g,b or r,g,b,a values
103              
104             The color channels are assumed to not be premultiplied with the alpha channel
105             ("un-premultiplied alpha").
106              
107             A running array[64] (zero-initialized) of previously seen pixel values is
108             maintained by the encoder and decoder. Each pixel that is seen by the encoder
109             and decoder is put into this array at the position formed by a hash function of
110             the color value. In the encoder, if the pixel value at the index matches the
111             current pixel, this index position is written to the stream as QOI_OP_INDEX.
112             The hash function for the index is:
113              
114             index_position = (r * 3 + g * 5 + b * 7 + a * 11) % 64
115              
116             Each chunk starts with a 2- or 8-bit tag, followed by a number of data bits. The
117             bit length of chunks is divisible by 8 - i.e. all chunks are byte aligned. All
118             values encoded in these data bits have the most significant bit on the left.
119              
120             The 8-bit tags have precedence over the 2-bit tags. A decoder must check for the
121             presence of an 8-bit tag first.
122              
123             The byte stream's end is marked with 7 0x00 bytes followed a single 0x01 byte.
124              
125              
126             The possible chunks are:
127              
128              
129             .- QOI_OP_INDEX ----------.
130             | Byte[0] |
131             | 7 6 5 4 3 2 1 0 |
132             |-------+-----------------|
133             | 0 0 | index |
134             `-------------------------`
135             2-bit tag b00
136             6-bit index into the color index array: 0..63
137              
138             A valid encoder must not issue 2 or more consecutive QOI_OP_INDEX chunks to the
139             same index. QOI_OP_RUN should be used instead.
140              
141              
142             .- QOI_OP_DIFF -----------.
143             | Byte[0] |
144             | 7 6 5 4 3 2 1 0 |
145             |-------+-----+-----+-----|
146             | 0 1 | dr | dg | db |
147             `-------------------------`
148             2-bit tag b01
149             2-bit red channel difference from the previous pixel between -2..1
150             2-bit green channel difference from the previous pixel between -2..1
151             2-bit blue channel difference from the previous pixel between -2..1
152              
153             The difference to the current channel values are using a wraparound operation,
154             so "1 - 2" will result in 255, while "255 + 1" will result in 0.
155              
156             Values are stored as unsigned integers with a bias of 2. E.g. -2 is stored as
157             0 (b00). 1 is stored as 3 (b11).
158              
159             The alpha value remains unchanged from the previous pixel.
160              
161              
162             .- QOI_OP_LUMA -------------------------------------.
163             | Byte[0] | Byte[1] |
164             | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
165             |-------+-----------------+-------------+-----------|
166             | 1 0 | green diff | dr - dg | db - dg |
167             `---------------------------------------------------`
168             2-bit tag b10
169             6-bit green channel difference from the previous pixel -32..31
170             4-bit red channel difference minus green channel difference -8..7
171             4-bit blue channel difference minus green channel difference -8..7
172              
173             The green channel is used to indicate the general direction of change and is
174             encoded in 6 bits. The red and blue channels (dr and db) base their diffs off
175             of the green channel difference and are encoded in 4 bits. I.e.:
176             dr_dg = (cur_px.r - prev_px.r) - (cur_px.g - prev_px.g)
177             db_dg = (cur_px.b - prev_px.b) - (cur_px.g - prev_px.g)
178              
179             The difference to the current channel values are using a wraparound operation,
180             so "10 - 13" will result in 253, while "250 + 7" will result in 1.
181              
182             Values are stored as unsigned integers with a bias of 32 for the green channel
183             and a bias of 8 for the red and blue channel.
184              
185             The alpha value remains unchanged from the previous pixel.
186              
187              
188             .- QOI_OP_RUN ------------.
189             | Byte[0] |
190             | 7 6 5 4 3 2 1 0 |
191             |-------+-----------------|
192             | 1 1 | run |
193             `-------------------------`
194             2-bit tag b11
195             6-bit run-length repeating the previous pixel: 1..62
196              
197             The run-length is stored with a bias of -1. Note that the run-lengths 63 and 64
198             (b111110 and b111111) are illegal as they are occupied by the QOI_OP_RGB and
199             QOI_OP_RGBA tags.
200              
201              
202             .- QOI_OP_RGB ------------------------------------------.
203             | Byte[0] | Byte[1] | Byte[2] | Byte[3] |
204             | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
205             |-------------------------+---------+---------+---------|
206             | 1 1 1 1 1 1 1 0 | red | green | blue |
207             `-------------------------------------------------------`
208             8-bit tag b11111110
209             8-bit red channel value
210             8-bit green channel value
211             8-bit blue channel value
212              
213             The alpha value remains unchanged from the previous pixel.
214              
215              
216             .- QOI_OP_RGBA ---------------------------------------------------.
217             | Byte[0] | Byte[1] | Byte[2] | Byte[3] | Byte[4] |
218             | 7 6 5 4 3 2 1 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 | 7 .. 0 |
219             |-------------------------+---------+---------+---------+---------|
220             | 1 1 1 1 1 1 1 1 | red | green | blue | alpha |
221             `-----------------------------------------------------------------`
222             8-bit tag b11111111
223             8-bit red channel value
224             8-bit green channel value
225             8-bit blue channel value
226             8-bit alpha channel value
227              
228             */
229              
230              
231             /* -----------------------------------------------------------------------------
232             Header - Public functions */
233              
234             #ifndef QOI_H
235             #define QOI_H
236              
237             #ifdef __cplusplus
238             extern "C" {
239             #endif
240              
241             /* A pointer to a qoi_desc struct has to be supplied to all of qoi's functions.
242             It describes either the input format (for qoi_write and qoi_encode), or is
243             filled with the description read from the file header (for qoi_read and
244             qoi_decode).
245              
246             The colorspace in this qoi_desc is an enum where
247             0 = sRGB, i.e. gamma scaled RGB channels and a linear alpha channel
248             1 = all channels are linear
249             You may use the constants QOI_SRGB or QOI_LINEAR. The colorspace is purely
250             informative. It will be saved to the file header, but does not affect
251             how chunks are en-/decoded. */
252              
253             #define QOI_SRGB 0
254             #define QOI_LINEAR 1
255              
256             typedef struct {
257             unsigned int width;
258             unsigned int height;
259             unsigned char channels;
260             unsigned char colorspace;
261             } qoi_desc;
262              
263             #ifndef QOI_NO_STDIO
264              
265             /* Encode raw RGB or RGBA pixels into a QOI image and write it to the file
266             system. The qoi_desc struct must be filled with the image width, height,
267             number of channels (3 = RGB, 4 = RGBA) and the colorspace.
268              
269             The function returns 0 on failure (invalid parameters, or fopen or malloc
270             failed) or the number of bytes written on success. */
271              
272             int qoi_write(const char *filename, const void *data, const qoi_desc *desc);
273              
274              
275             /* Read and decode a QOI image from the file system. If channels is 0, the
276             number of channels from the file header is used. If channels is 3 or 4 the
277             output format will be forced into this number of channels.
278              
279             The function either returns NULL on failure (invalid data, or malloc or fopen
280             failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
281             will be filled with the description from the file header.
282              
283             The returned pixel data should be free()d after use. */
284              
285             void *qoi_read(const char *filename, qoi_desc *desc, int channels);
286              
287             #endif /* QOI_NO_STDIO */
288              
289              
290             /* Encode raw RGB or RGBA pixels into a QOI image in memory.
291              
292             The function either returns NULL on failure (invalid parameters or malloc
293             failed) or a pointer to the encoded data on success. On success the out_len
294             is set to the size in bytes of the encoded data.
295              
296             The returned qoi data should be free()d after use. */
297              
298             void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len);
299              
300              
301             /* Decode a QOI image from memory.
302              
303             The function either returns NULL on failure (invalid parameters or malloc
304             failed) or a pointer to the decoded pixels. On success, the qoi_desc struct
305             is filled with the description from the file header.
306              
307             The returned pixel data should be free()d after use. */
308              
309             void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels);
310              
311              
312             #ifdef __cplusplus
313             }
314             #endif
315             #endif /* QOI_H */
316              
317              
318             /* -----------------------------------------------------------------------------
319             Implementation */
320              
321             #ifdef QOI_IMPLEMENTATION
322             #include
323             #include
324              
325             #ifndef QOI_MALLOC
326             #define QOI_MALLOC(sz) malloc(sz)
327             #define QOI_FREE(p) free(p)
328             #endif
329             #ifndef QOI_ZEROARR
330             #define QOI_ZEROARR(a) memset((a),0,sizeof(a))
331             #endif
332              
333             #define QOI_OP_INDEX 0x00 /* 00xxxxxx */
334             #define QOI_OP_DIFF 0x40 /* 01xxxxxx */
335             #define QOI_OP_LUMA 0x80 /* 10xxxxxx */
336             #define QOI_OP_RUN 0xc0 /* 11xxxxxx */
337             #define QOI_OP_RGB 0xfe /* 11111110 */
338             #define QOI_OP_RGBA 0xff /* 11111111 */
339              
340             #define QOI_MASK_2 0xc0 /* 11000000 */
341              
342             #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
343             #define QOI_MAGIC \
344             (((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
345             ((unsigned int)'i') << 8 | ((unsigned int)'f'))
346             #define QOI_HEADER_SIZE 14
347              
348             /* 2GB is the max file size that this implementation can safely handle. We guard
349             against anything larger than that, assuming the worst case with 5 bytes per
350             pixel, rounded down to a nice clean value. 400 million pixels ought to be
351             enough for anybody. */
352             #define QOI_PIXELS_MAX ((unsigned int)400000000)
353              
354             typedef union {
355             struct { unsigned char r, g, b, a; } rgba;
356             unsigned int v;
357             } qoi_rgba_t;
358              
359             static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
360              
361 12           static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) {
362 12           bytes[(*p)++] = (0xff000000 & v) >> 24;
363 12           bytes[(*p)++] = (0x00ff0000 & v) >> 16;
364 12           bytes[(*p)++] = (0x0000ff00 & v) >> 8;
365 12           bytes[(*p)++] = (0x000000ff & v);
366 12           }
367              
368 12           static unsigned int qoi_read_32(const unsigned char *bytes, int *p) {
369 12           unsigned int a = bytes[(*p)++];
370 12           unsigned int b = bytes[(*p)++];
371 12           unsigned int c = bytes[(*p)++];
372 12           unsigned int d = bytes[(*p)++];
373 12           return a << 24 | b << 16 | c << 8 | d;
374             }
375              
376 4           void *qoi_encode(const void *data, const qoi_desc *desc, int *out_len) {
377             int i, max_size, p, run;
378             int px_len, px_end, px_pos, channels;
379             unsigned char *bytes;
380             const unsigned char *pixels;
381             qoi_rgba_t index[64];
382             qoi_rgba_t px, px_prev;
383              
384 4 50         if (
385 4 50         data == NULL || out_len == NULL || desc == NULL ||
    50          
    50          
386 4 50         desc->width == 0 || desc->height == 0 ||
    50          
387 4 50         desc->channels < 3 || desc->channels > 4 ||
    50          
388 4 50         desc->colorspace > 1 ||
389 4           desc->height >= QOI_PIXELS_MAX / desc->width
390             ) {
391 0           return NULL;
392             }
393              
394 8           max_size =
395 8           desc->width * desc->height * (desc->channels + 1) +
396             QOI_HEADER_SIZE + sizeof(qoi_padding);
397              
398 4           p = 0;
399 4           bytes = (unsigned char *) QOI_MALLOC(max_size);
400 4 50         if (!bytes) {
401 0           return NULL;
402             }
403              
404 4           qoi_write_32(bytes, &p, QOI_MAGIC);
405 4           qoi_write_32(bytes, &p, desc->width);
406 4           qoi_write_32(bytes, &p, desc->height);
407 4           bytes[p++] = desc->channels;
408 4           bytes[p++] = desc->colorspace;
409              
410              
411 4           pixels = (const unsigned char *)data;
412              
413 4           QOI_ZEROARR(index);
414              
415 4           run = 0;
416 4           px_prev.rgba.r = 0;
417 4           px_prev.rgba.g = 0;
418 4           px_prev.rgba.b = 0;
419 4           px_prev.rgba.a = 255;
420 4           px = px_prev;
421              
422 4           px_len = desc->width * desc->height * desc->channels;
423 4           px_end = px_len - desc->channels;
424 4           channels = desc->channels;
425              
426 90004 100         for (px_pos = 0; px_pos < px_len; px_pos += channels) {
427 90000           px.rgba.r = pixels[px_pos + 0];
428 90000           px.rgba.g = pixels[px_pos + 1];
429 90000           px.rgba.b = pixels[px_pos + 2];
430              
431 90000 50         if (channels == 4) {
432 0           px.rgba.a = pixels[px_pos + 3];
433             }
434              
435 90000 100         if (px.v == px_prev.v) {
436 80721           run++;
437 80721 100         if (run == 62 || px_pos == px_end) {
    100          
438 432           bytes[p++] = QOI_OP_RUN | (run - 1);
439 80721           run = 0;
440             }
441             }
442             else {
443             int index_pos;
444              
445 9279 100         if (run > 0) {
446 1544           bytes[p++] = QOI_OP_RUN | (run - 1);
447 1544           run = 0;
448             }
449              
450 9279           index_pos = QOI_COLOR_HASH(px) % 64;
451              
452 9279 100         if (index[index_pos].v == px.v) {
453 5536           bytes[p++] = QOI_OP_INDEX | index_pos;
454             }
455             else {
456 3743           index[index_pos] = px;
457              
458 3743 50         if (px.rgba.a == px_prev.rgba.a) {
459 3743           signed char vr = px.rgba.r - px_prev.rgba.r;
460 3743           signed char vg = px.rgba.g - px_prev.rgba.g;
461 3743           signed char vb = px.rgba.b - px_prev.rgba.b;
462              
463 3743           signed char vg_r = vr - vg;
464 3743           signed char vg_b = vb - vg;
465              
466 3743 100         if (
467 2588 100         vr > -3 && vr < 2 &&
    100          
468 660 100         vg > -3 && vg < 2 &&
    50          
469 279 100         vb > -3 && vb < 2
470             ) {
471 78           bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
472             }
473 3665 100         else if (
474 2660 100         vg_r > -9 && vg_r < 8 &&
    100          
475 1412 100         vg > -33 && vg < 32 &&
    100          
476 1362 100         vg_b > -9 && vg_b < 8
477             ) {
478 1182           bytes[p++] = QOI_OP_LUMA | (vg + 32);
479 1182           bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8);
480             }
481             else {
482 2483           bytes[p++] = QOI_OP_RGB;
483 2483           bytes[p++] = px.rgba.r;
484 2483           bytes[p++] = px.rgba.g;
485 3743           bytes[p++] = px.rgba.b;
486             }
487             }
488             else {
489 0           bytes[p++] = QOI_OP_RGBA;
490 0           bytes[p++] = px.rgba.r;
491 0           bytes[p++] = px.rgba.g;
492 0           bytes[p++] = px.rgba.b;
493 0           bytes[p++] = px.rgba.a;
494             }
495             }
496             }
497 90000           px_prev = px;
498             }
499              
500 36 100         for (i = 0; i < (int)sizeof(qoi_padding); i++) {
501 32           bytes[p++] = qoi_padding[i];
502             }
503              
504 4           *out_len = p;
505 4           return bytes;
506             }
507              
508 4           void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) {
509             const unsigned char *bytes;
510             unsigned int header_magic;
511             unsigned char *pixels;
512             qoi_rgba_t index[64];
513             qoi_rgba_t px;
514             int px_len, chunks_len, px_pos;
515 4           int p = 0, run = 0;
516              
517 4 50         if (
518 4 50         data == NULL || desc == NULL ||
    50          
519 4 0         (channels != 0 && channels != 3 && channels != 4) ||
    0          
    50          
520             size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding)
521             ) {
522 0           return NULL;
523             }
524              
525 4           bytes = (const unsigned char *)data;
526              
527 4           header_magic = qoi_read_32(bytes, &p);
528 4           desc->width = qoi_read_32(bytes, &p);
529 4           desc->height = qoi_read_32(bytes, &p);
530 4           desc->channels = bytes[p++];
531 4           desc->colorspace = bytes[p++];
532              
533 4 50         if (
534 4 50         desc->width == 0 || desc->height == 0 ||
    50          
535 4 50         desc->channels < 3 || desc->channels > 4 ||
    50          
536 4 50         desc->colorspace > 1 ||
537 4 50         header_magic != QOI_MAGIC ||
538 4           desc->height >= QOI_PIXELS_MAX / desc->width
539             ) {
540 0           return NULL;
541             }
542              
543 4 50         if (channels == 0) {
544 4           channels = desc->channels;
545             }
546              
547 4           px_len = desc->width * desc->height * channels;
548 4           pixels = (unsigned char *) QOI_MALLOC(px_len);
549 4 50         if (!pixels) {
550 0           return NULL;
551             }
552              
553 4           QOI_ZEROARR(index);
554 4           px.rgba.r = 0;
555 4           px.rgba.g = 0;
556 4           px.rgba.b = 0;
557 4           px.rgba.a = 255;
558              
559 4           chunks_len = size - (int)sizeof(qoi_padding);
560 90004 100         for (px_pos = 0; px_pos < px_len; px_pos += channels) {
561 90000 100         if (run > 0) {
562 68619           run--;
563             }
564 21381 100         else if (p < chunks_len) {
565 9789           int b1 = bytes[p++];
566              
567 9789 100         if (b1 == QOI_OP_RGB) {
568 2087           px.rgba.r = bytes[p++];
569 2087           px.rgba.g = bytes[p++];
570 2087           px.rgba.b = bytes[p++];
571             }
572 7702 50         else if (b1 == QOI_OP_RGBA) {
573 0           px.rgba.r = bytes[p++];
574 0           px.rgba.g = bytes[p++];
575 0           px.rgba.b = bytes[p++];
576 0           px.rgba.a = bytes[p++];
577             }
578 7702 100         else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) {
579 4794           px = index[b1];
580             }
581 2908 100         else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) {
582 78           px.rgba.r += ((b1 >> 4) & 0x03) - 2;
583 78           px.rgba.g += ((b1 >> 2) & 0x03) - 2;
584 78           px.rgba.b += ( b1 & 0x03) - 2;
585             }
586 2830 100         else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) {
587 1109           int b2 = bytes[p++];
588 1109           int vg = (b1 & 0x3f) - 32;
589 1109           px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
590 1109           px.rgba.g += vg;
591 1109           px.rgba.b += vg - 8 + (b2 & 0x0f);
592             }
593 1721 50         else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) {
594 1721           run = (b1 & 0x3f);
595             }
596              
597 9789           index[QOI_COLOR_HASH(px) % 64] = px;
598             }
599              
600 90000           pixels[px_pos + 0] = px.rgba.r;
601 90000           pixels[px_pos + 1] = px.rgba.g;
602 90000           pixels[px_pos + 2] = px.rgba.b;
603            
604 90000 50         if (channels == 4) {
605 0           pixels[px_pos + 3] = px.rgba.a;
606             }
607             }
608              
609 4           return pixels;
610             }
611              
612             #ifndef QOI_NO_STDIO
613             #include
614              
615 0           int qoi_write(const char *filename, const void *data, const qoi_desc *desc) {
616 0           FILE *f = fopen(filename, "wb");
617             int size;
618             void *encoded;
619              
620 0 0         if (!f) {
621 0           return 0;
622             }
623              
624 0           encoded = qoi_encode(data, desc, &size);
625 0 0         if (!encoded) {
626 0           fclose(f);
627 0           return 0;
628             }
629              
630 0           fwrite(encoded, 1, size, f);
631 0           fclose(f);
632              
633 0           QOI_FREE(encoded);
634 0           return size;
635             }
636              
637 0           void *qoi_read(const char *filename, qoi_desc *desc, int channels) {
638 0           FILE *f = fopen(filename, "rb");
639             int size, bytes_read;
640             void *pixels, *data;
641              
642 0 0         if (!f) {
643 0           return NULL;
644             }
645              
646 0           fseek(f, 0, SEEK_END);
647 0           size = ftell(f);
648 0 0         if (size <= 0) {
649 0           fclose(f);
650 0           return NULL;
651             }
652 0           fseek(f, 0, SEEK_SET);
653              
654 0           data = QOI_MALLOC(size);
655 0 0         if (!data) {
656 0           fclose(f);
657 0           return NULL;
658             }
659              
660 0           bytes_read = fread(data, 1, size, f);
661 0           fclose(f);
662              
663 0           pixels = qoi_decode(data, bytes_read, desc, channels);
664 0           QOI_FREE(data);
665 0           return pixels;
666             }
667              
668             #endif /* QOI_NO_STDIO */
669             #endif /* QOI_IMPLEMENTATION */