Update README.md
[sm64pc.git] / tools / n64graphics.c
blobe07069dc419dace6fbaf206e99bb56beae867491
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <strings.h>
5 #define STBI_NO_LINEAR
6 #define STBI_NO_HDR
7 #define STBI_NO_TGA
8 #define STB_IMAGE_IMPLEMENTATION
9 #include <stb/stb_image.h>
10 #define STB_IMAGE_WRITE_IMPLEMENTATION
11 #include <stb/stb_image_write.h>
13 #include "n64graphics.h"
14 #include "utils.h"
16 // SCALE_M_N: upscale/downscale M-bit integer to N-bit
17 #define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F)
18 #define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF)
19 #define SCALE_4_8(VAL_) ((VAL_) * 0x11)
20 #define SCALE_8_4(VAL_) ((VAL_) / 0x11)
21 #define SCALE_3_8(VAL_) ((VAL_) * 0x24)
22 #define SCALE_8_3(VAL_) ((VAL_) / 0x24)
24 typedef enum
26 IMG_FORMAT_RGBA,
27 IMG_FORMAT_IA,
28 IMG_FORMAT_I,
29 IMG_FORMAT_CI,
30 } img_format;
33 //---------------------------------------------------------
34 // N64 RGBA/IA/I/CI -> internal RGBA/IA
35 //---------------------------------------------------------
37 rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth)
39 rgba *img;
40 int img_size;
42 img_size = width * height * sizeof(*img);
43 img = malloc(img_size);
44 if (!img) {
45 ERROR("Error allocating %d bytes\n", img_size);
46 return NULL;
49 if (depth == 16) {
50 for (int i = 0; i < width * height; i++) {
51 img[i].red = SCALE_5_8((raw[i*2] & 0xF8) >> 3);
52 img[i].green = SCALE_5_8(((raw[i*2] & 0x07) << 2) | ((raw[i*2+1] & 0xC0) >> 6));
53 img[i].blue = SCALE_5_8((raw[i*2+1] & 0x3E) >> 1);
54 img[i].alpha = (raw[i*2+1] & 0x01) ? 0xFF : 0x00;
56 } else if (depth == 32) {
57 for (int i = 0; i < width * height; i++) {
58 img[i].red = raw[i*4];
59 img[i].green = raw[i*4+1];
60 img[i].blue = raw[i*4+2];
61 img[i].alpha = raw[i*4+3];
65 return img;
68 ia *raw2ia(const uint8_t *raw, int width, int height, int depth)
70 ia *img;
71 int img_size;
73 img_size = width * height * sizeof(*img);
74 img = malloc(img_size);
75 if (!img) {
76 ERROR("Error allocating %u bytes\n", img_size);
77 return NULL;
80 switch (depth) {
81 case 16:
82 for (int i = 0; i < width * height; i++) {
83 img[i].intensity = raw[i*2];
84 img[i].alpha = raw[i*2+1];
86 break;
87 case 8:
88 for (int i = 0; i < width * height; i++) {
89 img[i].intensity = SCALE_4_8((raw[i] & 0xF0) >> 4);
90 img[i].alpha = SCALE_4_8(raw[i] & 0x0F);
92 break;
93 case 4:
94 for (int i = 0; i < width * height; i++) {
95 uint8_t bits;
96 bits = raw[i/2];
97 if (i % 2) {
98 bits &= 0xF;
99 } else {
100 bits >>= 4;
102 img[i].intensity = SCALE_3_8((bits >> 1) & 0x07);
103 img[i].alpha = (bits & 0x01) ? 0xFF : 0x00;
105 break;
106 case 1:
107 for (int i = 0; i < width * height; i++) {
108 uint8_t bits;
109 uint8_t mask;
110 bits = raw[i/8];
111 mask = 1 << (7 - (i % 8)); // MSb->LSb
112 bits = (bits & mask) ? 0xFF : 0x00;
113 img[i].intensity = bits;
114 img[i].alpha = bits;
116 break;
117 default:
118 ERROR("Error invalid depth %d\n", depth);
119 break;
122 return img;
125 ia *raw2i(const uint8_t *raw, int width, int height, int depth)
127 ia *img = NULL;
128 int img_size;
130 img_size = width * height * sizeof(*img);
131 img = malloc(img_size);
132 if (!img) {
133 ERROR("Error allocating %u bytes\n", img_size);
134 return NULL;
137 switch (depth) {
138 case 8:
139 for (int i = 0; i < width * height; i++) {
140 img[i].intensity = raw[i];
141 img[i].alpha = 0xFF;
143 break;
144 case 4:
145 for (int i = 0; i < width * height; i++) {
146 uint8_t bits;
147 bits = raw[i/2];
148 if (i % 2) {
149 bits &= 0xF;
150 } else {
151 bits >>= 4;
153 img[i].intensity = SCALE_4_8(bits);
154 img[i].alpha = 0xFF;
156 break;
157 default:
158 ERROR("Error invalid depth %d\n", depth);
159 break;
162 return img;
165 // extract RGBA from CI raw data and palette
166 // TODO: different palette depths
167 rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth)
169 uint8_t *raw_rgba;
170 rgba *img = NULL;
171 int raw_size;
173 // first convert to raw RGBA
174 raw_size = 2 * width * height;
175 raw_rgba = malloc(raw_size);
176 if (!raw_rgba) {
177 ERROR("Error allocating %u bytes\n", raw_size);
178 return NULL;
181 for (int i = 0; i < width * height; i++) {
182 raw_rgba[2*i] = palette[2*rawci[i]];
183 raw_rgba[2*i+1] = palette[2*rawci[i]+1];
186 // then convert to RGBA image data
187 img = raw2rgba(raw_rgba, width, height, depth);
189 free(raw_rgba);
191 return img;
195 //---------------------------------------------------------
196 // internal RGBA/IA -> N64 RGBA/IA/I/CI
197 // returns length written to 'raw' used or -1 on error
198 //---------------------------------------------------------
200 int rgba2raw(uint8_t *raw, const rgba *img, int width, int height, int depth)
202 int size = width * height * depth / 8;
203 INFO("Converting RGBA%d %dx%d to raw\n", depth, width, height);
205 if (depth == 16) {
206 for (int i = 0; i < width * height; i++) {
207 uint8_t r, g, b, a;
208 r = SCALE_8_5(img[i].red);
209 g = SCALE_8_5(img[i].green);
210 b = SCALE_8_5(img[i].blue);
211 a = img[i].alpha ? 0x1 : 0x0;
212 raw[i*2] = (r << 3) | (g >> 2);
213 raw[i*2+1] = ((g & 0x3) << 6) | (b << 1) | a;
215 } else if (depth == 32) {
216 for (int i = 0; i < width * height; i++) {
217 raw[i*4] = img[i].red;
218 raw[i*4+1] = img[i].green;
219 raw[i*4+2] = img[i].blue;
220 raw[i*4+3] = img[i].alpha;
222 } else {
223 ERROR("Error invalid depth %d\n", depth);
224 size = -1;
227 return size;
230 int ia2raw(uint8_t *raw, const ia *img, int width, int height, int depth)
232 int size = width * height * depth / 8;
233 INFO("Converting IA%d %dx%d to raw\n", depth, width, height);
235 switch (depth) {
236 case 16:
237 for (int i = 0; i < width * height; i++) {
238 raw[i*2] = img[i].intensity;
239 raw[i*2+1] = img[i].alpha;
241 break;
242 case 8:
243 for (int i = 0; i < width * height; i++) {
244 uint8_t val = SCALE_8_4(img[i].intensity);
245 uint8_t alpha = SCALE_8_4(img[i].alpha);
246 raw[i] = (val << 4) | alpha;
248 break;
249 case 4:
250 for (int i = 0; i < width * height; i++) {
251 uint8_t val = SCALE_8_3(img[i].intensity);
252 uint8_t alpha = img[i].alpha ? 0x01 : 0x00;
253 uint8_t old = raw[i/2];
254 if (i % 2) {
255 raw[i/2] = (old & 0xF0) | (val << 1) | alpha;
256 } else {
257 raw[i/2] = (old & 0x0F) | (((val << 1) | alpha) << 4);
260 break;
261 case 1:
262 for (int i = 0; i < width * height; i++) {
263 uint8_t val = img[i].intensity;
264 uint8_t old = raw[i/8];
265 uint8_t bit = 1 << (7 - (i % 8));
266 if (val) {
267 raw[i/8] = old | bit;
268 } else {
269 raw[i/8] = old & (~bit);
272 break;
273 default:
274 ERROR("Error invalid depth %d\n", depth);
275 size = -1;
276 break;
279 return size;
282 int i2raw(uint8_t *raw, const ia *img, int width, int height, int depth)
284 int size = width * height * depth / 8;
285 INFO("Converting I%d %dx%d to raw\n", depth, width, height);
287 switch (depth) {
288 case 8:
289 for (int i = 0; i < width * height; i++) {
290 raw[i] = img[i].intensity;
292 break;
293 case 4:
294 for (int i = 0; i < width * height; i++) {
295 uint8_t val = SCALE_8_4(img[i].intensity);
296 uint8_t old = raw[i/2];
297 if (i % 2) {
298 raw[i/2] = (old & 0xF0) | val;
299 } else {
300 raw[i/2] = (old & 0x0F) | (val << 4);
303 break;
304 default:
305 ERROR("Error invalid depth %d\n", depth);
306 size = -1;
307 break;
310 return size;
314 //---------------------------------------------------------
315 // internal RGBA/IA -> PNG
316 //---------------------------------------------------------
318 int rgba2png(const char *png_filename, const rgba *img, int width, int height)
320 int ret = 0;
321 INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename);
323 // convert to format stb_image_write expects
324 uint8_t *data = malloc(4*width*height);
325 if (data) {
326 for (int j = 0; j < height; j++) {
327 for (int i = 0; i < width; i++) {
328 int idx = j*width + i;
329 data[4*idx] = img[idx].red;
330 data[4*idx + 1] = img[idx].green;
331 data[4*idx + 2] = img[idx].blue;
332 data[4*idx + 3] = img[idx].alpha;
336 ret = stbi_write_png(png_filename, width, height, 4, data, 0);
338 free(data);
341 return ret;
344 int ia2png(const char *png_filename, const ia *img, int width, int height)
346 int ret = 0;
347 INFO("Saving IA %dx%d to \"%s\"\n", width, height, png_filename);
349 // convert to format stb_image_write expects
350 uint8_t *data = malloc(2*width*height);
351 if (data) {
352 for (int j = 0; j < height; j++) {
353 for (int i = 0; i < width; i++) {
354 int idx = j*width + i;
355 data[2*idx] = img[idx].intensity;
356 data[2*idx + 1] = img[idx].alpha;
360 ret = stbi_write_png(png_filename, width, height, 2, data, 0);
362 free(data);
365 return ret;
368 //---------------------------------------------------------
369 // PNG -> internal RGBA/IA
370 //---------------------------------------------------------
372 rgba *png2rgba(const char *png_filename, int *width, int *height)
374 rgba *img = NULL;
375 int w = 0;
376 int h = 0;
377 int channels = 0;
378 int img_size;
380 stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
381 if (!data || w <= 0 || h <= 0) {
382 ERROR("Error loading \"%s\"\n", png_filename);
383 return NULL;
385 INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
387 img_size = w * h * sizeof(*img);
388 img = malloc(img_size);
389 if (!img) {
390 ERROR("Error allocating %u bytes\n", img_size);
391 return NULL;
394 switch (channels) {
395 case 3: // red, green, blue
396 case 4: // red, green, blue, alpha
397 for (int j = 0; j < h; j++) {
398 for (int i = 0; i < w; i++) {
399 int idx = j*w + i;
400 img[idx].red = data[channels*idx];
401 img[idx].green = data[channels*idx + 1];
402 img[idx].blue = data[channels*idx + 2];
403 if (channels == 4) {
404 img[idx].alpha = data[channels*idx + 3];
405 } else {
406 img[idx].alpha = 0xFF;
410 break;
411 case 2: // grey, alpha
412 for (int j = 0; j < h; j++) {
413 for (int i = 0; i < w; i++) {
414 int idx = j*w + i;
415 img[idx].red = data[2*idx];
416 img[idx].green = data[2*idx];
417 img[idx].blue = data[2*idx];
418 img[idx].alpha = data[2*idx + 1];
421 break;
422 default:
423 ERROR("Don't know how to read channels: %d\n", channels);
424 free(img);
425 img = NULL;
428 // cleanup
429 stbi_image_free(data);
431 *width = w;
432 *height = h;
433 return img;
436 ia *png2ia(const char *png_filename, int *width, int *height)
438 ia *img = NULL;
439 int w = 0, h = 0;
440 int channels = 0;
441 int img_size;
443 stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);
444 if (!data || w <= 0 || h <= 0) {
445 ERROR("Error loading \"%s\"\n", png_filename);
446 return NULL;
448 INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);
450 img_size = w * h * sizeof(*img);
451 img = malloc(img_size);
452 if (!img) {
453 ERROR("Error allocating %d bytes\n", img_size);
454 return NULL;
457 switch (channels) {
458 case 3: // red, green, blue
459 case 4: // red, green, blue, alpha
460 ERROR("Warning: averaging RGB PNG to create IA\n");
461 for (int j = 0; j < h; j++) {
462 for (int i = 0; i < w; i++) {
463 int idx = j*w + i;
464 int sum = data[channels*idx] + data[channels*idx + 1] + data[channels*idx + 2];
465 img[idx].intensity = (sum + 1) / 3; // add 1 to round up where appropriate
466 if (channels == 4) {
467 img[idx].alpha = data[channels*idx + 3];
468 } else {
469 img[idx].alpha = 0xFF;
473 break;
474 case 2: // grey, alpha
475 for (int j = 0; j < h; j++) {
476 for (int i = 0; i < w; i++) {
477 int idx = j*w + i;
478 img[idx].intensity = data[2*idx];
479 img[idx].alpha = data[2*idx + 1];
482 break;
483 default:
484 ERROR("Don't know how to read channels: %d\n", channels);
485 free(img);
486 img = NULL;
489 // cleanup
490 stbi_image_free(data);
492 *width = w;
493 *height = h;
494 return img;
497 const char *n64graphics_get_read_version(void)
499 return "stb_image 2.19";
502 const char *n64graphics_get_write_version(void)
504 return "stb_image_write 1.09";
507 #ifdef N64GRAPHICS_STANDALONE
508 #define N64GRAPHICS_VERSION "0.3"
509 #include <string.h>
511 typedef enum
513 MODE_EXPORT,
514 MODE_IMPORT,
515 } tool_mode;
517 typedef struct
519 char *img_filename;
520 char *bin_filename;
521 tool_mode mode;
522 unsigned int offset;
523 img_format format;
524 int depth;
525 int width;
526 int height;
527 int truncate;
528 } graphics_config;
530 static const graphics_config default_config =
532 .img_filename = NULL,
533 .bin_filename = NULL,
534 .mode = MODE_EXPORT,
535 .offset = 0,
536 .format = IMG_FORMAT_RGBA,
537 .depth = 16,
538 .width = 32,
539 .height = 32,
540 .truncate = 1,
543 typedef struct
545 const char *name;
546 img_format format;
547 int depth;
548 } format_entry;
550 static const format_entry format_table[] =
552 {"rgba16", IMG_FORMAT_RGBA, 16},
553 {"rgba32", IMG_FORMAT_RGBA, 32},
554 {"ia1", IMG_FORMAT_IA, 1},
555 {"ia4", IMG_FORMAT_IA, 4},
556 {"ia8", IMG_FORMAT_IA, 8},
557 {"ia16", IMG_FORMAT_IA, 16},
558 {"i4", IMG_FORMAT_I, 4},
559 {"i8", IMG_FORMAT_I, 8},
560 {"ci8", IMG_FORMAT_CI, 8},
561 {"ci16", IMG_FORMAT_CI, 16},
564 static const char *format2str(img_format format, int depth)
566 for (unsigned i = 0; i < DIM(format_table); i++) {
567 if (format == format_table[i].format && depth == format_table[i].depth) {
568 return format_table[i].name;
571 return "unknown";
574 static int parse_format(graphics_config *config, const char *str)
576 for (unsigned i = 0; i < DIM(format_table); i++) {
577 if (!strcasecmp(str, format_table[i].name)) {
578 config->format = format_table[i].format;
579 config->depth = format_table[i].depth;
580 return 1;
583 return 0;
586 static void print_usage(void)
588 ERROR("Usage: n64graphics -e/-i BIN_FILE -g PNG_FILE [-o offset] [-f FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n"
589 "\n"
590 "n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n"
591 "\n"
592 "Required arguments:\n"
593 " -e BIN_FILE export from BIN_FILE to PNG_FILE\n"
594 " -i BIN_FILE import from PNG_FILE to BIN_FILE\n"
595 " -g PNG_FILE graphics file to import/export (.png)\n"
596 "Optional arguments:\n"
597 " -o OFFSET starting offset in BIN_FILE (prevents truncation during import)\n"
598 " -f FORMAT texture format: rgba16, rgba32, ia1, ia4, ia8, ia16, i4, i8, ci8, ci16 (default: %s)\n"
599 " -w WIDTH export texture width (default: %d)\n"
600 " -h HEIGHT export texture height (default: %d)\n"
601 " -v verbose logging\n"
602 " -V print version information\n",
603 format2str(default_config.format, default_config.depth),
604 default_config.width,
605 default_config.height);
608 static void print_version(void)
610 ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n"
611 " %s\n"
612 " %s\n",
613 n64graphics_get_read_version(), n64graphics_get_write_version());
616 // parse command line arguments
617 static int parse_arguments(int argc, char *argv[], graphics_config *config)
619 for (int i = 1; i < argc; i++) {
620 if (argv[i][0] == '-') {
621 switch (argv[i][1]) {
622 case 'e':
623 if (++i >= argc) return 0;
624 config->bin_filename = argv[i];
625 config->mode = MODE_EXPORT;
626 break;
627 case 'f':
628 if (++i >= argc) return 0;
629 if (!parse_format(config, argv[i])) {
630 return 0;
632 break;
633 case 'i':
634 if (++i >= argc) return 0;
635 config->bin_filename = argv[i];
636 config->mode = MODE_IMPORT;
637 break;
638 case 'g':
639 if (++i >= argc) return 0;
640 config->img_filename = argv[i];
641 break;
642 case 'h':
643 if (++i >= argc) return 0;
644 config->height = strtoul(argv[i], NULL, 0);
645 break;
646 case 'o':
647 if (++i >= argc) return 0;
648 config->offset = strtoul(argv[i], NULL, 0);
649 config->truncate = 0;
650 break;
651 case 'w':
652 if (++i >= argc) return 0;
653 config->width = strtoul(argv[i], NULL, 0);
654 break;
655 case 'v':
656 g_verbosity = 1;
657 break;
658 case 'V':
659 print_version();
660 exit(0);
661 break;
662 default:
663 return 0;
664 break;
666 } else {
667 return 0;
670 return 1;
673 int main(int argc, char *argv[])
675 graphics_config config = default_config;
676 rgba *imgr;
677 ia *imgi;
678 FILE *fp;
679 uint8_t *raw;
680 int raw_size;
681 int length = 0;
682 int flength;
683 int res;
685 int valid = parse_arguments(argc, argv, &config);
686 if (!valid || !config.bin_filename || !config.bin_filename) {
687 print_usage();
688 exit(EXIT_FAILURE);
691 if (config.mode == MODE_IMPORT) {
692 if (config.truncate) {
693 fp = fopen(config.bin_filename, "wb");
694 } else {
695 fp = fopen(config.bin_filename, "r+b");
697 if (!fp) {
698 ERROR("Error opening \"%s\"\n", config.bin_filename);
699 return -1;
701 if (!config.truncate) {
702 fseek(fp, config.offset, SEEK_SET);
704 switch (config.format) {
705 case IMG_FORMAT_RGBA:
706 imgr = png2rgba(config.img_filename, &config.width, &config.height);
707 raw_size = config.width * config.height * config.depth / 8;
708 raw = malloc(raw_size);
709 if (!raw) {
710 ERROR("Error allocating %u bytes\n", raw_size);
712 length = rgba2raw(raw, imgr, config.width, config.height, config.depth);
713 break;
714 case IMG_FORMAT_IA:
715 imgi = png2ia(config.img_filename, &config.width, &config.height);
716 raw_size = config.width * config.height * config.depth / 8;
717 raw = malloc(raw_size);
718 if (!raw) {
719 ERROR("Error allocating %u bytes\n", raw_size);
721 length = ia2raw(raw, imgi, config.width, config.height, config.depth);
722 break;
723 case IMG_FORMAT_I:
724 imgi = png2ia(config.img_filename, &config.width, &config.height);
725 raw_size = config.width * config.height * config.depth / 8;
726 raw = malloc(raw_size);
727 if (!raw) {
728 ERROR("Error allocating %u bytes\n", raw_size);
730 length = i2raw(raw, imgi, config.width, config.height, config.depth);
731 break;
732 default:
733 return EXIT_FAILURE;
735 if (length <= 0) {
736 ERROR("Error converting to raw format\n");
737 return EXIT_FAILURE;
739 INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.offset, config.bin_filename);
740 flength = fwrite(raw, 1, length, fp);
741 if (flength != length) {
742 ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename);
744 fclose(fp);
746 } else {
747 if (config.width <= 0 || config.height <= 0 || config.depth <= 0) {
748 ERROR("Error: must set position width and height for export\n");
749 return EXIT_FAILURE;
751 fp = fopen(config.bin_filename, "rb");
752 if (!fp) {
753 ERROR("Error opening \"%s\"\n", config.bin_filename);
754 return -1;
756 raw_size = config.width * config.height * config.depth / 8;
757 raw = malloc(raw_size);
758 if (config.offset > 0) {
759 fseek(fp, config.offset, SEEK_SET);
761 flength = fread(raw, 1, raw_size, fp);
762 if (flength != raw_size) {
763 ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename);
765 switch (config.format) {
766 case IMG_FORMAT_RGBA:
767 imgr = raw2rgba(raw, config.width, config.height, config.depth);
768 res = rgba2png(config.img_filename, imgr, config.width, config.height);
769 break;
770 case IMG_FORMAT_IA:
771 imgi = raw2ia(raw, config.width, config.height, config.depth);
772 res = ia2png(config.img_filename, imgi, config.width, config.height);
773 break;
774 case IMG_FORMAT_I:
775 imgi = raw2i(raw, config.width, config.height, config.depth);
776 res = ia2png(config.img_filename, imgi, config.width, config.height);
777 break;
778 default:
779 return EXIT_FAILURE;
781 if (!res) {
782 ERROR("Error writing to \"%s\"\n", config.img_filename);
786 return EXIT_SUCCESS;
788 #endif // N64GRAPHICS_STANDALONE