The repository formerly known as dotfiles
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

gfxx.c 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /* Copyright (c) 2018, Curtis McEnroe <programble@gmail.com>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU Affero General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU Affero General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU Affero General Public License
  14. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. #include <arpa/inet.h>
  17. #include <err.h>
  18. #include <fcntl.h>
  19. #include <math.h>
  20. #include <stdbool.h>
  21. #include <stdint.h>
  22. #include <stdio.h>
  23. #include <stdlib.h>
  24. #include <string.h>
  25. #include <sys/mman.h>
  26. #include <sys/stat.h>
  27. #include <sysexits.h>
  28. #include <unistd.h>
  29. #include <zlib.h>
  30. #include "gfx.h"
  31. #define MAX(a, b) ((a) > (b) ? (a) : (b))
  32. #define MASK(b) ((1 << (b)) - 1)
  33. #define RGB(r, g, b) ((uint32_t)(r) << 16 | (uint32_t)(g) << 8 | (uint32_t)(b))
  34. #define GRAY(n) RGB(n, n, n)
  35. static enum {
  36. COLOR_INDEXED,
  37. COLOR_GRAYSCALE,
  38. COLOR_RGB,
  39. COLOR__COUNT,
  40. } space = COLOR_RGB;
  41. static const char *COLOR__STR[COLOR__COUNT] = { "indexed", "grayscale", "rgb" };
  42. static uint32_t palette[256];
  43. static enum {
  44. ENDIAN_LITTLE,
  45. ENDIAN_BIG,
  46. } byteOrder, bitOrder;
  47. enum { PAD, R, G, B };
  48. static uint8_t bits[4] = { 8, 8, 8, 8 };
  49. #define BITS_COLOR (bits[R] + bits[G] + bits[B])
  50. #define BITS_TOTAL (bits[PAD] + BITS_COLOR)
  51. static size_t offset;
  52. static size_t width = 16;
  53. static bool flip;
  54. static bool mirror;
  55. static size_t scale = 1;
  56. static const char *prefix = "gfxx";
  57. static size_t size;
  58. static uint8_t *data;
  59. int init(int argc, char *argv[]) {
  60. const char *pal = NULL;
  61. const char *path = NULL;
  62. int opt;
  63. while (0 < (opt = getopt(argc, argv, "c:p:b:e:E:n:fmw:z:o:"))) {
  64. switch (opt) {
  65. break; case 'c':
  66. switch (optarg[0]) {
  67. break; case 'i': space = COLOR_INDEXED;
  68. break; case 'g': space = COLOR_GRAYSCALE;
  69. break; case 'r': space = COLOR_RGB;
  70. break; default: return EX_USAGE;
  71. }
  72. break; case 'p': pal = optarg;
  73. break; case 'e':
  74. switch (optarg[0]) {
  75. break; case 'l': byteOrder = ENDIAN_LITTLE;
  76. break; case 'b': byteOrder = ENDIAN_BIG;
  77. break; default: return EX_USAGE;
  78. }
  79. break; case 'E':
  80. switch (optarg[0]) {
  81. break; case 'l': bitOrder = ENDIAN_LITTLE;
  82. break; case 'b': bitOrder = ENDIAN_BIG;
  83. break; default: return EX_USAGE;
  84. }
  85. break; case 'b': {
  86. if (strlen(optarg) < 4) return EX_USAGE;
  87. for (int i = 0; i < 4; ++i) {
  88. bits[i] = optarg[i] - '0';
  89. }
  90. }
  91. break; case 'n': offset = strtoul(optarg, NULL, 0);
  92. break; case 'f': flip ^= true;
  93. break; case 'm': mirror ^= true;
  94. break; case 'w': width = strtoul(optarg, NULL, 0);
  95. break; case 'z': scale = strtoul(optarg, NULL, 0);
  96. break; case 'o': prefix = optarg;
  97. break; default: return EX_USAGE;
  98. }
  99. }
  100. if (argc > optind) path = argv[optind];
  101. if (!width || !scale) return EX_USAGE;
  102. if (pal) {
  103. FILE *file = fopen(pal, "r");
  104. if (!file) err(EX_NOINPUT, "%s", pal);
  105. fread(palette, 4, 256, file);
  106. if (ferror(file)) err(EX_IOERR, "%s", pal);
  107. fclose(file);
  108. } else {
  109. for (int i = 0; i < 256; ++i) {
  110. double h = i / 256.0 * 6.0;
  111. double x = 1.0 - fabs(fmod(h, 2.0) - 1.0);
  112. double r = 255.0, g = 255.0, b = 255.0;
  113. if (h <= 1.0) { g *= x; b = 0.0; }
  114. else if (h <= 2.0) { r *= x; b = 0.0; }
  115. else if (h <= 3.0) { r = 0.0; b *= x; }
  116. else if (h <= 4.0) { r = 0.0; g *= x; }
  117. else if (h <= 5.0) { r *= x; g = 0.0; }
  118. else if (h <= 6.0) { g = 0.0; b *= x; }
  119. palette[i] = (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
  120. }
  121. }
  122. if (path) {
  123. int fd = open(path, O_RDONLY);
  124. if (fd < 0) err(EX_NOINPUT, "%s", path);
  125. struct stat stat;
  126. int error = fstat(fd, &stat);
  127. if (error) err(EX_IOERR, "%s", path);
  128. size = stat.st_size;
  129. data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
  130. if (data == MAP_FAILED) err(EX_IOERR, "%s", path);
  131. } else {
  132. size = 1024 * 1024;
  133. data = malloc(size);
  134. if (!data) err(EX_OSERR, "malloc(%zu)", size);
  135. size = fread(data, 1, size, stdin);
  136. if (ferror(stdin)) err(EX_IOERR, "(stdin)");
  137. }
  138. return EX_OK;
  139. }
  140. static char options[128];
  141. static void formatOptions(void) {
  142. snprintf(
  143. options, sizeof(options),
  144. "gfxx -c %s -e%c -E%c -b %hhu%hhu%hhu%hhu -n 0x%zX %s%s-w %zu -z %zu",
  145. COLOR__STR[space],
  146. "lb"[byteOrder],
  147. "lb"[bitOrder],
  148. bits[PAD], bits[R], bits[G], bits[B],
  149. offset,
  150. flip ? "-f " : "",
  151. mirror ? "-m " : "",
  152. width,
  153. scale
  154. );
  155. }
  156. const char *status(void) {
  157. formatOptions();
  158. return options;
  159. }
  160. struct Iter {
  161. uint32_t *buf;
  162. size_t bufWidth;
  163. size_t bufHeight;
  164. size_t left;
  165. size_t x;
  166. size_t y;
  167. };
  168. static struct Iter iter(uint32_t *buf, size_t bufWidth, size_t bufHeight) {
  169. struct Iter it = { .buf = buf, .bufWidth = bufWidth, .bufHeight = bufHeight };
  170. if (mirror) it.x = width - 1;
  171. if (flip) it.y = bufHeight / scale - 1;
  172. return it;
  173. }
  174. static bool nextX(struct Iter *it) {
  175. if (mirror) {
  176. if (it->x == it->left) return false;
  177. it->x--;
  178. } else {
  179. it->x++;
  180. if (it->x == it->left + width) return false;
  181. }
  182. return true;
  183. }
  184. static bool nextY(struct Iter *it) {
  185. if (flip) {
  186. if (it->y == 0) {
  187. it->left += width;
  188. it->y = it->bufHeight / scale;
  189. }
  190. it->y--;
  191. } else {
  192. it->y++;
  193. if (it->y == it->bufHeight / scale) {
  194. it->left += width;
  195. it->y = 0;
  196. }
  197. }
  198. it->x = it->left;
  199. if (mirror) it->x += width - 1;
  200. return (it->left < it->bufWidth / scale);
  201. }
  202. static bool next(struct Iter *it) {
  203. return nextX(it) || nextY(it);
  204. }
  205. static void put(const struct Iter *it, uint32_t pixel) {
  206. size_t scaledX = it->x * scale;
  207. size_t scaledY = it->y * scale;
  208. for (size_t fillY = scaledY; fillY < scaledY + scale; ++fillY) {
  209. if (fillY >= it->bufHeight) break;
  210. for (size_t fillX = scaledX; fillX < scaledX + scale; ++fillX) {
  211. if (fillX >= it->bufWidth) break;
  212. it->buf[fillY * it->bufWidth + fillX] = pixel;
  213. }
  214. }
  215. }
  216. static uint8_t interp(uint8_t b, uint32_t n) {
  217. if (b == 8) return n;
  218. if (b == 0) return 0;
  219. return n * MASK(8) / MASK(b);
  220. }
  221. static uint32_t interpolate(uint32_t rgb) {
  222. uint32_t r, g, b;
  223. if (bitOrder == ENDIAN_LITTLE) {
  224. b = rgb & MASK(bits[B]);
  225. g = (rgb >>= bits[B]) & MASK(bits[G]);
  226. r = (rgb >>= bits[G]) & MASK(bits[R]);
  227. } else {
  228. r = rgb & MASK(bits[R]);
  229. g = (rgb >>= bits[R]) & MASK(bits[G]);
  230. b = (rgb >>= bits[G]) & MASK(bits[B]);
  231. }
  232. return RGB(interp(bits[R], r), interp(bits[G], g), interp(bits[B], b));
  233. }
  234. static void drawBits(struct Iter *it) {
  235. for (size_t i = offset; i < size; ++i) {
  236. for (uint8_t b = 0; b < 8; b += BITS_TOTAL) {
  237. uint8_t n;
  238. if (byteOrder == ENDIAN_BIG) {
  239. n = data[i] >> (8 - BITS_TOTAL - b) & MASK(BITS_TOTAL);
  240. } else {
  241. n = data[i] >> b & MASK(BITS_TOTAL);
  242. }
  243. if (space == COLOR_INDEXED) {
  244. put(it, palette[n]);
  245. } else if (space == COLOR_GRAYSCALE) {
  246. put(it, GRAY(interp(BITS_COLOR, n & MASK(BITS_COLOR))));
  247. } else if (space == COLOR_RGB) {
  248. put(it, interpolate(n));
  249. }
  250. if (!next(it)) return;
  251. }
  252. }
  253. }
  254. static void drawBytes(struct Iter *it) {
  255. uint8_t bytes = (BITS_TOTAL + 7) / 8;
  256. for (size_t i = offset; i + bytes <= size; i += bytes) {
  257. uint32_t n = 0;
  258. for (size_t b = 0; b < bytes; ++b) {
  259. n <<= 8;
  260. n |= (byteOrder == ENDIAN_BIG) ? data[i + b] : data[i + bytes - b - 1];
  261. }
  262. if (space == COLOR_INDEXED) {
  263. put(it, palette[n & 0xFF]);
  264. } else if (space == COLOR_GRAYSCALE) {
  265. put(it, GRAY(interp(BITS_COLOR, n & MASK(BITS_COLOR))));
  266. } else if (space == COLOR_RGB) {
  267. put(it, interpolate(n));
  268. }
  269. if (!next(it)) return;
  270. }
  271. }
  272. static struct {
  273. unsigned counter;
  274. char path[FILENAME_MAX];
  275. FILE *file;
  276. } out;
  277. static void outOpen(const char *ext) {
  278. snprintf(out.path, sizeof(out.path), "%s%04u.%s", prefix, ++out.counter, ext);
  279. out.file = fopen(out.path, "wx");
  280. if (out.file) {
  281. printf("%s\n", out.path);
  282. } else {
  283. warn("%s", out.path);
  284. }
  285. }
  286. static uint32_t crc;
  287. static void pngWrite(const void *ptr, size_t size) {
  288. fwrite(ptr, size, 1, out.file);
  289. if (ferror(out.file)) err(EX_IOERR, "%s", out.path);
  290. crc = crc32(crc, ptr, size);
  291. }
  292. static void pngUint(uint32_t host) {
  293. uint32_t net = htonl(host);
  294. pngWrite(&net, 4);
  295. }
  296. static void pngChunk(const char *type, uint32_t size) {
  297. pngUint(size);
  298. crc = crc32(0, Z_NULL, 0);
  299. pngWrite(type, 4);
  300. }
  301. static void pngDump(uint32_t *src, size_t srcWidth, size_t srcHeight) {
  302. int error;
  303. size_t stride = 1 + 3 * srcWidth;
  304. uint8_t data[stride * srcHeight];
  305. for (size_t y = 0; y < srcHeight; ++y) {
  306. data[y * stride] = 0;
  307. for (size_t x = 0; x < srcWidth; ++x) {
  308. uint8_t *p = &data[y * stride + 1 + 3 * x];
  309. p[0] = src[y * srcWidth + x] >> 16;
  310. p[1] = src[y * srcWidth + x] >> 8;
  311. p[2] = src[y * srcWidth + x];
  312. }
  313. }
  314. uLong deflateSize = compressBound(sizeof(data));
  315. uint8_t deflate[deflateSize];
  316. error = compress(deflate, &deflateSize, data, sizeof(data));
  317. if (error != Z_OK) errx(EX_SOFTWARE, "compress: %d", error);
  318. outOpen("png");
  319. if (!out.file) return;
  320. const uint8_t SIGNATURE[] = { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1A, '\n' };
  321. const uint8_t HEADER[] = { 8, 2, 0, 0, 0 }; // 8-bit truecolor
  322. const char SOFTWARE[] = "Software";
  323. formatOptions();
  324. uint8_t sbit[3] = { MAX(bits[R], 1), MAX(bits[G], 1), MAX(bits[B], 1) };
  325. pngWrite(SIGNATURE, sizeof(SIGNATURE));
  326. pngChunk("IHDR", 4 + 4 + sizeof(HEADER));
  327. pngUint(srcWidth);
  328. pngUint(srcHeight);
  329. pngWrite(HEADER, sizeof(HEADER));
  330. pngUint(crc);
  331. pngChunk("tEXt", sizeof(SOFTWARE) + strlen(options));
  332. pngWrite(SOFTWARE, sizeof(SOFTWARE));
  333. pngWrite(options, strlen(options));
  334. pngUint(crc);
  335. pngChunk("sBIT", sizeof(sbit));
  336. pngWrite(sbit, sizeof(sbit));
  337. pngUint(crc);
  338. pngChunk("IDAT", deflateSize);
  339. pngWrite(deflate, deflateSize);
  340. pngUint(crc);
  341. pngChunk("IEND", 0);
  342. pngUint(crc);
  343. error = fclose(out.file);
  344. if (error) err(EX_IOERR, "%s", out.path);
  345. }
  346. static enum {
  347. DUMP_NONE,
  348. DUMP_ONE,
  349. DUMP_ALL,
  350. } dump;
  351. void draw(uint32_t *buf, size_t bufWidth, size_t bufHeight) {
  352. memset(buf, 0, 4 * bufWidth * bufHeight);
  353. struct Iter it = iter(buf, bufWidth, bufHeight);
  354. if (BITS_TOTAL >= 8) {
  355. drawBytes(&it);
  356. } else {
  357. drawBits(&it);
  358. }
  359. if (dump) pngDump(buf, bufWidth, bufHeight);
  360. if (dump == DUMP_ONE) dump = DUMP_NONE;
  361. }
  362. static void palSample(void) {
  363. size_t temp = scale;
  364. scale = 1;
  365. draw(palette, 256, 1);
  366. scale = temp;
  367. }
  368. static void palDump(void) {
  369. outOpen("dat");
  370. if (!out.file) return;
  371. fwrite(palette, 4, 256, out.file);
  372. if (ferror(out.file)) err(EX_IOERR, "%s", out.path);
  373. int error = fclose(out.file);
  374. if (error) err(EX_IOERR, "%s", out.path);
  375. }
  376. static const uint8_t PRESETS[][4] = {
  377. { 0, 0, 1, 0 },
  378. { 0, 1, 1, 0 },
  379. { 1, 1, 1, 1 },
  380. { 2, 2, 2, 2 },
  381. { 0, 3, 3, 2 },
  382. { 4, 4, 4, 4 },
  383. { 1, 5, 5, 5 },
  384. { 0, 5, 6, 5 },
  385. { 0, 8, 8, 8 },
  386. { 8, 8, 8, 8 },
  387. };
  388. #define PRESETS_LEN (sizeof(PRESETS) / sizeof(PRESETS[0]))
  389. static uint8_t preset = PRESETS_LEN - 1;
  390. static void setPreset(void) {
  391. bits[PAD] = PRESETS[preset][PAD];
  392. bits[R] = PRESETS[preset][R];
  393. bits[G] = PRESETS[preset][G];
  394. bits[B] = PRESETS[preset][B];
  395. }
  396. static void setBit(char in) {
  397. static uint8_t bit = 0;
  398. bits[bit++] = in - '0';
  399. bit &= 3;
  400. }
  401. bool input(char in) {
  402. size_t pixel = (BITS_TOTAL + 7) / 8;
  403. size_t row = width * BITS_TOTAL / 8;
  404. switch (in) {
  405. break; case 'q': return false;
  406. break; case 'x': dump = DUMP_ONE;
  407. break; case 'X': dump ^= DUMP_ALL;
  408. break; case 'o': formatOptions(); printf("%s\n", options);
  409. break; case '[': if (!space--) space = COLOR__COUNT - 1;
  410. break; case ']': if (++space == COLOR__COUNT) space = 0;
  411. break; case 'p': palSample();
  412. break; case 'P': palDump();
  413. break; case '{': if (!preset--) preset = PRESETS_LEN - 1; setPreset();
  414. break; case '}': if (++preset == PRESETS_LEN) preset = 0; setPreset();
  415. break; case 'e': byteOrder ^= ENDIAN_BIG;
  416. break; case 'E': bitOrder ^= ENDIAN_BIG;
  417. break; case 'h': if (offset) offset--;
  418. break; case 'j': offset += pixel;
  419. break; case 'k': if (offset >= pixel) offset -= pixel;
  420. break; case 'l': offset++;
  421. break; case 'H': if (offset >= row) offset -= row;
  422. break; case 'J': offset += width * row;
  423. break; case 'K': if (offset >= width * row) offset -= width * row;
  424. break; case 'L': offset += row;
  425. break; case '.': width++;
  426. break; case ',': if (width > 1) width--;
  427. break; case '>': width *= 2;
  428. break; case '<': if (width > 1) width /= 2;
  429. break; case 'f': flip ^= true;
  430. break; case 'm': mirror ^= true;
  431. break; case '+': scale++;
  432. break; case '-': if (scale > 1) scale--;
  433. break; default: if (in >= '0' && in <= '9') setBit(in);
  434. }
  435. return true;
  436. }