diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/.gitignore | 2 | ||||
-rw-r--r-- | tools/Makefile | 16 | ||||
-rw-r--r-- | tools/common.h | 40 | ||||
-rw-r--r-- | tools/gfx.c | 345 | ||||
-rw-r--r-- | tools/scan_includes.c | 197 |
5 files changed, 600 insertions, 0 deletions
diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000..93050b9 --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,2 @@ +scan_includes +gfx diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..c235fee --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,16 @@ +.PHONY: all clean + +CC := gcc +CFLAGS := -O3 -std=c99 -Wall -Wextra -pedantic + +tools := scan_includes gfx + +all: $(tools) + @: + +clean: + rm -f $(tools) + +gfx: common.h +%: %.c + $(CC) $(CFLAGS) -o $@ $< diff --git a/tools/common.h b/tools/common.h new file mode 100644 index 0000000..4da0b2e --- /dev/null +++ b/tools/common.h @@ -0,0 +1,40 @@ +#ifndef GUARD_COMMON_H +#define GUARD_COMMON_H + +int __getopt_long_i__; +#define getopt_long(c, v, s, l) getopt_long(c, v, s, l, &__getopt_long_i__) + +FILE *fopen_verbose(char *filename, char *mode) { + FILE *f = fopen(filename, mode); + if (!f) { + fprintf(stderr, "Could not open file: \"%s\"\n", filename); + } + return f; +} + +uint8_t *read_u8(char *filename, int *size) { + FILE *f = fopen_verbose(filename, "rb"); + if (!f) { + exit(1); + } + fseek(f, 0, SEEK_END); + *size = ftell(f); + rewind(f); + uint8_t *data = malloc(*size); + if (*size != (int)fread(data, 1, *size, f)) { + fprintf(stderr, "Could not read file: \"%s\"\n", filename); + exit(1); + } + fclose(f); + return data; +} + +void write_u8(char *filename, uint8_t *data, int size) { + FILE *f = fopen_verbose(filename, "wb"); + if (f) { + fwrite(data, 1, size, f); + fclose(f); + } +} + +#endif // GUARD_COMMON_H diff --git a/tools/gfx.c b/tools/gfx.c new file mode 100644 index 0000000..7d6dd0e --- /dev/null +++ b/tools/gfx.c @@ -0,0 +1,345 @@ +#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] [--remove-whitespace] [--interleave] [--remove-duplicates [--keep-whitespace]] [--remove-xflip] [--remove-yflip] [--preserve indexes] [--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 remove_whitespace; + int help; + char *outfile; + int depth; + int interleave; + int remove_duplicates; + int keep_whitespace; + int remove_xflip; + int remove_yflip; + int *preserved; + int num_preserved; + 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}, + {"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}, + {"preserve", required_argument, 0, 'r'}, + {"png", required_argument, 0, 'p'}, + {"depth", required_argument, 0, 'd'}, + {"help", no_argument, 0, 'h'}, + {0} + }; + char *token; + 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 'r': + token = strtok(optarg, ","); + while (token) { + Options.num_preserved++; + Options.preserved = realloc(Options.preserved, Options.num_preserved * sizeof(int)); + Options.preserved[Options.num_preserved-1] = strtoul(token, NULL, 0); + token = strtok(NULL, ","); + } + break; + case 'p': + Options.png_file = optarg; + break; + case 0: + case -1: + break; + default: + usage(); + exit(1); + break; + } + } +} + +bool is_preserved(int index) { + for (int i = 0; i < Options.num_preserved; i++) { + if (Options.preserved[i] == index) { + return true; + } + } + return false; +} + +void shift_preserved(int removed_index) { + for (int i = 0; i < Options.num_preserved; i++) { + if (Options.preserved[i] >= removed_index) { + Options.preserved[i]--; + } + } +} + +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; +} + +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) && !is_preserved(i / 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; + + // Make sure we have a whole number of tiles, round down if required + graphic->size &= ~(tile_size - 1); + + int i = 0; + for (int j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (j < graphic->size && is_whitespace(&graphic->data[j], tile_size) && !is_preserved(j / tile_size - d)) { + shift_preserved(j / tile_size - d); + d++; + 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; + + // Make sure we have a whole number of tiles, round down if required + graphic->size &= ~(tile_size - 1); + + for (int i = 0, j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (j < graphic->size && tile_exists(&graphic->data[j], graphic->data, tile_size, num_tiles)) { + if ((Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) || is_preserved(j / tile_size - d)) { + break; + } + shift_preserved(j / tile_size - d); + d++; + 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[tile_size]; + memset(flip, 0, sizeof(flip)); + 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; + + // Make sure we have a whole number of tiles, round down if required + graphic->size &= ~(tile_size - 1); + + for (int i = 0, j = 0, d = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) { + while (j < graphic->size && flip_exists(&graphic->data[j], graphic->data, tile_size, num_tiles, xflip, yflip)) { + if ((Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) || is_preserved(j / tile_size - d)) { + break; + } + shift_preserved(j / tile_size - d); + d++; + 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); + } + 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; +} diff --git a/tools/scan_includes.c b/tools/scan_includes.c new file mode 100644 index 0000000..0a6230b --- /dev/null +++ b/tools/scan_includes.c @@ -0,0 +1,197 @@ +#define _DEFAULT_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <getopt.h> +#include <limits.h> + +void usage(void) { + printf("Usage: scan_includes [-h] [-s] [-i path] filename\n" + "-h, --help\n" + " Print usage and exit\n" + "-s, --strict\n" + " Fail if a file cannot be read\n" + "-i, --include\n" + " Add an include path\n"); +} + +struct Options { + bool help; + bool strict; + char **include_paths; + int include_paths_len; +}; + +struct Options Options = {0}; + +void *xmalloc(const size_t size) +{ + void *ptr = malloc(size); + if (!ptr) { + perror("malloc"); + exit(1); + } + return ptr; +} + +void options_add_file(char *fname) +{ + char *filename = xmalloc(strlen(fname) + 1); + strcpy(filename, fname); + if (!(Options.include_paths = realloc(Options.include_paths, + sizeof(Options.include_paths[0]) * ++Options.include_paths_len))) { + perror("realloc"); + exit(1); + } + Options.include_paths[Options.include_paths_len - 1] = filename; +} + +void scan_file(char *filename) { + FILE *f = fopen(filename, "r"); + + if (!f) { + if (Options.strict) { + fprintf(stderr, "Could not open file: '%s'\n", filename); + exit(1); + } else { + return; + } + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + char *buffer = xmalloc(size + 1); + char *orig = buffer; + size = fread(buffer, 1, size, f); + buffer[size] = '\0'; + fclose(f); + + for (; buffer && (buffer - orig < size); buffer++) { + bool is_include = false; + bool is_incbin = false; + switch (*buffer) { + case ';': + buffer = strchr(buffer, '\n'); + if (!buffer) { + fprintf(stderr, "%s: no newline at end of file\n", filename); + break; + } + break; + + case '"': + buffer++; + buffer = strchr(buffer, '"'); + if (!buffer) { + fprintf(stderr, "%s: unterminated string\n", filename); + break; + } + buffer++; + break; + + case 'i': + case 'I': + if ((strncmp(buffer, "INCBIN", 6) == 0) || (strncmp(buffer, "incbin", 6) == 0)) { + is_incbin = true; + } else if ((strncmp(buffer, "INCLUDE", 7) == 0) || (strncmp(buffer, "include", 7) == 0)) { + is_include = true; + } + if (is_incbin || is_include) { + buffer = strchr(buffer, '"'); + if (!buffer) { + break; + } + buffer++; + int length = strcspn(buffer, "\""); + char *include = xmalloc(length + 1); + strncpy(include, buffer, length); + include[length] = '\0'; + + f = fopen(include, "r"); + char *path = NULL; + if (!f) { + for (char **include_path = Options.include_paths; + include_path < Options.include_paths + Options.include_paths_len; + include_path++) { + size_t len = strlen(*include_path) + strlen(include) + 1; + path = xmalloc(len); + snprintf(path, len, "%s%s", *include_path, include); + f = fopen(path, "r"); + if (f) break; + free(path); + path = NULL; + } + if (!path && is_incbin && Options.include_paths_len == 1) { + char **include_path = Options.include_paths; + size_t len = strlen(*include_path) + strlen(include) + 1; + path = xmalloc(len); + snprintf(path, len, "%s%s", *include_path, include); + } + } + if (f) fclose(f); + + if (path) { + free(include); + include = path; + } + printf("%s ", include); + if (is_include) { + scan_file(include); + } + free(include); + buffer = strchr(buffer, '"'); + } + break; + + } + if (!buffer) { + break; + } + + } + + free(orig); +} + +int main(int argc, char* argv[]) { + int i = 0; + struct option long_options[] = { + {"strict", no_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {"include", required_argument, 0, 'i'}, + {0} + }; + int opt = -1; + while ((opt = getopt_long(argc, argv, "shi:", long_options, &i)) != -1) { + switch (opt) { + case 's': + Options.strict = true; + break; + case 'h': + Options.help = true; + break; + case 'i': + options_add_file(optarg); + break; + default: + usage(); + exit(1); + break; + } + } + argc -= optind; + argv += optind; + if (Options.help) { + usage(); + return 0; + } + if (argc < 1) { + usage(); + exit(1); + } + scan_file(argv[0]); + return 0; +} |