N64 QOI Demo
A N64 homebrew app that opens QOI images from ROM
Loading...
Searching...
No Matches
main.c
Go to the documentation of this file.
1/*
2
3 main.c
4
5 This file is the entry point of the N64 QOI Viewer ROM
6
7 TODO: Refactor main.c into functions and files
8
9 Code licensed under MIT License
10
11 Copyright (c) 2025-2026 Aftersol
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30
31*/
32
33
36
37
38#include <stdio.h>
39#include <string.h>
40
41#include <libdragon.h>
42
43#include "config.h"
44
45#include "qoi_viewer.h"
46
48#define POOL_IMG_SIZE 15
49
51#define MAX_STRING_SIZE MAX_FILENAME_LEN + 1
52
55
58
62inline joypad_inputs_t joypad_poll_port(joypad_port_t port) {
63 joypad_poll();
64 return joypad_get_inputs(port);
65}
66
69
85
88void readNames(name_node_pool_t* start_node) {
89 char sbuf[MAX_STRING_SIZE];
90
91 strncpy(sbuf, "rom:/", 6);
92 if (dfs_dir_findfirst(".", sbuf+5) == FLAGS_FILE) {
93 name_node_pool_t* node = start_node;
94
95 do {
96 if (node->num_images >= POOL_IMG_SIZE) {
97 // the program runs forever so no need to free pool
98 name_node_pool_t* new_node = (name_node_pool_t*)malloc(sizeof(name_node_pool_t));
99
100 assert(new_node != NULL);
101
102 new_node->prev = node;
103 new_node->next = start_node;
104 new_node->num_images = 0;
105
106 node->next = (name_node_pool_t*)new_node;
107 start_node->prev = (name_node_pool_t*)new_node;
108
109 node = (name_node_pool_t*)new_node;
110 }
111
112 sys_hw_memset(node->name[node->num_images], 0, MAX_STRING_SIZE);
113 snprintf(node->name[node->num_images], MAX_STRING_SIZE - 1, sbuf);
114
115 node->num_images++;
116
117 } while (dfs_dir_findnext(sbuf+5) == FLAGS_FILE);
118 }
119 else { // you can't compile with an empty directory
120 assert("No files found in ROM.");
121 }
122}
123
125static inline void init_program() {
126 console_init();
127
128 debug_init_usblog();
129 console_set_debug(true);
130
131 timer_init();
132 joypad_init();
133
134 dfs_init(DFS_DEFAULT_LOCATION);
135}
136
139 // QOI only supports 32 bit RGBA image
140 // so set display bits to 32 bits per pixel
141 display_init(
142 RESOLUTION_320x240,
143 DEPTH_32_BPP,
144 2, // double buffered
145 GAMMA_NONE,
146 FILTERS_RESAMPLE
147 );
148
149 rdpq_init();
150 rdpq_set_mode_standard();
151
152}
153
157
158 printf("QOI Image Viewer\n");
159
160 printf("Revision Date: %s\n", QOI_DEC_REVISION_DATE);
161
162 printf(
163 "Decoded %s in %f ms!\n",
164 info->name,
165 info->decodeTime * 1000.0f
166 ); // time in ms spent decoding
167
168 printf(
169 "First pixel of %s: %i %i %i %i\n",
170 info->name,
171 buffer0[0],
172 buffer0[1],
173 buffer0[2],
174 buffer0[3]
175 ); // get color of first pixel
176}
177
178
180int main(void) {
181
182
183 int index = 0, prev_index = 0;
184
185 name_node_pool_t start_node = (name_node_pool_t) {
186 // loop back into itself if there is only one pool sector
187 // upon user trying to enter the previous or next node
188 // after user reaches the beginning or end of a node respectively
189 .prev = &start_node,
190 .next = &start_node,
191 .num_images = 0
192 };
193
194 name_node_pool_t* current_node = &start_node;
195
197 .width = 0,
198 .height = 0,
199 .channels = 0,
200 .error = QOI_NOT_INITIALIZED
201 };
202
203 // Font for displaying debug text
204 rdpq_font_t *font;
205
206 init_program();
207
208 readNames(&start_node);
209
210 sys_hw_memset(buffer0, 0, IMG_BUFFER_SIZE); // clear the buffer
211
212 openQOIFile(start_node.name[0], &buffer0[0], &info);
213
214 assert(info.error == QOI_OK);
215
216 // somehow double buffering
217 // the image
218 // fixes black lines at the
219 // bottom of the screen
221
223
224 wait_ms(1000);
225
226 console_clear();
227 console_close();
228
229 start_viewer();
230
231 font = rdpq_font_load_builtin(FONT_BUILTIN_DEBUG_MONO);
232 rdpq_text_register_font(1, font);
233
234 info.renderDebugFont = true;
235
236 while (1) {
237 surface_t* disp;
238
239 while(!(disp = display_try_get())) {;}
240 joypad_port_t port = JOYPAD_PORT_1;
241
242 joypad_inputs_t input = joypad_poll_port(port);
243 joypad_buttons_t pressed = joypad_get_buttons_pressed(port);
244
245 // toggle debug text upon pressing these buttons
246 if (pressed.start || pressed.z) {
247 toggleDebugText(&info);
248 }
249
250 // go to previous image if left is pressed
251 if (
252 input.btn.b ||
253 input.btn.d_left ||
254 input.btn.l ||
255 input.btn.c_left ||
256 joypad_get_axis_pressed(port, JOYPAD_AXIS_STICK_X) == -1
257 ) {
258 index--;
259
260 if (index == -1) {
261 current_node = (name_node_pool_t*)current_node->prev;
262 index = current_node->num_images - 1;
263 assert(index >= 0);
264 }
265 }
266
267 // advance to next image if right is pressed
268 if (
269 input.btn.a ||
270 input.btn.d_right ||
271 input.btn.r ||
272 input.btn.c_right ||
273 joypad_get_axis_pressed(port, JOYPAD_AXIS_STICK_X) == 1
274 ) {
275 index++;
276 if (index >= current_node->num_images) {
277 current_node = (name_node_pool_t*)current_node->next;
278 index = 0;
279 }
280 }
281
282 // load next image upon pressing left or right
283 if (prev_index != index) {
284 prev_index = index;
285
286 openQOIFile(current_node->name[index], buffer0, &info);
288
289 assert(info.error == QOI_OK);
290 }
291
292 draw_image(disp, info);
293
294 }
295}
#define MAX_STRING_SIZE
Maximum length of a string. File names are limited by libdragon to 243 characters.
Definition main.c:51
#define POOL_IMG_SIZE
How many names can fit in a block.
Definition main.c:48
void readNames(name_node_pool_t *start_node)
Reads the names of QOI images from ROM.
Definition main.c:88
void start_viewer()
This function starts QOI viewer to display first QOI image decoded.
Definition main.c:138
uint8_t buffer1[IMG_BUFFER_SIZE]
second raw image buffer that decoded from qoi image file
Definition main.c:57
int main(void)
This function is the entry point for QOI Viewer.
Definition main.c:180
void printFirstDecodedValues(qoi_img_info_t *info)
Prints the first values of the pixel decoded by the QOI Decoder.
Definition main.c:156
joypad_inputs_t joypad_poll_port(joypad_port_t port)
Poll controller and get input from a specific port.
Definition main.c:62
uint8_t buffer0[IMG_BUFFER_SIZE]
Raw image buffer that decoded from qoi image file.
Definition main.c:54
void draw_image(surface_t *disp, qoi_img_info_t info)
This function draws image decoded from QOI.
Definition qoi_viewer.c:51
void openQOIFile(const char *filename, uint8_t *bytes, qoi_img_info_t *info)
This function decodes QOI file from from into the framebuffer.
Definition qoi_viewer.c:116
This header contains declaration of functions related to QOI viewer.
void toggleDebugText(qoi_img_info_t *info)
Toggles printing debugging text.
Definition qoi_viewer.h:105
@ QOI_NOT_INITIALIZED
QOI Image not yet decoded.
Definition qoi_viewer.h:55
@ QOI_OK
QOI Image decoded successfully.
Definition qoi_viewer.h:57
struct qoi_img_info qoi_img_info_t
Metadata about the QOI image and the QOI image viewer.
#define IMG_BUFFER_SIZE
Image buffer size: 320 pixels in width * 240 pixels in height * 4 channels.
Definition qoi_viewer.h:46
A container for holding a bunch of names.
Definition main.c:71
name_node_pool_t * prev
A pointer to the previous block of names.
Definition main.c:74
int num_images
Number of images occupied in the block. 32 bit integer for word alignment purposes.
Definition main.c:80
name_node_pool_t * next
A pointer to the next block of names.
Definition main.c:77
char name[POOL_IMG_SIZE][MAX_STRING_SIZE]
A list of names in a block.
Definition main.c:83
char name[256]
Names of QOI file.
Definition qoi_viewer.h:86
float decodeTime
Decoding time in seconds.
Definition qoi_viewer.h:83
bool renderDebugFont
Whether to toggle displaying debug text upon pressing the Start button on the N64 controller.
Definition qoi_viewer.h:89
qoi_error_code error
Error code as the result of decoding.
Definition qoi_viewer.h:80