summaryrefslogtreecommitdiff
path: root/src/bin/starpbm.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/starpbm.c')
-rw-r--r--src/bin/starpbm.c153
1 files changed, 153 insertions, 0 deletions
diff --git a/src/bin/starpbm.c b/src/bin/starpbm.c
new file mode 100644
index 0000000..fd3f235
--- /dev/null
+++ b/src/bin/starpbm.c
@@ -0,0 +1,153 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <err.h>
+
+#define MAX(x, y) (((x) > (y)) ? (x) : (y))
+
+enum cmd {
+ CMD_INIT,
+ CMD_PRINTABLE_WIDTH,
+ CMD_RASTER_MODE_START,
+ CMD_RASTER_PAGE_LENGTH,
+ CMD_START_PAGE,
+ CMD_END_PAGE,
+ CMD_END_JOB,
+};
+
+static void
+putcmd(FILE *f, const enum cmd cmd)
+{
+ assert(f);
+
+ const struct {
+ const uint8_t *bytes;
+ size_t len;
+ } map[] = {
+#define CMD(...) { .bytes = (uint8_t[]){__VA_ARGS__ }, .len = sizeof((uint8_t[]){__VA_ARGS__}) }
+ CMD(0x1b, '@'), // CMD_INIT
+ CMD(0x1b, 0x1e, 'A', 0x00), // CMD_PRINTABLE_WIDTH
+ CMD(0x1b, '*', 'r', 'R', 0x1b, '*', 'r', 'A'), // CMD_RASTER_MODE_START
+ CMD(0x1b, '*', 'r', 'P', '0', 0x00), // CMD_SET_RASTER_PAGE_LENGTH
+ CMD(0x00), // CMD_START_PAGE
+ CMD(0x1b, '*', 'r', 'Y', '1', 0x00, 0x1b, 0x0c), // CMD_END_PAGE
+ CMD(0x04, 0x1b, '*', 'r', 'B'), // CMD_END_JOB
+#undef CMD
+ };
+
+ if (fwrite(map[cmd].bytes, 1, map[cmd].len, f) != map[cmd].len)
+ err(EXIT_FAILURE, "fwrite");
+}
+
+static FILE*
+fopen_or_die(const char *path, const char *mode)
+{
+ assert(path && mode);
+
+ FILE *f;
+ if (!(f = fopen(path, mode)))
+ err(EXIT_FAILURE, "fopen(%s, %s)", path, mode);
+
+ return f;
+}
+
+static uint8_t*
+find_pixel_not_rev(const uint8_t *data, const size_t len, const uint8_t pixel)
+{
+ for (const uint8_t *p = data + MAX(len, 1) - 1; p != data; --p) {
+ if (*p != pixel)
+ return (uint8_t*)p;
+ }
+ return NULL;
+}
+
+uint8_t*
+pixels_from_pbm(FILE *f, size_t *out_w, size_t *out_h)
+{
+ // NOTE: This does not support comments or extra whitespaces
+ // Assumes PBM is structured!
+
+ uint8_t magic[3];
+ if (fread(magic, 1, sizeof(magic), f) != sizeof(magic))
+ return NULL;
+
+ if (memcmp(magic, "P4", 2) || !isspace(magic[2]))
+ errx(EXIT_FAILURE, "not a valid pbm (P4) file");
+
+ uint32_t cfg[2];
+ for (uint8_t i = 0; i < sizeof(cfg) / sizeof(cfg[0]); ++i) {
+ char buf[32];
+ size_t x = 0;
+ do {
+ if (fread(buf + x, 1, 1, f) != 1)
+ err(EXIT_FAILURE, "fread");
+ } while (!isspace(buf[x]) && ++x < sizeof(buf));
+
+ if (!isspace(buf[x]))
+ errx(EXIT_FAILURE, "not a valid pbm (P4) file");
+
+ cfg[i] = strtol(buf, NULL, 10);
+ }
+
+ if (!cfg[0] || !cfg[2])
+ errx(EXIT_FAILURE, "invalid width / height");
+
+ uint8_t *bw;
+#define div_round_up(x, y) (1 + ((x - 1) / y))
+ const size_t bw_sz = div_round_up(cfg[0], 8) * cfg[1];
+ if (!(bw = calloc(bw_sz, 1)))
+ err(EXIT_FAILURE, "calloc");
+
+ for (size_t read = 0, r; read < bw_sz; read += r) {
+ if (!(r = fread(bw + read, 1, bw_sz - read, f)))
+ errx(EXIT_FAILURE, "fread");
+ }
+
+ *out_w = div_round_up(cfg[0], 8), *out_h = cfg[1];
+ return bw;
+}
+
+int
+main(int argc, char *argv[])
+{
+ FILE *input = (argc >= 2 ? fopen_or_die(argv[1], "rb") : stdin);
+ FILE *output = (argc >= 3 ? fopen_or_die(argv[2], "wb") : stdout);
+
+ putcmd(output, CMD_INIT);
+ putcmd(output, CMD_PRINTABLE_WIDTH);
+ putcmd(output, CMD_RASTER_MODE_START);
+ putcmd(output, CMD_RASTER_PAGE_LENGTH);
+
+ size_t w, h;
+ uint8_t *pixels;
+ while ((pixels = pixels_from_pbm(input, &w, &h))) {
+ putcmd(output, CMD_START_PAGE);
+
+ size_t blank = 0;
+ for (size_t y = 0; y < h; ++y) {
+ const uint8_t *last_black;
+ if (!(last_black = find_pixel_not_rev(&pixels[y * w], w, 0))) {
+ ++blank;
+ continue;
+ }
+
+ if (blank) {
+ fprintf(output, "\x01b*rY%zu%c", blank, 0);
+ blank = 0;
+ }
+
+ const size_t pos = (last_black - &pixels[y * w]) + 1;
+ fprintf(output, "b%c%c", (char)(pos % 256), (char)(pos / 256));
+ fwrite(&pixels[y * w], 1, pos, output);
+ }
+
+ putcmd(output, CMD_END_PAGE);
+ free(pixels);
+ }
+
+ putcmd(output, CMD_END_JOB);
+ return EXIT_SUCCESS;
+}