summaryrefslogtreecommitdiffstats
path: root/img2gba/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'img2gba/src/main.c')
-rw-r--r--img2gba/src/main.c452
1 files changed, 452 insertions, 0 deletions
diff --git a/img2gba/src/main.c b/img2gba/src/main.c
new file mode 100644
index 0000000..8a5edfc
--- /dev/null
+++ b/img2gba/src/main.c
@@ -0,0 +1,452 @@
1#include <getopt.h>
2
3#include "shorthand.h"
4
5#define STB_IMAGE_IMPLEMENTATION
6#include "stb_image.h"
7
8#define TILE_SIZE 8
9#define PALETTE_SIZE 16
10#define DEFAULT_BG_COLOR 0x0000
11#define MAX_FILE_NAME 1024
12
13typedef u16 Color;
14
15static inline Color
16rgb15(u32 red, u32 green, u32 blue ) {
17 return (blue << 10) | (green << 5) | red;
18}
19
20static inline
21int quantize5(int color) {
22 return color * 31 / 255;
23}
24
25// TODO: Assuming 4bpp tiles. The tile is an index to a palette.
26// TODO: Write more documentation about this.
27typedef u32 Tile[TILE_SIZE];
28
29typedef struct Tiles {
30 Tile *data;
31 size_t size;
32 size_t capacity;
33} Tiles;
34
35typedef struct Palette {
36 Color data[PALETTE_SIZE];
37 size_t size;
38 size_t capacity;
39} Palette;
40
41typedef struct Image {
42 unsigned char *data;
43 int width;
44 int height;
45 int n_channels;
46} Image;
47
48u8
49pack_1bb(u32 hex) {
50 u32 low_nibble = hex & 0xFFFF;
51 u32 high_nibble = (hex >> 16) & 0xFFFF;
52 u32 low = ((low_nibble >> 12) & 0x1);
53 low += 2 * ((low_nibble >> 8) & 0x1);
54 low += 4 * ((low_nibble >> 4) & 0x1);
55 low += 8 * ((low_nibble >> 0) & 0x1);
56 u32 high = ((high_nibble >> 12) & 0x1);
57 high += 2 * ((high_nibble >> 8) & 0x1);
58 high += 4 * ((high_nibble >> 4) & 0x1);
59 high += 8 * ((high_nibble >> 0) & 0x1);
60 return (low << 4) | high;
61}
62
63void
64export_c_file(Tiles *tiles, Palette *palette, FILE *file, bool pack_1bpp) {
65 // Output tiles.
66 if (pack_1bpp) {
67 fprintf(file, "static const u32 packed_tiles[] = {\n ");
68 size_t counter = 0;
69 for (size_t i = 0; i < tiles->capacity; ++i) {
70 for (size_t j = 0; j < TILE_SIZE; j += 4, counter++) {
71 if (counter == 4) {
72 counter = 0;
73 fprintf(file, "\n ");
74 }
75 u32 tile_a = tiles->data[i][j];
76 u32 tile_b = tiles->data[i][j + 1];
77 u32 tile_c = tiles->data[i][j + 2];
78 u32 tile_d = tiles->data[i][j + 3];
79 u32 packed = pack_1bb(tile_d) << 0;
80 packed |= pack_1bb(tile_c) << 8;
81 packed |= pack_1bb(tile_b) << 16;
82 packed |= pack_1bb(tile_a) << 24;
83 fprintf(file, "0x%08x,", packed);
84 if (counter < 3) {
85 fprintf(file, " ");
86 }
87 }
88 }
89 fprintf(file, "\n};\n");
90 fprintf(file, "\n");
91 } else {
92 fprintf(file, "static const u32 tiles[%lu][%u] = {\n", tiles->capacity, TILE_SIZE);
93 for (size_t i = 0; i < tiles->capacity; ++i) {
94 fprintf(file, " {");
95 for (size_t j = 0; j < TILE_SIZE; ++j) {
96 fprintf(file, "0x%08x", tiles->data[i][j]);
97 if (j != (TILE_SIZE - 1)) {
98 fprintf(file, ", ");
99 }
100 }
101 fprintf(file, "},\n");
102 }
103 fprintf(file, "};\n");
104 fprintf(file, "\n");
105 }
106
107 // Output palette.
108 fprintf(file, "u16 palette[%lu] = {\n", palette->capacity);
109 fprintf(file, " ");
110 size_t counter = 0;
111 for (size_t i = 0; i < palette->capacity; ++i, ++counter) {
112 if (counter == 4) {
113 counter = 0;
114 fprintf(file, "\n ");
115 }
116 fprintf(file, "0x%04x,", palette->data[i]);
117 if (counter < 3) {
118 fprintf(file, " ");
119 }
120 }
121 fprintf(file, "\n");
122 fprintf(file, "};\n");
123}
124
125void
126export_bin_file(Tiles *tiles, Palette *palette, FILE *file, FILE *filepal, bool pack_1bpp) {
127 if (pack_1bpp) {
128 u64 counter = 0;
129 for (size_t i = 0; i < tiles->capacity; ++i) {
130 for (size_t j = 0; j < TILE_SIZE; j += 4, counter++) {
131 u32 tile_a = tiles->data[i][j];
132 u32 tile_b = tiles->data[i][j + 1];
133 u32 tile_c = tiles->data[i][j + 2];
134 u32 tile_d = tiles->data[i][j + 3];
135 u32 packed = pack_1bb(tile_a) << 0;
136 packed |= pack_1bb(tile_b) << 8;
137 packed |= pack_1bb(tile_c) << 16;
138 packed |= pack_1bb(tile_d) << 24;
139 fwrite(&packed, sizeof(u32), 1, file);
140 }
141 }
142 // Pad with zeros to avoid alignment issues.
143 if (counter) {
144 u32 zero = 0;
145 fwrite(&zero, sizeof(u32), counter % 8, file);
146 }
147 } else {
148 for (size_t i = 0; i < tiles->capacity; ++i) {
149 fwrite(&tiles->data[i][0], sizeof(u32), TILE_SIZE, file);
150 }
151 fwrite(palette->data, sizeof(u16), PALETTE_SIZE, filepal);
152 }
153}
154
155void
156extract_tile(Tiles *tiles, Palette *palette, Image *img,
157 size_t offset_x, size_t offset_y) {
158 for (size_t j = 0; j < TILE_SIZE; ++j) {
159 u32 col_index = 0x00000000;
160 for (size_t i = 0; i < TILE_SIZE; ++i) {
161 size_t pal_index = palette->size;
162 // Find the memory index for this pixel.
163 int idx = (i + offset_x) + (j + offset_y) * img->width;
164 idx *= img->n_channels;
165
166 int red = img->data[idx];
167 int green = img->data[idx + 1];
168 int blue = img->data[idx + 2];
169
170 // Quantize to 5 bits per channel.
171 red = quantize5(red);
172 green = quantize5(green);
173 blue = quantize5(blue);
174
175 Color clr = rgb15(red, green, blue);
176 bool found = false;
177 // Brute force search, but since the palette size will likely be
178 // small, shouldn't be a big problem.
179 if (palette->size < palette->capacity) {
180 // If the palette is not full try to find if the color is
181 // already stored.
182 for (size_t p = 0; p < palette->capacity; ++p) {
183 if (clr == palette->data[p]) {
184 pal_index = p;
185 found = true;
186 break;
187 }
188 }
189 } else {
190 // If the palette is full, find the closest perceived color
191 // instead.
192 size_t best_index = 0;
193 double best_distance = 1e99;
194 for (size_t p = 0; p < palette->capacity; ++p) {
195 Color pal_clr = palette->data[p];
196 int pal_red = pal_clr & 0x1F;
197 int pal_green = (pal_clr >> 5) & 0x1F;
198 int pal_blue = (pal_clr >> 10) & 0x1F;
199 double r = (pal_red - red);
200 double g = (pal_green - green);
201 double b = (pal_blue - blue);
202 double distance = r * r * 0.3 + g * g * 0.59 + b * b * 0.11;
203 if (distance < best_distance) {
204 best_distance = distance;
205 best_index = p;
206 }
207 }
208 pal_index = best_index;
209 found = true;
210 }
211 if (!found) {
212 palette->data[palette->size++] = clr;
213 }
214 col_index |= pal_index << i * 4; // TODO: FlipH?
215 }
216 tiles->data[tiles->size][j] = col_index;
217 }
218 tiles->size++;
219}
220
221void
222print_usage(void) {
223 printf("Usage: %s [options] <filename>\n", BIN_NAME);
224 printf("\n");
225 printf("\t-b <RRGGBB> Selects the background color used for transparency.\n");
226 printf("\t-o <out_file> Path to the output file. If blank, stdout will be used with C array export.\n");
227 printf("\t-x <0> Start x position (in pixels).\n");
228 printf("\t-y <0> Start y position (in pixels).\n");
229 printf("\t-W <8> Select the sprite width in pixels. Must be a multiple of the tile size.\n");
230 printf("\t-H <8> Select the sprite height in pixels. Must be a multiple of the tile size.\n");
231 printf("\t-1 Bit pack the output to 1bbp (4x size reduction).\n");
232 printf("\t-w <0> Width of region to extract from (x0,y0). Defaults to image width.\n");
233 printf("\t-h <0> Height of region to extract from (x0,y0). Defaults to image height.\n");
234 printf("\t-v Verbose mode\n");
235 printf("\n");
236}
237
238int
239main(int argc, char *argv[]) {
240 // Initialize default parameters.
241 u16 background_color = DEFAULT_BG_COLOR;
242 u16 palette_size = PALETTE_SIZE;
243 size_t sprite_width = TILE_SIZE;
244 size_t sprite_height = TILE_SIZE;
245 char *out_file_path = NULL;
246 bool pack_1bpp = false;
247 size_t start_x = 0;
248 size_t start_y = 0;
249 size_t width = 0;
250 size_t height = 0;
251 bool verbose = false;
252
253 int option;
254 while ((option = getopt(argc, argv, "b:o:W:H:1x:y:w:h:v")) != -1) {
255 switch (option) {
256 case 'b': {
257 // Background color.
258 char *bg = optarg;
259 if (strlen(bg) != 6) {
260 fprintf(stderr, "%s: Invalid background color.\n", BIN_NAME);
261 print_usage();
262 return EXIT_FAILURE;
263 }
264 unsigned int red = 0;
265 unsigned int green = 0;
266 unsigned int blue = 0;
267
268 // NOTE: Undefined behaviour, here we go! Also, no error checks,
269 // so if an invalid number is given it will be parsed as 0.
270 char r_str[3] = {bg[0], bg[1], '\0'};
271 char g_str[3] = {bg[2], bg[3], '\0'};
272 char b_str[3] = {bg[4], bg[5], '\0'};
273 sscanf(r_str, "%x", &red);
274 sscanf(g_str, "%x", &green);
275 sscanf(b_str, "%x", &blue);
276
277 // Quantize and transform to rgb15.
278 red = quantize5(red);
279 green = quantize5(green);
280 blue = quantize5(blue);
281 background_color = rgb15(red, green, blue);
282 } break;
283 case 'W': {
284 // Sprite width.
285 char *arg = optarg;
286 sprite_width = 0;
287 sprite_width = atoi(arg);
288
289 // Check if a valid number is given.
290 if (sprite_width == 0 || sprite_width % TILE_SIZE != 0) {
291 fprintf(stderr, "%s: Invalid sprite width.\n", BIN_NAME);
292 print_usage();
293 return EXIT_FAILURE;
294 }
295 } break;
296 case 'H': {
297 // Sprite height.
298 char *arg = optarg;
299 sprite_height = 0;
300 sprite_height = atoi(arg);
301
302 // Check if a valid number is given.
303 if (sprite_height == 0 || sprite_height % TILE_SIZE != 0) {
304 fprintf(stderr, "%s: Invalid sprite height.\n", BIN_NAME);
305 print_usage();
306 return EXIT_FAILURE;
307 }
308 } break;
309 case 'o': {
310 // Output file.
311 out_file_path = optarg;
312 } break;
313 case '1': {
314 // Pack output to 1bpp.
315 pack_1bpp = true;
316 } break;
317 case 'x': {
318 // Start x position in pixels.
319 start_x = atoi(optarg);
320 } break;
321 case 'y': {
322 // Start y position in pixels.
323 start_y = atoi(optarg);
324 } break;
325 case 'w': {
326 // Width of region to extract from (x0,y0).
327 width = atoi(optarg);
328 } break;
329 case 'h': {
330 // Height of region to extract from (x0,y0).
331 height = atoi(optarg);
332 } break;
333 case 'v': {
334 verbose = true;
335 } break;
336 default: {
337 print_usage();
338 return EXIT_FAILURE;
339 } break;
340 }
341 }
342
343 // Get the path to the file to be exported.
344 if (optind != argc - 1) {
345 fprintf(stderr, "%s: No filename given.\n", BIN_NAME);
346 print_usage();
347 return EXIT_FAILURE;
348 }
349 char *file_name = argv[optind];
350
351 // Fill the palette with the background color if one was given.
352 Palette palette = {
353 .size = 1,
354 .capacity = palette_size,
355 };
356 for (size_t i = 0; i < palette_size; ++i) {
357 palette.data[i] = background_color;
358 }
359
360 Image img = {0};
361
362 // Open the given file.
363 FILE *input_file = fopen(file_name, "rb");
364 if (input_file == NULL) {
365 fprintf(stderr, "%s: can't open input file: %s\n", BIN_NAME, file_name);
366 return EXIT_FAILURE;
367 }
368 img.data = stbi_load_from_file(input_file, &img.width, &img.height, &img.n_channels, 0);
369 fclose(input_file);
370
371 // TODO: Implement support for different file inputs.
372 if (img.n_channels != 3) {
373 fprintf(stderr, "File format not supported. Only 3 channel files for now.\n");
374 return EXIT_FAILURE;
375 }
376
377 if (width == 0) {
378 width = img.width;
379 }
380 if (height == 0) {
381 height = img.height;
382 }
383 size_t n_tiles_x = width / TILE_SIZE;
384 size_t n_tiles_y = height / TILE_SIZE;
385 size_t n_tiles = n_tiles_x * n_tiles_y;
386
387 // Allocate memory for the tiles in this file, with zero-initialization.
388 Tiles tiles = {
389 .data = calloc(n_tiles, sizeof(Tile)),
390 .size = 0,
391 .capacity = n_tiles,
392 };
393
394 size_t n_sprites_x = width / sprite_width;
395 size_t n_sprites_y = height / sprite_height;
396 size_t n_sprite_tiles_x = sprite_width / TILE_SIZE;
397 size_t n_sprite_tiles_y = sprite_height / TILE_SIZE;
398 for (size_t p = 0; p < n_sprites_y; ++p) {
399 for (size_t k = 0; k < n_sprites_x; ++k) {
400 for (size_t j = 0; j < n_sprite_tiles_y; ++j) {
401 int offset_x = start_x + k * sprite_width;
402 int offset_y = start_y + p * sprite_height + j * TILE_SIZE;
403 for (size_t i = 0; i < n_sprite_tiles_x; ++i) {
404 extract_tile(&tiles, &palette, &img, offset_x, offset_y);
405 offset_x += TILE_SIZE;
406 }
407 }
408 }
409 }
410
411 if (out_file_path == NULL) {
412 export_c_file(&tiles, &palette, stdout, pack_1bpp);
413 } else {
414 char file_name[MAX_FILE_NAME] = {0};
415 char file_name_pal[MAX_FILE_NAME] = {0};
416 strncpy(file_name, out_file_path, MAX_FILE_NAME - 1);
417 strncpy(file_name_pal, out_file_path, MAX_FILE_NAME - 1);
418 FILE *file = fopen(strcat(file_name, ".img.bin"), "w");
419 if (!file) {
420 fprintf(stderr, "%s: can't open output file: %s\n", BIN_NAME, file_name);
421 return EXIT_FAILURE;
422 }
423 if (pack_1bpp) {
424 export_bin_file(&tiles, &palette, file, NULL, pack_1bpp);
425 fclose(file);
426 if (verbose) {
427 printf("Exporting binary file (1bpp packed)\n");
428 printf("FILE IMG: %s\n", file_name);
429 }
430 } else {
431 FILE *file_pal = fopen(strcat(file_name_pal, ".pal.bin"), "w");
432 if (!file_pal) {
433 fprintf(stderr, "%s: can't open output file: %s\n", BIN_NAME, file_name_pal);
434 fclose(file);
435 return EXIT_FAILURE;
436 }
437 if (verbose) {
438 printf("Exporting binary file\n");
439 printf("FILE IMG: %s\n", file_name);
440 printf("FILE PAL: %s\n", file_name_pal);
441 }
442 export_bin_file(&tiles, &palette, file, file_pal, pack_1bpp);
443 fclose(file);
444 fclose(file_pal);
445 }
446 }
447
448 // Cleanup resources and exit.
449 free(tiles.data);
450 stbi_image_free(img.data);
451 return EXIT_SUCCESS;
452}