github.com/Konstantin8105/c4go@v0.0.0-20240505174241-768bb1c65a51/tests/raylib/external/qoi.h (about) 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 <stdlib.h> 323 #include <string.h> 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 static void qoi_write_32(unsigned char *bytes, int *p, unsigned int v) { 362 bytes[(*p)++] = (0xff000000 & v) >> 24; 363 bytes[(*p)++] = (0x00ff0000 & v) >> 16; 364 bytes[(*p)++] = (0x0000ff00 & v) >> 8; 365 bytes[(*p)++] = (0x000000ff & v); 366 } 367 368 static unsigned int qoi_read_32(const unsigned char *bytes, int *p) { 369 unsigned int a = bytes[(*p)++]; 370 unsigned int b = bytes[(*p)++]; 371 unsigned int c = bytes[(*p)++]; 372 unsigned int d = bytes[(*p)++]; 373 return a << 24 | b << 16 | c << 8 | d; 374 } 375 376 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 if ( 385 data == NULL || out_len == NULL || desc == NULL || 386 desc->width == 0 || desc->height == 0 || 387 desc->channels < 3 || desc->channels > 4 || 388 desc->colorspace > 1 || 389 desc->height >= QOI_PIXELS_MAX / desc->width 390 ) { 391 return NULL; 392 } 393 394 max_size = 395 desc->width * desc->height * (desc->channels + 1) + 396 QOI_HEADER_SIZE + sizeof(qoi_padding); 397 398 p = 0; 399 bytes = (unsigned char *) QOI_MALLOC(max_size); 400 if (!bytes) { 401 return NULL; 402 } 403 404 qoi_write_32(bytes, &p, QOI_MAGIC); 405 qoi_write_32(bytes, &p, desc->width); 406 qoi_write_32(bytes, &p, desc->height); 407 bytes[p++] = desc->channels; 408 bytes[p++] = desc->colorspace; 409 410 411 pixels = (const unsigned char *)data; 412 413 QOI_ZEROARR(index); 414 415 run = 0; 416 px_prev.rgba.r = 0; 417 px_prev.rgba.g = 0; 418 px_prev.rgba.b = 0; 419 px_prev.rgba.a = 255; 420 px = px_prev; 421 422 px_len = desc->width * desc->height * desc->channels; 423 px_end = px_len - desc->channels; 424 channels = desc->channels; 425 426 for (px_pos = 0; px_pos < px_len; px_pos += channels) { 427 if (channels == 4) { 428 px = *(qoi_rgba_t *)(pixels + px_pos); 429 } 430 else { 431 px.rgba.r = pixels[px_pos + 0]; 432 px.rgba.g = pixels[px_pos + 1]; 433 px.rgba.b = pixels[px_pos + 2]; 434 } 435 436 if (px.v == px_prev.v) { 437 run++; 438 if (run == 62 || px_pos == px_end) { 439 bytes[p++] = QOI_OP_RUN | (run - 1); 440 run = 0; 441 } 442 } 443 else { 444 int index_pos; 445 446 if (run > 0) { 447 bytes[p++] = QOI_OP_RUN | (run - 1); 448 run = 0; 449 } 450 451 index_pos = QOI_COLOR_HASH(px) % 64; 452 453 if (index[index_pos].v == px.v) { 454 bytes[p++] = QOI_OP_INDEX | index_pos; 455 } 456 else { 457 index[index_pos] = px; 458 459 if (px.rgba.a == px_prev.rgba.a) { 460 signed char vr = px.rgba.r - px_prev.rgba.r; 461 signed char vg = px.rgba.g - px_prev.rgba.g; 462 signed char vb = px.rgba.b - px_prev.rgba.b; 463 464 signed char vg_r = vr - vg; 465 signed char vg_b = vb - vg; 466 467 if ( 468 vr > -3 && vr < 2 && 469 vg > -3 && vg < 2 && 470 vb > -3 && vb < 2 471 ) { 472 bytes[p++] = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2); 473 } 474 else if ( 475 vg_r > -9 && vg_r < 8 && 476 vg > -33 && vg < 32 && 477 vg_b > -9 && vg_b < 8 478 ) { 479 bytes[p++] = QOI_OP_LUMA | (vg + 32); 480 bytes[p++] = (vg_r + 8) << 4 | (vg_b + 8); 481 } 482 else { 483 bytes[p++] = QOI_OP_RGB; 484 bytes[p++] = px.rgba.r; 485 bytes[p++] = px.rgba.g; 486 bytes[p++] = px.rgba.b; 487 } 488 } 489 else { 490 bytes[p++] = QOI_OP_RGBA; 491 bytes[p++] = px.rgba.r; 492 bytes[p++] = px.rgba.g; 493 bytes[p++] = px.rgba.b; 494 bytes[p++] = px.rgba.a; 495 } 496 } 497 } 498 px_prev = px; 499 } 500 501 for (i = 0; i < (int)sizeof(qoi_padding); i++) { 502 bytes[p++] = qoi_padding[i]; 503 } 504 505 *out_len = p; 506 return bytes; 507 } 508 509 void *qoi_decode(const void *data, int size, qoi_desc *desc, int channels) { 510 const unsigned char *bytes; 511 unsigned int header_magic; 512 unsigned char *pixels; 513 qoi_rgba_t index[64]; 514 qoi_rgba_t px; 515 int px_len, chunks_len, px_pos; 516 int p = 0, run = 0; 517 518 if ( 519 data == NULL || desc == NULL || 520 (channels != 0 && channels != 3 && channels != 4) || 521 size < QOI_HEADER_SIZE + (int)sizeof(qoi_padding) 522 ) { 523 return NULL; 524 } 525 526 bytes = (const unsigned char *)data; 527 528 header_magic = qoi_read_32(bytes, &p); 529 desc->width = qoi_read_32(bytes, &p); 530 desc->height = qoi_read_32(bytes, &p); 531 desc->channels = bytes[p++]; 532 desc->colorspace = bytes[p++]; 533 534 if ( 535 desc->width == 0 || desc->height == 0 || 536 desc->channels < 3 || desc->channels > 4 || 537 desc->colorspace > 1 || 538 header_magic != QOI_MAGIC || 539 desc->height >= QOI_PIXELS_MAX / desc->width 540 ) { 541 return NULL; 542 } 543 544 if (channels == 0) { 545 channels = desc->channels; 546 } 547 548 px_len = desc->width * desc->height * channels; 549 pixels = (unsigned char *) QOI_MALLOC(px_len); 550 if (!pixels) { 551 return NULL; 552 } 553 554 QOI_ZEROARR(index); 555 px.rgba.r = 0; 556 px.rgba.g = 0; 557 px.rgba.b = 0; 558 px.rgba.a = 255; 559 560 chunks_len = size - (int)sizeof(qoi_padding); 561 for (px_pos = 0; px_pos < px_len; px_pos += channels) { 562 if (run > 0) { 563 run--; 564 } 565 else if (p < chunks_len) { 566 int b1 = bytes[p++]; 567 568 if (b1 == QOI_OP_RGB) { 569 px.rgba.r = bytes[p++]; 570 px.rgba.g = bytes[p++]; 571 px.rgba.b = bytes[p++]; 572 } 573 else if (b1 == QOI_OP_RGBA) { 574 px.rgba.r = bytes[p++]; 575 px.rgba.g = bytes[p++]; 576 px.rgba.b = bytes[p++]; 577 px.rgba.a = bytes[p++]; 578 } 579 else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { 580 px = index[b1]; 581 } 582 else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { 583 px.rgba.r += ((b1 >> 4) & 0x03) - 2; 584 px.rgba.g += ((b1 >> 2) & 0x03) - 2; 585 px.rgba.b += ( b1 & 0x03) - 2; 586 } 587 else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { 588 int b2 = bytes[p++]; 589 int vg = (b1 & 0x3f) - 32; 590 px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); 591 px.rgba.g += vg; 592 px.rgba.b += vg - 8 + (b2 & 0x0f); 593 } 594 else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { 595 run = (b1 & 0x3f); 596 } 597 598 index[QOI_COLOR_HASH(px) % 64] = px; 599 } 600 601 if (channels == 4) { 602 *(qoi_rgba_t*)(pixels + px_pos) = px; 603 } 604 else { 605 pixels[px_pos + 0] = px.rgba.r; 606 pixels[px_pos + 1] = px.rgba.g; 607 pixels[px_pos + 2] = px.rgba.b; 608 } 609 } 610 611 return pixels; 612 } 613 614 #ifndef QOI_NO_STDIO 615 #include <stdio.h> 616 617 int qoi_write(const char *filename, const void *data, const qoi_desc *desc) { 618 FILE *f = fopen(filename, "wb"); 619 int size; 620 void *encoded; 621 622 if (!f) { 623 return 0; 624 } 625 626 encoded = qoi_encode(data, desc, &size); 627 if (!encoded) { 628 fclose(f); 629 return 0; 630 } 631 632 fwrite(encoded, 1, size, f); 633 fclose(f); 634 635 QOI_FREE(encoded); 636 return size; 637 } 638 639 void *qoi_read(const char *filename, qoi_desc *desc, int channels) { 640 FILE *f = fopen(filename, "rb"); 641 int size, bytes_read; 642 void *pixels, *data; 643 644 if (!f) { 645 return NULL; 646 } 647 648 fseek(f, 0, SEEK_END); 649 size = ftell(f); 650 if (size <= 0) { 651 fclose(f); 652 return NULL; 653 } 654 fseek(f, 0, SEEK_SET); 655 656 data = QOI_MALLOC(size); 657 if (!data) { 658 fclose(f); 659 return NULL; 660 } 661 662 bytes_read = fread(data, 1, size, f); 663 fclose(f); 664 665 pixels = qoi_decode(data, bytes_read, desc, channels); 666 QOI_FREE(data); 667 return pixels; 668 } 669 670 #endif /* QOI_NO_STDIO */ 671 #endif /* QOI_IMPLEMENTATION */