Collaborative ASCII art https://ascii.town
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.

image.c 7.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. /* Copyright (C) 2018, 2019 C. 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 <stdbool.h>
  19. #include <stdint.h>
  20. #include <stdio.h>
  21. #include <stdlib.h>
  22. #include <string.h>
  23. #include <sys/mman.h>
  24. #include <sys/stat.h>
  25. #include <sysexits.h>
  26. #include <unistd.h>
  27. #include <zlib.h>
  28. #ifdef __FreeBSD__
  29. #include <sys/capsicum.h>
  30. #endif
  31. #ifdef HAVE_KCGI
  32. #include <sys/types.h>
  33. #include <stdarg.h>
  34. #include <stdint.h>
  35. #include <kcgi.h>
  36. #endif
  37. // XXX: Include this after kcgi.h to avoid conflicts.
  38. // <https://github.com/kristapsdz/kcgi/pull/58>
  39. #include <stdnoreturn.h>
  40. #include "png.h"
  41. #include "torus.h"
  42. static const uint8_t Palette[16][3] = {
  43. { 0x00, 0x00, 0x00 },
  44. { 0xAA, 0x00, 0x00 },
  45. { 0x00, 0xAA, 0x00 },
  46. { 0xAA, 0x55, 0x00 },
  47. { 0x00, 0x00, 0xAA },
  48. { 0xAA, 0x00, 0xAA },
  49. { 0x00, 0xAA, 0xAA },
  50. { 0xAA, 0xAA, 0xAA },
  51. { 0x55, 0x55, 0x55 },
  52. { 0xFF, 0x55, 0x55 },
  53. { 0x55, 0xFF, 0x55 },
  54. { 0xFF, 0xFF, 0x55 },
  55. { 0x55, 0x55, 0xFF },
  56. { 0xFF, 0x55, 0xFF },
  57. { 0x55, 0xFF, 0xFF },
  58. { 0xFF, 0xFF, 0xFF },
  59. };
  60. static struct {
  61. uint32_t magic;
  62. uint32_t version;
  63. uint32_t size;
  64. uint32_t flags;
  65. struct {
  66. uint32_t len;
  67. uint32_t size;
  68. uint32_t height;
  69. uint32_t width;
  70. } glyph;
  71. } font;
  72. static uint8_t *glyphs;
  73. static void fontLoad(const char *path) {
  74. FILE *file = fopen(path, "r");
  75. if (!file) err(EX_NOINPUT, "%s", path);
  76. size_t len = fread(&font, sizeof(font), 1, file);
  77. if (ferror(file)) err(EX_IOERR, "%s", path);
  78. if (len < 1) errx(EX_DATAERR, "%s: truncated header", path);
  79. if (font.magic != 0x864AB572 || font.size != sizeof(font)) {
  80. errx(EX_DATAERR, "%s: invalid header", path);
  81. }
  82. glyphs = calloc(font.glyph.len, font.glyph.size);
  83. if (!glyphs) err(EX_OSERR, "calloc");
  84. len = fread(glyphs, font.glyph.size, font.glyph.len, file);
  85. if (ferror(file)) err(EX_IOERR, "%s", path);
  86. if (len < font.glyph.len) errx(EX_DATAERR, "%s: truncated glyphs", path);
  87. fclose(file);
  88. }
  89. static struct Tile (*tiles)[TileRows][TileCols];
  90. static void tilesMap(const char *path) {
  91. int fd = open(path, O_RDONLY);
  92. if (fd < 0) err(EX_NOINPUT, "%s", path);
  93. struct stat stat;
  94. int error = fstat(fd, &stat);
  95. if (error) err(EX_IOERR, "%s", path);
  96. if ((size_t)stat.st_size < TilesSize) {
  97. errx(EX_DATAERR, "%s: truncated tiles", path);
  98. }
  99. tiles = mmap(NULL, TilesSize, PROT_READ, MAP_SHARED, fd, 0);
  100. if (tiles == MAP_FAILED) err(EX_OSERR, "mmap");
  101. close(fd);
  102. error = madvise(tiles, TilesSize, MADV_RANDOM);
  103. if (error) err(EX_OSERR, "madvise");
  104. #ifdef MADV_NOCORE
  105. error = madvise(tiles, TilesSize, MADV_NOCORE);
  106. if (error) err(EX_OSERR, "madvise");
  107. #endif
  108. }
  109. static void render(FILE *stream, uint32_t tileX, uint32_t tileY) {
  110. uint32_t width = CellCols * font.glyph.width;
  111. uint32_t height = CellRows * font.glyph.height;
  112. pngHead(stream, width, height, 8, PNGIndexed);
  113. pngPalette(stream, (uint8_t *)Palette, sizeof(Palette));
  114. uint8_t data[height][1 + width];
  115. memset(data, PNGNone, sizeof(data));
  116. uint32_t widthBytes = (font.glyph.width + 7) / 8;
  117. uint8_t (*bits)[font.glyph.len][font.glyph.height][widthBytes];
  118. bits = (void *)glyphs;
  119. struct Tile *tile = &(*tiles)[tileY][tileX];
  120. for (uint32_t cellY = 0; cellY < CellRows; ++cellY) {
  121. for (uint32_t cellX = 0; cellX < CellCols; ++cellX) {
  122. uint8_t cell = tile->cells[cellY][cellX];
  123. uint8_t fg = tile->colors[cellY][cellX] & 0x0F;
  124. uint8_t bg = tile->colors[cellY][cellX] >> 4;
  125. uint32_t glyphX = font.glyph.width * cellX;
  126. uint32_t glyphY = font.glyph.height * cellY;
  127. for (uint32_t y = 0; y < font.glyph.height; ++y) {
  128. for (uint32_t x = 0; x < font.glyph.width; ++x) {
  129. uint8_t bit = (*bits)[cell][y][x / 8] >> (7 - x % 8) & 1;
  130. data[glyphY + y][1 + glyphX + x] = (bit ? fg : bg);
  131. }
  132. }
  133. }
  134. }
  135. uLong zlen = compressBound(sizeof(data));
  136. uint8_t zdata[zlen];
  137. int error = compress(zdata, &zlen, (uint8_t *)data, sizeof(data));
  138. if (error) errx(EX_SOFTWARE, "compress: %d", error);
  139. pngDeflated(stream, zdata, (size_t)zlen);
  140. pngTail(stream);
  141. }
  142. #ifdef HAVE_KCGI
  143. enum { KeyX, KeyY, KeysLen };
  144. static const struct kvalid Keys[KeysLen] = {
  145. [KeyX] = { .name = "x", .valid = kvalid_int },
  146. [KeyY] = { .name = "y", .valid = kvalid_int },
  147. };
  148. enum { PageTile, PagesLen };
  149. static const char *Pages[PagesLen] = {
  150. [PageTile] = "tile",
  151. };
  152. static noreturn void errkcgi(int eval, enum kcgi_err code, const char *str) {
  153. errx(eval, "%s: %s", str, kcgi_strerror(code));
  154. }
  155. struct Stream {
  156. struct kreq *req;
  157. bool hup;
  158. };
  159. // XXX: Swallow writes after the connection is closed.
  160. static int streamWrite(void *cookie, const char *buf, int len) {
  161. struct Stream *stream = cookie;
  162. if (stream->hup) return len;
  163. enum kcgi_err error = khttp_write(stream->req, buf, (size_t)len);
  164. if (error) {
  165. if (error != KCGI_HUP) errkcgi(EX_IOERR, error, "khttp_write");
  166. stream->hup = true;
  167. }
  168. return len;
  169. }
  170. static void worker(void) {
  171. struct kfcgi *fcgi;
  172. enum kcgi_err error = khttp_fcgi_init(
  173. &fcgi, Keys, KeysLen, Pages, PagesLen, PageTile
  174. );
  175. if (error) errkcgi(EX_CONFIG, error, "khttp_fcgi_init");
  176. for (;;) {
  177. struct kreq req;
  178. error = khttp_fcgi_parse(fcgi, &req);
  179. if (error) errkcgi(EX_DATAERR, error, "khttp_fcgi_parse");
  180. uint32_t tileX = TileInitX;
  181. uint32_t tileY = TileInitY;
  182. if (req.fieldmap[KeyX]) {
  183. tileX = (uint32_t)req.fieldmap[KeyX]->parsed.i % TileCols;
  184. }
  185. if (req.fieldmap[KeyY]) {
  186. tileY = (uint32_t)req.fieldmap[KeyY]->parsed.i % TileRows;
  187. }
  188. error = khttp_head(
  189. &req, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]
  190. );
  191. if (error == KCGI_HUP) goto next;
  192. if (error) errkcgi(EX_IOERR, error, "khttp_head");
  193. error = khttp_head(
  194. &req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_IMAGE_PNG]
  195. );
  196. if (error == KCGI_HUP) goto next;
  197. if (error) errkcgi(EX_IOERR, error, "khttp_head");
  198. // XXX: kcgi never enables compression for FastCGI.
  199. error = khttp_body(&req);
  200. if (error == KCGI_HUP) goto next;
  201. if (error) errkcgi(EX_IOERR, error, "khttp_body");
  202. struct Stream cookie = { .req = &req };
  203. FILE *stream = fwopen(&cookie, streamWrite);
  204. if (!stream) err(EX_OSERR, "fwopen");
  205. render(stream, tileX, tileY);
  206. fclose(stream);
  207. next:
  208. khttp_free(&req);
  209. }
  210. }
  211. #endif /* HAVE_KCGI */
  212. int main(int argc, char *argv[]) {
  213. bool kcgi = false;
  214. const char *fontPath = DefaultFontPath;
  215. const char *dataPath = DefaultDataPath;
  216. uint32_t tileX = TileInitX;
  217. uint32_t tileY = TileInitY;
  218. int opt;
  219. while (0 < (opt = getopt(argc, argv, "d:f:kx:y:"))) {
  220. switch (opt) {
  221. break; case 'd': dataPath = optarg;
  222. break; case 'f': fontPath = optarg;
  223. break; case 'k': kcgi = true;
  224. break; case 'x': tileX = strtoul(optarg, NULL, 0) % TileCols;
  225. break; case 'y': tileY = strtoul(optarg, NULL, 0) % TileRows;
  226. break; default: return EX_USAGE;
  227. }
  228. }
  229. fontLoad(fontPath);
  230. tilesMap(dataPath);
  231. #ifdef __FreeBSD__
  232. int error = cap_enter();
  233. if (error) err(EX_OSERR, "cap_enter");
  234. #endif
  235. #ifdef HAVE_KCGI
  236. if (kcgi) worker();
  237. #endif
  238. render(stdout, tileX, tileY);
  239. }