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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. /* Copyright (C) 2018 Curtis McEnroe <june@causal.agency>
  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 <err.h>
  17. #include <fcntl.h>
  18. #include <math.h>
  19. #include <stdbool.h>
  20. #include <stdint.h>
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <sys/mman.h>
  25. #include <sys/stat.h>
  26. #include <sysexits.h>
  27. #include <unistd.h>
  28. #include "gfx.h"
  29. #include "png.h"
  30. #define MAX(a, b) ((a) > (b) ? (a) : (b))
  31. #define MASK(b) ((1 << (b)) - 1)
  32. static uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) {
  33. return (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
  34. }
  35. static uint32_t gray(uint8_t n) {
  36. return rgb(n, n, n);
  37. }
  38. static enum {
  39. ColorIndexed,
  40. ColorGrayscale,
  41. ColorRGB,
  42. ColorCount,
  43. } space = ColorRGB;
  44. static const char *ColorStr[ColorCount] = { "indexed", "grayscale", "rgb" };
  45. static uint32_t palette[256];
  46. static enum {
  47. EndianLittle,
  48. EndianBig,
  49. } byteOrder, bitOrder;
  50. enum { Pad, R, G, B };
  51. static uint8_t bits[4] = { 8, 8, 8, 8 };
  52. static uint8_t bitsColor(void) {
  53. return bits[R] + bits[G] + bits[B];
  54. }
  55. static uint8_t bitsTotal(void) {
  56. return bits[Pad] + bitsColor();
  57. }
  58. static size_t offset;
  59. static size_t width = 16;
  60. static bool flip;
  61. static bool mirror;
  62. static size_t scale = 1;
  63. static const char *prefix = "gfxx";
  64. static size_t size;
  65. static uint8_t *data;
  66. int init(int argc, char *argv[]) {
  67. const char *pal = NULL;
  68. const char *path = NULL;
  69. int opt;
  70. while (0 < (opt = getopt(argc, argv, "c:p:b:e:E:n:fmw:z:o:"))) {
  71. switch (opt) {
  72. break; case 'c':
  73. switch (optarg[0]) {
  74. break; case 'i': space = ColorIndexed;
  75. break; case 'g': space = ColorGrayscale;
  76. break; case 'r': space = ColorRGB;
  77. break; default: return EX_USAGE;
  78. }
  79. break; case 'p': pal = optarg;
  80. break; case 'e':
  81. switch (optarg[0]) {
  82. break; case 'l': byteOrder = EndianLittle;
  83. break; case 'b': byteOrder = EndianBig;
  84. break; default: return EX_USAGE;
  85. }
  86. break; case 'E':
  87. switch (optarg[0]) {
  88. break; case 'l': bitOrder = EndianLittle;
  89. break; case 'b': bitOrder = EndianBig;
  90. break; default: return EX_USAGE;
  91. }
  92. break; case 'b': {
  93. if (strlen(optarg) < 4) return EX_USAGE;
  94. for (int i = 0; i < 4; ++i) {
  95. bits[i] = optarg[i] - '0';
  96. }
  97. }
  98. break; case 'n': offset = strtoul(optarg, NULL, 0);
  99. break; case 'f': flip ^= true;
  100. break; case 'm': mirror ^= true;
  101. break; case 'w': width = strtoul(optarg, NULL, 0);
  102. break; case 'z': scale = strtoul(optarg, NULL, 0);
  103. break; case 'o': prefix = optarg;
  104. break; default: return EX_USAGE;
  105. }
  106. }
  107. if (argc > optind) path = argv[optind];
  108. if (!width || !scale) return EX_USAGE;
  109. if (pal) {
  110. FILE *file = fopen(pal, "r");
  111. if (!file) err(EX_NOINPUT, "%s", pal);
  112. fread(palette, 4, 256, file);
  113. if (ferror(file)) err(EX_IOERR, "%s", pal);
  114. fclose(file);
  115. } else {
  116. for (int i = 0; i < 256; ++i) {
  117. double h = i / 256.0 * 6.0;
  118. double x = 1.0 - fabs(fmod(h, 2.0) - 1.0);
  119. double r = 255.0, g = 255.0, b = 255.0;
  120. if (h <= 1.0) { g *= x; b = 0.0; }
  121. else if (h <= 2.0) { r *= x; b = 0.0; }
  122. else if (h <= 3.0) { r = 0.0; b *= x; }
  123. else if (h <= 4.0) { r = 0.0; g *= x; }
  124. else if (h <= 5.0) { r *= x; g = 0.0; }
  125. else if (h <= 6.0) { g = 0.0; b *= x; }
  126. palette[i] = (uint32_t)r << 16 | (uint32_t)g << 8 | (uint32_t)b;
  127. }
  128. }
  129. if (path) {
  130. int fd = open(path, O_RDONLY);
  131. if (fd < 0) err(EX_NOINPUT, "%s", path);
  132. struct stat stat;
  133. int error = fstat(fd, &stat);
  134. if (error) err(EX_IOERR, "%s", path);
  135. size = stat.st_size;
  136. data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
  137. if (data == MAP_FAILED) err(EX_IOERR, "%s", path);
  138. } else {
  139. size = 1024 * 1024;
  140. data = malloc(size);
  141. if (!data) err(EX_OSERR, "malloc(%zu)", size);
  142. size = fread(data, 1, size, stdin);
  143. if (ferror(stdin)) err(EX_IOERR, "(stdin)");
  144. }
  145. return EX_OK;
  146. }
  147. static char options[128];
  148. static void formatOptions(void) {
  149. snprintf(
  150. options, sizeof(options),
  151. "gfxx -c %s -e%c -E%c -b %hhu%hhu%hhu%hhu -n 0x%zX %s%s-w %zu -z %zu",
  152. ColorStr[space],
  153. "lb"[byteOrder],
  154. "lb"[bitOrder],
  155. bits[Pad], bits[R], bits[G], bits[B],
  156. offset,
  157. flip ? "-f " : "",
  158. mirror ? "-m " : "",
  159. width,
  160. scale
  161. );
  162. }
  163. const char *status(void) {
  164. formatOptions();
  165. return options;
  166. }
  167. struct Iter {
  168. uint32_t *buf;
  169. size_t bufWidth;
  170. size_t bufHeight;
  171. size_t left;
  172. size_t x;
  173. size_t y;
  174. };
  175. static struct Iter iter(uint32_t *buf, size_t bufWidth, size_t bufHeight) {
  176. struct Iter it = { .buf = buf, .bufWidth = bufWidth, .bufHeight = bufHeight };
  177. if (mirror) it.x = width - 1;
  178. if (flip) it.y = bufHeight / scale - 1;
  179. return it;
  180. }
  181. static bool nextX(struct Iter *it) {
  182. if (mirror) {
  183. if (it->x == it->left) return false;
  184. it->x--;
  185. } else {
  186. it->x++;
  187. if (it->x == it->left + width) return false;
  188. }
  189. return true;
  190. }
  191. static bool nextY(struct Iter *it) {
  192. if (flip) {
  193. if (it->y == 0) {
  194. it->left += width;
  195. it->y = it->bufHeight / scale;
  196. }
  197. it->y--;
  198. } else {
  199. it->y++;
  200. if (it->y == it->bufHeight / scale) {
  201. it->left += width;
  202. it->y = 0;
  203. }
  204. }
  205. it->x = it->left;
  206. if (mirror) it->x += width - 1;
  207. return (it->left < it->bufWidth / scale);
  208. }
  209. static bool next(struct Iter *it) {
  210. return nextX(it) || nextY(it);
  211. }
  212. static void put(const struct Iter *it, uint32_t pixel) {
  213. size_t scaledX = it->x * scale;
  214. size_t scaledY = it->y * scale;
  215. for (size_t fillY = scaledY; fillY < scaledY + scale; ++fillY) {
  216. if (fillY >= it->bufHeight) break;
  217. for (size_t fillX = scaledX; fillX < scaledX + scale; ++fillX) {
  218. if (fillX >= it->bufWidth) break;
  219. it->buf[fillY * it->bufWidth + fillX] = pixel;
  220. }
  221. }
  222. }
  223. static uint8_t interp(uint8_t b, uint32_t n) {
  224. if (b == 8) return n;
  225. if (b == 0) return 0;
  226. return n * MASK(8) / MASK(b);
  227. }
  228. static uint32_t interpolate(uint32_t orig) {
  229. uint32_t r, g, b;
  230. if (bitOrder == EndianLittle) {
  231. b = orig & MASK(bits[B]);
  232. g = (orig >>= bits[B]) & MASK(bits[G]);
  233. r = (orig >>= bits[G]) & MASK(bits[R]);
  234. } else {
  235. r = orig & MASK(bits[R]);
  236. g = (orig >>= bits[R]) & MASK(bits[G]);
  237. b = (orig >>= bits[G]) & MASK(bits[B]);
  238. }
  239. return rgb(interp(bits[R], r), interp(bits[G], g), interp(bits[B], b));
  240. }
  241. static void drawBits(struct Iter *it) {
  242. for (size_t i = offset; i < size; ++i) {
  243. for (uint8_t b = 0; b < 8; b += bitsTotal()) {
  244. uint8_t n;
  245. if (byteOrder == EndianBig) {
  246. n = data[i] >> (8 - bitsTotal() - b) & MASK(bitsTotal());
  247. } else {
  248. n = data[i] >> b & MASK(bitsTotal());
  249. }
  250. if (space == ColorIndexed) {
  251. put(it, palette[n]);
  252. } else if (space == ColorGrayscale) {
  253. put(it, gray(interp(bitsColor(), n & MASK(bitsColor()))));
  254. } else if (space == ColorRGB) {
  255. put(it, interpolate(n));
  256. }
  257. if (!next(it)) return;
  258. }
  259. }
  260. }
  261. static void drawBytes(struct Iter *it) {
  262. uint8_t bytes = (bitsTotal() + 7) / 8;
  263. for (size_t i = offset; i + bytes <= size; i += bytes) {
  264. uint32_t n = 0;
  265. for (size_t b = 0; b < bytes; ++b) {
  266. n <<= 8;
  267. n |= (byteOrder == EndianBig) ? data[i + b] : data[i + bytes - b - 1];
  268. }
  269. if (space == ColorIndexed) {
  270. put(it, palette[n & 0xFF]);
  271. } else if (space == ColorGrayscale) {
  272. put(it, gray(interp(bitsColor(), n & MASK(bitsColor()))));
  273. } else if (space == ColorRGB) {
  274. put(it, interpolate(n));
  275. }
  276. if (!next(it)) return;
  277. }
  278. }
  279. static struct {
  280. unsigned counter;
  281. char path[FILENAME_MAX];
  282. FILE *file;
  283. } out;
  284. static void outOpen(const char *ext) {
  285. snprintf(out.path, sizeof(out.path), "%s%04u.%s", prefix, ++out.counter, ext);
  286. out.file = fopen(out.path, "wx");
  287. if (out.file) {
  288. printf("%s\n", out.path);
  289. } else {
  290. warn("%s", out.path);
  291. }
  292. }
  293. static void pngDump(uint32_t *src, size_t srcWidth, size_t srcHeight) {
  294. int error;
  295. size_t stride = 1 + 3 * srcWidth;
  296. uint8_t data[stride * srcHeight];
  297. for (size_t y = 0; y < srcHeight; ++y) {
  298. data[y * stride] = 0;
  299. for (size_t x = 0; x < srcWidth; ++x) {
  300. uint8_t *p = &data[y * stride + 1 + 3 * x];
  301. p[0] = src[y * srcWidth + x] >> 16;
  302. p[1] = src[y * srcWidth + x] >> 8;
  303. p[2] = src[y * srcWidth + x];
  304. }
  305. }
  306. outOpen("png");
  307. if (!out.file) return;
  308. const char Software[] = "Software";
  309. formatOptions();
  310. uint8_t sbit[3] = { MAX(bits[R], 1), MAX(bits[G], 1), MAX(bits[B], 1) };
  311. pngHead(out.file, srcWidth, srcHeight, 8, PNGTruecolor);
  312. pngChunk(out.file, "tEXt", sizeof(Software) + strlen(options));
  313. pngWrite(out.file, (uint8_t *)Software, sizeof(Software));
  314. pngWrite(out.file, (uint8_t *)options, strlen(options));
  315. pngInt32(out.file, ~pngCRC);
  316. pngChunk(out.file, "sBIT", sizeof(sbit));
  317. pngWrite(out.file, sbit, sizeof(sbit));
  318. pngInt32(out.file, ~pngCRC);
  319. pngData(out.file, data, sizeof(data));
  320. pngTail(out.file);
  321. error = fclose(out.file);
  322. if (error) err(EX_IOERR, "%s", out.path);
  323. }
  324. static enum {
  325. DumpNone,
  326. DumpOne,
  327. DumpAll,
  328. } dump;
  329. void draw(uint32_t *buf, size_t bufWidth, size_t bufHeight) {
  330. memset(buf, 0, 4 * bufWidth * bufHeight);
  331. struct Iter it = iter(buf, bufWidth, bufHeight);
  332. if (bitsTotal() >= 8) {
  333. drawBytes(&it);
  334. } else {
  335. drawBits(&it);
  336. }
  337. if (dump) pngDump(buf, bufWidth, bufHeight);
  338. if (dump == DumpOne) dump = DumpNone;
  339. }
  340. static void palSample(void) {
  341. size_t temp = scale;
  342. scale = 1;
  343. draw(palette, 256, 1);
  344. scale = temp;
  345. }
  346. static void palDump(void) {
  347. outOpen("dat");
  348. if (!out.file) return;
  349. fwrite(palette, 4, 256, out.file);
  350. if (ferror(out.file)) err(EX_IOERR, "%s", out.path);
  351. int error = fclose(out.file);
  352. if (error) err(EX_IOERR, "%s", out.path);
  353. }
  354. static const uint8_t Presets[][4] = {
  355. { 0, 0, 1, 0 },
  356. { 0, 1, 1, 0 },
  357. { 1, 1, 1, 1 },
  358. { 2, 2, 2, 2 },
  359. { 0, 3, 3, 2 },
  360. { 4, 4, 4, 4 },
  361. { 1, 5, 5, 5 },
  362. { 0, 5, 6, 5 },
  363. { 0, 8, 8, 8 },
  364. { 8, 8, 8, 8 },
  365. };
  366. static const size_t PresetsLen = sizeof(Presets) / sizeof(Presets[0]);
  367. static uint8_t preset = PresetsLen - 1;
  368. static void setPreset(void) {
  369. bits[Pad] = Presets[preset][Pad];
  370. bits[R] = Presets[preset][R];
  371. bits[G] = Presets[preset][G];
  372. bits[B] = Presets[preset][B];
  373. }
  374. static void setBit(char in) {
  375. static uint8_t bit = 0;
  376. bits[bit++] = in - '0';
  377. bit &= 3;
  378. }
  379. bool input(char in) {
  380. size_t pixel = (bitsTotal() + 7) / 8;
  381. size_t row = width * bitsTotal() / 8;
  382. switch (in) {
  383. break; case 'q': return false;
  384. break; case 'x': dump = DumpOne;
  385. break; case 'X': dump ^= DumpAll;
  386. break; case 'o': formatOptions(); printf("%s\n", options);
  387. break; case '[': if (!space--) space = ColorCount - 1;
  388. break; case ']': if (++space == ColorCount) space = 0;
  389. break; case 'p': palSample();
  390. break; case 'P': palDump();
  391. break; case '{': if (!preset--) preset = PresetsLen - 1; setPreset();
  392. break; case '}': if (++preset == PresetsLen) preset = 0; setPreset();
  393. break; case 'e': byteOrder ^= EndianBig;
  394. break; case 'E': bitOrder ^= EndianBig;
  395. break; case 'h': if (offset) offset--;
  396. break; case 'j': offset += pixel;
  397. break; case 'k': if (offset >= pixel) offset -= pixel;
  398. break; case 'l': offset++;
  399. break; case 'H': if (offset >= row) offset -= row;
  400. break; case 'J': offset += width * row;
  401. break; case 'K': if (offset >= width * row) offset -= width * row;
  402. break; case 'L': offset += row;
  403. break; case '.': width++;
  404. break; case ',': if (width > 1) width--;
  405. break; case '>': width *= 2;
  406. break; case '<': if (width > 1) width /= 2;
  407. break; case 'f': flip ^= true;
  408. break; case 'm': mirror ^= true;
  409. break; case '+': scale++;
  410. break; case '-': if (scale > 1) scale--;
  411. break; default: if (in >= '0' && in <= '9') setBit(in);
  412. }
  413. return true;
  414. }