N64 QOI Encoder Demo 1.0.1
Encoder for N64 that saves it to SD card
Loading...
Searching...
No Matches
qoi_enc.c
Go to the documentation of this file.
1
55
56#include <stdio.h>
57#include <stdlib.h>
58#include <string.h>
59
60#include "qoi_enc_n64.h"
61
91
95#define ENC_BUFFER_SIZE 16384
96
102uint16_t read_be_u16(uint16_t val)
103{
104 uint8_t* val_ptr = (uint8_t*)&val;
105 return (uint16_t)((val_ptr[1] << 0) | (val_ptr[0] << 8));
106}
107
113uint32_t read_be_u32(uint32_t val)
114{
115 uint8_t* val_ptr = (uint8_t*)&val;
116 return (uint32_t)((val_ptr[3] << 0) | (val_ptr[2] << 8) | (val_ptr[1] << 16) | (val_ptr[0] << 24));
117}
118
122const char version_number[] = "version 1.2.n64.0";
123
127const char revised_date[] = "2026-04-16";
128
133{
134 printf("QOI Encoder\nversion: %s -- revised %s\n", version_number, revised_date);
135}
136
140void print_help(void)
141{
142 printf("Example usage: qoi_enc <filename> <width> <height> <bitdepth> <output>\n");
143 printf("16: 16-bit RGBA5551\n32: 32-bit RGBA32\n");
144}
145
152int main(int argc, char* argv[])
153{
154
155 qoi_desc_t desc;
156 qoi_enc_t enc;
157
158 /* Raw file to be opened */
159 FILE* fp;
160 /* File size of the raw file */
161 size_t file_size;
162
163 uint8_t* file_buffer0 = NULL;
164 uint8_t* file_buffer1 = NULL;
165
166 uint8_t header[16];
167
168 uint32_t width, height;
169 uint8_t channels, colorspace;
170
171 uint8_t bitdepth;
172
173 /* 16-bit RGBA5551 */
174 uint16_t* rgba5551_frame_buffer;
175
176 /* Scratchpad when converted to RGBA32 */
177 uint32_t* rgba32_scratch_frame_buffer;
178
179 /* For attaching it to encoder to limit memory consumption */
180 uint8_t enc_buffer[ENC_BUFFER_SIZE];
181
183
184 if (argc < 6)
185 {
186 print_help();
187 return -1;
188 }
189 else
190 {
191
192 if (strlen(argv[ENC_INPUT_FILENAME]) <= 0)
193 {
194 print_help();
195 return -1;
196 }
197
198 width = strtoul(argv[ENC_WIDTH], NULL, 0);
199 height = strtoul(argv[ENC_HEIGHT], NULL, 0);
200 bitdepth = strtoul(argv[ENC_BITDEPTH], NULL, 0);
201
202 /* Check for valid bitdepth */
203 if (!(bitdepth == 16 || bitdepth == 32)) {
204 print_help();
205 return -1;
206 }
207
208 /* Framebuffer on N64 is always rgba screenshot */
209 channels = 4;
210
211 /* The N64 framebuffer uses sRGB color space */
212 colorspace = 0;
213
214 if (width < 0 || height < 0)
215 {
216 print_help();
217 return -1;
218 }
219 }
220
221 printf("Opening %s\n", argv[ENC_INPUT_FILENAME]);
222
223 fp = fopen(argv[ENC_INPUT_FILENAME], "rb");
224
225 if (!fp)
226 {
227 print_help();
228 return -1;
229 }
230
231 fseek(fp, 0, SEEK_END);
232
233 file_size = ftell(fp);
234
235 if ((size_t)width * (size_t)height * (bitdepth == 16 ? 2 : 4) < file_size)
236 {
237 size_t image_size = (size_t)width * (size_t)height * (bitdepth == 16 ? 2 : 4);
238 size_t size_difference = image_size - file_size;
239
240 printf(
241 "%zu %s are required for the file, %s. Your requested image dimensions and bit-depth allocates %zu %s. That is %zu %s difference\n",
242 file_size,
243 (file_size > 1) ? "bytes" : "byte",
244 argv[ENC_INPUT_FILENAME],
245 image_size,
246 (image_size > 1) ? "bytes" : "byte",
247 size_difference,
248 /* for printing plurals from of the word "byte" */
249 (size_difference > 1) ? "bytes" : "byte"
250 );
251 print_help();
252
253 return -1;
254 }
255
256 fseek(fp, 0, SEEK_SET);
257
258 file_buffer0 = (uint8_t*)calloc(file_size + 1, sizeof(uint8_t)); /* RGBA5551 framebuffer from file */
259
260 if (!file_buffer0)
261 {
262 fclose(fp);
263 return 1;
264 }
265
266 if (bitdepth == 16) {
267 file_buffer1 = (uint8_t*)calloc(file_size + 1, sizeof(uint8_t)*2); /* RGBA framebuffer */
268 if (!file_buffer1)
269 {
270 fclose(fp);
271 free(file_buffer0);
272 file_buffer0 = NULL;
273 return 1;
274 }
275 }
276
277 if (fread(file_buffer0, 1, file_size, fp) < file_size)
278 {
279 if (ferror(fp))
280 {
281 printf("An error has occur while reading %s\n", argv[ENC_INPUT_FILENAME]);
282 print_help();
283
284 fclose(fp);
285 free(file_buffer0);
286
287 return 1;
288 }
289 }
290
291 fclose(fp);
292
293 if (bitdepth == 32) {
294 /* RGBA is simple to read */
295 rgba32_scratch_frame_buffer = (uint32_t*)file_buffer0;
296 } else {
297 /* Very convoluted conversion from big endian to RGBA32 */
298 rgba5551_frame_buffer = (uint16_t*)file_buffer0;
299
300 for (size_t i = 0; i < file_size / sizeof(uint16_t); i++) {
301
302 /* 16-bit rgba5551 to rgba32 logic */
303 int r = (read_be_u16(rgba5551_frame_buffer[i]) >> 11) & 0x1F;
304 int g = (read_be_u16(rgba5551_frame_buffer[i]) >> 6) & 0x1F;
305 int b = (read_be_u16(rgba5551_frame_buffer[i]) >> 1) & 0x1F;
306 int a = (read_be_u16(rgba5551_frame_buffer[i]) >> 0) & 0x01;
307
308 /* expand to 8 bit */
309 r = (r << 3) | (r >> 2);
310 g = (g << 3) | (g >> 2);
311 b = (b << 3) | (b >> 2);
312 a = a * 255;
313
314 /* Store the converted RGBA32 values in the file buffer */
315 file_buffer1[i*4] = r;
316 file_buffer1[i*4+1] = g;
317 file_buffer1[i*4+2] = b;
318 file_buffer1[i*4+3] = a;
319
320 }
321
322 free(file_buffer0); /* Destroy RGBA5551 file */
323 file_buffer0 = NULL;
324 rgba5551_frame_buffer = NULL;
325
326 rgba32_scratch_frame_buffer = (uint32_t*)file_buffer1;
327 }
328
329 qoi_desc_init(&desc);
330
331 qoi_set_dimensions(&desc, width, height);
332 qoi_set_channels(&desc, channels);
333 qoi_set_colorspace(&desc, colorspace);
334
335 fp = fopen(argv[ENC_OUTPUT_FILENAME], "wb");
336 if (!fp) {
337 goto cleanup;
338 }
339
340 printf(
341 "Encoding %s to %s. Please wait . . .\n",
342 argv[ENC_INPUT_FILENAME],
344 );
345
346 qoi_enc_init(&desc, &enc);
347
348 /* qoi_enc_alloc_buffer(&enc, ENC_BUFFER_SIZE); */
349
350 qoi_enc_set_buffer(&enc, enc_buffer, ENC_BUFFER_SIZE, false);
351
352 write_qoi_header(&desc, header);
353
354 fwrite(header, 1, 14, fp);
355
356 /* Begin encoding RGBA32 data to QOI file */
357
358 /* Encode the pixel data */
359 for (uint32_t px = 0; px < enc.len; px++)
360 {
361 uint32_t rgba = rgba32_scratch_frame_buffer[px];
362 qoi_encode_chunk(&desc, &enc, &rgba);
363
364 if (enc.pixels_written >= enc.len) {
365 fwrite(enc.enc_buffer, 1, enc.buffer_offset, fp);
366 break;
367 };
368
369 /* Write the buffer to the file when it is almost full,
370 leaving space for the padding bytes at the end of the file */
371 if (enc.buffer_offset >= enc.buffer_len-8) {
372 fwrite(enc.enc_buffer, 1, enc.buffer_offset, fp);
374 }
375
376 }
377
378 /* Write QOI padding per QOI specifications */
379 fwrite(QOI_PADDING, sizeof(uint64_t), 1, fp);
380
381 /* qoi_enc_free_buffer(&enc); */
382
383 /* Finish writing file */
384 fclose(fp);
385
386 printf("Done\n");
387
388 cleanup:
389
390 if (bitdepth == 32) {
391 free(file_buffer0);
392 file_buffer0 = NULL;
393 } else {
394 /* Destroy RGBA32 file */
395 free(file_buffer1);
396 file_buffer1 = NULL;
397 rgba32_scratch_frame_buffer = NULL;
398 }
399
400 rgba32_scratch_frame_buffer = NULL;
401
402 return 0;
403}
int main(void)
Entry point for this N64 ROM.
Definition main.c:232
#define ENC_BUFFER_SIZE
Small buffer size for encoder because of memory constrant of 4MB for N64 without the 4MB Expansion Pa...
Definition main.c:49
const char revised_date[]
Date of the last revision.
Definition qoi_enc.c:127
void print_help(void)
Prints the help information for the encoder.
Definition qoi_enc.c:140
QOI_ENC_ARGS
Enumeration for command-line argument indices.
Definition qoi_enc.c:65
@ ENC_HEIGHT
The height of the image.
Definition qoi_enc.c:81
@ ENC_WIDTH
The width of the image.
Definition qoi_enc.c:77
@ ENC_INPUT_FILENAME
The input filename.
Definition qoi_enc.c:73
@ ENC_NAME
The name of the encoder program, typically "qoi_enc".
Definition qoi_enc.c:69
@ ENC_BITDEPTH
The bit depth of the image.
Definition qoi_enc.c:85
@ ENC_OUTPUT_FILENAME
The output filename.
Definition qoi_enc.c:89
void print_version(void)
Prints the version information of the encoder.
Definition qoi_enc.c:132
const char version_number[]
Version number of the encoder.
Definition qoi_enc.c:122
uint16_t read_be_u16(uint16_t val)
Reads a big-endian 16-bit unsigned integer from memory.
Definition qoi_enc.c:102
uint32_t read_be_u32(uint32_t val)
Reads a big-endian 32-bit unsigned integer from memory.
Definition qoi_enc.c:113
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.
static const uint8_t QOI_PADDING[8]
QOI end of file padding bytes.
Definition qoi_enc_n64.h:91
void qoi_encode_chunk(qoi_desc_t *desc, qoi_enc_t *enc, void *qoi_pixel_bytes)
Encode pixel data into QOI opcodes.
bool qoi_enc_init(qoi_desc_t *desc, qoi_enc_t *enc)
Initalize the QOI encoder 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.
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.
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
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).
uint32_t pixels_written
The total number of pixels encoded so far.
QOI Encoder Library for N64.