13 #include "n64graphics.h"
37 int imageWidth
, imageHeight
;
38 int tileWidth
, tileHeight
;
41 bool optimizePositions
;
44 static const ImageProps IMAGE_PROPERTIES
[ImageType_MAX
][2] = {
46 {248, 248, 31, 31, 8, 8, true, true},
47 {256, 256, 32, 32, 8, 8, true, true},
50 {316, 228, 79, 19, 4, 12, false, false},
51 {320, 240, 80, 20, 4, 12, false, false},
54 {320, 224, 64, 32, 5, 7, false, false},
55 {320, 224, 64, 32, 5, 7, false, false},
63 static const TableDimension TABLE_DIMENSIONS
[ImageType_MAX
] = {
70 ImageType type
= InvalidType
;
71 OperationMode mode
= InvalidMode
;
76 bool expanded
= false;
78 bool storeNamesOnly
= false;
80 static void allocate_tiles() {
81 const ImageProps props
= IMAGE_PROPERTIES
[type
][true];
82 int tileWidth
= props
.tileWidth
;
83 int tileHeight
= props
.tileHeight
;
84 int numRows
= props
.numRows
;
85 int numCols
= props
.numCols
;
87 int tileSize
= tileWidth
* tileHeight
* sizeof(rgba
);
88 int totalSize
= numRows
* numCols
* tileSize
;
89 tiles
= calloc(1, numRows
* numCols
* sizeof(TextureTile
));
90 rgba
*tileData
= calloc(1, totalSize
);
91 for (int row
= 0; row
< numRows
; ++row
) {
92 for (int col
= 0; col
< numCols
; ++col
) {
93 tiles
[row
* numCols
+ col
].px
= (tileData
+ (row
* numCols
+ col
) * (tileWidth
* tileHeight
));
98 static void free_tiles() {
103 static void split_tile(int col
, int row
, rgba
*image
, bool expanded
) {
104 const ImageProps props
= IMAGE_PROPERTIES
[type
][expanded
];
105 int tileWidth
= props
.tileWidth
;
106 int tileHeight
= props
.tileHeight
;
107 int imageWidth
= props
.imageWidth
;
108 int numCols
= props
.numCols
;
110 int expandedWidth
= IMAGE_PROPERTIES
[type
][true].tileWidth
;
112 for (int y
= 0; y
< tileHeight
; y
++) {
113 for (int x
= 0; x
< tileWidth
; x
++) {
114 int ny
= row
* tileHeight
+ y
;
115 int nx
= col
* tileWidth
+ x
;
116 tiles
[row
* numCols
+ col
].px
[y
* expandedWidth
+ x
] = image
[(ny
* imageWidth
+ nx
)];
121 static void expand_tiles(ImageType imageType
) {
122 const ImageProps props
= IMAGE_PROPERTIES
[imageType
][true];
123 int numRows
= props
.numRows
;
124 int numCols
= props
.numCols
;
125 int tileWidth
= props
.tileWidth
;
126 int tileHeight
= props
.tileHeight
;
128 // If the image type wraps,
129 // Copy each tile's left edge to the previous tile's right edge
130 // Each tile's height is still tileHeight - 1
132 for (int row
= 0; row
< numRows
; ++row
) {
133 for (int col
= 0; col
< numCols
; ++col
) {
134 int nextCol
= (col
+ 1) % numCols
;
135 for (int y
= 0; y
< (tileHeight
- 1); ++y
) {
136 tiles
[row
* numCols
+ col
].px
[(tileWidth
- 1) + y
* tileWidth
] = tiles
[row
* numCols
+ nextCol
].px
[y
* tileWidth
];
141 // Don't wrap, copy the second to last column instead.
142 for (int row
= 0; row
< numRows
; ++row
) {
143 for (int col
= 0; col
< numCols
- 1; ++col
) {
144 int nextCol
= (col
+ 1) % numCols
;
145 for (int y
= 0; y
< (tileHeight
- 1); ++y
) {
146 tiles
[row
* numCols
+ col
].px
[(tileWidth
- 1) + y
* tileWidth
] = tiles
[row
* numCols
+ nextCol
].px
[y
* tileWidth
];
149 for (int y
= 0; y
< (tileHeight
- 1); ++y
) {
150 tiles
[row
* numCols
+ (numCols
- 1)].px
[(tileWidth
- 1) + y
* tileWidth
] = tiles
[row
* numCols
+ (numCols
- 1)].px
[(tileWidth
- 2) + y
* tileWidth
];
156 // Copy each tile's top edge to the previous tile's bottom edge, EXCEPT for the bottom row, which
157 // just duplicates its second-to-last row
158 for (int row
= 0; row
< numRows
; ++row
) {
159 if (row
< numRows
- 1) {
160 for (int col
= 0; col
< numCols
; ++col
) {
161 int nextRow
= (row
+ 1) % numRows
;
162 for (int x
= 0; x
< tileWidth
; ++x
) {
163 tiles
[row
* numCols
+ col
].px
[x
+ (tileHeight
- 1) * tileWidth
] = tiles
[nextRow
* numCols
+ col
].px
[x
];
167 // For the last row of tiles, duplicate each one's second to last row
169 for (int col
= 0; col
< numCols
; ++col
) {
170 for (int x
= 0; x
< tileWidth
; ++x
) {
171 tiles
[row
* numCols
+ col
].px
[x
+ (tileHeight
- 1) * tileWidth
] = tiles
[row
* numCols
+ col
].px
[x
+ (tileHeight
- 2) * tileWidth
];
178 static void init_tiles(rgba
*image
, bool expanded
) {
179 const ImageProps props
= IMAGE_PROPERTIES
[type
][expanded
];
181 for (int row
= 0; row
< props
.numRows
; row
++) {
182 for (int col
= 0; col
< props
.numCols
; col
++) {
183 split_tile(col
, row
, image
, expanded
);
187 // Expand the tiles to their full size
193 static void assign_tile_positions() {
194 const ImageProps props
= IMAGE_PROPERTIES
[type
][true];
195 const size_t TILE_SIZE
= props
.tileWidth
* props
.tileHeight
* sizeof(rgba
);
197 unsigned int newPos
= 0;
198 for (int i
= 0; i
< props
.numRows
* props
.numCols
; i
++) {
199 if (props
.optimizePositions
) {
200 for (int j
= 0; j
< i
; j
++) {
201 if (!tiles
[j
].useless
&& memcmp(tiles
[j
].px
, tiles
[i
].px
, TILE_SIZE
) == 0) {
202 tiles
[i
].useless
= 1;
209 if (!tiles
[i
].useless
) {
210 tiles
[i
].pos
= newPos
;
216 /* write pngs to disc */
218 const ImageProps props
= IMAGE_PROPERTIES
[type
][true];
219 char buffer
[PATH_MAX
];
221 if (realpath(writeDir
, buffer
) == NULL
) {
222 fprintf(stderr
, "err: Could not find find img dir %s", writeDir
);
230 strcat(buffer
, skyboxName
);
233 strcat(buffer
, "cake");
236 strcat(buffer
, "cake_eu");
243 int dirLength
= strlen(buffer
);
244 char *filename
= buffer
+ dirLength
;
245 for (int i
= 0; i
< props
.numRows
* props
.numCols
; i
++) {
246 if (!tiles
[i
].useless
) {
248 snprintf(filename
, PATH_MAX
, ".%d.rgba16.png", tiles
[i
].pos
);
249 rgba2png(buffer
, tiles
[i
].px
, props
.tileWidth
, props
.tileHeight
);
254 static unsigned int get_index(TextureTile
*t
, unsigned int i
) {
261 static void print_raw_data(FILE *cFile
, TextureTile
*tile
) {
262 ImageProps props
= IMAGE_PROPERTIES
[type
][true];
263 uint8_t *raw
= malloc(props
.tileWidth
* props
.tileHeight
* 2);
264 int size
= rgba2raw(raw
, tile
->px
, props
.tileWidth
, props
.tileHeight
, 16);
265 for (int i
= 0; i
< size
; ++i
) {
266 fprintf(cFile
, "0x%hhX,", raw
[i
]);
271 static void write_skybox_c() { /* write c data to disc */
272 const ImageProps props
= IMAGE_PROPERTIES
[type
][true];
274 char fBuffer
[PATH_MAX
] = "";
277 if (realpath(output
, fBuffer
) == NULL
) {
278 fprintf(stderr
, "err: Could not find find src dir %s", output
);
282 sprintf(fBuffer
, "%s/%s_skybox.c", output
, skyboxName
);
283 cFile
= fopen(fBuffer
, "w"); /* reset file */
288 fprintf(stderr
, "err: Could not open %s\n", fBuffer
);
291 fprintf(cFile
, "#include \"sm64.h\"\n\n#include \"make_const_nonconst.h\"\n\n");
293 for (int i
= 0; i
< props
.numRows
* props
.numCols
; i
++) {
294 if (!tiles
[i
].useless
) {
295 if (storeNamesOnly
) {
298 "ALIGNED8 static const u8 %s_skybox_texture_%05X[] = "
299 "\"textures/skybox_tiles/%s.%d.rgba16\";\n\n",
300 skyboxName
, tiles
[i
].pos
, skyboxName
, tiles
[i
].pos
303 fprintf(cFile
, "ALIGNED8 static const u8 %s_skybox_texture_%05X[] = {\n", skyboxName
, tiles
[i
].pos
);
304 print_raw_data(cFile
, &tiles
[i
]);
305 fputs("};\n\n", cFile
);
310 fprintf(cFile
, "const u8 *const %s_skybox_ptrlist[] = {\n", skyboxName
);
312 for (int row
= 0; row
< 8; row
++) {
313 for (int col
= 0; col
< 10; col
++) {
314 fprintf(cFile
, "%s_skybox_texture_%05X,\n", skyboxName
, get_index(tiles
, row
* 8 + (col
% 8)));
318 fputs("};\n\n", cFile
);
322 static void write_cake_c() {
323 char buffer
[PATH_MAX
] = "";
324 if (realpath(output
, buffer
) == NULL
) {
325 fprintf(stderr
, "err: Could not find find src dir %s", output
);
329 if (type
== CakeEU
) {
330 strcat(buffer
, "/cake_eu.inc.c");
333 strcat(buffer
, "/cake.inc.c");
336 FILE *cFile
= fopen(buffer
, "w");
338 const char *euSuffx
= "";
339 if (type
== CakeEU
) {
343 int numTiles
= TABLE_DIMENSIONS
[type
].cols
* TABLE_DIMENSIONS
[type
].rows
;
344 for (int i
= 0; i
< numTiles
; ++i
) {
345 if (storeNamesOnly
) {
348 "ALIGNED8 static const u8 cake_end_texture_%s%d[] = "
349 "\"textures/skybox_tiles/cake%s.%d.rgba16\";\n\n",
350 euSuffx
, i
, *euSuffx
? "_eu" : "", tiles
[i
].pos
353 fprintf(cFile
, "ALIGNED8 static const u8 cake_end_texture_%s%d[] = {\n", euSuffx
, i
);
354 print_raw_data(cFile
, &tiles
[i
]);
355 fputs("};\n\n", cFile
);
361 // input: the skybox tiles + the table = up to 64 32x32 images (rgba16) + 80 pointers (u32)
362 // some pointers point to duplicate entries
363 void combine_skybox(const char *input
, const char *output
) {
364 enum { W
= 10, H
= 8, W2
= 8 };
366 FILE *file
= fopen(input
, "rb");
367 if (!file
) goto fail
;
368 if (fseek(file
, 0, SEEK_END
)) goto fail
;
370 ssize_t fileSize
= ftell(file
);
371 if (fileSize
< 8*10*4) goto fail
;
374 size_t tableIndex
= fileSize
- 8*10*4;
375 if (tableIndex
% (32*32*2) != 0) goto fail
;
377 // there are at most 64 tiles before the table
379 size_t tileIndex
= 0;
380 for (size_t pos
= 0; pos
< tableIndex
; pos
+= 32*32*2) {
381 uint8_t buf
[32*32*2];
382 if (fread(buf
, sizeof(buf
), 1, file
) != 1) goto fail
;
383 tiles
[tileIndex
] = raw2rgba(buf
, 32, 32, 16);
388 if (fread(table
, sizeof(table
), 1, file
) != 1) goto fail
;
390 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
391 reverse_endian((unsigned char *) table
, W
*H
*4);
394 uint32_t base
= table
[0];
395 for (int i
= 0; i
< W
*H
; i
++) {
399 // Convert the 256x256 skybox to an editable 248x248 image by skipping the duplicated rows and columns
400 // every 32nd column is a repeat of the 33rd, and
401 // every 32nd row is a repeat of the 33rd, EXCEPT for the last row, but that only matters when
402 // expanding the tiles
403 rgba combined
[31*H
* 31*W2
];
404 for (int i
= 0; i
< H
; i
++) {
405 for (int j
= 0; j
< W2
; j
++) {
406 int index
= table
[i
*W
+j
] / 0x800;
407 for (int y
= 0; y
< 31; y
++) {
408 for (int x
= 0; x
< 31; x
++) {
409 combined
[(i
*31 + y
) * (31*W2
) + (j
*31 + x
)] = tiles
[index
][y
*32 + x
];
414 if (!rgba2png(output
, combined
, 31*W2
, 31*H
)) {
415 fprintf(stderr
, "Failed to write skybox image.\n");
420 fprintf(stderr
, "Failed to read skybox binary.\n");
424 void combine_cakeimg(const char *input
, const char *output
, bool eu
) {
425 int W
, H
, SMALLH
, SMALLW
;
438 FILE *file
= fopen(input
, "rb");
439 if (!file
) goto fail
;
443 combined
= malloc((SMALLH
-1)*H
* (SMALLW
-1)*W
* sizeof(rgba
));
444 for (int i
= 0; i
< H
; i
++) {
445 for (int j
= 0; j
< W
; j
++) {
447 uint8_t buf
[SMALLH
* SMALLW
* 2];
448 if (fread(buf
, sizeof(buf
), 1, file
) != 1) goto fail
;
449 rgba
*tile
= raw2rgba(buf
, SMALLH
, SMALLW
, 16);
451 //Only write the unique parts of each tile
452 for (int y
= 0; y
< SMALLH
- 1; y
++) {
453 for (int x
= 0; x
< SMALLW
- 1; x
++) {
454 combined
[(i
*(SMALLH
-1) + y
) * (SMALLW
-1)*W
+ (j
*(SMALLW
-1) + x
)] = tile
[y
*(SMALLW
) + x
];
459 if (!rgba2png(output
, combined
, (SMALLW
-1)*W
, (SMALLH
-1)*H
)) {
460 fprintf(stderr
, "Failed to write cake image.\n");
465 combined
= malloc(SMALLH
*H
* SMALLW
*W
* sizeof(rgba
));
466 for (int i
= 0; i
< H
; i
++) {
467 for (int j
= 0; j
< W
; j
++) {
468 uint8_t buf
[SMALLH
* SMALLW
* 2];
469 if (fread(buf
, sizeof(buf
), 1, file
) != 1) goto fail
;
470 rgba
*tile
= raw2rgba(buf
, SMALLH
, SMALLW
, 16);
471 for (int y
= 0; y
< SMALLH
; y
++) {
472 for (int x
= 0; x
< SMALLW
; x
++) {
473 combined
[(i
*SMALLH
+ y
) * SMALLW
*W
+ (j
*SMALLW
+ x
)] = tile
[y
*SMALLW
+ x
];
478 if (!rgba2png(output
, combined
, SMALLW
*W
, SMALLH
*H
)) {
479 fprintf(stderr
, "Failed to write cake image.\n");
485 fprintf(stderr
, "Failed to read cake binary.\n");
489 // Modified from n64split
490 static void usage() {
492 "Usage: %s --type sky|cake|cake_eu {--combine INPUT OUTPUT | --split INPUT OUTPUT}\n"
494 "Optional arguments:\n"
495 " --write-tiles OUTDIR Also create the individual tiles' PNG files\n"
496 " --store-names Store texture file names instead of actual data\n", programName
);
499 // Modified from n64split
500 static int parse_arguments(int argc
, char *argv
[]) {
501 for (int i
= 1; i
< argc
; ++i
) {
502 if (strcmp(argv
[i
], "--combine") == 0) {
503 if (++i
>= argc
|| mode
!= InvalidMode
) {
516 if (strcmp(argv
[i
], "--split") == 0) {
517 if (++i
>= argc
|| mode
!= InvalidMode
) {
530 if (strcmp(argv
[i
], "--type") == 0) {
531 if (++i
>= argc
|| type
!= InvalidType
) {
535 if (strcmp(argv
[i
], "sky") == 0) {
537 } else if(strcmp(argv
[i
], "cake-eu") == 0) {
539 } else if(strcmp(argv
[i
], "cake") == 0) {
544 if (strcmp(argv
[i
], "--write-tiles") == 0) {
545 if (++i
>= argc
|| argv
[i
][0] == '-') {
553 if (strcmp(argv
[i
], "--store-names") == 0) {
554 storeNamesOnly
= true;
564 bool imageMatchesDimensions(int width
, int height
) {
565 bool matchesDimensions
= false;
566 for (int expand
= false; expand
<= true; ++expand
) {
567 if (width
== IMAGE_PROPERTIES
[type
][expand
].imageWidth
&&
568 height
== IMAGE_PROPERTIES
[type
][expand
].imageHeight
) {
569 matchesDimensions
= true;
574 if (!matchesDimensions
) {
575 if (type
!= CakeEU
) {
576 fprintf(stderr
, "err: That type of image must be either %d x %d or %d x %d. Yours is %d x %d.\n",
577 IMAGE_PROPERTIES
[type
][false].imageWidth
, IMAGE_PROPERTIES
[type
][false].imageHeight
,
578 IMAGE_PROPERTIES
[type
][true].imageWidth
, IMAGE_PROPERTIES
[type
][true].imageHeight
,
582 fprintf(stderr
, "err: That type of image must be %d x %d. Yours is %d x %d.\n",
583 IMAGE_PROPERTIES
[type
][true].imageWidth
, IMAGE_PROPERTIES
[type
][true].imageHeight
,
590 if (type
== CakeEU
) {
597 int main(int argc
, char *argv
[]) {
598 if (parse_arguments(argc
, argv
) == false) {
602 if (type
== Skybox
&& mode
== Split
) {
603 // Extract the skybox's name (ie: bbh, bidw) from the input png
604 char *base
= basename(input
);
605 strcpy(skyboxName
, base
);
606 char *extension
= strrchr(skyboxName
, '.');
607 if (extension
) *extension
= '\0';
614 combine_skybox(input
, output
);
617 combine_cakeimg(input
, output
, 0);
620 combine_cakeimg(input
, output
, 1);
631 rgba
*image
= png2rgba(input
, &width
, &height
);
633 fprintf(stderr
, "err: Could not load image %s\n", argv
[1]);
637 if (!imageMatchesDimensions(width
, height
)) {
643 init_tiles(image
, expanded
);
646 assign_tile_positions();
651 assign_tile_positions();
655 fprintf(stderr
, "err: Unknown image type.\n");