From f54e7e8c2aeb4fafebf0d5bd5570b060462c9ecf Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Thu, 1 Feb 2018 08:31:01 +0200 Subject: Initial commit --- src/bin/starpbm.c | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/bin/starpbm.c (limited to 'src/bin/starpbm.c') 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 +#include +#include +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3