gfx.c 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <stdbool.h>
  4. #include <getopt.h>
  5. #include <string.h>
  6. #include <stdint.h>
  7. #include "common.h"
  8. static void usage(void) {
  9. 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");
  10. }
  11. static void error(char *message) {
  12. fputs(message, stderr);
  13. fputs("\n", stderr);
  14. }
  15. struct Options {
  16. int trim_whitespace;
  17. int remove_whitespace;
  18. int help;
  19. char *outfile;
  20. int depth;
  21. int interleave;
  22. int remove_duplicates;
  23. int keep_whitespace;
  24. int remove_xflip;
  25. int remove_yflip;
  26. char *png_file;
  27. };
  28. struct Options Options = {
  29. .depth = 2,
  30. };
  31. void get_args(int argc, char *argv[]) {
  32. struct option long_options[] = {
  33. {"remove-whitespace", no_argument, &Options.remove_whitespace, 1},
  34. {"trim-whitespace", no_argument, &Options.trim_whitespace, 1},
  35. {"interleave", no_argument, &Options.interleave, 1},
  36. {"remove-duplicates", no_argument, &Options.remove_duplicates, 1},
  37. {"keep-whitespace", no_argument, &Options.keep_whitespace, 1},
  38. {"remove-xflip", no_argument, &Options.remove_xflip, 1},
  39. {"remove-yflip", no_argument, &Options.remove_yflip, 1},
  40. {"png", required_argument, 0, 'p'},
  41. {"depth", required_argument, 0, 'd'},
  42. {"help", no_argument, 0, 'h'},
  43. {0}
  44. };
  45. for (int opt = 0; opt != -1;) {
  46. switch (opt = getopt_long(argc, argv, "ho:d:p:", long_options)) {
  47. case 'h':
  48. Options.help = true;
  49. break;
  50. case 'o':
  51. Options.outfile = optarg;
  52. break;
  53. case 'd':
  54. Options.depth = strtoul(optarg, NULL, 0);
  55. break;
  56. case 'p':
  57. Options.png_file = optarg;
  58. break;
  59. case 0:
  60. case -1:
  61. break;
  62. default:
  63. usage();
  64. exit(1);
  65. break;
  66. }
  67. }
  68. }
  69. struct Graphic {
  70. int size;
  71. uint8_t *data;
  72. };
  73. bool is_whitespace(uint8_t *tile, int tile_size) {
  74. uint8_t WHITESPACE = 0;
  75. for (int i = 0; i < tile_size; i++) {
  76. if (tile[i] != WHITESPACE) {
  77. return false;
  78. }
  79. }
  80. return true;
  81. }
  82. void trim_whitespace(struct Graphic *graphic) {
  83. int tile_size = Options.depth * 8;
  84. for (int i = graphic->size - tile_size; i > 0; i -= tile_size) {
  85. if (is_whitespace(&graphic->data[i], tile_size)) {
  86. graphic->size = i;
  87. } else {
  88. break;
  89. }
  90. }
  91. }
  92. void remove_whitespace(struct Graphic *graphic) {
  93. int tile_size = Options.depth * 8;
  94. if (Options.interleave) tile_size *= 2;
  95. int i = 0;
  96. for (int j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
  97. while (is_whitespace(&graphic->data[j], tile_size)) {
  98. j += tile_size;
  99. }
  100. if (j >= graphic->size) {
  101. break;
  102. }
  103. if (j > i) {
  104. memcpy(&graphic->data[i], &graphic->data[j], tile_size);
  105. }
  106. }
  107. graphic->size = i;
  108. }
  109. bool tile_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles) {
  110. for (int i = 0; i < num_tiles; i++) {
  111. bool match = true;
  112. for (int j = 0; j < tile_size; j++) {
  113. if (tile[j] != tiles[i * tile_size + j]) {
  114. match = false;
  115. }
  116. }
  117. if (match) {
  118. return true;
  119. }
  120. }
  121. return false;
  122. }
  123. void remove_duplicates(struct Graphic *graphic) {
  124. int tile_size = Options.depth * 8;
  125. if (Options.interleave) tile_size *= 2;
  126. int num_tiles = 0;
  127. for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
  128. while (tile_exists(&graphic->data[j], graphic->data, tile_size, num_tiles)) {
  129. if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) {
  130. break;
  131. }
  132. j += tile_size;
  133. }
  134. if (j >= graphic->size) {
  135. break;
  136. }
  137. if (j > i) {
  138. memcpy(&graphic->data[i], &graphic->data[j], tile_size);
  139. }
  140. num_tiles++;
  141. }
  142. graphic->size = num_tiles * tile_size;
  143. }
  144. bool flip_exists(uint8_t *tile, uint8_t *tiles, int tile_size, int num_tiles, bool xflip, bool yflip) {
  145. uint8_t *flip = calloc(tile_size, 1);
  146. int half_size = tile_size / 2;
  147. for (int i = 0; i < tile_size; i++) {
  148. int byte = i;
  149. if (yflip) {
  150. byte = tile_size - 1 - (i ^ 1);
  151. if (Options.interleave && i < half_size) {
  152. byte = half_size - 1 - (i ^ 1);
  153. }
  154. }
  155. if (xflip) {
  156. for (int bit = 0; bit < 8; bit++) {
  157. flip[byte] |= ((tile[i] >> bit) & 1) << (7 - bit);
  158. }
  159. } else {
  160. flip[byte] = tile[i];
  161. }
  162. }
  163. if (tile_exists(flip, tiles, tile_size, num_tiles)) {
  164. return true;
  165. }
  166. return false;
  167. }
  168. void remove_flip(struct Graphic *graphic, bool xflip, bool yflip) {
  169. int tile_size = Options.depth * 8;
  170. if (Options.interleave) tile_size *= 2;
  171. int num_tiles = 0;
  172. for (int i = 0, j = 0; i < graphic->size && j < graphic->size; i += tile_size, j += tile_size) {
  173. while (flip_exists(&graphic->data[j], graphic->data, tile_size, num_tiles, xflip, yflip)) {
  174. if (Options.keep_whitespace && is_whitespace(&graphic->data[j], tile_size)) {
  175. break;
  176. }
  177. j += tile_size;
  178. }
  179. if (j >= graphic->size) {
  180. break;
  181. }
  182. if (j > i) {
  183. memcpy(&graphic->data[i], &graphic->data[j], tile_size);
  184. }
  185. num_tiles++;
  186. }
  187. graphic->size = num_tiles * tile_size;
  188. }
  189. void interleave(struct Graphic *graphic, int width) {
  190. int tile_size = Options.depth * 8;
  191. int width_tiles = width / 8;
  192. int num_tiles = graphic->size / tile_size;
  193. uint8_t *interleaved = malloc(graphic->size);
  194. for (int i = 0; i < num_tiles; i++) {
  195. int tile = i * 2;
  196. int row = i / width_tiles;
  197. tile -= width_tiles * row;
  198. if (row % 2) {
  199. tile -= width_tiles;
  200. tile += 1;
  201. }
  202. memcpy(&interleaved[tile * tile_size], &graphic->data[i * tile_size], tile_size);
  203. }
  204. graphic->size = num_tiles * tile_size;
  205. memcpy(graphic->data, interleaved, graphic->size);
  206. free(interleaved);
  207. }
  208. int png_get_width(char *filename) {
  209. FILE *f = fopen_verbose(filename, "rb");
  210. if (!f) {
  211. exit(1);
  212. }
  213. const int OFFSET_WIDTH = 16;
  214. uint8_t bytes[4];
  215. fseek(f, OFFSET_WIDTH, SEEK_SET);
  216. size_t size = 4;
  217. size_t result = fread(bytes, 1, size, f);
  218. fclose(f);
  219. if (result != size) {
  220. fprintf(stderr, "Could not read file at offset 0x%x: \"%s\"\n", OFFSET_WIDTH, filename);
  221. exit(1);
  222. }
  223. int width = 0;
  224. for (int i = 0; i < 4; i++) {
  225. width |= bytes[i] << (8 * (3 - i));
  226. }
  227. return width;
  228. }
  229. int main(int argc, char *argv[]) {
  230. get_args(argc, argv);
  231. argc -= optind;
  232. argv += optind;
  233. if (Options.help) {
  234. usage();
  235. return 0;
  236. }
  237. if (argc < 1) {
  238. usage();
  239. exit(1);
  240. }
  241. char *infile = argv[0];
  242. struct Graphic graphic;
  243. graphic.data = read_u8(infile, &graphic.size);
  244. if (Options.trim_whitespace) {
  245. trim_whitespace(&graphic);
  246. }
  247. if (Options.interleave) {
  248. if (!Options.png_file) {
  249. error("interleave: need --png to infer dimensions");
  250. usage();
  251. exit(1);
  252. }
  253. int width = png_get_width(Options.png_file);
  254. interleave(&graphic, width);
  255. }
  256. if (Options.remove_duplicates) {
  257. remove_duplicates(&graphic);
  258. }
  259. if (Options.remove_xflip) {
  260. remove_flip(&graphic, true, false);
  261. }
  262. if (Options.remove_yflip) {
  263. remove_flip(&graphic, false, true);
  264. }
  265. if (Options.remove_xflip && Options.remove_yflip) {
  266. remove_flip(&graphic, true, true);
  267. }
  268. if (Options.remove_whitespace) {
  269. remove_whitespace(&graphic);
  270. }
  271. if (Options.outfile) {
  272. write_u8(Options.outfile, graphic.data, graphic.size);
  273. }
  274. free(graphic.data);
  275. return 0;
  276. }