N64 QOI Demo
A N64 homebrew app that opens QOI images from ROM
Loading...
Searching...
No Matches
sQOI.h
1/*
2
3 ,';::::::::::::::::::::::::::::::::::::::::::::::::::::::;',
4 oWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMWo
5 dMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMd
6 dMMMMMMMMMMWX0OkkOKNMMMMMMMMMMMWX0OkO0XWMMMMMMMNXNMMMMMMMd
7 dMMMMMMMWKd:;;;,'..'ckNMMMMWKkd;.......,lONMMMK:.cKMMMMMMd
8 dMMMMMMNx;:xKNNNX0d, ,OWMNd. .:d0KK0xc. .:0WMk. .OMMMMMMd
9 dMMMMMWx,dNMMMMMMMMNl. .kNo..ckWMMMMMMW0; 'OWk. .kMMMMMMd
10 dMMMMMN:'OMMMMMWKk0W0' :x' lXNMMMMMMMMMO. :Xx. .kMMMMMMd
11 dMMMMMWl.;KMMMWx. :Kx. ;x' :0XMMMMMMMMMO. ;Kx. .xMMMMMMd
12 dMMMMMMXxx00OOk; .'. .dXc .'lKWMMMMMW0; lNd. .xMMMMMMd
13 dMMMMMMMWx'. .oNMKc .cdkOkd:. :KWd .dMMMMMMd
14 dMMMMMMMWx. .oXMMMNxc;. .,dXMWd. .xMMMMMMd
15 .dWMMMMMMMWKdc;,,,::' .oNMMMMMN0dl:;;:cdONMMMMXxlxXMMMMMM
16 .dWMMMMMMMMMMMWWWWMWXOkOXWMMMMMMMMMMWWMMMMMMMMMMMMMMMMMMMM
17 dWMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
18 .;kOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOk;.
19 c. ...................................................... .c
20
21 THE QUITE OK IMAGE FORMAT
22 Implementation based on the Version 1.0 created by Dominic Szablewski
23 Published on 2022.01.05 – qoiformat.org
24
25 Implementation version - 1.0 - revised 2023-30-07
26
27 QOI specification - https://qoiformat.org/qoi-specification.pdf
28
29 Reference QOI implementation - https://github.com/phoboslab/qoi
30
31 A QOI file consists of a 14-byte header, followed by any number of
32 data “chunks” and an 8-byte end marker.
33
34 qoi_header {
35 char magic[4]; // magic bytes "qoif"
36 uint32_t width; // image width in pixels (BE)
37 uint32_t height; // image height in pixels (BE)
38 uint8_t channels; // 3 = RGB, 4 = RGBA
39 uint8_t colorspace; // 0 = sRGB with linear alpha
40 // 1 = all channels linear
41 };
42
43*/
44
45/*
46
47 Define SIMPLIFIED_QOI_IMPLEMENTATION in only "one"
48 of your source code files (.c, .cpp .cxx), not your header files
49 before you include this library to create this implementation
50
51 #define SIMPLIFIED_QOI_IMPLEMENTATION
52 #include "sQOI.h"
53
54*/
55
56/*
57
58 MIT License
59
60 Copyright (c) 2024-2025 Aftersol
61
62 Permission is hereby granted, free of charge, to any person obtaining a copy
63 of this software and associated documentation files (the "Software"), to deal
64 in the Software without restriction, including without limitation the rights
65 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
66 copies of the Software, and to permit persons to whom the Software is
67 furnished to do so, subject to the following conditions:
68
69 The above copyright notice and this permission notice shall be included in all
70 copies or substantial portions of the Software.
71
72 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
77 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
78 SOFTWARE.
79
80*/
81
82#ifndef SIMPLIFIED_QOI_H_IMPLEMENTATION
83#define SIMPLIFIED_QOI_H_IMPLEMENTATION
84
85#ifdef SIMPLIFIED_QOI_IMPLEMENTATION
86
87#ifdef __cplusplus
88extern "C" {
89#endif
90
91#include <stdint.h>
92#include <stddef.h>
93#include <stdbool.h>
94
95/* QOI OPCODES */
96
97#define QOI_TAG 0xC0
98#define QOI_TAG_MASK 0x3F
99
100#define QOI_OP_RGB 0xFE
101#define QOI_OP_RGBA 0xFF
102
103#define QOI_OP_INDEX 0x00
104#define QOI_OP_DIFF 0x40
105#define QOI_OP_LUMA 0x80
106#define QOI_OP_RUN 0xC0
107
108enum qoi_pixel_color {QOI_RED, QOI_GREEN, QOI_BLUE, QOI_ALPHA};
109enum qoi_channels {QOI_WHITESPACE = 3, QOI_TRANSPARENT = 4};
110enum qoi_colorspace {QOI_SRGB, QOI_LINEAR};
111
112/* QOI magic number */
113static const uint8_t QOI_MAGIC[4] = {'q', 'o', 'i', 'f'};
114
115/* QOI end of file */
116static const uint8_t QOI_PADDING[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
117
118/* QOI descriptor as read by the header */
119typedef struct
120{
121 uint32_t width; /* in big endian */
122 uint32_t height; /* in big endian */
123 uint8_t channels;
124 uint8_t colorspace;
125} qoi_desc_t;
126
127/* pixel values */
128typedef union
129{
130 struct {
131 uint8_t red;
132 uint8_t green;
133 uint8_t blue;
134 uint8_t alpha;
135 };
136 uint8_t channels[4];
137 uint32_t concatenated_pixel_values;
138} qoi_pixel_t;
139
140typedef struct
141{
142 /*
143 A running array[64] (zero-initialized) of previously seen pixel
144 values is maintained by the encoder and decoder. Each pixel that is
145 seen by the encoder and decoder is put into this array at the
146 position formed by a hash function of the color value.
147 */
148 qoi_pixel_t buffer[64];
149
150 qoi_pixel_t prev_pixel;
151
152 size_t pixel_offset, len;
153
154 uint8_t* data;
155 uint8_t* offset;
156
157 uint8_t run : 8;
158 uint32_t pad : 24;
159
160} qoi_enc_t;
161
162typedef struct
163{
164 /*
165 A running array[64] (zero-initialized) of previously seen pixel
166 values is maintained by the encoder and decoder. Each pixel that is
167 seen by the encoder and decoder is put into this array at the
168 position formed by a hash function of the color value.
169 */
170 qoi_pixel_t buffer[64];
171
172 qoi_pixel_t prev_pixel;
173
174 size_t pixel_seek, img_area, qoi_len;
175
176 uint8_t* data;
177 uint8_t* offset;
178
179 uint8_t run : 8;
180 uint32_t pad : 24;
181} qoi_dec_t;
182
183/* Machine specific code */
184
185static inline uint32_t qoi_get_be32(uint32_t value);
186static inline uint32_t qoi_to_be32(uint32_t value);
187
188/* Pixel related code */
189
190void qoi_set_pixel_rgb(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue);
191void qoi_set_pixel_rgba(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha);
192
193void qoi_initalize_pixel(qoi_pixel_t* pixel);
194static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2, const uint8_t channels);
195static inline int32_t qoi_get_index_position(qoi_pixel_t pixel);
196
197/* QOI descriptor functions */
198
199bool qoi_desc_init(qoi_desc_t *desc);
200
201void qoi_set_dimensions(qoi_desc_t *desc, uint32_t width, uint32_t height);
202void qoi_set_channels(qoi_desc_t* desc, uint8_t channels);
203void qoi_set_colorspace(qoi_desc_t* desc, uint8_t colorspace);
204
205void write_qoi_header(qoi_desc_t *desc, void* dest);
206bool read_qoi_header(qoi_desc_t *desc, void* data);
207
208/* QOI encoder functions */
209
210bool qoi_enc_init(qoi_desc_t* desc, qoi_enc_t* enc, void* data);
211bool qoi_enc_done(qoi_enc_t* enc);
212
213void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes);
214
215static inline void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px);
216static inline void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px);
217
218static inline void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos);
219static inline void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff);
220static inline void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg);
221static inline void qoi_enc_run(qoi_enc_t *enc);
222
223/* QOI decoder functions */
224
225bool qoi_dec_init(qoi_desc_t* desc, qoi_dec_t* dec, void* data, size_t len);
226bool qoi_dec_done(qoi_dec_t* dec);
227
228qoi_pixel_t qoi_decode_chunk(qoi_dec_t* dec);
229
230static inline void qoi_dec_rgb(qoi_dec_t* dec);
231static inline void qoi_dec_rgba(qoi_dec_t* dec);
232
233static inline void qoi_dec_index(qoi_dec_t* dec, uint8_t tag);
234static inline void qoi_dec_diff(qoi_dec_t* dec, uint8_t tag);
235static inline void qoi_dec_luma(qoi_dec_t* dec, uint8_t tag);
236static inline void qoi_dec_run(qoi_dec_t* dec, uint8_t tag);
237
238/* Extract a 32-bit big endian integer regardless of endianness */
239static inline uint32_t qoi_get_be32(uint32_t value)
240{
241 uint8_t* bytes = (uint8_t*)&value;
242 uint32_t be_value = (uint32_t) (
243 (bytes[0] << 24) |
244 (bytes[1] << 16) |
245 (bytes[2] << 8) |
246 (bytes[3])
247 );
248
249 return be_value;
250}
251
252/* Write a 32-bit big endian integer regardless of endianness */
253static inline uint32_t qoi_to_be32(uint32_t value)
254{
255 uint8_t bytes[4];
256
257 bytes[0] = (value >> 24);
258 bytes[1] = (value >> 16);
259 bytes[2] = (value >> 8);
260 bytes[3] = (value);
261
262 return *((uint32_t*)bytes);
263}
264
265/* Compares two pixels for the same color */
266static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2, const uint8_t channels)
267{
268 if (channels < 4) /* RGB pixels have three channels; RGBA pixels have four channels for the alpha channel */
269 {
270 /* O2 optimization will mask out these alpha values using AND instruction so it compares only the colors of a pixel */
271 pixel1.alpha = 0;
272 pixel2.alpha = 0;
273 }
274
275 return pixel1.concatenated_pixel_values == pixel2.concatenated_pixel_values; /* compare pixels */
276}
277
278/* Sets the RGB pixel by a certain pixel value */
279inline void qoi_set_pixel_rgb(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue)
280{
281 pixel->red = red;
282 pixel->green = green;
283 pixel->blue = blue;
284}
285
286/* Sets the RGBA pixel by a certain pixel value including an transparency alpha value */
287inline void qoi_set_pixel_rgba(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
288{
289 pixel->red = red;
290 pixel->green = green;
291 pixel->blue = blue;
292 pixel->alpha = alpha;
293}
294
295/* Initalizes the pixels to the default state */
296void qoi_initalize_pixel(qoi_pixel_t* pixel)
297{
298 if (pixel == NULL) return;
299 qoi_set_pixel_rgba(pixel, 0, 0, 0, 0);
300}
301
302/* Hashing function for pixels: up to 64 possible hash values */
303static inline int32_t qoi_get_index_position(qoi_pixel_t pixel)
304{
305 return (pixel.red * 3 + pixel.green * 5 + pixel.blue * 7 + pixel.alpha * 11) % 64;
306}
307
308/* Initalize the QOI desciptor to the default value */
309bool qoi_desc_init(qoi_desc_t *desc)
310{
311 if (desc == NULL) return false;
312
313 desc->width = 0;
314 desc->height = 0;
315 desc->channels = 0;
316 desc->colorspace = 0;
317
318 return true;
319};
320
321/* Sets the image dimensions of an image for QOI descriptor */
322inline void qoi_set_dimensions(qoi_desc_t* desc, uint32_t width, uint32_t height)
323{
324 desc->width = width;
325 desc->height = height;
326}
327
328/* Sets the amount of channels of an image for QOI descriptor */
329inline void qoi_set_channels(qoi_desc_t* desc, uint8_t channels)
330{
331 desc->channels = channels;
332}
333
334/* Sets the colorspace of an image for QOI descriptor */
335inline void qoi_set_colorspace(qoi_desc_t *desc, uint8_t colorspace)
336{
337 desc->colorspace = colorspace;
338}
339
340/* Writes the QOI metadata information to the file */
341void write_qoi_header(qoi_desc_t *desc, void* dest)
342{
343 if (dest == NULL || desc == NULL) return;
344
345 uint8_t *byte = (uint8_t*)dest;
346
347 /* Write the magic characters to the file first */
348 byte[0] = QOI_MAGIC[0];
349 byte[1] = QOI_MAGIC[1];
350 byte[2] = QOI_MAGIC[2];
351 byte[3] = QOI_MAGIC[3];
352
353 /* Writes all the metadata information about the image to the file */
354
355 uint32_t* dimension_ptr = (uint32_t*)&byte[4];
356
357 /* Writes the width and height values of the image to QOI header which stores them in big endian */
358 dimension_ptr[0] = qoi_to_be32(desc->width);
359 dimension_ptr[1] = qoi_to_be32(desc->height);
360
361 byte[12] = desc->channels;
362 byte[13] = desc->colorspace;
363}
364
365/* Check for vaild QOIF file */
366bool read_qoi_header(qoi_desc_t *desc, void* data)
367{
368 if (data == NULL || desc == NULL) return false;
369
370 uint8_t *byte = (uint8_t*)data;
371
372 /* Check magic values for vaild QOIF file */
373 if (!(byte[0] == QOI_MAGIC[0] &&
374 byte[1] == QOI_MAGIC[1] &&
375 byte[2] == QOI_MAGIC[2] &&
376 byte[3] == QOI_MAGIC[3])
377 ) return false;
378
379 /* Read the header for information on how big should the image be and how to decode the image */
380
381 qoi_set_dimensions(desc, *((uint32_t*)&byte[4]), *((uint32_t*)&byte[8]));
382 qoi_set_channels(desc, *((uint8_t*)&byte[12]));
383 qoi_set_colorspace(desc, *((uint8_t*)&byte[13]));
384
385 /* Get width and height of the image from QOI header which stores these values in big endian */
386 desc->width = qoi_get_be32(desc->width);
387 desc->height = qoi_get_be32(desc->height);
388
389 return true;
390}
391
392/* Initalize the QOI encoder to the default state */
393bool qoi_enc_init(qoi_desc_t* desc, qoi_enc_t* enc, void* data)
394{
395 if (enc == NULL || desc == NULL || data == NULL) return false;
396
397 for (uint8_t element = 0; element < 64; element++)
398 qoi_initalize_pixel(&enc->buffer[element]); /* Initalize all the pixels in the buffer to zero for each channel of each pixels */
399
400 enc->len = (size_t)desc->width * (size_t)desc->height;
401
402 enc->pad = 0;
403 enc->run = 0;
404 enc->pixel_offset = 0;
405
406 /*
407 The decoder and encoder start with
408 {r: 0, g: 0, b: 0, a: 255}
409 as the previous pixel value.
410 */
411 qoi_set_pixel_rgba(&enc->prev_pixel, 0, 0, 0, 255);
412
413 enc->data = (uint8_t*)data;
414 enc->offset = enc->data + 14;
415
416 return true;
417}
418
419/* Check if the encoder has finished processing the image */
420bool qoi_enc_done(qoi_enc_t* enc)
421{
422 return (enc->pixel_offset >= enc->len); /* Has the encoder encoded all the pixels yet? */
423}
424
425/* Initalize the decoder to the default state */
426bool qoi_dec_init(qoi_desc_t* desc, qoi_dec_t* dec, void* data, size_t len)
427{
428 if (dec == NULL || data == NULL || len < 14) return false;
429
430 /*
431 A running array[64] (zero-initialized) of previously seen pixel
432 values is maintained by the encoder and decoder.
433 */
434 for (uint8_t element = 0; element < 64; element++)
435 qoi_initalize_pixel(&dec->buffer[element]);
436
437 qoi_set_pixel_rgba(&dec->prev_pixel, 0, 0, 0, 255);
438
439 dec->run = 0;
440 dec->pad = 0;
441
442 dec->pixel_seek = 0;
443 dec->img_area = (size_t)desc->width * (size_t)desc->height;
444 dec->qoi_len = len;
445
446 dec->data = (uint8_t*)data;
447 dec->offset = dec->data + 14;
448
449 return true;
450}
451
452/* Has the decoder reached the end of file? */
453bool qoi_dec_done(qoi_dec_t* dec)
454{
455 /* Subtract eight from qoi_len because of QOI padding */
456 return (dec->offset - dec->data > dec->qoi_len - 8) || (dec->pixel_seek >= dec->img_area); /* Has the decoder decoded all the pixels yet or reached the end of the file? */
457}
458
459/* Place the RGB information into the QOI file */
460static inline void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px)
461{
462 uint8_t tag[4] = {
463 QOI_OP_RGB,
464 px.red,
465 px.green,
466 px.blue
467 };
468
469 enc->offset[0] = tag[0]; /* RGB opcode */
470 enc->offset[1] = tag[1]; /* Red */
471 enc->offset[2] = tag[2]; /* Green */
472 enc->offset[3] = tag[3]; /* Blue */
473
474 enc->offset += 4;
475}
476
477/* Place the RGBA information into the QOI file */
478static inline void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px)
479{
480 uint8_t tag[5] =
481 {
482 QOI_OP_RGBA,
483 px.red,
484 px.green,
485 px.blue,
486 px.alpha
487 };
488
489 enc->offset[0] = tag[0]; /* RGBA opcode */
490 enc->offset[1] = tag[1]; /* Red */
491 enc->offset[2] = tag[2]; /* Green */
492 enc->offset[3] = tag[3]; /* Blue */
493 enc->offset[4] = tag[4]; /* Alpha */
494
495 enc->offset += 5;
496}
497
498/* Place the index position of the buffer into the QOI file */
499static inline void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos)
500{
501 /* The run-length is stored with a bias of -1 */
502 uint8_t tag = QOI_OP_INDEX | index_pos;
503 enc->offset++[0] = tag;
504}
505
506/* Place the differences between color values into the QOI file */
507static inline void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff)
508{
509 uint8_t tag =
510 QOI_OP_DIFF |
511 (uint8_t)(red_diff + 2) << 4 |
512 (uint8_t)(green_diff + 2) << 2 |
513 (uint8_t)(blue_diff + 2);
514
515 enc->offset[0] = tag;
516
517 enc->offset++;
518}
519
520/* Place the luma values into the QOI file */
521static inline void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg)
522{
523 uint8_t tag[2] = {
524 QOI_OP_LUMA | (uint8_t)(green_diff + 32),
525 (uint8_t)(dr_dg + 8) << 4 | (uint8_t)(db_dg + 8)
526 };
527
528 enc->offset[0] = tag[0];
529 enc->offset[1] = tag[1];
530
531 enc->offset += 2;
532}
533
534/* Place the run length of a pixel color information into the QOI file */
535static inline void qoi_enc_run(qoi_enc_t *enc)
536{
537 /* The run-length is stored with a bias of -1 */
538 uint8_t tag = QOI_OP_RUN | (enc->run - 1);
539 enc->run = 0;
540
541 enc->offset++[0] = tag;
542}
543
544/*
545 WARNING: In this function below, you must provide enough memory to put the encoded images
546 The safest amount of space to store encoded images is the equation below
547
548 (image width) * (image height) * ((amount of channels in a pixel) + 1) = bytes required to store encoded image
549*/
550
551void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes)
552{
553
554 /*
555 Assume that the pixel byte order is the following below
556 bytes[0] = red;
557 bytes[1] = green;
558 bytes[2] = blue;
559 bytes[3] = alpha;
560 */
561
562 qoi_pixel_t cur_pixel = *((qoi_pixel_t*)qoi_pixel_bytes);
563
564 /* Assume an RGB pixel with three channels has an alpha value that makes pixels opaque */
565 if (desc->channels < 4)
566 cur_pixel.alpha = 255;
567
568 uint8_t index_pos = qoi_get_index_position(cur_pixel);
569
570 /* Increment run length by 1 if pixels are the same */
571 if (qoi_cmp_pixel(cur_pixel, enc->prev_pixel, desc->channels))
572 {
573 /* Note that the runlengths 63 and 64 (b111110 and b111111) are illegal as they are
574 occupied by the QOI_OP_RGB and QOI_OP_RGBA tags. */
575 if (++enc->run >= 62 || enc->pixel_offset >= enc->len)
576 {
577 qoi_enc_run(enc);
578 }
579 }
580 else
581 {
582 if (enc->run > 0)
583 {
584 /* Write opcode for because there are differences in pixels
585 The run-length is stored with a bias of -1 */
586 qoi_enc_run(enc);
587 }
588
589 /* Check if pixels exist in one of the pixel hash buffers */
590 if (qoi_cmp_pixel(enc->buffer[index_pos], cur_pixel, 4))
591 {
592 qoi_enc_index(enc, index_pos);
593 }
594 else
595 {
596 enc->buffer[index_pos] = cur_pixel;
597
598 /* QOI doesn't have opcodes for alpha values so check alpha values between two pixels first */
599 if (desc->channels > 3 && cur_pixel.alpha != enc->prev_pixel.alpha)
600 {
601 qoi_enc_rgba(enc, cur_pixel);
602 }
603 else
604 {
605 /* Check the difference between color values to determine opcode */
606 int8_t red_diff, green_diff, blue_diff;
607 int8_t dr_dg, db_dg;
608
609 red_diff = cur_pixel.red - enc->prev_pixel.red;
610 green_diff = cur_pixel.green - enc->prev_pixel.green;
611 blue_diff = cur_pixel.blue - enc->prev_pixel.blue;
612
613 dr_dg = red_diff - green_diff;
614 db_dg = blue_diff - green_diff;
615
616 if (
617 red_diff >= -2 && red_diff <= 1 &&
618 green_diff >= -2 && green_diff <= 1 &&
619 blue_diff >= -2 && blue_diff <= 1
620 )
621 {
622 qoi_enc_diff(enc, red_diff, green_diff, blue_diff);
623 }
624
625 else if (
626 dr_dg >= -8 && dr_dg <= 7 &&
627 green_diff >= -32 && green_diff <= 31 &&
628 db_dg >= -8 && db_dg <= 7
629 )
630 {
631 qoi_enc_luma(enc, green_diff, dr_dg, db_dg);
632 }
633
634 /* otherwise write an RGB tag containting the RGB values of a pixel */
635 else
636 {
637 qoi_enc_rgb(enc, cur_pixel);
638 }
639 }
640
641 }
642 }
643
644 /* Advance the pixel offset by one and sets the previous pixel to the current pixel */
645 enc->prev_pixel = cur_pixel;
646 enc->pixel_offset++;
647
648 /* Write QOI padding when finished encoding the image */
649 if (qoi_enc_done(enc))
650 {
651 enc->offset[0] = QOI_PADDING[0];
652 enc->offset[1] = QOI_PADDING[1];
653 enc->offset[2] = QOI_PADDING[2];
654 enc->offset[3] = QOI_PADDING[3];
655 enc->offset[4] = QOI_PADDING[4];
656 enc->offset[5] = QOI_PADDING[5];
657 enc->offset[6] = QOI_PADDING[6];
658 enc->offset[7] = QOI_PADDING[7];
659
660 enc->offset += 8;
661 }
662}
663
664/* Get and set the RGB values from the QOI file */
665static inline void qoi_dec_rgb(qoi_dec_t* dec)
666{
667 dec->prev_pixel.red = dec->offset[1];
668 dec->prev_pixel.green = dec->offset[2];
669 dec->prev_pixel.blue = dec->offset[3];
670
671 dec->offset += 4;
672}
673
674/* Get and set the RGB values from the QOI file */
675static inline void qoi_dec_rgba(qoi_dec_t* dec)
676{
677 dec->prev_pixel.red = dec->offset[1];
678 dec->prev_pixel.green = dec->offset[2];
679 dec->prev_pixel.blue = dec->offset[3];
680 dec->prev_pixel.alpha = dec->offset[4];
681
682 dec->offset += 5;
683}
684
685/* Get and set the seek position of the buffer from the QOI file */
686static inline void qoi_dec_index(qoi_dec_t* dec, uint8_t tag)
687{
688 dec->prev_pixel = dec->buffer[tag & QOI_TAG_MASK];
689
690 dec->offset += 1;
691}
692
693/* Get the differences between values of the color channels from the QOI file */
694static inline void qoi_dec_diff(qoi_dec_t* dec, uint8_t tag)
695{
696 uint8_t diff = tag & QOI_TAG_MASK;
697
698 /* Do some wizardary to get the differences between three color channel values */
699
700 uint8_t red_diff = ((diff >> 4) & 0x03) - 2;
701 uint8_t green_diff = ((diff >> 2) & 0x03) - 2;
702 uint8_t blue_diff = (diff & 0x03) - 2;
703
704 /* Add up the differences between three color channel values individually */
705
706 dec->prev_pixel.red += red_diff;
707 dec->prev_pixel.green += green_diff;
708 dec->prev_pixel.blue += blue_diff;
709
710 dec->offset += 1;
711}
712
713/* Gets the luma values from the QOI file */
714static inline void qoi_dec_luma(qoi_dec_t* dec, uint8_t tag)
715{
716 uint8_t lumaGreen = (tag & QOI_TAG_MASK) - 32;
717
718 /* Do some lumaGreen wizardary to get and add the differences between three color channel values */
719
720 dec->prev_pixel.red += lumaGreen + ((dec->offset[1] & 0xF0) >> 4) - 8;
721 dec->prev_pixel.green += lumaGreen;
722 dec->prev_pixel.blue += lumaGreen + (dec->offset[1] & 0x0F) - 8;
723
724 dec->offset += 2;
725}
726
727/* Gets the run length of the pixel from the QOI file */
728static inline void qoi_dec_run(qoi_dec_t* dec, uint8_t tag){
729 dec->run = tag & QOI_TAG_MASK;
730
731 dec->offset += 1;
732}
733
734/*
735 WARNING: In this function below, you must provide enough memory to put the decoded images
736 The safest amount of space to store decoded images is the equation below
737
738 (image width) * (image height) * (amount of channels in a pixel) = bytes required to store decoded image
739*/
740
741qoi_pixel_t qoi_decode_chunk(qoi_dec_t* dec)
742{
743
744 if (dec->run > 0)
745 dec->run--;
746
747 else
748 {
749 uint8_t tag = dec->offset[0]; /* opcode for qoi decompression */
750
751 /*
752 The 8-bit tags have precedence over the 2-bit tags.
753 A decoder must check for the presence of an 8-bit tag first.
754 */
755
756 if (tag == QOI_OP_RGB) /* RGB pixel */
757 {
758 qoi_dec_rgb(dec);
759 }
760 else if (tag == QOI_OP_RGBA) /* RGBA pixel */
761 {
762 qoi_dec_rgba(dec);
763 }
764 else
765 {
766 uint8_t tag_type = (tag & QOI_TAG); /* opcode for qoi decompression */
767
768 switch(tag_type)
769 {
770 case QOI_OP_INDEX:
771 {
772 qoi_dec_index(dec, tag);
773
774 break;
775 }
776 case QOI_OP_DIFF:
777 {
778 qoi_dec_diff(dec, tag);
779
780 break;
781 }
782 case QOI_OP_LUMA:
783 {
784 qoi_dec_luma(dec, tag);
785
786 break;
787 }
788 case QOI_OP_RUN:
789 {
790 qoi_dec_run(dec, tag);
791
792 break;
793 }
794 default:
795 {
796 dec->offset += 1; /* move on to the new packet if there is an invaild opcode */
797
798 break;
799 }
800 }
801 }
802
803 dec->buffer[qoi_get_index_position(dec->prev_pixel)] = dec->prev_pixel;
804 }
805
806 dec->pixel_seek++;
807 return dec->prev_pixel;
808}
809
810#ifdef __cplusplus
811}
812#endif
813
814#endif /* SIMPLIFIED_QOI_IMPLEMENTATION */
815
816#endif /* SIMPLIFIED_QOI_H_IMPLEMENTATION */