Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
MorsGames
GitHub Repository: MorsGames/sm64plus
Path: blob/master/tools/n64graphics_ci_dir/n64graphics_ci.c
7857 views
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
}
574
575