minesweeper

A minewseeper implementation to play around with Hare and Raylib
git clone https://git.tronto.net/minesweeper
Download | Log | Files | Refs | README | LICENSE

qoi.h (18314B)


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