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.

pngo.c 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  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 PAIR(a, b) ((uint16_t)(a) << 8 | (uint16_t)(b))
  29. #define CRC_INIT (crc32(0, Z_NULL, 0))
  30. static bool verbose;
  31. static const char *path;
  32. static FILE *file;
  33. static uint32_t crc;
  34. static void readExpect(void *ptr, size_t size, const char *expect) {
  35. fread(ptr, size, 1, file);
  36. if (ferror(file)) err(EX_IOERR, "%s", path);
  37. if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect);
  38. crc = crc32(crc, ptr, size);
  39. }
  40. static void writeExpect(const void *ptr, size_t size) {
  41. fwrite(ptr, size, 1, file);
  42. if (ferror(file)) err(EX_IOERR, "%s", path);
  43. crc = crc32(crc, ptr, size);
  44. }
  45. static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n";
  46. static void readSignature(void) {
  47. uint8_t signature[8];
  48. readExpect(signature, 8, "signature");
  49. if (0 != memcmp(signature, Signature, 8)) {
  50. errx(EX_DATAERR, "%s: invalid signature", path);
  51. }
  52. }
  53. static void writeSignature(void) {
  54. writeExpect(Signature, sizeof(Signature));
  55. }
  56. struct PACKED Chunk {
  57. uint32_t size;
  58. char type[4];
  59. };
  60. static const char *typeStr(struct Chunk chunk) {
  61. static char buf[5];
  62. memcpy(buf, chunk.type, 4);
  63. return buf;
  64. }
  65. static struct Chunk readChunk(void) {
  66. struct Chunk chunk;
  67. readExpect(&chunk, sizeof(chunk), "chunk");
  68. chunk.size = ntohl(chunk.size);
  69. crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
  70. return chunk;
  71. }
  72. static void writeChunk(struct Chunk chunk) {
  73. chunk.size = htonl(chunk.size);
  74. writeExpect(&chunk, sizeof(chunk));
  75. crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
  76. }
  77. static void readCrc(void) {
  78. uint32_t expected = crc;
  79. uint32_t found;
  80. readExpect(&found, sizeof(found), "CRC32");
  81. found = ntohl(found);
  82. if (found != expected) {
  83. errx(
  84. EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
  85. path, expected, found
  86. );
  87. }
  88. }
  89. static void writeCrc(void) {
  90. uint32_t net = htonl(crc);
  91. writeExpect(&net, sizeof(net));
  92. }
  93. static void skipChunk(struct Chunk chunk) {
  94. if (!(chunk.type[0] & 0x20)) {
  95. errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, typeStr(chunk));
  96. }
  97. uint8_t discard[4096];
  98. while (chunk.size > sizeof(discard)) {
  99. readExpect(discard, sizeof(discard), "chunk data");
  100. chunk.size -= sizeof(discard);
  101. }
  102. if (chunk.size) readExpect(discard, chunk.size, "chunk data");
  103. readCrc();
  104. }
  105. static struct PACKED {
  106. uint32_t width;
  107. uint32_t height;
  108. uint8_t depth;
  109. enum PACKED {
  110. Grayscale = 0,
  111. Truecolor = 2,
  112. Indexed = 3,
  113. GrayscaleAlpha = 4,
  114. TruecolorAlpha = 6,
  115. } color;
  116. enum PACKED { Deflate } compression;
  117. enum PACKED { Adaptive } filter;
  118. enum PACKED { Progressive, Adam7 } interlace;
  119. } header;
  120. static_assert(13 == sizeof(header), "header size");
  121. static size_t pixelBits(void) {
  122. switch (header.color) {
  123. case Grayscale: return 1 * header.depth;
  124. case Truecolor: return 3 * header.depth;
  125. case Indexed: return 1 * header.depth;
  126. case GrayscaleAlpha: return 2 * header.depth;
  127. case TruecolorAlpha: return 4 * header.depth;
  128. default: abort();
  129. }
  130. }
  131. static size_t pixelSize(void) {
  132. return (pixelBits() + 7) / 8;
  133. }
  134. static size_t lineSize(void) {
  135. return (header.width * pixelBits() + 7) / 8;
  136. }
  137. static size_t dataSize(void) {
  138. return (1 + lineSize()) * header.height;
  139. }
  140. static const char *ColorStr[] = {
  141. [Grayscale] = "grayscale",
  142. [Truecolor] = "truecolor",
  143. [Indexed] = "indexed",
  144. [GrayscaleAlpha] = "grayscale alpha",
  145. [TruecolorAlpha] = "truecolor alpha",
  146. };
  147. static void printHeader(void) {
  148. fprintf(
  149. stderr,
  150. "%s: %ux%u %hhu-bit %s\n",
  151. path,
  152. header.width, header.height,
  153. header.depth, ColorStr[header.color]
  154. );
  155. }
  156. static void readHeader(struct Chunk chunk) {
  157. if (chunk.size != sizeof(header)) {
  158. errx(
  159. EX_DATAERR, "%s: expected IHDR size %zu, found %u",
  160. path, sizeof(header), chunk.size
  161. );
  162. }
  163. readExpect(&header, sizeof(header), "header");
  164. readCrc();
  165. header.width = ntohl(header.width);
  166. header.height = ntohl(header.height);
  167. if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path);
  168. if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path);
  169. switch (PAIR(header.color, header.depth)) {
  170. case PAIR(Grayscale, 1):
  171. case PAIR(Grayscale, 2):
  172. case PAIR(Grayscale, 4):
  173. case PAIR(Grayscale, 8):
  174. case PAIR(Grayscale, 16):
  175. case PAIR(Truecolor, 8):
  176. case PAIR(Truecolor, 16):
  177. case PAIR(Indexed, 1):
  178. case PAIR(Indexed, 2):
  179. case PAIR(Indexed, 4):
  180. case PAIR(Indexed, 8):
  181. case PAIR(GrayscaleAlpha, 8):
  182. case PAIR(GrayscaleAlpha, 16):
  183. case PAIR(TruecolorAlpha, 8):
  184. case PAIR(TruecolorAlpha, 16):
  185. break;
  186. default:
  187. errx(
  188. EX_DATAERR, "%s: invalid color type %hhu and bit depth %hhu",
  189. path, header.color, header.depth
  190. );
  191. }
  192. if (header.compression != Deflate) {
  193. errx(
  194. EX_DATAERR, "%s: invalid compression method %hhu",
  195. path, header.compression
  196. );
  197. }
  198. if (header.filter != Adaptive) {
  199. errx(EX_DATAERR, "%s: invalid filter method %hhu", path, header.filter);
  200. }
  201. if (header.interlace > Adam7) {
  202. errx(EX_DATAERR, "%s: invalid interlace method %hhu", path, header.interlace);
  203. }
  204. if (verbose) printHeader();
  205. }
  206. static void writeHeader(void) {
  207. if (verbose) printHeader();
  208. struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" };
  209. writeChunk(ihdr);
  210. header.width = htonl(header.width);
  211. header.height = htonl(header.height);
  212. writeExpect(&header, sizeof(header));
  213. writeCrc();
  214. header.width = ntohl(header.width);
  215. header.height = ntohl(header.height);
  216. }
  217. static struct {
  218. uint32_t len;
  219. uint8_t entries[256][3];
  220. } palette;
  221. static struct {
  222. uint32_t len;
  223. uint8_t alpha[256];
  224. } trans;
  225. static void paletteClear(void) {
  226. palette.len = 0;
  227. trans.len = 0;
  228. }
  229. static uint32_t paletteIndex(bool alpha, const uint8_t *rgba) {
  230. uint32_t i;
  231. for (i = 0; i < palette.len; ++i) {
  232. if (alpha && i < trans.len && trans.alpha[i] != rgba[3]) continue;
  233. if (0 == memcmp(palette.entries[i], rgba, 3)) break;
  234. }
  235. return i;
  236. }
  237. static bool paletteAdd(bool alpha, const uint8_t *rgba) {
  238. uint32_t i = paletteIndex(alpha, rgba);
  239. if (i < palette.len) return true;
  240. if (i == 256) return false;
  241. memcpy(palette.entries[i], rgba, 3);
  242. palette.len++;
  243. if (alpha) {
  244. trans.alpha[i] = rgba[3];
  245. trans.len++;
  246. }
  247. return true;
  248. }
  249. static void transCompact(void) {
  250. uint32_t i;
  251. for (i = 0; i < trans.len; ++i) {
  252. if (trans.alpha[i] == 0xFF) break;
  253. }
  254. if (i == trans.len) return;
  255. for (uint32_t j = i + 1; j < trans.len; ++j) {
  256. if (trans.alpha[j] == 0xFF) continue;
  257. uint8_t alpha = trans.alpha[i];
  258. trans.alpha[i] = trans.alpha[j];
  259. trans.alpha[j] = alpha;
  260. uint8_t rgb[3];
  261. memcpy(rgb, palette.entries[i], 3);
  262. memcpy(palette.entries[i], palette.entries[j], 3);
  263. memcpy(palette.entries[j], rgb, 3);
  264. i++;
  265. }
  266. trans.len = i;
  267. }
  268. static void readPalette(struct Chunk chunk) {
  269. if (chunk.size % 3) {
  270. errx(EX_DATAERR, "%s: PLTE size %u not divisible by 3", path, chunk.size);
  271. }
  272. palette.len = chunk.size / 3;
  273. if (palette.len > 256) {
  274. errx(EX_DATAERR, "%s: PLTE length %u > 256", path, palette.len);
  275. }
  276. readExpect(palette.entries, chunk.size, "palette data");
  277. readCrc();
  278. if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len);
  279. }
  280. static void writePalette(void) {
  281. if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len);
  282. struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" };
  283. writeChunk(plte);
  284. writeExpect(palette.entries, plte.size);
  285. writeCrc();
  286. }
  287. static void readTrans(struct Chunk chunk) {
  288. trans.len = chunk.size;
  289. if (trans.len > 256) {
  290. errx(EX_DATAERR, "%s: tRNS length %u > 256", path, trans.len);
  291. }
  292. readExpect(trans.alpha, chunk.size, "transparency alpha");
  293. readCrc();
  294. if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len);
  295. }
  296. static void writeTrans(void) {
  297. if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len);
  298. struct Chunk trns = { .size = trans.len, .type = "tRNS" };
  299. writeChunk(trns);
  300. writeExpect(trans.alpha, trns.size);
  301. writeCrc();
  302. }
  303. static uint8_t *data;
  304. static void allocData(void) {
  305. data = malloc(dataSize());
  306. if (!data) err(EX_OSERR, "malloc(%zu)", dataSize());
  307. }
  308. static void readData(struct Chunk chunk) {
  309. if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize());
  310. struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() };
  311. int error = inflateInit(&stream);
  312. if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg);
  313. for (;;) {
  314. if (0 != memcmp(chunk.type, "IDAT", 4)) {
  315. errx(EX_DATAERR, "%s: missing IDAT chunk", path);
  316. }
  317. uint8_t *idat = malloc(chunk.size);
  318. if (!idat) err(EX_OSERR, "malloc");
  319. readExpect(idat, chunk.size, "image data");
  320. readCrc();
  321. stream.next_in = idat;
  322. stream.avail_in = chunk.size;
  323. int error = inflate(&stream, Z_SYNC_FLUSH);
  324. free(idat);
  325. if (error == Z_STREAM_END) break;
  326. if (error != Z_OK) {
  327. errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
  328. }
  329. chunk = readChunk();
  330. }
  331. inflateEnd(&stream);
  332. if (stream.total_out != dataSize()) {
  333. errx(
  334. EX_DATAERR, "%s: expected data size %zu, found %lu",
  335. path, dataSize(), stream.total_out
  336. );
  337. }
  338. if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, stream.total_in);
  339. }
  340. static void writeData(void) {
  341. if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize());
  342. uLong size = compressBound(dataSize());
  343. uint8_t *deflate = malloc(size);
  344. if (!deflate) err(EX_OSERR, "malloc");
  345. int error = compress2(deflate, &size, data, dataSize(), Z_BEST_COMPRESSION);
  346. if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error);
  347. struct Chunk idat = { .size = size, .type = "IDAT" };
  348. writeChunk(idat);
  349. writeExpect(deflate, size);
  350. writeCrc();
  351. free(deflate);
  352. if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, size);
  353. }
  354. static void writeEnd(void) {
  355. struct Chunk iend = { .size = 0, .type = "IEND" };
  356. writeChunk(iend);
  357. writeCrc();
  358. }
  359. enum PACKED Filter {
  360. None,
  361. Sub,
  362. Up,
  363. Average,
  364. Paeth,
  365. FilterCount,
  366. };
  367. struct Bytes {
  368. uint8_t x;
  369. uint8_t a;
  370. uint8_t b;
  371. uint8_t c;
  372. };
  373. static uint8_t paethPredictor(struct Bytes f) {
  374. int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
  375. int32_t pa = abs(p - (int32_t)f.a);
  376. int32_t pb = abs(p - (int32_t)f.b);
  377. int32_t pc = abs(p - (int32_t)f.c);
  378. if (pa <= pb && pa <= pc) return f.a;
  379. if (pb <= pc) return f.b;
  380. return f.c;
  381. }
  382. static uint8_t recon(enum Filter type, struct Bytes f) {
  383. switch (type) {
  384. case None: return f.x;
  385. case Sub: return f.x + f.a;
  386. case Up: return f.x + f.b;
  387. case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
  388. case Paeth: return f.x + paethPredictor(f);
  389. default: abort();
  390. }
  391. }
  392. static uint8_t filt(enum Filter type, struct Bytes f) {
  393. switch (type) {
  394. case None: return f.x;
  395. case Sub: return f.x - f.a;
  396. case Up: return f.x - f.b;
  397. case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
  398. case Paeth: return f.x - paethPredictor(f);
  399. default: abort();
  400. }
  401. }
  402. static struct Line {
  403. enum Filter type;
  404. uint8_t data[];
  405. } **lines;
  406. static void allocLines(void) {
  407. lines = calloc(header.height, sizeof(*lines));
  408. if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines));
  409. }
  410. static void scanlines(void) {
  411. size_t stride = 1 + lineSize();
  412. for (uint32_t y = 0; y < header.height; ++y) {
  413. lines[y] = (struct Line *)&data[y * stride];
  414. if (lines[y]->type >= FilterCount) {
  415. errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type);
  416. }
  417. }
  418. }
  419. static struct Bytes origBytes(uint32_t y, size_t i) {
  420. bool a = (i >= pixelSize()), b = (y > 0), c = (a && b);
  421. return (struct Bytes) {
  422. .x = lines[y]->data[i],
  423. .a = a ? lines[y]->data[i - pixelSize()] : 0,
  424. .b = b ? lines[y - 1]->data[i] : 0,
  425. .c = c ? lines[y - 1]->data[i - pixelSize()] : 0,
  426. };
  427. }
  428. static void reconData(void) {
  429. for (uint32_t y = 0; y < header.height; ++y) {
  430. for (size_t i = 0; i < lineSize(); ++i) {
  431. lines[y]->data[i] =
  432. recon(lines[y]->type, origBytes(y, i));
  433. }
  434. lines[y]->type = None;
  435. }
  436. }
  437. static void filterData(void) {
  438. if (header.color == Indexed || header.depth < 8) return;
  439. for (uint32_t y = header.height - 1; y < header.height; --y) {
  440. uint8_t filter[FilterCount][lineSize()];
  441. uint32_t heuristic[FilterCount] = {0};
  442. enum Filter minType = None;
  443. for (enum Filter type = None; type < FilterCount; ++type) {
  444. for (size_t i = 0; i < lineSize(); ++i) {
  445. filter[type][i] = filt(type, origBytes(y, i));
  446. heuristic[type] += abs((int8_t)filter[type][i]);
  447. }
  448. if (heuristic[type] < heuristic[minType]) minType = type;
  449. }
  450. lines[y]->type = minType;
  451. memcpy(lines[y]->data, filter[minType], lineSize());
  452. }
  453. }
  454. static void discardAlpha(void) {
  455. if (header.color != GrayscaleAlpha && header.color != TruecolorAlpha) return;
  456. size_t sampleSize = header.depth / 8;
  457. size_t colorSize = pixelSize() - sampleSize;
  458. for (uint32_t y = 0; y < header.height; ++y) {
  459. for (uint32_t x = 0; x < header.width; ++x) {
  460. for (size_t i = 0; i < sampleSize; ++i) {
  461. if (lines[y]->data[x * pixelSize() + colorSize + i] != 0xFF) return;
  462. }
  463. }
  464. }
  465. uint8_t *ptr = data;
  466. for (uint32_t y = 0; y < header.height; ++y) {
  467. *ptr++ = lines[y]->type;
  468. for (uint32_t x = 0; x < header.width; ++x) {
  469. memmove(ptr, &lines[y]->data[x * pixelSize()], colorSize);
  470. ptr += colorSize;
  471. }
  472. }
  473. header.color = (header.color == GrayscaleAlpha) ? Grayscale : Truecolor;
  474. scanlines();
  475. }
  476. static void discardColor(void) {
  477. if (header.color != Truecolor && header.color != TruecolorAlpha) return;
  478. size_t sampleSize = header.depth / 8;
  479. for (uint32_t y = 0; y < header.height; ++y) {
  480. for (uint32_t x = 0; x < header.width; ++x) {
  481. uint8_t *r = &lines[y]->data[x * pixelSize()];
  482. uint8_t *g = r + sampleSize;
  483. uint8_t *b = g + sampleSize;
  484. if (0 != memcmp(r, g, sampleSize)) return;
  485. if (0 != memcmp(g, b, sampleSize)) return;
  486. }
  487. }
  488. uint8_t *ptr = data;
  489. for (uint32_t y = 0; y < header.height; ++y) {
  490. *ptr++ = lines[y]->type;
  491. for (uint32_t x = 0; x < header.width; ++x) {
  492. uint8_t *pixel = &lines[y]->data[x * pixelSize()];
  493. memmove(ptr, pixel, sampleSize);
  494. ptr += sampleSize;
  495. if (header.color == TruecolorAlpha) {
  496. memmove(ptr, pixel + 3 * sampleSize, sampleSize);
  497. ptr += sampleSize;
  498. }
  499. }
  500. }
  501. header.color = (header.color == Truecolor) ? Grayscale : GrayscaleAlpha;
  502. scanlines();
  503. }
  504. static void indexColor(void) {
  505. if (header.color != Truecolor && header.color != TruecolorAlpha) return;
  506. if (header.depth != 8) return;
  507. bool alpha = (header.color == TruecolorAlpha);
  508. for (uint32_t y = 0; y < header.height; ++y) {
  509. for (uint32_t x = 0; x < header.width; ++x) {
  510. if (!paletteAdd(alpha, &lines[y]->data[x * pixelSize()])) return;
  511. }
  512. }
  513. transCompact();
  514. uint8_t *ptr = data;
  515. for (uint32_t y = 0; y < header.height; ++y) {
  516. *ptr++ = lines[y]->type;
  517. for (uint32_t x = 0; x < header.width; ++x) {
  518. *ptr++ = paletteIndex(alpha, &lines[y]->data[x * pixelSize()]);
  519. }
  520. }
  521. header.color = Indexed;
  522. scanlines();
  523. }
  524. static void reduceDepth8(void) {
  525. if (header.color != Grayscale && header.color != Indexed) return;
  526. if (header.depth != 8) return;
  527. if (header.color == Grayscale) {
  528. for (uint32_t y = 0; y < header.height; ++y) {
  529. for (size_t i = 0; i < lineSize(); ++i) {
  530. uint8_t a = lines[y]->data[i];
  531. if ((a >> 4) != (a & 0x0F)) return;
  532. }
  533. }
  534. } else if (palette.len > 16) {
  535. return;
  536. }
  537. uint8_t *ptr = data;
  538. for (uint32_t y = 0; y < header.height; ++y) {
  539. *ptr++ = lines[y]->type;
  540. for (size_t i = 0; i < lineSize(); i += 2) {
  541. uint8_t iByte = lines[y]->data[i];
  542. uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
  543. uint8_t a = iByte & 0x0F;
  544. uint8_t b = jByte & 0x0F;
  545. *ptr++ = a << 4 | b;
  546. }
  547. }
  548. header.depth = 4;
  549. scanlines();
  550. }
  551. static void reduceDepth4(void) {
  552. if (header.depth != 4) return;
  553. if (header.color == Grayscale) {
  554. for (uint32_t y = 0; y < header.height; ++y) {
  555. for (size_t i = 0; i < lineSize(); ++i) {
  556. uint8_t a = lines[y]->data[i] >> 4;
  557. uint8_t b = lines[y]->data[i] & 0x0F;
  558. if ((a >> 2) != (a & 0x03)) return;
  559. if ((b >> 2) != (b & 0x03)) return;
  560. }
  561. }
  562. } else if (palette.len > 4) {
  563. return;
  564. }
  565. uint8_t *ptr = data;
  566. for (uint32_t y = 0; y < header.height; ++y) {
  567. *ptr++ = lines[y]->type;
  568. for (size_t i = 0; i < lineSize(); i += 2) {
  569. uint8_t iByte = lines[y]->data[i];
  570. uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
  571. uint8_t a = iByte >> 4 & 0x03, b = iByte & 0x03;
  572. uint8_t c = jByte >> 4 & 0x03, d = jByte & 0x03;
  573. *ptr++ = a << 6 | b << 4 | c << 2 | d;
  574. }
  575. }
  576. header.depth = 2;
  577. scanlines();
  578. }
  579. static void reduceDepth2(void) {
  580. if (header.depth != 2) return;
  581. if (header.color == Grayscale) {
  582. for (uint32_t y = 0; y < header.height; ++y) {
  583. for (size_t i = 0; i < lineSize(); ++i) {
  584. uint8_t a = lines[y]->data[i] >> 6;
  585. uint8_t b = lines[y]->data[i] >> 4 & 0x03;
  586. uint8_t c = lines[y]->data[i] >> 2 & 0x03;
  587. uint8_t d = lines[y]->data[i] & 0x03;
  588. if ((a >> 1) != (a & 0x01)) return;
  589. if ((b >> 1) != (b & 0x01)) return;
  590. if ((c >> 1) != (c & 0x01)) return;
  591. if ((d >> 1) != (d & 0x01)) return;
  592. }
  593. }
  594. } else if (palette.len > 2) {
  595. return;
  596. }
  597. uint8_t *ptr = data;
  598. for (uint32_t y = 0; y < header.height; ++y) {
  599. *ptr++ = lines[y]->type;
  600. for (size_t i = 0; i < lineSize(); i += 2) {
  601. uint8_t iByte = lines[y]->data[i];
  602. uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
  603. uint8_t a = iByte >> 6 & 0x01, b = iByte >> 4 & 0x01;
  604. uint8_t c = iByte >> 2 & 0x01, d = iByte & 0x01;
  605. uint8_t e = jByte >> 6 & 0x01, f = jByte >> 4 & 0x01;
  606. uint8_t g = jByte >> 2 & 0x01, h = jByte & 0x01;
  607. *ptr++ = a << 7 | b << 6 | c << 5 | d << 4 | e << 3 | f << 2 | g << 1 | h;
  608. }
  609. }
  610. header.depth = 1;
  611. scanlines();
  612. }
  613. static void reduceDepth(void) {
  614. reduceDepth8();
  615. reduceDepth4();
  616. reduceDepth2();
  617. }
  618. static void optimize(const char *inPath, const char *outPath) {
  619. if (inPath) {
  620. path = inPath;
  621. file = fopen(path, "r");
  622. if (!file) err(EX_NOINPUT, "%s", path);
  623. } else {
  624. path = "(stdin)";
  625. file = stdin;
  626. }
  627. readSignature();
  628. struct Chunk ihdr = readChunk();
  629. if (0 != memcmp(ihdr.type, "IHDR", 4)) {
  630. errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr));
  631. }
  632. readHeader(ihdr);
  633. if (header.interlace != Progressive) {
  634. errx(
  635. EX_CONFIG, "%s: unsupported interlace method %hhu",
  636. path, header.interlace
  637. );
  638. }
  639. paletteClear();
  640. allocData();
  641. for (;;) {
  642. struct Chunk chunk = readChunk();
  643. if (0 == memcmp(chunk.type, "PLTE", 4)) {
  644. readPalette(chunk);
  645. } else if (0 == memcmp(chunk.type, "tRNS", 4)) {
  646. readTrans(chunk);
  647. } else if (0 == memcmp(chunk.type, "IDAT", 4)) {
  648. readData(chunk);
  649. } else if (0 != memcmp(chunk.type, "IEND", 4)) {
  650. skipChunk(chunk);
  651. } else {
  652. break;
  653. }
  654. }
  655. fclose(file);
  656. allocLines();
  657. scanlines();
  658. reconData();
  659. discardAlpha();
  660. discardColor();
  661. indexColor();
  662. reduceDepth();
  663. filterData();
  664. free(lines);
  665. if (outPath) {
  666. path = outPath;
  667. file = fopen(path, "w");
  668. if (!file) err(EX_CANTCREAT, "%s", path);
  669. } else {
  670. path = "(stdout)";
  671. file = stdout;
  672. }
  673. writeSignature();
  674. writeHeader();
  675. if (header.color == Indexed) {
  676. writePalette();
  677. if (trans.len) writeTrans();
  678. }
  679. writeData();
  680. writeEnd();
  681. free(data);
  682. int error = fclose(file);
  683. if (error) err(EX_IOERR, "%s", path);
  684. }
  685. int main(int argc, char *argv[]) {
  686. bool stdio = false;
  687. char *output = NULL;
  688. int opt;
  689. while (0 < (opt = getopt(argc, argv, "co:v"))) {
  690. switch (opt) {
  691. break; case 'c': stdio = true;
  692. break; case 'o': output = optarg;
  693. break; case 'v': verbose = true;
  694. break; default: return EX_USAGE;
  695. }
  696. }
  697. if (argc - optind == 1 && (output || stdio)) {
  698. optimize(argv[optind], output);
  699. } else if (optind < argc) {
  700. for (int i = optind; i < argc; ++i) {
  701. optimize(argv[i], argv[i]);
  702. }
  703. } else {
  704. optimize(NULL, output);
  705. }
  706. return EX_OK;
  707. }