82#ifndef SIMPLIFIED_QOI_H_IMPLEMENTATION
83#define SIMPLIFIED_QOI_H_IMPLEMENTATION
85#ifdef SIMPLIFIED_QOI_IMPLEMENTATION
98#define QOI_TAG_MASK 0x3F
100#define QOI_OP_RGB 0xFE
101#define QOI_OP_RGBA 0xFF
103#define QOI_OP_INDEX 0x00
104#define QOI_OP_DIFF 0x40
105#define QOI_OP_LUMA 0x80
106#define QOI_OP_RUN 0xC0
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};
113static const uint8_t QOI_MAGIC[4] = {
'q',
'o',
'i',
'f'};
116static const uint8_t QOI_PADDING[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
137 uint32_t concatenated_pixel_values;
148 qoi_pixel_t buffer[64];
150 qoi_pixel_t prev_pixel;
152 size_t pixel_offset, len;
170 qoi_pixel_t buffer[64];
172 qoi_pixel_t prev_pixel;
174 size_t pixel_seek, img_area, qoi_len;
185static inline uint32_t qoi_get_be32(uint32_t value);
186static inline uint32_t qoi_to_be32(uint32_t value);
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);
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);
199bool qoi_desc_init(qoi_desc_t *desc);
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);
205void write_qoi_header(qoi_desc_t *desc,
void* dest);
206bool read_qoi_header(qoi_desc_t *desc,
void* data);
210bool qoi_enc_init(qoi_desc_t* desc, qoi_enc_t* enc,
void* data);
211bool qoi_enc_done(qoi_enc_t* enc);
213void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc,
void *qoi_pixel_bytes);
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);
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);
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);
228qoi_pixel_t qoi_decode_chunk(qoi_dec_t* dec);
230static inline void qoi_dec_rgb(qoi_dec_t* dec);
231static inline void qoi_dec_rgba(qoi_dec_t* dec);
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);
239static inline uint32_t qoi_get_be32(uint32_t value)
241 uint8_t* bytes = (uint8_t*)&value;
242 uint32_t be_value = (uint32_t) (
253static inline uint32_t qoi_to_be32(uint32_t value)
257 bytes[0] = (value >> 24);
258 bytes[1] = (value >> 16);
259 bytes[2] = (value >> 8);
262 return *((uint32_t*)bytes);
266static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2,
const uint8_t channels)
275 return pixel1.concatenated_pixel_values == pixel2.concatenated_pixel_values;
279inline void qoi_set_pixel_rgb(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue)
282 pixel->green = green;
287inline void qoi_set_pixel_rgba(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
290 pixel->green = green;
292 pixel->alpha = alpha;
296void qoi_initalize_pixel(qoi_pixel_t* pixel)
298 if (pixel == NULL)
return;
299 qoi_set_pixel_rgba(pixel, 0, 0, 0, 0);
303static inline int32_t qoi_get_index_position(qoi_pixel_t pixel)
305 return (pixel.red * 3 + pixel.green * 5 + pixel.blue * 7 + pixel.alpha * 11) % 64;
309bool qoi_desc_init(qoi_desc_t *desc)
311 if (desc == NULL)
return false;
316 desc->colorspace = 0;
322inline void qoi_set_dimensions(qoi_desc_t* desc, uint32_t width, uint32_t height)
325 desc->height = height;
329inline void qoi_set_channels(qoi_desc_t* desc, uint8_t channels)
331 desc->channels = channels;
335inline void qoi_set_colorspace(qoi_desc_t *desc, uint8_t colorspace)
337 desc->colorspace = colorspace;
341void write_qoi_header(qoi_desc_t *desc,
void* dest)
343 if (dest == NULL || desc == NULL)
return;
345 uint8_t *
byte = (uint8_t*)dest;
348 byte[0] = QOI_MAGIC[0];
349 byte[1] = QOI_MAGIC[1];
350 byte[2] = QOI_MAGIC[2];
351 byte[3] = QOI_MAGIC[3];
355 uint32_t* dimension_ptr = (uint32_t*)&
byte[4];
358 dimension_ptr[0] = qoi_to_be32(desc->width);
359 dimension_ptr[1] = qoi_to_be32(desc->height);
361 byte[12] = desc->channels;
362 byte[13] = desc->colorspace;
366bool read_qoi_header(qoi_desc_t *desc,
void* data)
368 if (data == NULL || desc == NULL)
return false;
370 uint8_t *
byte = (uint8_t*)data;
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])
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]));
386 desc->width = qoi_get_be32(desc->width);
387 desc->height = qoi_get_be32(desc->height);
393bool qoi_enc_init(qoi_desc_t* desc, qoi_enc_t* enc,
void* data)
395 if (enc == NULL || desc == NULL || data == NULL)
return false;
397 for (uint8_t element = 0; element < 64; element++)
398 qoi_initalize_pixel(&enc->buffer[element]);
400 enc->len = (size_t)desc->width * (
size_t)desc->height;
404 enc->pixel_offset = 0;
411 qoi_set_pixel_rgba(&enc->prev_pixel, 0, 0, 0, 255);
413 enc->data = (uint8_t*)data;
414 enc->offset = enc->data + 14;
420bool qoi_enc_done(qoi_enc_t* enc)
422 return (enc->pixel_offset >= enc->len);
426bool qoi_dec_init(qoi_desc_t* desc, qoi_dec_t* dec,
void* data,
size_t len)
428 if (dec == NULL || data == NULL || len < 14)
return false;
434 for (uint8_t element = 0; element < 64; element++)
435 qoi_initalize_pixel(&dec->buffer[element]);
437 qoi_set_pixel_rgba(&dec->prev_pixel, 0, 0, 0, 255);
443 dec->img_area = (size_t)desc->width * (
size_t)desc->height;
446 dec->data = (uint8_t*)data;
447 dec->offset = dec->data + 14;
453bool qoi_dec_done(qoi_dec_t* dec)
456 return (dec->offset - dec->data > dec->qoi_len - 8) || (dec->pixel_seek >= dec->img_area);
460static inline void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px)
469 enc->offset[0] = tag[0];
470 enc->offset[1] = tag[1];
471 enc->offset[2] = tag[2];
472 enc->offset[3] = tag[3];
478static inline void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px)
489 enc->offset[0] = tag[0];
490 enc->offset[1] = tag[1];
491 enc->offset[2] = tag[2];
492 enc->offset[3] = tag[3];
493 enc->offset[4] = tag[4];
499static inline void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos)
502 uint8_t tag = QOI_OP_INDEX | index_pos;
503 enc->offset++[0] = tag;
507static inline void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff)
511 (uint8_t)(red_diff + 2) << 4 |
512 (uint8_t)(green_diff + 2) << 2 |
513 (uint8_t)(blue_diff + 2);
515 enc->offset[0] = tag;
521static inline void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg)
524 QOI_OP_LUMA | (uint8_t)(green_diff + 32),
525 (uint8_t)(dr_dg + 8) << 4 | (uint8_t)(db_dg + 8)
528 enc->offset[0] = tag[0];
529 enc->offset[1] = tag[1];
535static inline void qoi_enc_run(qoi_enc_t *enc)
538 uint8_t tag = QOI_OP_RUN | (enc->run - 1);
541 enc->offset++[0] = tag;
551void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc,
void *qoi_pixel_bytes)
562 qoi_pixel_t cur_pixel = *((qoi_pixel_t*)qoi_pixel_bytes);
565 if (desc->channels < 4)
566 cur_pixel.alpha = 255;
568 uint8_t index_pos = qoi_get_index_position(cur_pixel);
571 if (qoi_cmp_pixel(cur_pixel, enc->prev_pixel, desc->channels))
575 if (++enc->run >= 62 || enc->pixel_offset >= enc->len)
590 if (qoi_cmp_pixel(enc->buffer[index_pos], cur_pixel, 4))
592 qoi_enc_index(enc, index_pos);
596 enc->buffer[index_pos] = cur_pixel;
599 if (desc->channels > 3 && cur_pixel.alpha != enc->prev_pixel.alpha)
601 qoi_enc_rgba(enc, cur_pixel);
606 int8_t red_diff, green_diff, blue_diff;
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;
613 dr_dg = red_diff - green_diff;
614 db_dg = blue_diff - green_diff;
617 red_diff >= -2 && red_diff <= 1 &&
618 green_diff >= -2 && green_diff <= 1 &&
619 blue_diff >= -2 && blue_diff <= 1
622 qoi_enc_diff(enc, red_diff, green_diff, blue_diff);
626 dr_dg >= -8 && dr_dg <= 7 &&
627 green_diff >= -32 && green_diff <= 31 &&
628 db_dg >= -8 && db_dg <= 7
631 qoi_enc_luma(enc, green_diff, dr_dg, db_dg);
637 qoi_enc_rgb(enc, cur_pixel);
645 enc->prev_pixel = cur_pixel;
649 if (qoi_enc_done(enc))
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];
665static inline void qoi_dec_rgb(qoi_dec_t* dec)
667 dec->prev_pixel.red = dec->offset[1];
668 dec->prev_pixel.green = dec->offset[2];
669 dec->prev_pixel.blue = dec->offset[3];
675static inline void qoi_dec_rgba(qoi_dec_t* dec)
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];
686static inline void qoi_dec_index(qoi_dec_t* dec, uint8_t tag)
688 dec->prev_pixel = dec->buffer[tag & QOI_TAG_MASK];
694static inline void qoi_dec_diff(qoi_dec_t* dec, uint8_t tag)
696 uint8_t diff = tag & QOI_TAG_MASK;
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;
706 dec->prev_pixel.red += red_diff;
707 dec->prev_pixel.green += green_diff;
708 dec->prev_pixel.blue += blue_diff;
714static inline void qoi_dec_luma(qoi_dec_t* dec, uint8_t tag)
716 uint8_t lumaGreen = (tag & QOI_TAG_MASK) - 32;
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;
728static inline void qoi_dec_run(qoi_dec_t* dec, uint8_t tag){
729 dec->run = tag & QOI_TAG_MASK;
741qoi_pixel_t qoi_decode_chunk(qoi_dec_t* dec)
749 uint8_t tag = dec->offset[0];
756 if (tag == QOI_OP_RGB)
760 else if (tag == QOI_OP_RGBA)
766 uint8_t tag_type = (tag & QOI_TAG);
772 qoi_dec_index(dec, tag);
778 qoi_dec_diff(dec, tag);
784 qoi_dec_luma(dec, tag);
790 qoi_dec_run(dec, tag);
803 dec->buffer[qoi_get_index_position(dec->prev_pixel)] = dec->prev_pixel;
807 return dec->prev_pixel;