pokegold-spaceworld/tools/gfx.c
Tauwasser 40c8b5f0a3 tools/gfx: add option to trim trailing tiles of solid color
This helps when the last tile is completely white and the shape of the
png has additional tiles. In that case, the last tile is cut as well.

Signed-off-by: Tauwasser <Tauwasser@tauwasser.eu>
2018-06-03 22:16:21 +02:00

339 lines
8.0 KiB
C
Executable File

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <getopt.h>
#include <string.h>
#include <stdint.h>
#include "common.h"
static void usage(void) {
fprintf(stderr, "Usage: gfx [[--trim-whitespace] | [--trim-trailing]] [--remove-whitespace] [--interleave] "
"[--remove-duplicates [--keep-whitespace]] [--remove-xflip] [--remove-yflip] [--png filename] "
"[-d depth] [-h] [-o outfile] infile\n");
}
static void error(char *message) {
fputs(message, stderr);
fputs("\n", stderr);
}
struct Options {
int trim_whitespace;
int trim_trailing;
int remove_whitespace;
int help;
char *outfile;
int depth;
int interleave;
int remove_duplicates;
int keep_whitespace;
int remove_xflip;
int remove_yflip;
char *png_file;
};
struct Options Options = {
.depth = 2,
};
void get_args(int argc, char *argv[]) {
struct option long_options[] = {
{"remove-whitespace", no_argument, &Options.remove_whitespace, 1},
{"trim-whitespace", no_argument, &Options.trim_whitespace, 1},
{"trim-trailing", no_argument, &Options.trim_trailing, 1},
{"interleave", no_argument, &Options.interleave, 1},
{"remove-duplicates", no_argument, &Options.remove_duplicates, 1},
{"keep-whitespace", no_argument, &Options.keep_whitespace, 1},
{"remove-xflip", no_argument, &Options.remove_xflip, 1},
{"remove-yflip", no_argument, &Options.remove_yflip, 1},
{"png", required_argument, 0, 'p'},
{"depth", required_argument, 0, 'd'},
{"help", no_argument, 0, 'h'},
{0}
};
for (int opt = 0; opt != -1;) {
switch (opt = getopt_long(argc, argv, "ho:d:p:", long_options)) {
case 'h':
Options.help = true;
break;
case 'o':
Options.outfile = optarg;
break;
case 'd':
Options.depth = strtoul(optarg, NULL, 0);
break;
case 'p':
Options.png_file = optarg;
break;
case 0:
case -1:
break;
default:
usage();
exit(1);
break;
}
}
}
struct Graphic {
int size;
uint8_t *data;
};
bool is_whitespace(uint8_t *tile, int tile_size) {
uint8_t WHITESPACE = 0;
for (int i = 0; i < tile_size; i++) {
if (tile[i] != WHITESPACE) {
return false;
}
}
return true;
}
/**
* is_solid_color
* Check if a tile is solid color, i.e. each
* bitplane is filled with the same value.
* Different bitplanes can have different values.
*
* Returns true if solid color, else false.
*/
bool is_solid_color(uint8_t *tile, int depth, int tile_size) {
const uint8_t ONES = 0xff;
const uint8_t ZEROS = 0x00;
for (int i = 0; i < depth; i++) {
if (tile[i] != ONES && tile[i] != ZEROS) {
return false;
}
}
return !memcmp(&tile[0], &tile[depth], tile_size - depth);
}
void trim_whitespace(struct Graphic *graphic) {
int tile_size = Options.depth * 8;
for (int i = graphic->size - tile_size; i > 0; i -= tile_size) {
if (is_whitespace(&graphic->data[i], tile_size)) {
graphic->size = i;
} else {
break;
}
}
}
void trim_trailing(struct Graphic *graphic) {
int tile_size = Options.depth * 8;
int last_tile = graphic->size - tile_size;
if (!is_solid_color(&graphic->data[last_tile], Options.depth, tile_size)) {
return;
}
for (int i = graphic->size - 2 * tile_size; i > 0; i -= tile_size) {
if (!memcmp(&graphic->data[i], &graphic->data[last_tile], tile_size)) {
graphic->size = i;
} else {
break;
}
}
}
void remove_whitespace(struct Graphic *graphic) {
int tile_size = Options.depth * 8;
if (Options.interleave) tile_size *= 2;
int i = 0;
for (int j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
while (is_whitespace(&graphic->data[j], tile_size)) {
j += tile_size;
}
if (j >= graphic->size) {
break;
}
if (j > i) {
memcpy(&graphic->data[i], &graphic->data[j], tile_size);
}
}
graphic->size = i;
}
bool tile_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles) {
for (int i = 0; i < num_tiles; i++) {
bool match = true;
for (int j = 0; j < tile_size; j++) {
if (tile[j] != tiles[i * tile_size + j]) {
match = false;
}
}
if (match) {
return true;
}
}
return false;
}
void remove_duplicates(struct Graphic *graphic) {
int tile_size = Options.depth * 8;
if (Options.interleave) tile_size *= 2;
int num_tiles = 0;
for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
while (tile_exists(&graphic->data[j], graphic->data, tile_size, num_tiles)) {
if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) {
break;
}
j += tile_size;
}
if (j >= graphic->size) {
break;
}
if (j > i) {
memcpy(&graphic->data[i], &graphic->data[j], tile_size);
}
num_tiles++;
}
graphic->size = num_tiles * tile_size;
}
bool flip_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles, bool xflip, bool yflip) {
uint8_t *flip = calloc(tile_size, 1);
int half_size = tile_size / 2;
for (int i = 0; i < tile_size; i++) {
int byte = i;
if (yflip) {
byte = tile_size - 1 - (i ^ 1);
if (Options.interleave && i < half_size) {
byte = half_size - 1 - (i ^ 1);
}
}
if (xflip) {
for (int bit = 0; bit < 8; bit++) {
flip[byte] |= ((tile[i] >> bit) & 1) << (7 - bit);
}
} else {
flip[byte] = tile[i];
}
}
if (tile_exists(flip, tiles, tile_size, num_tiles)) {
return true;
}
return false;
}
void remove_flip(struct Graphic *graphic, bool xflip, bool yflip) {
int tile_size = Options.depth * 8;
if (Options.interleave) tile_size *= 2;
int num_tiles = 0;
for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
while (flip_exists(&graphic->data[j], graphic->data, tile_size, num_tiles, xflip, yflip)) {
if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) {
break;
}
j += tile_size;
}
if (j >= graphic->size) {
break;
}
if (j > i) {
memcpy(&graphic->data[i], &graphic->data[j], tile_size);
}
num_tiles++;
}
graphic->size = num_tiles * tile_size;
}
void interleave(struct Graphic *graphic, int width) {
int tile_size = Options.depth * 8;
int width_tiles = width / 8;
int num_tiles = graphic->size / tile_size;
uint8_t *interleaved = malloc(graphic->size);
for (int i = 0; i < num_tiles; i++) {
int tile = i * 2;
int row = i / width_tiles;
tile -= width_tiles * row;
if (row % 2) {
tile -= width_tiles;
tile += 1;
}
memcpy(&interleaved[tile * tile_size], &graphic->data[i * tile_size], tile_size);
}
graphic->size = num_tiles * tile_size;
memcpy(graphic->data, interleaved, graphic->size);
free(interleaved);
}
int png_get_width(char *filename) {
FILE *f = fopen_verbose(filename, "rb");
if (!f) {
exit(1);
}
const int OFFSET_WIDTH = 16;
uint8_t bytes[4];
fseek(f, OFFSET_WIDTH, SEEK_SET);
size_t size = 4;
size_t result = fread(bytes, 1, size, f);
fclose(f);
if (result != size) {
fprintf(stderr, "Could not read file at offset 0x%x: \"%s\"\n", OFFSET_WIDTH, filename);
exit(1);
}
int width = 0;
for (int i = 0; i < 4; i++) {
width |= bytes[i] << (8 * (3 - i));
}
return width;
}
int main(int argc, char *argv[]) {
get_args(argc, argv);
argc -= optind;
argv += optind;
if (Options.help) {
usage();
return 0;
}
if (argc < 1) {
usage();
exit(1);
}
char *infile = argv[0];
struct Graphic graphic;
graphic.data = read_u8(infile, &graphic.size);
if (Options.trim_whitespace) {
trim_whitespace(&graphic);
} else if (Options.trim_trailing) {
trim_trailing(&graphic);
}
if (Options.interleave) {
if (!Options.png_file) {
error("interleave: need --png to infer dimensions");
usage();
exit(1);
}
int width = png_get_width(Options.png_file);
interleave(&graphic, width);
}
if (Options.remove_duplicates) {
remove_duplicates(&graphic);
}
if (Options.remove_xflip) {
remove_flip(&graphic, true, false);
}
if (Options.remove_yflip) {
remove_flip(&graphic, false, true);
}
if (Options.remove_xflip && Options.remove_yflip) {
remove_flip(&graphic, true, true);
}
if (Options.remove_whitespace) {
remove_whitespace(&graphic);
}
if (Options.outfile) {
write_u8(Options.outfile, graphic.data, graphic.size);
}
free(graphic.data);
return 0;
}