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"
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)
33 //---------------------------------------------------------
34 // N64 RGBA/IA/I/CI -> internal RGBA/IA
35 //---------------------------------------------------------
37 rgba
*raw2rgba(const uint8_t *raw
, int width
, int height
, int depth
)
42 img_size
= width
* height
* sizeof(*img
);
43 img
= malloc(img_size
);
45 ERROR("Error allocating %d bytes\n", img_size
);
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];
68 ia
*raw2ia(const uint8_t *raw
, int width
, int height
, int depth
)
73 img_size
= width
* height
* sizeof(*img
);
74 img
= malloc(img_size
);
76 ERROR("Error allocating %u bytes\n", img_size
);
82 for (int i
= 0; i
< width
* height
; i
++) {
83 img
[i
].intensity
= raw
[i
*2];
84 img
[i
].alpha
= raw
[i
*2+1];
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);
94 for (int i
= 0; i
< width
* height
; i
++) {
102 img
[i
].intensity
= SCALE_3_8((bits
>> 1) & 0x07);
103 img
[i
].alpha
= (bits
& 0x01) ? 0xFF : 0x00;
107 for (int i
= 0; i
< width
* height
; i
++) {
111 mask
= 1 << (7 - (i
% 8)); // MSb->LSb
112 bits
= (bits
& mask
) ? 0xFF : 0x00;
113 img
[i
].intensity
= bits
;
118 ERROR("Error invalid depth %d\n", depth
);
125 ia
*raw2i(const uint8_t *raw
, int width
, int height
, int depth
)
130 img_size
= width
* height
* sizeof(*img
);
131 img
= malloc(img_size
);
133 ERROR("Error allocating %u bytes\n", img_size
);
139 for (int i
= 0; i
< width
* height
; i
++) {
140 img
[i
].intensity
= raw
[i
];
145 for (int i
= 0; i
< width
* height
; i
++) {
153 img
[i
].intensity
= SCALE_4_8(bits
);
158 ERROR("Error invalid depth %d\n", depth
);
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
)
173 // first convert to raw RGBA
174 raw_size
= 2 * width
* height
;
175 raw_rgba
= malloc(raw_size
);
177 ERROR("Error allocating %u bytes\n", raw_size
);
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
);
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
);
206 for (int i
= 0; i
< width
* height
; i
++) {
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
;
223 ERROR("Error invalid depth %d\n", depth
);
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
);
237 for (int i
= 0; i
< width
* height
; i
++) {
238 raw
[i
*2] = img
[i
].intensity
;
239 raw
[i
*2+1] = img
[i
].alpha
;
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
;
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];
255 raw
[i
/2] = (old
& 0xF0) | (val
<< 1) | alpha
;
257 raw
[i
/2] = (old
& 0x0F) | (((val
<< 1) | alpha
) << 4);
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));
267 raw
[i
/8] = old
| bit
;
269 raw
[i
/8] = old
& (~bit
);
274 ERROR("Error invalid depth %d\n", depth
);
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
);
289 for (int i
= 0; i
< width
* height
; i
++) {
290 raw
[i
] = img
[i
].intensity
;
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];
298 raw
[i
/2] = (old
& 0xF0) | val
;
300 raw
[i
/2] = (old
& 0x0F) | (val
<< 4);
305 ERROR("Error invalid depth %d\n", depth
);
314 //---------------------------------------------------------
315 // internal RGBA/IA -> PNG
316 //---------------------------------------------------------
318 int rgba2png(const char *png_filename
, const rgba
*img
, int width
, int height
)
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
);
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);
344 int ia2png(const char *png_filename
, const ia
*img
, int width
, int height
)
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
);
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);
368 //---------------------------------------------------------
369 // PNG -> internal RGBA/IA
370 //---------------------------------------------------------
372 rgba
*png2rgba(const char *png_filename
, int *width
, int *height
)
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
);
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
);
390 ERROR("Error allocating %u bytes\n", img_size
);
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
++) {
400 img
[idx
].red
= data
[channels
*idx
];
401 img
[idx
].green
= data
[channels
*idx
+ 1];
402 img
[idx
].blue
= data
[channels
*idx
+ 2];
404 img
[idx
].alpha
= data
[channels
*idx
+ 3];
406 img
[idx
].alpha
= 0xFF;
411 case 2: // grey, alpha
412 for (int j
= 0; j
< h
; j
++) {
413 for (int i
= 0; i
< 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];
423 ERROR("Don't know how to read channels: %d\n", channels
);
429 stbi_image_free(data
);
436 ia
*png2ia(const char *png_filename
, int *width
, int *height
)
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
);
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
);
453 ERROR("Error allocating %d bytes\n", img_size
);
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
++) {
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
467 img
[idx
].alpha
= data
[channels
*idx
+ 3];
469 img
[idx
].alpha
= 0xFF;
474 case 2: // grey, alpha
475 for (int j
= 0; j
< h
; j
++) {
476 for (int i
= 0; i
< w
; i
++) {
478 img
[idx
].intensity
= data
[2*idx
];
479 img
[idx
].alpha
= data
[2*idx
+ 1];
484 ERROR("Don't know how to read channels: %d\n", channels
);
490 stbi_image_free(data
);
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"
530 static const graphics_config default_config
=
532 .img_filename
= NULL
,
533 .bin_filename
= NULL
,
536 .format
= IMG_FORMAT_RGBA
,
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
;
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
;
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"
590 "n64graphics v" N64GRAPHICS_VERSION
": N64 graphics manipulator\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"
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]) {
623 if (++i
>= argc
) return 0;
624 config
->bin_filename
= argv
[i
];
625 config
->mode
= MODE_EXPORT
;
628 if (++i
>= argc
) return 0;
629 if (!parse_format(config
, argv
[i
])) {
634 if (++i
>= argc
) return 0;
635 config
->bin_filename
= argv
[i
];
636 config
->mode
= MODE_IMPORT
;
639 if (++i
>= argc
) return 0;
640 config
->img_filename
= argv
[i
];
643 if (++i
>= argc
) return 0;
644 config
->height
= strtoul(argv
[i
], NULL
, 0);
647 if (++i
>= argc
) return 0;
648 config
->offset
= strtoul(argv
[i
], NULL
, 0);
649 config
->truncate
= 0;
652 if (++i
>= argc
) return 0;
653 config
->width
= strtoul(argv
[i
], NULL
, 0);
673 int main(int argc
, char *argv
[])
675 graphics_config config
= default_config
;
685 int valid
= parse_arguments(argc
, argv
, &config
);
686 if (!valid
|| !config
.bin_filename
|| !config
.bin_filename
) {
691 if (config
.mode
== MODE_IMPORT
) {
692 if (config
.truncate
) {
693 fp
= fopen(config
.bin_filename
, "wb");
695 fp
= fopen(config
.bin_filename
, "r+b");
698 ERROR("Error opening \"%s\"\n", config
.bin_filename
);
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
);
710 ERROR("Error allocating %u bytes\n", raw_size
);
712 length
= rgba2raw(raw
, imgr
, config
.width
, config
.height
, config
.depth
);
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
);
719 ERROR("Error allocating %u bytes\n", raw_size
);
721 length
= ia2raw(raw
, imgi
, config
.width
, config
.height
, config
.depth
);
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
);
728 ERROR("Error allocating %u bytes\n", raw_size
);
730 length
= i2raw(raw
, imgi
, config
.width
, config
.height
, config
.depth
);
736 ERROR("Error converting to raw format\n");
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
);
747 if (config
.width
<= 0 || config
.height
<= 0 || config
.depth
<= 0) {
748 ERROR("Error: must set position width and height for export\n");
751 fp
= fopen(config
.bin_filename
, "rb");
753 ERROR("Error opening \"%s\"\n", config
.bin_filename
);
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
);
771 imgi
= raw2ia(raw
, config
.width
, config
.height
, config
.depth
);
772 res
= ia2png(config
.img_filename
, imgi
, config
.width
, config
.height
);
775 imgi
= raw2i(raw
, config
.width
, config
.height
, config
.depth
);
776 res
= ia2png(config
.img_filename
, imgi
, config
.width
, config
.height
);
782 ERROR("Error writing to \"%s\"\n", config
.img_filename
);
788 #endif // N64GRAPHICS_STANDALONE