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