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.

glitch.c 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  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 <assert.h>
  18. #include <err.h>
  19. #include <stdbool.h>
  20. #include <stdint.h>
  21. #include <stdio.h>
  22. #include <stdlib.h>
  23. #include <string.h>
  24. #include <sysexits.h>
  25. #include <unistd.h>
  26. #include <zlib.h>
  27. #define PACKED __attribute__((packed))
  28. #define CRC_INIT (crc32(0, Z_NULL, 0))
  29. static const char *path;
  30. static FILE *file;
  31. static uint32_t crc;
  32. static void readExpect(void *ptr, size_t size, const char *expect) {
  33. fread(ptr, size, 1, file);
  34. if (ferror(file)) err(EX_IOERR, "%s", path);
  35. if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect);
  36. crc = crc32(crc, ptr, size);
  37. }
  38. static void writeExpect(const void *ptr, size_t size) {
  39. fwrite(ptr, size, 1, file);
  40. if (ferror(file)) err(EX_IOERR, "%s", path);
  41. crc = crc32(crc, ptr, size);
  42. }
  43. static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n";
  44. static void readSignature(void) {
  45. uint8_t signature[8];
  46. readExpect(signature, 8, "signature");
  47. if (0 != memcmp(signature, Signature, 8)) {
  48. errx(EX_DATAERR, "%s: invalid signature", path);
  49. }
  50. }
  51. static void writeSignature(void) {
  52. writeExpect(Signature, sizeof(Signature));
  53. }
  54. struct PACKED Chunk {
  55. uint32_t size;
  56. char type[4];
  57. };
  58. static const char *typeStr(struct Chunk chunk) {
  59. static char buf[5];
  60. memcpy(buf, chunk.type, 4);
  61. return buf;
  62. }
  63. static struct Chunk readChunk(void) {
  64. struct Chunk chunk;
  65. readExpect(&chunk, sizeof(chunk), "chunk");
  66. chunk.size = ntohl(chunk.size);
  67. crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
  68. return chunk;
  69. }
  70. static void writeChunk(struct Chunk chunk) {
  71. chunk.size = htonl(chunk.size);
  72. writeExpect(&chunk, sizeof(chunk));
  73. crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
  74. }
  75. static void readCrc(void) {
  76. uint32_t expected = crc;
  77. uint32_t found;
  78. readExpect(&found, sizeof(found), "CRC32");
  79. found = ntohl(found);
  80. if (found != expected) {
  81. errx(
  82. EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
  83. path, expected, found
  84. );
  85. }
  86. }
  87. static void writeCrc(void) {
  88. uint32_t net = htonl(crc);
  89. writeExpect(&net, sizeof(net));
  90. }
  91. static void skipChunk(struct Chunk chunk) {
  92. uint8_t discard[chunk.size];
  93. readExpect(discard, sizeof(discard), "chunk data");
  94. readCrc();
  95. }
  96. static struct PACKED {
  97. uint32_t width;
  98. uint32_t height;
  99. uint8_t depth;
  100. enum PACKED {
  101. Grayscale = 0,
  102. Truecolor = 2,
  103. Indexed = 3,
  104. GrayscaleAlpha = 4,
  105. TruecolorAlpha = 6,
  106. } color;
  107. uint8_t compression;
  108. uint8_t filter;
  109. uint8_t interlace;
  110. } header;
  111. static_assert(13 == sizeof(header), "header size");
  112. static size_t pixelBits(void) {
  113. switch (header.color) {
  114. case Grayscale: return 1 * header.depth;
  115. case Truecolor: return 3 * header.depth;
  116. case Indexed: return 1 * header.depth;
  117. case GrayscaleAlpha: return 2 * header.depth;
  118. case TruecolorAlpha: return 4 * header.depth;
  119. default: abort();
  120. }
  121. }
  122. static size_t pixelSize(void) {
  123. return (pixelBits() + 7) / 8;
  124. }
  125. static size_t lineSize(void) {
  126. return (header.width * pixelBits() + 7) / 8;
  127. }
  128. static size_t dataSize(void) {
  129. return (1 + lineSize()) * header.height;
  130. }
  131. static void readHeader(void) {
  132. struct Chunk ihdr = readChunk();
  133. if (0 != memcmp(ihdr.type, "IHDR", 4)) {
  134. errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr));
  135. }
  136. if (ihdr.size != sizeof(header)) {
  137. errx(
  138. EX_DATAERR, "%s: expected IHDR size %zu, found %u",
  139. path, sizeof(header), ihdr.size
  140. );
  141. }
  142. readExpect(&header, sizeof(header), "header");
  143. readCrc();
  144. header.width = ntohl(header.width);
  145. header.height = ntohl(header.height);
  146. if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path);
  147. if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path);
  148. }
  149. static void writeHeader(void) {
  150. struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" };
  151. writeChunk(ihdr);
  152. header.width = htonl(header.width);
  153. header.height = htonl(header.height);
  154. writeExpect(&header, sizeof(header));
  155. writeCrc();
  156. header.width = ntohl(header.width);
  157. header.height = ntohl(header.height);
  158. }
  159. static struct {
  160. uint32_t len;
  161. uint8_t entries[256][3];
  162. } palette;
  163. static void readPalette(void) {
  164. struct Chunk chunk;
  165. for (;;) {
  166. chunk = readChunk();
  167. if (0 == memcmp(chunk.type, "PLTE", 4)) break;
  168. skipChunk(chunk);
  169. }
  170. palette.len = chunk.size / 3;
  171. readExpect(palette.entries, chunk.size, "palette data");
  172. readCrc();
  173. }
  174. static void writePalette(void) {
  175. struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" };
  176. writeChunk(plte);
  177. writeExpect(palette.entries, plte.size);
  178. writeCrc();
  179. }
  180. static uint8_t *data;
  181. static void readData(void) {
  182. data = malloc(dataSize());
  183. if (!data) err(EX_OSERR, "malloc(%zu)", dataSize());
  184. struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() };
  185. int error = inflateInit(&stream);
  186. if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg);
  187. for (;;) {
  188. struct Chunk chunk = readChunk();
  189. if (0 == memcmp(chunk.type, "IDAT", 4)) {
  190. uint8_t *idat = malloc(chunk.size);
  191. if (!idat) err(EX_OSERR, "malloc");
  192. readExpect(idat, chunk.size, "image data");
  193. readCrc();
  194. stream.next_in = idat;
  195. stream.avail_in = chunk.size;
  196. int error = inflate(&stream, Z_SYNC_FLUSH);
  197. free(idat);
  198. if (error == Z_STREAM_END) break;
  199. if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
  200. } else if (0 == memcmp(chunk.type, "IEND", 4)) {
  201. errx(EX_DATAERR, "%s: missing IDAT chunk", path);
  202. } else {
  203. skipChunk(chunk);
  204. }
  205. }
  206. inflateEnd(&stream);
  207. if (stream.total_out != dataSize()) {
  208. errx(
  209. EX_DATAERR, "%s: expected data size %zu, found %lu",
  210. path, dataSize(), stream.total_out
  211. );
  212. }
  213. }
  214. static void writeData(void) {
  215. uLong size = compressBound(dataSize());
  216. uint8_t *deflate = malloc(size);
  217. if (!deflate) err(EX_OSERR, "malloc");
  218. int error = compress2(deflate, &size, data, dataSize(), Z_BEST_SPEED);
  219. if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error);
  220. struct Chunk idat = { .size = size, .type = "IDAT" };
  221. writeChunk(idat);
  222. writeExpect(deflate, size);
  223. writeCrc();
  224. free(deflate);
  225. }
  226. static void writeEnd(void) {
  227. struct Chunk iend = { .size = 0, .type = "IEND" };
  228. writeChunk(iend);
  229. writeCrc();
  230. }
  231. enum PACKED Filter {
  232. None,
  233. Sub,
  234. Up,
  235. Average,
  236. Paeth,
  237. FilterCount,
  238. };
  239. static struct {
  240. bool brokenPaeth;
  241. bool filt;
  242. bool recon;
  243. uint8_t declareFilter;
  244. uint8_t applyFilter;
  245. enum Filter declareFilters[255];
  246. enum Filter applyFilters[255];
  247. bool invert;
  248. bool mirror;
  249. bool zeroX;
  250. bool zeroY;
  251. } options;
  252. struct Bytes {
  253. uint8_t x;
  254. uint8_t a;
  255. uint8_t b;
  256. uint8_t c;
  257. };
  258. static uint8_t paethPredictor(struct Bytes f) {
  259. int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
  260. int32_t pa = abs(p - (int32_t)f.a);
  261. int32_t pb = abs(p - (int32_t)f.b);
  262. int32_t pc = abs(p - (int32_t)f.c);
  263. if (pa <= pb && pa <= pc) return f.a;
  264. if (options.brokenPaeth) {
  265. if (pb < pc) return f.b;
  266. } else {
  267. if (pb <= pc) return f.b;
  268. }
  269. return f.c;
  270. }
  271. static uint8_t recon(enum Filter type, struct Bytes f) {
  272. switch (type) {
  273. case None: return f.x;
  274. case Sub: return f.x + f.a;
  275. case Up: return f.x + f.b;
  276. case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
  277. case Paeth: return f.x + paethPredictor(f);
  278. default: abort();
  279. }
  280. }
  281. static uint8_t filt(enum Filter type, struct Bytes f) {
  282. switch (type) {
  283. case None: return f.x;
  284. case Sub: return f.x - f.a;
  285. case Up: return f.x - f.b;
  286. case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
  287. case Paeth: return f.x - paethPredictor(f);
  288. default: abort();
  289. }
  290. }
  291. static struct Line {
  292. enum Filter type;
  293. uint8_t data[];
  294. } **lines;
  295. static void scanlines(void) {
  296. lines = calloc(header.height, sizeof(*lines));
  297. if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines));
  298. size_t stride = 1 + lineSize();
  299. for (uint32_t y = 0; y < header.height; ++y) {
  300. lines[y] = (struct Line *)&data[y * stride];
  301. if (lines[y]->type >= FilterCount) {
  302. errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type);
  303. }
  304. }
  305. }
  306. static struct Bytes origBytes(uint32_t y, size_t i) {
  307. bool a = (i >= pixelSize()), b = (y > 0), c = (a && b);
  308. return (struct Bytes) {
  309. .x = lines[y]->data[i],
  310. .a = a ? lines[y]->data[i - pixelSize()] : 0,
  311. .b = b ? lines[y - 1]->data[i] : 0,
  312. .c = c ? lines[y - 1]->data[i - pixelSize()] : 0,
  313. };
  314. }
  315. static void reconData(void) {
  316. for (uint32_t y = 0; y < header.height; ++y) {
  317. for (size_t i = 0; i < lineSize(); ++i) {
  318. if (options.filt) {
  319. lines[y]->data[i] = filt(lines[y]->type, origBytes(y, i));
  320. } else {
  321. lines[y]->data[i] = recon(lines[y]->type, origBytes(y, i));
  322. }
  323. }
  324. lines[y]->type = None;
  325. }
  326. }
  327. static void filterData(void) {
  328. for (uint32_t y = header.height - 1; y < header.height; --y) {
  329. uint8_t filter[FilterCount][lineSize()];
  330. uint32_t heuristic[FilterCount] = {0};
  331. enum Filter minType = None;
  332. for (enum Filter type = None; type < FilterCount; ++type) {
  333. for (size_t i = 0; i < lineSize(); ++i) {
  334. if (options.recon) {
  335. filter[type][i] = recon(type, origBytes(y, i));
  336. } else {
  337. filter[type][i] = filt(type, origBytes(y, i));
  338. }
  339. heuristic[type] += abs((int8_t)filter[type][i]);
  340. }
  341. if (heuristic[type] < heuristic[minType]) minType = type;
  342. }
  343. if (options.declareFilter) {
  344. lines[y]->type = options.declareFilters[y % options.declareFilter];
  345. } else {
  346. lines[y]->type = minType;
  347. }
  348. if (options.applyFilter) {
  349. enum Filter type = options.applyFilters[y % options.applyFilter];
  350. memcpy(lines[y]->data, filter[type], lineSize());
  351. } else {
  352. memcpy(lines[y]->data, filter[minType], lineSize());
  353. }
  354. }
  355. }
  356. static void invert(void) {
  357. for (uint32_t y = 0; y < header.height; ++y) {
  358. for (size_t i = 0; i < lineSize(); ++i) {
  359. lines[y]->data[i] ^= 0xFF;
  360. }
  361. }
  362. }
  363. static void mirror(void) {
  364. for (uint32_t y = 0; y < header.height; ++y) {
  365. for (size_t i = 0, j = lineSize() - 1; i < j; ++i, --j) {
  366. uint8_t t = lines[y]->data[i];
  367. lines[y]->data[i] = lines[y]->data[j];
  368. lines[y]->data[j] = t;
  369. }
  370. }
  371. }
  372. static void zeroX(void) {
  373. for (uint32_t y = 0; y < header.height; ++y) {
  374. memset(lines[y]->data, 0, pixelSize());
  375. }
  376. }
  377. static void zeroY(void) {
  378. memset(lines[0]->data, 0, lineSize());
  379. }
  380. static void glitch(const char *inPath, const char *outPath) {
  381. if (inPath) {
  382. path = inPath;
  383. file = fopen(path, "r");
  384. if (!file) err(EX_NOINPUT, "%s", path);
  385. } else {
  386. path = "(stdin)";
  387. file = stdin;
  388. }
  389. readSignature();
  390. readHeader();
  391. if (header.color == Indexed) readPalette();
  392. readData();
  393. fclose(file);
  394. scanlines();
  395. reconData();
  396. filterData();
  397. if (options.invert) invert();
  398. if (options.mirror) mirror();
  399. if (options.zeroX) zeroX();
  400. if (options.zeroY) zeroY();
  401. free(lines);
  402. if (outPath) {
  403. path = outPath;
  404. file = fopen(path, "w");
  405. if (!file) err(EX_CANTCREAT, "%s", path);
  406. } else {
  407. path = "(stdout)";
  408. file = stdout;
  409. }
  410. writeSignature();
  411. writeHeader();
  412. if (header.color == Indexed) writePalette();
  413. writeData();
  414. writeEnd();
  415. free(data);
  416. int error = fclose(file);
  417. if (error) err(EX_IOERR, "%s", path);
  418. }
  419. static enum Filter parseFilter(const char *s) {
  420. switch (s[0]) {
  421. case 'N': case 'n': return None;
  422. case 'S': case 's': return Sub;
  423. case 'U': case 'u': return Up;
  424. case 'A': case 'a': return Average;
  425. case 'P': case 'p': return Paeth;
  426. default: errx(EX_USAGE, "invalid filter type %s", s);
  427. }
  428. }
  429. static uint8_t parseFilters(enum Filter *filters, const char *s) {
  430. uint8_t len = 0;
  431. do {
  432. filters[len++] = parseFilter(s);
  433. s = strchr(s, ',');
  434. } while (s++);
  435. return len;
  436. }
  437. int main(int argc, char *argv[]) {
  438. bool stdio = false;
  439. char *output = NULL;
  440. int opt;
  441. while (0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"))) {
  442. switch (opt) {
  443. break; case 'a':
  444. options.applyFilter = parseFilters(options.applyFilters, optarg);
  445. break; case 'c': stdio = true;
  446. break; case 'd':
  447. options.declareFilter = parseFilters(options.declareFilters, optarg);
  448. break; case 'f': options.filt = true;
  449. break; case 'i': options.invert = true;
  450. break; case 'm': options.mirror = true;
  451. break; case 'o': output = optarg;
  452. break; case 'p': options.brokenPaeth = true;
  453. break; case 'r': options.recon = true;
  454. break; case 'x': options.zeroX = true;
  455. break; case 'y': options.zeroY = true;
  456. break; default: return EX_USAGE;
  457. }
  458. }
  459. if (argc - optind == 1 && (output || stdio)) {
  460. glitch(argv[optind], output);
  461. } else if (optind < argc) {
  462. for (int i = optind; i < argc; ++i) {
  463. glitch(argv[i], argv[i]);
  464. }
  465. } else {
  466. glitch(NULL, output);
  467. }
  468. return EX_OK;
  469. }