Path: blob/master/tools/n64graphics_ci_dir/n64graphics_ci.c
7857 views
#include <stdio.h>1#include <stdlib.h>2#include <strings.h>34#define STBI_NO_LINEAR5#define STBI_NO_HDR6#define STBI_NO_TGA7#define STB_IMAGE_IMPLEMENTATION8#include "../stb/stb_image.h"9#define STB_IMAGE_WRITE_IMPLEMENTATION10#include "../stb/stb_image_write.h"1112#include "exoquant/exoquant.h"1314#include "n64graphics_ci.h"15#include "utils.h"1617// SCALE_M_N: upscale/downscale M-bit integer to N-bit18#define SCALE_5_8(VAL_) (((VAL_) * 0xFF) / 0x1F)19#define SCALE_8_5(VAL_) ((((VAL_) + 4) * 0x1F) / 0xFF)20#define SCALE_4_8(VAL_) ((VAL_) * 0x11)21#define SCALE_8_4(VAL_) ((VAL_) / 0x11)22#define SCALE_3_8(VAL_) ((VAL_) * 0x24)23#define SCALE_8_3(VAL_) ((VAL_) / 0x24)2425typedef enum26{27IMG_FORMAT_CI28} img_format;2930rgba *raw2rgba(const uint8_t *raw, int width, int height, int depth)31{32rgba *img;33int img_size;3435img_size = width * height * sizeof(*img);36img = malloc(img_size);37if (!img) {38ERROR("Error allocating %d bytes\n", img_size);39return NULL;40}4142if (depth == 16) {43for (int i = 0; i < width * height; i++) {44img[i].red = SCALE_5_8((raw[i * 2] & 0xF8) >> 3);45img[i].green = SCALE_5_8(((raw[i * 2] & 0x07) << 2) | ((raw[i * 2 + 1] & 0xC0) >> 6));46img[i].blue = SCALE_5_8((raw[i * 2 + 1] & 0x3E) >> 1);47img[i].alpha = (raw[i * 2 + 1] & 0x01) ? 0xFF : 0x00;48}49}50else if (depth == 32) {51for (int i = 0; i < width * height; i++) {52img[i].red = raw[i * 4];53img[i].green = raw[i * 4 + 1];54img[i].blue = raw[i * 4 + 2];55img[i].alpha = raw[i * 4 + 3];56}57}5859return img;60}6162// extract RGBA from CI raw data and palette63rgba *rawci2rgba(const uint8_t *rawci, const uint8_t *palette, int width, int height, int depth)64{65uint8_t *raw_rgba;66rgba *img = NULL;67int raw_size;68int pal_index_1;69int pal_index_2;7071// first convert to raw RGBA72raw_size = 2 * width * height;73raw_rgba = malloc(raw_size);74if (!raw_rgba) {75ERROR("Error allocating %u bytes\n", raw_size);76return NULL;77}7879if (depth == 4) { // CI480for (int i = 0; i < (width * height) / 2; i++) {81pal_index_1 = rawci[i] >> 4;82pal_index_2 = rawci[i] & 0xF;83raw_rgba[i * 4 + 0] = palette[pal_index_1 * 2 + 0];84raw_rgba[i * 4 + 1] = palette[pal_index_1 * 2 + 1];85raw_rgba[i * 4 + 2] = palette[pal_index_2 * 2 + 0];86raw_rgba[i * 4 + 3] = palette[pal_index_2 * 2 + 1];87}88} else { // CI889for (int i = 0; i < width * height; i++) {90pal_index_1 = rawci[i];91raw_rgba[i * 2 + 0] = palette[pal_index_1 * 2 + 0];92raw_rgba[i * 2 + 1] = palette[pal_index_1 * 2 + 1];93}94}9596// then convert to RGBA image data97img = raw2rgba(raw_rgba, width, height, 16);9899free(raw_rgba);100101return img;102}103104int rgba2rawci(uint8_t *raw, uint8_t *out_palette, int *pal_len, const rgba *img, int width, int height, int depth)105{106int size = width * height * depth / 8;107int num_colors = *pal_len / 2;108109INFO("Converting RGBA %dx%d to raw CI%d\n", width, height, depth);110111uint8_t *rgba32_palette = malloc(num_colors * 4);112uint8_t *ci8_raw = malloc(width * height);113114// Use ExoQuant to convert the RGBA32 data buffer to an CI8 output115exq_data *pExq = exq_init();116exq_feed(pExq, (uint8_t*)img, width * height);117exq_quantize_hq(pExq, num_colors);118exq_get_palette(pExq, rgba32_palette, num_colors);119exq_map_image_ordered(pExq, width, height, (uint8_t*)img, ci8_raw);120exq_free(pExq);121122if (depth == 4) {123// Convert CI8 image to CI4 image124for (int i = 0; i < size; i++) {125raw[i] = (ci8_raw[i * 2 + 0] << 4) | ci8_raw[i * 2 + 1];126}127}128else {129memcpy(raw, ci8_raw, size);130}131132// Convert RGBA32 palette to RGBA16133for (int i = 0; i < num_colors; i++) {134unsigned char red = (rgba32_palette[i * 4 + 0] / 8) & 0x1F;135unsigned char green = (rgba32_palette[i * 4 + 1] / 8) & 0x1F;136unsigned char blue = (rgba32_palette[i * 4 + 2] / 8) & 0x1F;137unsigned char alpha = rgba32_palette[i * 4 + 3] > 0 ? 1 : 0; // 1 bit alpha138139out_palette[i * 2 + 0] = (red << 3) | (green >> 2);140out_palette[i * 2 + 1] = ((green & 3) << 6) | (blue << 1) | alpha;141}142143free(rgba32_palette);144free(ci8_raw);145return size;146}147148rgba *png2rgba(const char *png_filename, int *width, int *height)149{150rgba *img = NULL;151int w = 0;152int h = 0;153int channels = 0;154int img_size;155156stbi_uc *data = stbi_load(png_filename, &w, &h, &channels, STBI_default);157if (!data || w <= 0 || h <= 0) {158ERROR("Error loading png file \"%s\"\n", png_filename);159return NULL;160}161INFO("Read \"%s\" %dx%d channels: %d\n", png_filename, w, h, channels);162163img_size = w * h * sizeof(*img);164img = malloc(img_size);165if (!img) {166ERROR("Error allocating %u bytes\n", img_size);167return NULL;168}169170switch (channels) {171case 3: // red, green, blue172case 4: // red, green, blue, alpha173for (int j = 0; j < h; j++) {174for (int i = 0; i < w; i++) {175int idx = j*w + i;176img[idx].red = data[channels*idx];177img[idx].green = data[channels*idx + 1];178img[idx].blue = data[channels*idx + 2];179if (channels == 4) {180img[idx].alpha = data[channels*idx + 3];181}182else {183img[idx].alpha = 0xFF;184}185}186}187break;188case 2: // grey, alpha189for (int j = 0; j < h; j++) {190for (int i = 0; i < w; i++) {191int idx = j*w + i;192img[idx].red = data[2 * idx];193img[idx].green = data[2 * idx];194img[idx].blue = data[2 * idx];195img[idx].alpha = data[2 * idx + 1];196}197}198break;199default:200ERROR("Don't know how to read channels: %d\n", channels);201free(img);202img = NULL;203}204205// cleanup206stbi_image_free(data);207208*width = w;209*height = h;210return img;211}212213int rgba2png(const char *png_filename, const rgba *img, int width, int height)214{215int ret = 0;216INFO("Saving RGBA %dx%d to \"%s\"\n", width, height, png_filename);217218// convert to format stb_image_write expects219uint8_t *data = malloc(4 * width*height);220if (data) {221for (int j = 0; j < height; j++) {222for (int i = 0; i < width; i++) {223int idx = j*width + i;224data[4 * idx] = img[idx].red;225data[4 * idx + 1] = img[idx].green;226data[4 * idx + 2] = img[idx].blue;227data[4 * idx + 3] = img[idx].alpha;228}229}230231ret = stbi_write_png(png_filename, width, height, 4, data, 0);232233free(data);234}235236return ret;237}238239const char *n64graphics_get_read_version(void)240{241return "stb_image 2.19";242}243244const char *n64graphics_get_write_version(void)245{246return "stb_image_write 1.09";247}248249/***************************************************************/250251#define N64GRAPHICS_VERSION "0.4 - CI Only Branch"252#include <string.h>253254typedef enum255{256MODE_EXPORT,257MODE_IMPORT,258} tool_mode;259260typedef struct261{262char *img_filename;263char *bin_filename;264tool_mode mode;265unsigned int offset;266img_format format;267int depth;268int width;269int height;270int truncate;271} graphics_config;272273static const graphics_config default_config =274{275.img_filename = NULL,276.bin_filename = NULL,277.mode = MODE_EXPORT,278.offset = 0,279.format = IMG_FORMAT_CI,280.depth = 8,281.width = 32,282.height = 32,283.truncate = 1,284};285286typedef struct287{288const char *name;289img_format format;290int depth;291} format_entry;292293static const format_entry format_table[] =294{295{ "ci4", IMG_FORMAT_CI, 4 },296{ "ci8", IMG_FORMAT_CI, 8 },297};298299static const char *format2str(img_format format, int depth)300{301for (unsigned i = 0; i < DIM(format_table); i++) {302if (format == format_table[i].format && depth == format_table[i].depth) {303return format_table[i].name;304}305}306return "unknown";307}308309static int parse_format(graphics_config *config, const char *str)310{311for (unsigned i = 0; i < DIM(format_table); i++) {312if (!strcasecmp(str, format_table[i].name)) {313config->format = format_table[i].format;314config->depth = format_table[i].depth;315return 1;316}317}318return 0;319}320321static void print_usage(void)322{323ERROR("Usage: n64graphics_ci -e/-i BIN_FILE -g PNG_FILE [-o offset] [-f FORMAT] [-w WIDTH] [-h HEIGHT] [-V]\n"324"\n"325"n64graphics v" N64GRAPHICS_VERSION ": N64 graphics manipulator\n"326"\n"327"Required arguments:\n"328" -e BIN_FILE export from BIN_FILE to PNG_FILE\n"329" -i BIN_FILE import from PNG_FILE to BIN_FILE\n"330" -g PNG_FILE graphics file to import/export (.png)\n"331"Optional arguments:\n"332" -o OFFSET starting offset in BIN_FILE (prevents truncation during import)\n"333" -f FORMAT texture format: ci4, ci8 (default: %s)\n"334" -w WIDTH export texture width (default: %d)\n"335" -h HEIGHT export texture height (default: %d)\n"336" -v verbose logging\n"337" -V print version information\n",338format2str(default_config.format, default_config.depth),339default_config.width,340default_config.height);341}342343static void print_version(void)344{345ERROR("n64graphics v" N64GRAPHICS_VERSION ", using:\n"346" %s\n"347" %s\n",348n64graphics_get_read_version(), n64graphics_get_write_version());349}350351// parse command line arguments352static int parse_arguments(int argc, char *argv[], graphics_config *config)353{354for (int i = 1; i < argc; i++) {355if (argv[i][0] == '-') {356switch (argv[i][1]) {357case 'e':358if (++i >= argc) return 0;359config->bin_filename = argv[i];360config->mode = MODE_EXPORT;361break;362case 'f':363if (++i >= argc) return 0;364if (!parse_format(config, argv[i])) {365return 0;366}367break;368case 'i':369if (++i >= argc) return 0;370config->bin_filename = argv[i];371config->mode = MODE_IMPORT;372break;373case 'g':374if (++i >= argc) return 0;375config->img_filename = argv[i];376break;377case 'h':378if (++i >= argc) return 0;379config->height = strtoul(argv[i], NULL, 0);380break;381case 'o':382if (++i >= argc) return 0;383config->offset = strtoul(argv[i], NULL, 0);384config->truncate = 0;385break;386case 'w':387if (++i >= argc) return 0;388config->width = strtoul(argv[i], NULL, 0);389break;390case 'v':391g_verbosity = 1;392break;393case 'V':394print_version();395exit(0);396break;397default:398return 0;399break;400}401}402else {403return 0;404}405}406return 1;407}408409char* getPaletteFilename(char* ci_filename)410{411int bin_filename_len;412int extension_len;413char* pal_bin_filename;414char* extension_loc;415char* extension;416417// Write Palette file418bin_filename_len = strlen(ci_filename);419extension_loc = strrchr(ci_filename, '.');420extension_len = (int)(extension_loc - ci_filename);421extension = malloc(extension_len);422memcpy(extension, extension_loc, extension_len);423pal_bin_filename = malloc(bin_filename_len + 4); // +4 to include ".pal"424if (!pal_bin_filename) {425ERROR("Error allocating bytes for palette filename\n");426}427memcpy(pal_bin_filename, ci_filename, bin_filename_len);428strcpy(pal_bin_filename + bin_filename_len, ".pal");429//strcpy(pal_bin_filename + bin_filename_len, extension);430431free(extension);432433return pal_bin_filename;434}435436int main(int argc, char* argv[])437{438graphics_config config = default_config;439rgba *imgr;440FILE *fp;441uint8_t *raw;442int raw_size;443int length = 0;444int flength;445int res;446FILE *fp_pal; // for ci palette447uint8_t *pal;448int pal_len;449char* pal_bin_filename;450451int valid = parse_arguments(argc, argv, &config);452if (!valid || !config.bin_filename || !config.bin_filename || !config.img_filename) {453print_usage();454exit(EXIT_FAILURE);455}456457if (config.mode == MODE_IMPORT) {458printf("%s\n", config.bin_filename);459if (config.truncate) {460fp = fopen(config.bin_filename, "wb");461}462else {463fp = fopen(config.bin_filename, "r+b");464}465if (!fp) {466ERROR("Error opening binary file \"%s\"\n", config.bin_filename);467return -1;468}469if (!config.truncate) {470fseek(fp, config.offset, SEEK_SET);471}472switch (config.format) {473case IMG_FORMAT_CI:474imgr = png2rgba(config.img_filename, &config.width, &config.height);475raw_size = config.width * config.height * config.depth / 8;476raw = malloc(raw_size);477if (!raw) {478ERROR("Error allocating %u bytes\n", raw_size);479}480if (config.depth == 4) {481pal_len = 16 * 2; // CI4482}483else {484pal_len = 256 * 2; // CI8485}486pal = malloc(pal_len);487if (!pal) {488ERROR("Error allocating %u bytes for palette\n", pal_len);489}490length = rgba2rawci(raw, pal, &pal_len, imgr, config.width, config.height, config.depth);491//length = rgba2raw(raw, imgr, config.width, config.height, config.depth);492break;493}494if (length <= 0) {495ERROR("Error converting to raw format\n");496return EXIT_FAILURE;497}498INFO("Writing 0x%X bytes to offset 0x%X of \"%s\"\n", length, config.offset, config.bin_filename);499flength = fwrite(raw, 1, length, fp);500if (flength != length) {501ERROR("Error writing %d bytes to \"%s\"\n", length, config.bin_filename);502}503fclose(fp);504505if (config.format == IMG_FORMAT_CI) {506pal_bin_filename = getPaletteFilename(config.bin_filename);507508fp_pal = fopen(pal_bin_filename, "wb");509INFO("Writing 0x%X bytes to palette file \"%s\"\n", pal_len, pal_bin_filename);510flength = fwrite(pal, 1, pal_len, fp_pal);511if (flength != pal_len) {512ERROR("Error writing %d bytes to \"%s\"\n", pal_len, pal_bin_filename);513}514fclose(fp_pal);515free(pal_bin_filename);516}517} else { // Export518if (config.width <= 0 || config.height <= 0 || config.depth <= 0) {519ERROR("Error: must set position width and height for export\n");520return EXIT_FAILURE;521}522fp = fopen(config.bin_filename, "rb");523if (!fp) {524ERROR("Error opening \"%s\"\n", config.bin_filename);525return -1;526}527raw_size = config.width * config.height * config.depth / 8;528raw = malloc(raw_size);529if (config.offset > 0) {530fseek(fp, config.offset, SEEK_SET);531}532flength = fread(raw, 1, raw_size, fp);533if (flength != raw_size) {534ERROR("Error reading %d bytes from \"%s\"\n", raw_size, config.bin_filename);535}536switch (config.format) {537case IMG_FORMAT_CI:538// Read Palette file539pal_bin_filename = getPaletteFilename(config.bin_filename);540fp_pal = fopen(pal_bin_filename, "rb");541if (!fp_pal) {542ERROR("Error opening \"%s\"\n", pal_bin_filename);543return -1;544}545if (config.depth == 4) {546pal_len = 16 * 2; // CI4547}548else {549pal_len = 256 * 2; // CI8550}551552pal = malloc(pal_len);553if (config.offset > 0) {554fseek(fp, config.offset, SEEK_SET);555}556flength = fread(pal, 1, pal_len, fp_pal);557558imgr = rawci2rgba(raw, pal, config.width, config.height, config.depth);559res = rgba2png(config.img_filename, imgr, config.width, config.height);560561free(pal_bin_filename);562break;563default:564return EXIT_FAILURE;565}566if (!res) {567ERROR("Error writing to \"%s\"\n", config.img_filename);568}569}570571return 0;572}573574575