2021-09-21 21:37:43 +00:00
|
|
|
#define PROGRAM_NAME "pokemon_animation_graphics"
|
|
|
|
#define USAGE_OPTS "[-h|--help] [-o|--output front.animated.2bpp] [-t|--tilemap front.animated.tilemap] [--girafarig] front.2bpp front.dimensions"
|
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
#include "common.h"
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2017-05-29 03:22:10 +00:00
|
|
|
struct Options {
|
2021-09-02 21:15:50 +00:00
|
|
|
const char *out_filename;
|
|
|
|
const char *map_filename;
|
|
|
|
bool girafarig;
|
2017-05-29 03:22:10 +00:00
|
|
|
};
|
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
void parse_args(int argc, char *argv[], struct Options *options) {
|
|
|
|
struct option long_options[] = {
|
|
|
|
{"output", required_argument, 0, 'o'},
|
|
|
|
{"tilemap", required_argument, 0, 't'},
|
|
|
|
{"girafarig", no_argument, 0, 'g'},
|
|
|
|
{"help", no_argument, 0, 'h'},
|
|
|
|
{0}
|
|
|
|
};
|
|
|
|
for (int opt; (opt = getopt_long(argc, argv, "o:t:h", long_options)) != -1;) {
|
|
|
|
switch (opt) {
|
|
|
|
case 'o':
|
|
|
|
options->out_filename = optarg;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
options->map_filename = optarg;
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
options->girafarig = true;
|
|
|
|
break;
|
|
|
|
case 'h':
|
2021-09-21 21:37:43 +00:00
|
|
|
usage_exit(0);
|
2021-09-02 21:15:50 +00:00
|
|
|
break;
|
|
|
|
default:
|
2021-09-21 21:37:43 +00:00
|
|
|
usage_exit(1);
|
2021-09-02 21:15:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
#define TILE_SIZE 16
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
void transpose_tiles(uint8_t *tiles, int width, int size) {
|
|
|
|
uint8_t *new_tiles = malloc_verbose(size);
|
|
|
|
for (int i = 0; i < size; i++) {
|
|
|
|
int j = i / TILE_SIZE * width * TILE_SIZE;
|
|
|
|
j = (j / size) * TILE_SIZE + j % size + i % TILE_SIZE;
|
2016-08-25 01:56:07 +00:00
|
|
|
new_tiles[j] = tiles[i];
|
|
|
|
}
|
|
|
|
memcpy(tiles, new_tiles, size);
|
|
|
|
free(new_tiles);
|
|
|
|
}
|
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
int get_tile_index(const uint8_t *tile, const uint8_t *tiles, int num_tiles, int preferred_tile_id) {
|
2017-05-29 03:22:10 +00:00
|
|
|
if (preferred_tile_id >= 0 && preferred_tile_id < num_tiles) {
|
2021-09-02 21:15:50 +00:00
|
|
|
if (!memcmp(tile, &tiles[preferred_tile_id * TILE_SIZE], TILE_SIZE)) {
|
2017-05-29 03:22:10 +00:00
|
|
|
return preferred_tile_id;
|
2016-08-25 01:56:07 +00:00
|
|
|
}
|
2017-05-29 03:22:10 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
for (int i = 0; i < num_tiles; i++) {
|
|
|
|
if (!memcmp(tile, &tiles[i * TILE_SIZE], TILE_SIZE)) {
|
2016-08-25 01:56:07 +00:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2021-09-02 22:37:36 +00:00
|
|
|
uint8_t *read_tiles(const char *filename, int width, long *tiles_size) {
|
|
|
|
int frame_size = width * width * TILE_SIZE;
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
uint8_t *tiles = read_u8(filename, tiles_size);
|
|
|
|
if (!*tiles_size) {
|
|
|
|
error_exit("%s: empty file\n", filename);
|
|
|
|
} else if (*tiles_size % TILE_SIZE) {
|
|
|
|
error_exit("%s: not divisible into 8x8-px 2bpp tiles\n", filename);
|
|
|
|
} else if (*tiles_size % frame_size) {
|
2021-09-02 22:37:36 +00:00
|
|
|
error_exit("%s: not divisible into %dx%d-tile frames\n", filename, width, width);
|
2017-05-28 05:11:32 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
|
|
|
|
int num_frames = *tiles_size / frame_size;
|
|
|
|
for (int i = 0; i < num_frames; i++) {
|
|
|
|
transpose_tiles(&tiles[i * frame_size], width, frame_size);
|
2017-06-30 04:45:30 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
|
|
|
|
return tiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
void write_graphics(const char *filename, const uint8_t *tiles, long tiles_size, int num_tiles_per_frame, bool girafarig) {
|
|
|
|
int max_size = tiles_size;
|
|
|
|
int max_num_tiles = max_size / TILE_SIZE;
|
|
|
|
if (girafarig) {
|
|
|
|
// Ensure space for a duplicate of tile 0 at the end
|
|
|
|
max_size += TILE_SIZE;
|
|
|
|
}
|
|
|
|
uint8_t *data = malloc_verbose(max_size);
|
|
|
|
|
|
|
|
int num_tiles = 0;
|
|
|
|
#define DATA_APPEND_TILES(tile, length) do { \
|
|
|
|
memcpy(&data[num_tiles * TILE_SIZE], &tiles[(tile) * TILE_SIZE], (length) * TILE_SIZE); \
|
|
|
|
num_tiles += (length); \
|
|
|
|
} while (0)
|
|
|
|
// Copy the first frame directly
|
|
|
|
DATA_APPEND_TILES(0, num_tiles_per_frame);
|
|
|
|
// Skip redundant tiles in the animated frames
|
|
|
|
for (int i = num_tiles_per_frame; i < max_num_tiles; i++) {
|
|
|
|
int index = get_tile_index(&tiles[i * TILE_SIZE], data, num_tiles, i % num_tiles_per_frame);
|
|
|
|
if (index == -1) {
|
|
|
|
DATA_APPEND_TILES(i, 1);
|
|
|
|
}
|
2017-06-30 04:45:30 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
if (girafarig) {
|
|
|
|
// Add a duplicate of tile 0 to the end
|
|
|
|
DATA_APPEND_TILES(0, 1);
|
2017-12-28 06:25:25 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
#undef DATA_APPEND_TILES
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
write_u8(filename, data, num_tiles * TILE_SIZE);
|
|
|
|
free(data);
|
|
|
|
}
|
2017-05-28 05:11:32 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
void write_tilemap(const char *filename, const uint8_t *tiles, long tiles_size, int num_tiles_per_frame, bool girafarig) {
|
|
|
|
int size = tiles_size / TILE_SIZE;
|
|
|
|
uint8_t *data = malloc_verbose(size);
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
int num_tiles = num_tiles_per_frame;
|
|
|
|
// Copy the first frame directly
|
|
|
|
for (int i = 0; i < num_tiles_per_frame; i++) {
|
|
|
|
data[i] = i;
|
2016-08-25 01:56:07 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
// Skip redundant tiles in the animated frames
|
|
|
|
for (int i = num_tiles_per_frame; i < size; i++) {
|
|
|
|
int index = get_tile_index(&tiles[i * TILE_SIZE], tiles, i, i % num_tiles_per_frame);
|
|
|
|
int tile;
|
|
|
|
if (girafarig && index == 0) {
|
2017-05-29 03:22:10 +00:00
|
|
|
tile = num_tiles;
|
|
|
|
} else if (index == -1) {
|
|
|
|
tile = num_tiles++;
|
2016-08-25 01:56:07 +00:00
|
|
|
} else {
|
2021-09-02 21:15:50 +00:00
|
|
|
tile = data[index];
|
2016-08-25 01:56:07 +00:00
|
|
|
}
|
2021-09-02 21:15:50 +00:00
|
|
|
data[i] = tile;
|
2017-05-29 03:22:10 +00:00
|
|
|
}
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
write_u8(filename, data, size);
|
|
|
|
free(data);
|
2016-08-25 01:56:07 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
int main(int argc, char *argv[]) {
|
|
|
|
struct Options options = {0};
|
|
|
|
parse_args(argc, argv, &options);
|
|
|
|
|
2016-08-25 01:56:07 +00:00
|
|
|
argc -= optind;
|
|
|
|
argv += optind;
|
|
|
|
if (argc < 2) {
|
2021-09-21 21:37:43 +00:00
|
|
|
usage_exit(1);
|
2017-12-28 06:25:25 +00:00
|
|
|
}
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 22:37:36 +00:00
|
|
|
int width;
|
|
|
|
read_dimensions(argv[1], &width);
|
2021-09-02 21:15:50 +00:00
|
|
|
long tiles_size;
|
2021-09-02 22:37:36 +00:00
|
|
|
uint8_t *tiles = read_tiles(argv[0], width, &tiles_size);
|
2016-08-25 01:56:07 +00:00
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
if (options.out_filename) {
|
2021-09-02 22:37:36 +00:00
|
|
|
write_graphics(options.out_filename, tiles, tiles_size, width * width, options.girafarig);
|
2021-09-02 21:15:50 +00:00
|
|
|
}
|
|
|
|
if (options.map_filename) {
|
2021-09-02 22:37:36 +00:00
|
|
|
write_tilemap(options.map_filename, tiles, tiles_size, width * width, options.girafarig);
|
2016-08-25 01:56:07 +00:00
|
|
|
}
|
|
|
|
|
2021-09-02 21:15:50 +00:00
|
|
|
free(tiles);
|
2016-08-25 01:56:07 +00:00
|
|
|
return 0;
|
|
|
|
}
|