#include "mini_qoi.h" // ==== utilities ==== static inline uint32_t beu32_get(const uint8_t *a) { uint32_t val = 0; val |= (((uint32_t)a[0]) << 24); val |= (((uint32_t)a[1]) << 16); val |= (((uint32_t)a[2]) << 8); val |= ((uint32_t)a[3]); return val; } // ==== mqoi_desc_t ==== /* Initializes an mQOI image descriptor object. */ void mqoi_desc_init(mqoi_desc_t *desc) { memset(desc, 0, sizeof(mqoi_desc_t)); } /* Pushes a byte to the mQOI image descriptor object. */ void mqoi_desc_push(mqoi_desc_t *desc, uint8_t byte) { ((uint8_t *)desc)[++desc->head] = byte; } /* Reads a byte from the mQOI image descriptor object. Returns NULL when there are none left to read. */ uint8_t *mqoi_desc_pop(mqoi_desc_t *desc) { if (desc->head >= sizeof(mqoi_desc_t) - 1) return NULL; return (uint8_t *)(desc + (++desc->head)); } /* Checks if a mQOI image descriptor is valid, and reads out its width and height into w and h. If it returns a nonzero code, it is invalid (use mqoi_desc_err_t to understand what it means) */ uint8_t mqoi_desc_verify(mqoi_desc_t *desc, uint32_t *w, uint32_t *h) { if (desc->magic[0] != 'q' || desc->magic[1] != 'o' || desc->magic[2] != 'i' || desc->magic[3] != 'f') { return MQOI_DESC_INVALID_MAGIC; } *w = beu32_get(desc->width); *h = beu32_get(desc->height); if (desc->channels != MQOI_CHANNELS_RGB && desc->channels != MQOI_CHANNELS_RGBA) { return MQOI_DESC_INVALID_CHANNELS; } if (desc->colorspace != MQOI_COLORSPACE_SRGB && desc->colorspace != MQOI_COLORSPACE_LINEAR) { return MQOI_DESC_INVALID_COLORSPACE; } return MQOI_DESC_OK; } /* Returns true when the mQOI image descriptor object is completely populated. */ inline bool mqoi_desc_done(const mqoi_desc_t *desc) { return desc->head >= sizeof(mqoi_desc_t) - 1; } // ==== mqoi_dec_t ==== /* Initializes an mQOI decoder object. If number of pixels in the image are given (via n_pix), the mqoi_dec_done function will work. */ void mqoi_dec_init(mqoi_dec_t *dec, uint32_t n_pix) { memset(dec, 0, sizeof(mqoi_dec_t)); dec->prev_px.a = 0xff; dec->pix_left = n_pix; } /* Pushes a byte to the mQOI decoder. Don't call this more than once unless all the pixels have been popped via mqoi_dec_pop! Also, please don't intermingle this function with calls to mqoi_dec_take! */ void mqoi_dec_push(mqoi_dec_t *dec, uint8_t byte) { dec->curr_chunk.value[dec->curr_chunk_head++] = byte; if (dec->curr_chunk_size == 0 && dec->curr_chunk_head == 1) { // if we have the head of a new chunk, get its size switch (dec->curr_chunk.head) { // test 8-bit tags case MQOI_OP8_RUN_RGBA: dec->curr_chunk_size = 5; break; case MQOI_OP8_RUN_RGB: dec->curr_chunk_size = 4; break; default: { // test 2-bit tags // chunk size is 1 unless it's an OP_LUMA chunk dec->curr_chunk_size = 1 + ((dec->curr_chunk.head & MQOI_MASK_OP_2B) == MQOI_OP2_LUMA); break; } } } } /* Automatically read up to 5 bytes so that a subsequent call of mqoi_dec_pop will not return NULL. Returns the number of bytes that have been "taken" from the bytes array. Please don't intermingle this function with calls to mqoi_dec_push! */ uint8_t mqoi_dec_take(mqoi_dec_t *dec, const uint8_t *bytes) { dec->curr_chunk.value[dec->curr_chunk_head++] = *(bytes++); switch (dec->curr_chunk.head) { // test 8-bit tags case MQOI_OP8_RUN_RGBA: dec->curr_chunk_size = 5; break; case MQOI_OP8_RUN_RGB: dec->curr_chunk_size = 4; break; default: { // test 2-bit tags // chunk size is 1 unless it's an OP_LUMA chunk dec->curr_chunk_size = 1 + ((dec->curr_chunk.head & MQOI_MASK_OP_2B) == MQOI_OP2_LUMA); break; } } while (dec->curr_chunk_head < dec->curr_chunk_size) { dec->curr_chunk.value[dec->curr_chunk_head++] = *(bytes++); } return dec->curr_chunk_head; } /* Pops a pixel from the mQOI decoder. Returns NULL if more data is needed, otherwise it returns the address of the next pixel. */ mqoi_rgba_t *mqoi_dec_pop(mqoi_dec_t *dec) { if (dec->curr_chunk_size && dec->curr_chunk_head >= dec->curr_chunk_size) { // if we're at the end of the current chunk mqoi_rgba_t px = {.a = dec->prev_px.a}; mqoi_rgba_t *px_ptr = NULL; bool chunk_done = true; switch (dec->curr_chunk.head) { // test 8-bit tags case MQOI_OP8_RUN_RGBA: // rgba handled px.a = dec->curr_chunk.rgba.a; case MQOI_OP8_RUN_RGB: // rgb handled px.r = dec->curr_chunk.rgb.r; px.g = dec->curr_chunk.rgb.g; px.b = dec->curr_chunk.rgb.b; break; default: { // test 2-bit tags switch (dec->curr_chunk.head & MQOI_MASK_OP_2B) { case MQOI_OP2_INDEX: px_ptr = &dec->hashtable[dec->curr_chunk.head]; // no need to mask bits because the top bits are zero break; case MQOI_OP2_DIFF: // shift out each channel and compute difference px.b = dec->prev_px.b - 2 + (dec->curr_chunk.head & 0b11); // read out db dec->curr_chunk.head >>= 2; // shift in dg px.g = dec->prev_px.g - 2 + (dec->curr_chunk.head & 0b11); // read out dg dec->curr_chunk.head >>= 2; // shift in dr px.r = dec->prev_px.r - 2 + (dec->curr_chunk.head & 0b11); // read out dr break; case MQOI_OP2_LUMA: { int8_t dg = (dec->curr_chunk.head & MQOI_MASK_OP_LUMA_DG) - 32; px.g = dec->prev_px.g + dg; px.b = dec->prev_px.b + (dec->curr_chunk.drdb & 0b1111) - 8 + dg; dec->curr_chunk.drdb >>= 4; px.r = dec->prev_px.r + (dec->curr_chunk.drdb & 0b1111) - 8 + dg; break; } case MQOI_OP2_RUN: { uint8_t run = dec->curr_chunk.head & MQOI_MASK_OP_RUN; px_ptr = &dec->prev_px; if (run) { // if there are still pixels left to emit, chunk_done = false; // don't reset the chunk (bring us back to the block) dec->curr_chunk.head--; } break; } } break; } } if (px_ptr == NULL) { // if the return pixel is null (not in hashtable yet) uint8_t px_hash = MQOI_RGBA_HASH(px); // hash it px_ptr = &dec->hashtable[px_hash]; // get its hashed position px_ptr->r = px.r; px_ptr->g = px.g; px_ptr->b = px.b; px_ptr->a = px.a; } if (chunk_done) { // if the chunk is done, reset the head dec->curr_chunk_head = 0; dec->curr_chunk_size = 0; } dec->prev_px.r = px_ptr->r; dec->prev_px.g = px_ptr->g; dec->prev_px.b = px_ptr->b; dec->prev_px.a = px_ptr->a; dec->pix_left--; return px_ptr; } return NULL; } /* Returns true if the decoder has emitted all pixels necessary to complete the image being decoded. Note that this function will only work if n_pix was given during the initialization of the decoder. */ inline bool mqoi_dec_done(const mqoi_dec_t *dec) { return dec->pix_left == 0; }