diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Makefile | 18 | ||||
-rw-r--r-- | tools/common.h | 37 | ||||
-rw-r--r-- | tools/gfx.c | 291 | ||||
-rw-r--r-- | tools/pcm.c | 255 | ||||
-rw-r--r-- | tools/scan_includes.c | 132 |
5 files changed, 733 insertions, 0 deletions
diff --git a/tools/Makefile b/tools/Makefile new file mode 100644 index 0000000..e5df0a9 --- /dev/null +++ b/tools/Makefile @@ -0,0 +1,18 @@ +.PHONY: all clean + +CC := gcc +CFLAGS := -std=c99 -Wall -Wextra + +tools := \ + gfx \ + pcm \ + scan_includes + +all: $(tools) + @: + +clean: + rm -f $(tools) + +%: %.c + $(CC) $(CFLAGS) -o $@ $< diff --git a/tools/common.h b/tools/common.h new file mode 100644 index 0000000..bc877cc --- /dev/null +++ b/tools/common.h @@ -0,0 +1,37 @@ +#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); + fread(data, 1, *size, f); + 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..3e5624e --- /dev/null +++ b/tools/gfx.c @@ -0,0 +1,291 @@ +#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] [--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; + 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}, + {"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; +} + +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 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); + fread(bytes, 1, 4, f); + fclose(f); + + 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/pcm.c b/tools/pcm.c new file mode 100644 index 0000000..b10ef54 --- /dev/null +++ b/tools/pcm.c @@ -0,0 +1,255 @@ +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <string.h> +#include <stdint.h> + +#include "common.h" + +static void usage(void) { + fprintf(stderr, "Usage: pcm [-h] [-s sample_rate] [-o outfile] infile\n"); +} + +struct Options { + char *outfile; + int sample_rate; + int help; +}; + +struct Options Options = { + .sample_rate = 22050 +}; + +struct Riff { + char tag[5]; + uint32_t size; + char wave_tag[5]; + uint8_t *data; +}; + +struct Chunk { + char tag[5]; + uint32_t size; + uint8_t *data; +}; + +struct Wav { + uint16_t format; + uint16_t num_channels; + uint32_t sample_rate; + uint32_t data_rate; + uint16_t sample_width; + uint16_t bits_per_sample; + + // extensions + uint16_t extension_size; + uint16_t valid_bits_per_sample; + uint32_t channel_mask; + + // results + int samples_size; + uint8_t *samples; +}; + +struct Pcm { + int size; + uint8_t *data; +}; + +#define READ_U16(ptr, i) \ + ptr[i] << 0 \ + | ptr[i+1] << 8 + +#define READ_U32(ptr, i) \ + ptr[i] << 0 \ + | ptr[i+1] << 8 \ + | ptr[i+2] << 16 \ + | ptr[i+3] << 24 + +enum { + WAVE_FORMAT_PCM = 1, +}; + +void read_wav(char *filename, struct Wav *wav) { + int size = 0; + uint8_t *data = read_u8(filename, &size); + + struct Riff riff = {0}; + int pos = 0; + memcpy(riff.tag, &data[pos], 4); pos += 4; + riff.size = READ_U32(data, pos); pos += 4; + int riff_end = pos + riff.size; + memcpy(riff.wave_tag, &data[pos], 4); pos += 4; + + if (strcmp(riff.tag, "RIFF")) { + fprintf(stderr, "'%s': unknown tag '%s' at 0x%x (expected 'RIFF')\n", + filename, riff.tag, 4); + exit(1); + } + + if (strcmp(riff.wave_tag, "WAVE")) { + fprintf(stderr, "'%s': unknown tag '%s' at 0x%x (expected 'WAVE')\n", + filename, riff.wave_tag, 8); + exit(1); + } + + while (pos < riff_end) { + struct Chunk chunk = {0}; + memcpy(chunk.tag, &data[pos], 4); pos += 4; + chunk.size = READ_U32(data, pos); pos += 4; + int start_pos = pos; + + if (!strcmp(chunk.tag, "fmt ")) { + wav->format = READ_U16(data, pos); pos += 2; + wav->num_channels = READ_U16(data, pos); pos += 2; + wav->sample_rate = READ_U32(data, pos); pos += 4; + wav->data_rate = READ_U32(data, pos); pos += 4; + wav->sample_width = READ_U16(data, pos); pos += 2; + wav->bits_per_sample = READ_U16(data, pos); pos += 2; + + if (wav->format != WAVE_FORMAT_PCM) { + /* + wav->extension_size = READ_U16(data, pos); pos += 2; + wav->valid_bits_per_sample = READ_U16(data, pos); pos += 2; + wav->channel_mask = READ_U32(data, pos); pos += 4; + */ + + fprintf(stderr, "unsupported wave format: 0x%04x\n", wav->format); + exit(1); + } + + } else if (!strcmp(chunk.tag, "data")) { + wav->samples = &data[pos]; + wav->samples_size = chunk.size; + pos += chunk.size; + } else { + pos += chunk.size; + } + + if (pos != start_pos + (int)chunk.size) { + fprintf(stderr, "BUG: failed to read '%s' chunk at 0x%x (0x%x/0x%x bytes)\n", + chunk.tag, start_pos, pos - start_pos, chunk.size - start_pos); + exit(1); + } + } + + if (pos > riff_end) { + fprintf(stderr, "BUG: read past end of 'RIFF' chunk (0x%x/0x%x bytes)\n", + pos, riff_end); + } + + if (pos > size) { + fprintf(stderr, "BUG: read past end of file (0x%x/0x%x bytes)\n", + pos, size); + } + + // only use the first channel + int sample_distance = wav->sample_width * wav->num_channels; + + // approximate the desired sample rate + float interval = ((float)wav->sample_rate) / Options.sample_rate; + if (interval == 0) { + fprintf(stderr, "input sample rate too low or output sample rate too high (in: %dHz, out: %dHz)\n", + wav->sample_rate, Options.sample_rate); + exit(1); + } + + int num_samples = wav->samples_size / sample_distance; + + int new_samples_size = num_samples / interval; + uint8_t *new_samples = malloc(new_samples_size); + + int new_num_samples = 0; + + float interval_i = 0; + int i = 0; + while (i < num_samples) { + uint8_t sample = 0; + int index = i * sample_distance; + if (wav->sample_width == 1) { + sample = wav->samples[index]; + } else if (wav->sample_width == 2) { + sample = wav->samples[index] + (wav->samples[index + 1] << 8); + } else { + fprintf(stderr, "unsupported sample width: %d\n", wav->sample_width); + exit(1); + } + + new_samples[new_num_samples++] = sample; + + interval_i += interval; + i = (int)interval_i; + } + + wav->samples = new_samples; + wav->samples_size = new_num_samples; + + free(data); +} + +void wav_to_pcm(struct Wav *wav, struct Pcm *pcm) { + pcm->size = wav->samples_size / 8; + pcm->data = calloc(pcm->size, 1); + + long total = 0; + for (int i = 0; i < wav->samples_size; i++) { + total += wav->samples[i]; + } + float average = (double)total / wav->samples_size; + + for (int i = 0; i < wav->samples_size; i++) { + int bit = 0; + if (wav->samples[i] >= average) { + bit = 1; + } + pcm->data[i / 8] |= bit << (7 - (i % 8)); + } +} + +int main(int argc, char *argv[]) { + struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"--sample-rate", required_argument, 0, 's'}, + {0} + }; + while (1) { + int opt = getopt_long(argc, argv, "ho:", long_options); + if (opt == -1) { + break; + } + switch (opt) { + case 'h': + Options.help = 1; + break; + case 'o': + Options.outfile = optarg; + break; + case 's': + Options.sample_rate = strtoul(optarg, NULL, 0); + break; + default: + usage(); + exit(1); + break; + } + } + argc -= optind; + argv += optind; + + if (Options.help) { + usage(); + return 0; + } + if (argc < 1) { + usage(); + exit(1); + } + char *infile = argv[0]; + struct Wav wav = {0}; + struct Pcm pcm = {0}; + read_wav(infile, &wav); + wav_to_pcm(&wav, &pcm); + if (Options.outfile) { + write_u8(Options.outfile, pcm.data, pcm.size); + } +} diff --git a/tools/scan_includes.c b/tools/scan_includes.c new file mode 100644 index 0000000..02a7810 --- /dev/null +++ b/tools/scan_includes.c @@ -0,0 +1,132 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <getopt.h> + +void usage(void) { + printf("Usage: scan_includes [-h] [-s] filename\n" + "-h, --help\n" + " Print usage and exit\n" + "-s, --strict\n" + " Fail if a file cannot be read\n"); +} + +struct Options { + bool help; + bool strict; +}; + +struct Options Options = {0}; + +void scan_file(char* filename) { + FILE *f = fopen(filename, "rb"); + 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 = malloc(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; + + 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; + } + int length = strcspn(buffer, "\""); + char *include = malloc(length + 1); + strncpy(include, buffer, length); + include[length] = '\0'; + printf("%s ", include); + if (is_include) { + scan_file(include); + } + free(include); + buffer = strchr(buffer, '"'); + } + break; + + } + if (!buffer) { + break; + } + + } + + free(orig); +} + +void get_args(int argc, char *argv[]) { + while (1) { + struct option long_options[] = { + {"strict", no_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {0} + }; + int i = 0; + int opt = getopt_long(argc, argv, "sh", long_options, &i); + + if (opt == -1) { + break; + } + + switch (opt) { + case 's': + Options.strict = true; + break; + case 'h': + Options.help = true; + break; + default: + usage(); + exit(1); + break; + } + } +} + +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); + } + scan_file(argv[0]); + return 0; +} |