N64 QOI Encoder Demo 1.0.1
Encoder for N64 that saves it to SD card
Loading...
Searching...
No Matches
qoi_enc_n64.h
Go to the documentation of this file.
1
44
45#ifndef QOI_ENC_N64_H
46#define QOI_ENC_N64_H
47
48#ifdef __cplusplus
49extern "C" {
50#endif
51
52#include <stdint.h>
53#include <stddef.h>
54#include <stdbool.h>
55
56#define QOI_ALLOW_MEM_ALLOC 1
57#define QOI_ALLOW_MEM_FREE 1
58
59#if QOI_ALLOW_MEM_ALLOC
60#include <stdlib.h>
61#endif
62
63/* QOI OPCODES */
64
66#define QOI_TAG 0xC0
68#define QOI_TAG_MASK 0x3F
69
71#define QOI_OP_RGB 0xFE
73#define QOI_OP_RGBA 0xFF
75#define QOI_OP_INDEX 0x00
77#define QOI_OP_DIFF 0x40
79#define QOI_OP_LUMA 0x80
81#define QOI_OP_RUN 0xC0
82
86
88static const uint8_t QOI_MAGIC[4] = {'q', 'o', 'i', 'f'};
89
91static const uint8_t QOI_PADDING[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
92
94typedef struct
95{
97 uint32_t width;
99 uint32_t height;
101 uint8_t channels;
103 uint8_t colorspace;
104} qoi_desc_t;
105
107typedef union
108{
110 struct {
112 uint8_t red;
114 uint8_t green;
116 uint8_t blue;
118 uint8_t alpha;
119 };
120
122 uint8_t channels[4];
124 uint32_t concatenated_pixel_values;
126
127
129typedef struct
130{
136 qoi_pixel_t pix_buffer[64];
137
139 qoi_pixel_t prev_pixel;
140
142 uint8_t* enc_buffer;
144 uint32_t buffer_offset;
146 uint32_t buffer_len;
147
149 uint32_t pixel_offset;
151 uint32_t pixels_written;
153 uint32_t len;
155 uint8_t run;
156} qoi_enc_t;
157
158
159/* Machine specific code */
160
161static inline uint32_t qoi_get_be32(uint32_t value);
162static inline uint32_t qoi_to_be32(uint32_t value);
163
164/* Pixel related code */
165
166void qoi_set_pixel_rgb(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue);
167void qoi_set_pixel_rgba(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha);
168
170static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2, const uint8_t channels);
171static inline int32_t qoi_get_index_position(qoi_pixel_t pixel);
172
173/* QOI descriptor functions */
174
175bool qoi_desc_init(qoi_desc_t *desc);
176
177void qoi_set_dimensions(qoi_desc_t *desc, uint32_t width, uint32_t height);
178void qoi_set_channels(qoi_desc_t* desc, uint8_t channels);
179void qoi_set_colorspace(qoi_desc_t* desc, uint8_t colorspace);
180
181void write_qoi_header(qoi_desc_t *desc, void* dest);
182bool read_qoi_header(qoi_desc_t *desc, void* data);
183
184/* QOI encoder functions */
185
186bool qoi_enc_init(qoi_desc_t* desc, qoi_enc_t* enc);
187
188#if QOI_ALLOW_MEM_ALLOC
189bool qoi_enc_alloc_buffer(qoi_enc_t *enc, uint32_t data_len, bool shouldFreePrevBuffer);
190bool qoi_enc_set_buffer(qoi_enc_t *enc, void* newBuffer, uint32_t len, bool shouldFreePrevBuffer);
192#else
193bool qoi_enc_set_buffer(qoi_enc_t *enc, void* newBuffer, uint32_t len);
194#endif
195
197
198void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes);
199
200static inline void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px);
201static inline void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px);
202
203static inline void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos);
204static inline void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff);
205static inline void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg);
206static inline void qoi_enc_run(qoi_enc_t *enc);
207
211static inline uint32_t qoi_get_be32(uint32_t value)
212{
213 uint8_t* bytes = (uint8_t*)&value;
214 uint32_t be_value = (uint32_t) (
215 (bytes[0] << 24) |
216 (bytes[1] << 16) |
217 (bytes[2] << 8) |
218 (bytes[3])
219 );
220
221 return be_value;
222}
223
227static inline uint32_t qoi_to_be32(uint32_t value)
228{
229 uint8_t bytes[4];
230
231 bytes[0] = (value >> 24);
232 bytes[1] = (value >> 16);
233 bytes[2] = (value >> 8);
234 bytes[3] = (value);
235
236 return *((uint32_t*)bytes);
237}
238
244static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2, const uint8_t channels)
245{
246 if (channels < 4) /* RGB pixels have three channels; RGBA pixels have four channels for the alpha channel */
247 {
248 /* O2 optimization will mask out these alpha values using OR instruction so it compares only the colors of a pixel */
249 pixel1.alpha = 255;
250 pixel2.alpha = 255;
251 }
252
253 return pixel1.concatenated_pixel_values == pixel2.concatenated_pixel_values; /* compare pixels */
254}
255
261inline void qoi_set_pixel_rgb(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue)
262{
263 pixel->red = red;
264 pixel->green = green;
265 pixel->blue = blue;
266}
267
274inline void qoi_set_pixel_rgba(qoi_pixel_t* pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
275{
276 pixel->red = red;
277 pixel->green = green;
278 pixel->blue = blue;
279 pixel->alpha = alpha;
280}
281
285{
286 if (pixel == NULL) return;
287 *pixel = (qoi_pixel_t){0};
288}
289
293static inline int32_t qoi_get_index_position(qoi_pixel_t pixel)
294{
295 return (pixel.red * 3 + pixel.green * 5 + pixel.blue * 7 + pixel.alpha * 11) % 64;
296}
297
302{
303 if (desc == NULL) return false;
304
305 desc->width = 0;
306 desc->height = 0;
307 desc->channels = 0;
308 desc->colorspace = 0;
309
310 return true;
311}
312
317inline void qoi_set_dimensions(qoi_desc_t* desc, uint32_t width, uint32_t height)
318{
319 desc->width = width;
320 desc->height = height;
321}
322
326inline void qoi_set_channels(qoi_desc_t* desc, uint8_t channels)
327{
328 desc->channels = channels;
329}
330
334inline void qoi_set_colorspace(qoi_desc_t *desc, uint8_t colorspace)
335{
336 desc->colorspace = colorspace;
337}
338
342void write_qoi_header(qoi_desc_t *desc, void* dest)
343{
344 if (dest == NULL || desc == NULL) return;
345
346 uint8_t *byte = (uint8_t*)dest;
347
348 /* Write the magic characters to the file first */
349 *(uint32_t*)byte = *(uint32_t*)QOI_MAGIC;
350
351 /* Writes all the metadata information about the image to the file */
352
353 uint32_t* dimension_ptr = (uint32_t*)&byte[4];
354
355 /* Writes the width and height values of the image to QOI header which stores them in big endian */
356 dimension_ptr[0] = qoi_to_be32(desc->width);
357 dimension_ptr[1] = qoi_to_be32(desc->height);
358
359 byte[12] = desc->channels;
360 byte[13] = desc->colorspace;
361}
362
366static inline void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px)
367{
368 enc->enc_buffer[enc->buffer_offset + 0] = QOI_OP_RGB;
369 enc->enc_buffer[enc->buffer_offset + 1] = px.red;
370 enc->enc_buffer[enc->buffer_offset + 2] = px.green;
371 enc->enc_buffer[enc->buffer_offset + 3] = px.blue;
372 enc->buffer_offset += 4;
373}
374
378static inline void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px)
379{
380 enc->enc_buffer[enc->buffer_offset + 0] = QOI_OP_RGBA;
381 enc->enc_buffer[enc->buffer_offset + 1] = px.red;
382 enc->enc_buffer[enc->buffer_offset + 2] = px.green;
383 enc->enc_buffer[enc->buffer_offset + 3] = px.blue;
384 enc->enc_buffer[enc->buffer_offset + 4] = px.alpha;
385 enc->buffer_offset += 5;
386}
387
391static inline void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos)
392{
393 /* The run-length is stored with a bias of -1 */
394 uint8_t tag = QOI_OP_INDEX | index_pos;
395 enc->enc_buffer[enc->buffer_offset++] = tag;
396}
397
400static inline void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff)
401{
402 uint8_t tag =
404 (uint8_t)(red_diff + 2) << 4 |
405 (uint8_t)(green_diff + 2) << 2 |
406 (uint8_t)(blue_diff + 2);
407
408 enc->enc_buffer[enc->buffer_offset++] = tag;
409}
410
413static inline void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg)
414{
415 enc->enc_buffer[enc->buffer_offset + 0] = QOI_OP_LUMA | (uint8_t)(green_diff + 32);
416 enc->enc_buffer[enc->buffer_offset + 1] = (uint8_t)(dr_dg + 8) << 4 | (uint8_t)(db_dg + 8);
417
418 enc->buffer_offset += 2;
419}
420
423static inline void qoi_enc_run(qoi_enc_t *enc)
424{
425 /* The run-length is stored with a bias of -1 */
426 uint8_t tag = QOI_OP_RUN | (enc->run - 1);
427 enc->run = 0;
428
429 enc->enc_buffer[enc->buffer_offset++] = tag;
430}
431
434void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes)
435{
436
437 /*
438 Assume that the pixel byte order is the following below
439 bytes[0] = red;
440 bytes[1] = green;
441 bytes[2] = blue;
442 bytes[3] = alpha;
443 */
444
445 qoi_pixel_t cur_pixel = *((qoi_pixel_t*)qoi_pixel_bytes);
446
447 /* Assume an RGB pixel with three channels has an alpha value that makes pixels opaque */
448 if (desc->channels < 4)
449 cur_pixel.alpha = 255;
450
451 uint8_t index_pos = qoi_get_index_position(cur_pixel);
452 enc->pixels_written++;
453
454 /* Increment run length by 1 if pixels are the same */
455 if (qoi_cmp_pixel(cur_pixel, enc->prev_pixel, desc->channels))
456 {
457 /* Note that the runlengths 63 and 64 (b111110 and b111111) are illegal as they are
458 occupied by the QOI_OP_RGB and QOI_OP_RGBA tags. */
459 if (++enc->run >= 62 || enc->pixels_written >= enc->len)
460 {
461 qoi_enc_run(enc);
462 }
463 }
464 else
465 {
466 if (enc->run > 0)
467 {
468 /* Write opcode for because there are differences in pixels
469 The run-length is stored with a bias of -1 */
470 qoi_enc_run(enc);
471 }
472
473 /* Check if pixels exist in one of the pixel hash buffers */
474 if (qoi_cmp_pixel(enc->pix_buffer[index_pos], cur_pixel, 4))
475 {
476 qoi_enc_index(enc, index_pos);
477 }
478 else
479 {
480 enc->pix_buffer[index_pos] = cur_pixel;
481
482 /* QOI doesn't have opcodes for alpha values so check alpha values between two pixels first */
483 if (desc->channels > 3 && cur_pixel.alpha != enc->prev_pixel.alpha)
484 {
485 qoi_enc_rgba(enc, cur_pixel);
486 }
487 else
488 {
489 /* Check the difference between color values to determine opcode */
490 int8_t red_diff, green_diff, blue_diff;
491 int8_t dr_dg, db_dg;
492
493 red_diff = cur_pixel.red - enc->prev_pixel.red;
494 green_diff = cur_pixel.green - enc->prev_pixel.green;
495 blue_diff = cur_pixel.blue - enc->prev_pixel.blue;
496
497 dr_dg = red_diff - green_diff;
498 db_dg = blue_diff - green_diff;
499
500 if (
501 red_diff >= -2 && red_diff <= 1 &&
502 green_diff >= -2 && green_diff <= 1 &&
503 blue_diff >= -2 && blue_diff <= 1
504 )
505 {
506 qoi_enc_diff(enc, red_diff, green_diff, blue_diff);
507 }
508
509 else if (
510 dr_dg >= -8 && dr_dg <= 7 &&
511 green_diff >= -32 && green_diff <= 31 &&
512 db_dg >= -8 && db_dg <= 7
513 )
514 {
515 qoi_enc_luma(enc, green_diff, dr_dg, db_dg);
516 }
517
518 /* otherwise write an RGB tag containting the RGB values of a pixel */
519 else
520 {
521 qoi_enc_rgb(enc, cur_pixel);
522 }
523 }
524
525 }
526 }
527
528 /* Advance the pixel offset by one and sets the previous pixel to the current pixel */
529 enc->prev_pixel = cur_pixel;
530
531}
532
538{
539 if (enc == NULL || desc == NULL) return false;
540
541 for (uint8_t element = 0; element < 64; element++) {
542 /* Initalize all the pixels in the buffer to zero for each channel of each pixels */
543 qoi_initalize_pixel(&enc->pix_buffer[element]);
544 }
545
546 enc->len = (uint32_t)desc->width * (uint32_t)desc->height;
547
548 enc->run = 0;
549 enc->pixels_written = 0;
550
551 /*
552 The decoder and encoder start with
553 {r: 0, g: 0, b: 0, a: 255}
554 as the previous pixel value.
555 */
556 qoi_set_pixel_rgba(&enc->prev_pixel, 0, 0, 0, 255);
557
558 return true;
559}
560
561#if QOI_ALLOW_MEM_ALLOC
568bool qoi_enc_set_buffer(qoi_enc_t *enc, void* newBuffer, uint32_t len, bool shouldFreePrevBuffer)
569#else
575bool qoi_enc_set_buffer(qoi_enc_t *enc, void* newBuffer, uint32_t len)
576#endif
577{
578 if (enc == NULL || newBuffer == NULL || len == 0) return false;
579
580 #if QOI_ALLOW_MEM_ALLOC
581 if (shouldFreePrevBuffer == true && enc->enc_buffer)
582 {
583 free(enc->enc_buffer);
584 }
585 #endif
586
587 enc->enc_buffer = (uint8_t*)newBuffer;
588 enc->buffer_len = len;
589
591
592 return true;
593}
594
600#if QOI_ALLOW_MEM_ALLOC
601bool qoi_enc_alloc_buffer(qoi_enc_t *enc, uint32_t len, bool shouldFreePrevBuffer)
602{
603 if (enc == NULL || len == 0) return false;
604
605 uint8_t *newBuffer = (uint8_t*)malloc(len * sizeof(uint8_t));;
606
607 if (newBuffer == NULL)
608 {
609 return false;
610 }
611
612 if (shouldFreePrevBuffer == true && enc->enc_buffer) {
613 free(enc->enc_buffer);
614 }
615
616 enc->enc_buffer = newBuffer;
617 enc->buffer_len = len;
618
620
621 return true;
622}
623#endif
624
625
626#if QOI_ALLOW_MEM_ALLOC
631{
632 if (enc == NULL || enc->enc_buffer == NULL)
633 goto qoi_enc_free_buffer_skip;
634
635 free(enc->enc_buffer);
636
637 enc->enc_buffer = NULL;
638 enc->buffer_len = 0;
639
641
642qoi_enc_free_buffer_skip:
643 return true;
644}
645#endif
646
651{
652 if (enc == NULL) return false;
653
654 enc->buffer_offset = 0;
655
656 return true;
657}
658
659#ifdef __cplusplus
660}
661#endif
662
663#endif // QOI_ENC_N64_H
static void qoi_enc_rgba(qoi_enc_t *enc, qoi_pixel_t px)
Place the RGBA information into the QOI file.
static uint32_t qoi_get_be32(uint32_t value)
Extract a 32-bit big endian integer regardless of endianness.
#define QOI_OP_RGB
11111110: Followed by 3 bytes of RGB data, representing the color of the pixel. This is used when the...
Definition qoi_enc_n64.h:71
void qoi_set_pixel_rgba(qoi_pixel_t *pixel, uint8_t red, uint8_t green, uint8_t blue, uint8_t alpha)
Sets the RGBA pixel by a certain pixel value including an transparency alpha value.
#define QOI_OP_INDEX
00xxxxxx: Use the color at the index xx in the color index array.
Definition qoi_enc_n64.h:75
bool qoi_desc_init(qoi_desc_t *desc)
Initalize the QOI desciptor to the default values.
void write_qoi_header(qoi_desc_t *desc, void *dest)
Writes the QOI metadata information to the file.
void qoi_set_dimensions(qoi_desc_t *desc, uint32_t width, uint32_t height)
Sets the image dimensions of an image for QOI descriptor.
bool read_qoi_header(qoi_desc_t *desc, void *data)
static void qoi_enc_luma(qoi_enc_t *enc, uint8_t green_diff, uint8_t dr_dg, uint8_t db_dg)
Place the luma values into the QOI opcode.
void qoi_set_pixel_rgb(qoi_pixel_t *pixel, uint8_t red, uint8_t green, uint8_t blue)
Sets the RGB pixel by a certain pixel value.
static const uint8_t QOI_MAGIC[4]
QOI magic number.
Definition qoi_enc_n64.h:88
#define QOI_OP_RGBA
11111110: Followed by 3 bytes of RGB data and 1 byte of alpha data, representing the color of the pix...
Definition qoi_enc_n64.h:73
static const uint8_t QOI_PADDING[8]
QOI end of file padding bytes.
Definition qoi_enc_n64.h:91
static void qoi_enc_diff(qoi_enc_t *enc, uint8_t red_diff, uint8_t green_diff, uint8_t blue_diff)
Place the differences between color values into the QOI opcode.
void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes)
Encode pixel data into QOI opcodes.
qoi_pixel_color
Definition qoi_enc_n64.h:83
@ QOI_RED
Definition qoi_enc_n64.h:83
@ QOI_GREEN
Definition qoi_enc_n64.h:83
@ QOI_ALPHA
Definition qoi_enc_n64.h:83
@ QOI_BLUE
Definition qoi_enc_n64.h:83
static bool qoi_cmp_pixel(qoi_pixel_t pixel1, qoi_pixel_t pixel2, const uint8_t channels)
Compares two pixels for the same color.
static void qoi_enc_run(qoi_enc_t *enc)
Place the run length of a pixel color information into the QOI opcode.
bool qoi_enc_init(qoi_desc_t *desc, qoi_enc_t *enc)
Initalize the QOI encoder to the default state.
void qoi_initalize_pixel(qoi_pixel_t *pixel)
Initalizes the pixels to the default state.
void qoi_set_channels(qoi_desc_t *desc, uint8_t channels)
Sets the amount of channels of an image for QOI descriptor.
#define QOI_OP_LUMA
10xxxxxx: The color is represented by a luminance value and two chroma values.
Definition qoi_enc_n64.h:79
qoi_channels
Definition qoi_enc_n64.h:84
@ QOI_TRANSPARENT
Definition qoi_enc_n64.h:84
@ QOI_WHITESPACE
Definition qoi_enc_n64.h:84
static int32_t qoi_get_index_position(qoi_pixel_t pixel)
Hashing function for pixels: up to 64 possible hash values.
#define QOI_OP_DIFF
01xxxxxx: The color is the same as the previous pixel, with each channel optionally modified by a sma...
Definition qoi_enc_n64.h:77
qoi_colorspace
Definition qoi_enc_n64.h:85
@ QOI_LINEAR
Definition qoi_enc_n64.h:85
@ QOI_SRGB
Definition qoi_enc_n64.h:85
bool qoi_enc_set_buffer(qoi_enc_t *enc, void *newBuffer, uint32_t len, bool shouldFreePrevBuffer)
Sets a buffer for the QOI encoder, automatically free if free buffer flag is set.
bool qoi_enc_reset_buffer(qoi_enc_t *enc)
Resets the buffer of the QOI encoder to the default state.
bool qoi_enc_alloc_buffer(qoi_enc_t *enc, uint32_t data_len, bool shouldFreePrevBuffer)
Allocates a buffer for the QOI encoder.
static void qoi_enc_index(qoi_enc_t *enc, uint8_t index_pos)
Place the index position of the buffer into the QOI file.
bool qoi_enc_free_buffer(qoi_enc_t *enc)
Frees the buffer allocated for the QOI encoder.
#define QOI_OP_RUN
11xxxxxx: The color is the same as the previous pixel, and this run continues for xx pixels.
Definition qoi_enc_n64.h:81
static void qoi_enc_rgb(qoi_enc_t *enc, qoi_pixel_t px)
Place the RGB information into the QOI file.
static uint32_t qoi_to_be32(uint32_t value)
Write a 32-bit big endian integer regardless of endianness.
void qoi_set_colorspace(qoi_desc_t *desc, uint8_t colorspace)
Sets the colorspace of an image for QOI descriptor.
QOI descriptor as read by the header of a QOI file.
Definition qoi_enc_n64.h:95
uint8_t channels
The number of channels in the image, which is either 3 for RGB or 4 for RGBA.
uint32_t width
The width of the image in pixels, stored in big endian format.
Definition qoi_enc_n64.h:97
uint32_t height
The height of the image in pixels, stored in big endian format.
Definition qoi_enc_n64.h:99
uint8_t colorspace
The color space of the image, which is either 0 for sRGB with linear alpha or 1 for linear RGB.
QOI encoder structure.
uint32_t buffer_offset
The offset of the buffer to write the next encoded data to.
uint8_t * enc_buffer
The buffer to store the encoded QOI data before writing to the file.
uint32_t buffer_len
The total length of the buffer.
uint32_t len
The total length of the pixel data to encode (width * height).
uint8_t run
The run length of the current pixel value being encoded.
qoi_pixel_t prev_pixel
The previous pixel value is used to compare with the current pixel value to determine which QOI opcod...
uint32_t pixels_written
The total number of pixels encoded so far.
qoi_pixel_t pix_buffer[64]
The total number of pixels in the image to encode.
QOI pixel structure for storing the color values of a pixel in both individual channels and as a conc...
uint8_t blue
The blue value of the pixel.
uint32_t concatenated_pixel_values
The concatenated pixel values in a single 32-bit integer for easy comparison of pixels as a single va...
uint8_t green
The green value of the pixel.
uint8_t alpha
The alpha value of the pixel, which represents the transparency of the pixel. A value of 0 means the ...
uint8_t red
The red value of the pixel.