n64graphics_ci.c (17170B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <strings.h> 4 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" 12 13 #include "exoquant/exoquant.h" 14 15 #include "n64graphics_ci.h" 16 #include "utils.h" 17 18 // SCALE_M_N: upscale/downscale M-bit integer to N-bit 19 #define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F) 20 #define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF) 21 #define SCALE_4_8(VAL_) ((VAL_) * 0x11) 22 #define SCALE_8_4(VAL_) ((VAL_) / 0x11) 23 #define SCALE_3_8(VAL_) ((VAL_) * 0x24) 24 #define SCALE_8_3(VAL_) ((VAL_) / 0x24) 25 26 typedef enum 27 { 28 IMG_FORMAT_CI 29 } img_format; 30 31 rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth) 32 { 33 rgba *img; 34 int img_size; 35 36 img_size = width * height * sizeof(*img); 37 img = malloc(img_size); 38 if (!img) { 39 ERROR("Error allocating %d bytes\n", img_size); 40 return NULL; 41 } 42 43 if (depth == 16) { 44 for (int i = 0; i < width * height; i++) { 45 img[i].red = SCALE_5_8((raw[i * 2] & 0xF8) >> 3); 46 img[i].green = SCALE_5_8(((raw[i * 2] & 0x07) << 2) | ((raw[i * 2 + 1] & 0xC0) >> 6)); 47 img[i].blue = SCALE_5_8((raw[i * 2 + 1] & 0x3E) >> 1); 48 img[i].alpha = (raw[i * 2 + 1] & 0x01) ? 0xFF : 0x00; 49 } 50 } 51 else if (depth == 32) { 52 for (int i = 0; i < width * height; i++) { 53 img[i].red = raw[i * 4]; 54 img[i].green = raw[i * 4 + 1]; 55 img[i].blue = raw[i * 4 + 2]; 56 img[i].alpha = raw[i * 4 + 3]; 57 } 58 } 59 60 return img; 61 } 62 63 // extract RGBA from CI raw data and palette 64 rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth) 65 { 66 uint8_t *raw_rgba; 67 rgba *img = NULL; 68 int raw_size; 69 int pal_index_1; 70 int pal_index_2; 71 72 // first convert to raw RGBA 73 raw_size = 2 * width * height; 74 raw_rgba = malloc(raw_size); 75 if (!raw_rgba) { 76 ERROR("Error allocating %u bytes\n", raw_size); 77 return NULL; 78 } 79 80 if (depth == 4) { // CI4 81 for (int i = 0; i < (width * height) / 2; i++) { 82 pal_index_1 = rawci[i] >> 4; 83 pal_index_2 = rawci[i] & 0xF; 84 raw_rgba[i * 4 + 0] = palette[pal_index_1 * 2 + 0]; 85 raw_rgba[i * 4 + 1] = palette[pal_index_1 * 2 + 1]; 86 raw_rgba[i * 4 + 2] = palette[pal_index_2 * 2 + 0]; 87 raw_rgba[i * 4 + 3] = palette[pal_index_2 * 2 + 1]; 88 } 89 } else { // CI8 90 for (int i = 0; i < width * height; i++) { 91 pal_index_1 = rawci[i]; 92 raw_rgba[i * 2 + 0] = palette[pal_index_1 * 2 + 0]; 93 raw_rgba[i * 2 + 1] = palette[pal_index_1 * 2 + 1]; 94 } 95 } 96 97 // then convert to RGBA image data 98 img = raw2rgba(raw_rgba, width, height, 16); 99 100 free(raw_rgba); 101 102 return img; 103 } 104 105 int rgba2rawci(uint8_t *raw, uint8_t *out_palette, int *pal_len, const rgba *img, int width, int height, int depth) 106 { 107 int size = width * height * depth / 8; 108 int num_colors = *pal_len / 2; 109 110 INFO("Converting RGBA %dx%d to raw CI%d\n", width, height, depth); 111 112 uint8_t *rgba32_palette = malloc(num_colors * 4); 113 uint8_t *ci8_raw = malloc(width * height); 114 115 // Use ExoQuant to convert the RGBA32 data buffer to an CI8 output 116 exq_data *pExq = exq_init(); 117 exq_feed(pExq, (uint8_t*)img, width * height); 118 exq_quantize_hq(pExq, num_colors); 119 exq_get_palette(pExq, rgba32_palette, num_colors); 120 exq_map_image_ordered(pExq, width, height, (uint8_t*)img, ci8_raw); 121 exq_free(pExq); 122 123 if (depth == 4) { 124 // Convert CI8 image to CI4 image 125 for (int i = 0; i < size; i++) { 126 raw[i] = (ci8_raw[i * 2 + 0] << 4) | ci8_raw[i * 2 + 1]; 127 } 128 } 129 else { 130 memcpy(raw, ci8_raw, size); 131 } 132 133 // Convert RGBA32 palette to RGBA16 134 for (int i = 0; i < num_colors; i++) { 135 unsigned char red = (rgba32_palette[i * 4 + 0] / 8) & 0x1F; 136 unsigned char green = (rgba32_palette[i * 4 + 1] / 8) & 0x1F; 137 unsigned char blue = (rgba32_palette[i * 4 + 2] / 8) & 0x1F; 138 unsigned char alpha = rgba32_palette[i * 4 + 3] > 0 ? 1 : 0; // 1 bit alpha 139 140 out_palette[i * 2 + 0] = (red << 3) | (green >> 2); 141 out_palette[i * 2 + 1] = ((green & 3) << 6) | (blue << 1) | alpha; 142 } 143 144 free(rgba32_palette); 145 free(ci8_raw); 146 return size; 147 } 148 149 rgba *png2rgba(const char *png_filename, int *width, int *height) 150 { 151 rgba *img = NULL; 152 int w = 0; 153 int h = 0; 154 int channels = 0; 155 int img_size; 156 157 stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default); 158 if (!data || w <= 0 || h <= 0) { 159 ERROR("Error loading png file \"%s\"\n", png_filename); 160 return NULL; 161 } 162 INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels); 163 164 img_size = w * h * sizeof(*img); 165 img = malloc(img_size); 166 if (!img) { 167 ERROR("Error allocating %u bytes\n", img_size); 168 return NULL; 169 } 170 171 switch (channels) { 172 case 3: // red, green, blue 173 case 4: // red, green, blue, alpha 174 for (int j = 0; j < h; j++) { 175 for (int i = 0; i < w; i++) { 176 int idx = j*w + i; 177 img[idx].red = data[channels*idx]; 178 img[idx].green = data[channels*idx + 1]; 179 img[idx].blue = data[channels*idx + 2]; 180 if (channels == 4) { 181 img[idx].alpha = data[channels*idx + 3]; 182 } 183 else { 184 img[idx].alpha = 0xFF; 185 } 186 } 187 } 188 break; 189 case 2: // grey, alpha 190 for (int j = 0; j < h; j++) { 191 for (int i = 0; i < w; i++) { 192 int idx = j*w + i; 193 img[idx].red = data[2 * idx]; 194 img[idx].green = data[2 * idx]; 195 img[idx].blue = data[2 * idx]; 196 img[idx].alpha = data[2 * idx + 1]; 197 } 198 } 199 break; 200 default: 201 ERROR("Don't know how to read channels: %d\n", channels); 202 free(img); 203 img = NULL; 204 } 205 206 // cleanup 207 stbi_image_free(data); 208 209 *width = w; 210 *height = h; 211 return img; 212 } 213 214 int rgba2png(const char *png_filename, const rgba *img, int width, int height) 215 { 216 int ret = 0; 217 INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename); 218 219 // convert to format stb_image_write expects 220 uint8_t *data = malloc(4 * width*height); 221 if (data) { 222 for (int j = 0; j < height; j++) { 223 for (int i = 0; i < width; i++) { 224 int idx = j*width + i; 225 data[4 * idx] = img[idx].red; 226 data[4 * idx + 1] = img[idx].green; 227 data[4 * idx + 2] = img[idx].blue; 228 data[4 * idx + 3] = img[idx].alpha; 229 } 230 } 231 232 ret = stbi_write_png(png_filename, width, height, 4, data, 0); 233 234 free(data); 235 } 236 237 return ret; 238 } 239 240 const char *n64graphics_get_read_version(void) 241 { 242 return "stb_image 2.19"; 243 } 244 245 const char *n64graphics_get_write_version(void) 246 { 247 return "stb_image_write 1.09"; 248 } 249 250 /***************************************************************/ 251 252 #define N64GRAPHICS_VERSION "0.4 - CI Only Branch" 253 #include <string.h> 254 255 typedef enum 256 { 257 MODE_EXPORT, 258 MODE_IMPORT, 259 } tool_mode; 260 261 typedef struct 262 { 263 char *img_filename; 264 char *bin_filename; 265 tool_mode mode; 266 unsigned int offset; 267 img_format format; 268 int depth; 269 int width; 270 int height; 271 int truncate; 272 } graphics_config; 273 274 static const graphics_config default_config = 275 { 276 .img_filename = NULL, 277 .bin_filename = NULL, 278 .mode = MODE_EXPORT, 279 .offset = 0, 280 .format = IMG_FORMAT_CI, 281 .depth = 8, 282 .width = 32, 283 .height = 32, 284 .truncate = 1, 285 }; 286 287 typedef struct 288 { 289 const char *name; 290 img_format format; 291 int depth; 292 } format_entry; 293 294 static const format_entry format_table[] = 295 { 296 { "ci4", IMG_FORMAT_CI, 4 }, 297 { "ci8", IMG_FORMAT_CI, 8 }, 298 }; 299 300 static const char *format2str(img_format format, int depth) 301 { 302 for (unsigned i = 0; i < DIM(format_table); i++) { 303 if (format == format_table[i].format && depth == format_table[i].depth) { 304 return format_table[i].name; 305 } 306 } 307 return "unknown"; 308 } 309 310 static int parse_format(graphics_config *config, const char *str) 311 { 312 for (unsigned i = 0; i < DIM(format_table); i++) { 313 if (!strcasecmp(str, format_table[i].name)) { 314 config->format = format_table[i].format; 315 config->depth = format_table[i].depth; 316 return 1; 317 } 318 } 319 return 0; 320 } 321 322 static void print_usage(void) 323 { 324 ERROR("Usage: n64graphics_ci -e/-i BIN_FILE -g PNG_FILE [-o offset] [-f FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n" 325 "\n" 326 "n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n" 327 "\n" 328 "Required arguments:\n" 329 " -e BIN_FILE export from BIN_FILE to PNG_FILE\n" 330 " -i BIN_FILE import from PNG_FILE to BIN_FILE\n" 331 " -g PNG_FILE graphics file to import/export (.png)\n" 332 "Optional arguments:\n" 333 " -o OFFSET starting offset in BIN_FILE (prevents truncation during import)\n" 334 " -f FORMAT texture format: ci4, ci8 (default: %s)\n" 335 " -w WIDTH export texture width (default: %d)\n" 336 " -h HEIGHT export texture height (default: %d)\n" 337 " -v verbose logging\n" 338 " -V print version information\n", 339 format2str(default_config.format, default_config.depth), 340 default_config.width, 341 default_config.height); 342 } 343 344 static void print_version(void) 345 { 346 ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n" 347 " %s\n" 348 " %s\n", 349 n64graphics_get_read_version(), n64graphics_get_write_version()); 350 } 351 352 // parse command line arguments 353 static int parse_arguments(int argc, char *argv[], graphics_config *config) 354 { 355 for (int i = 1; i < argc; i++) { 356 if (argv[i][0] == '-') { 357 switch (argv[i][1]) { 358 case 'e': 359 if (++i >= argc) return 0; 360 config->bin_filename = argv[i]; 361 config->mode = MODE_EXPORT; 362 break; 363 case 'f': 364 if (++i >= argc) return 0; 365 if (!parse_format(config, argv[i])) { 366 return 0; 367 } 368 break; 369 case 'i': 370 if (++i >= argc) return 0; 371 config->bin_filename = argv[i]; 372 config->mode = MODE_IMPORT; 373 break; 374 case 'g': 375 if (++i >= argc) return 0; 376 config->img_filename = argv[i]; 377 break; 378 case 'h': 379 if (++i >= argc) return 0; 380 config->height = strtoul(argv[i], NULL, 0); 381 break; 382 case 'o': 383 if (++i >= argc) return 0; 384 config->offset = strtoul(argv[i], NULL, 0); 385 config->truncate = 0; 386 break; 387 case 'w': 388 if (++i >= argc) return 0; 389 config->width = strtoul(argv[i], NULL, 0); 390 break; 391 case 'v': 392 g_verbosity = 1; 393 break; 394 case 'V': 395 print_version(); 396 exit(0); 397 break; 398 default: 399 return 0; 400 break; 401 } 402 } 403 else { 404 return 0; 405 } 406 } 407 return 1; 408 } 409 410 char* getPaletteFilename(char* ci_filename) 411 { 412 int bin_filename_len; 413 int extension_len; 414 char* pal_bin_filename; 415 char* extension_loc; 416 char* extension; 417 418 // Write Palette file 419 bin_filename_len = strlen(ci_filename); 420 extension_loc = strrchr(ci_filename, '.'); 421 extension_len = (int)(extension_loc - ci_filename); 422 extension = malloc(extension_len); 423 memcpy(extension, extension_loc, extension_len); 424 pal_bin_filename = malloc(bin_filename_len + 4); // +4 to include ".pal" 425 if (!pal_bin_filename) { 426 ERROR("Error allocating bytes for palette filename\n"); 427 } 428 memcpy(pal_bin_filename, ci_filename, bin_filename_len); 429 strcpy(pal_bin_filename + bin_filename_len, ".pal"); 430 //strcpy(pal_bin_filename + bin_filename_len, extension); 431 432 free(extension); 433 434 return pal_bin_filename; 435 } 436 437 int main(int argc, char* argv[]) 438 { 439 graphics_config config = default_config; 440 rgba *imgr; 441 FILE *fp; 442 uint8_t *raw; 443 int raw_size; 444 int length = 0; 445 int flength; 446 int res; 447 FILE *fp_pal; // for ci palette 448 uint8_t *pal; 449 int pal_len; 450 char* pal_bin_filename; 451 452 int valid = parse_arguments(argc, argv, &config); 453 if (!valid || !config.bin_filename || !config.bin_filename || !config.img_filename) { 454 print_usage(); 455 exit(EXIT_FAILURE); 456 } 457 458 if (config.mode == MODE_IMPORT) { 459 printf("%s\n", config.bin_filename); 460 if (config.truncate) { 461 fp = fopen(config.bin_filename, "wb"); 462 } 463 else { 464 fp = fopen(config.bin_filename, "r+b"); 465 } 466 if (!fp) { 467 ERROR("Error opening binary file \"%s\"\n", config.bin_filename); 468 return -1; 469 } 470 if (!config.truncate) { 471 fseek(fp, config.offset, SEEK_SET); 472 } 473 switch (config.format) { 474 case IMG_FORMAT_CI: 475 imgr = png2rgba(config.img_filename, &config.width, &config.height); 476 raw_size = config.width * config.height * config.depth / 8; 477 raw = malloc(raw_size); 478 if (!raw) { 479 ERROR("Error allocating %u bytes\n", raw_size); 480 } 481 if (config.depth == 4) { 482 pal_len = 16 * 2; // CI4 483 } 484 else { 485 pal_len = 256 * 2; // CI8 486 } 487 pal = malloc(pal_len); 488 if (!pal) { 489 ERROR("Error allocating %u bytes for palette\n", pal_len); 490 } 491 length = rgba2rawci(raw, pal, &pal_len, imgr, config.width, config.height, config.depth); 492 //length = rgba2raw(raw, imgr, config.width, config.height, config.depth); 493 break; 494 } 495 if (length <= 0) { 496 ERROR("Error converting to raw format\n"); 497 return EXIT_FAILURE; 498 } 499 INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.offset, config.bin_filename); 500 flength = fwrite(raw, 1, length, fp); 501 if (flength != length) { 502 ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename); 503 } 504 fclose(fp); 505 506 if (config.format == IMG_FORMAT_CI) { 507 pal_bin_filename = getPaletteFilename(config.bin_filename); 508 509 fp_pal = fopen(pal_bin_filename, "wb"); 510 INFO("Writing 0x%X bytes to palette file \"%s\"\n", pal_len, pal_bin_filename); 511 flength = fwrite(pal, 1, pal_len, fp_pal); 512 if (flength != pal_len) { 513 ERROR("Error writing %d bytes to \"%s\"\n", pal_len, pal_bin_filename); 514 } 515 fclose(fp_pal); 516 free(pal_bin_filename); 517 } 518 } else { // Export 519 if (config.width <= 0 || config.height <= 0 || config.depth <= 0) { 520 ERROR("Error: must set position width and height for export\n"); 521 return EXIT_FAILURE; 522 } 523 fp = fopen(config.bin_filename, "rb"); 524 if (!fp) { 525 ERROR("Error opening \"%s\"\n", config.bin_filename); 526 return -1; 527 } 528 raw_size = config.width * config.height * config.depth / 8; 529 raw = malloc(raw_size); 530 if (config.offset > 0) { 531 fseek(fp, config.offset, SEEK_SET); 532 } 533 flength = fread(raw, 1, raw_size, fp); 534 if (flength != raw_size) { 535 ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename); 536 } 537 switch (config.format) { 538 case IMG_FORMAT_CI: 539 // Read Palette file 540 pal_bin_filename = getPaletteFilename(config.bin_filename); 541 fp_pal = fopen(pal_bin_filename, "rb"); 542 if (!fp_pal) { 543 ERROR("Error opening \"%s\"\n", pal_bin_filename); 544 return -1; 545 } 546 if (config.depth == 4) { 547 pal_len = 16 * 2; // CI4 548 } 549 else { 550 pal_len = 256 * 2; // CI8 551 } 552 553 pal = malloc(pal_len); 554 if (config.offset > 0) { 555 fseek(fp, config.offset, SEEK_SET); 556 } 557 flength = fread(pal, 1, pal_len, fp_pal); 558 559 imgr = rawci2rgba(raw, pal, config.width, config.height, config.depth); 560 res = rgba2png(config.img_filename, imgr, config.width, config.height); 561 562 free(pal_bin_filename); 563 break; 564 default: 565 return EXIT_FAILURE; 566 } 567 if (!res) { 568 ERROR("Error writing to \"%s\"\n", config.img_filename); 569 } 570 } 571 572 return 0; 573 }