diff --git a/Makefile b/Makefile index 592ddc1..189f517 100644 --- a/Makefile +++ b/Makefile @@ -3,8 +3,14 @@ LDLIBS += -lm all: img2cpi cpi2png wsvpn +test-cpi2png: cpi2png + ./cpi2png ./cpi-images/rat.cpi /tmp/rat.png + img2cpi: img2cpi.c cc-common.o dependencies/stb/stb_image.o dependencies/stb/stb_image_resize2.o - $(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) + +cpi2png: cpi2png.c cc-common.o dependencies/stb/stb_image_write.o + $(CC) $(LDFLAGS) $^ $(LDLIBS) -o "$@" $(CPPFLAGS) dependencies/stb/%.o: dependencies/stb/%.h $(CC) -DSTB_IMAGE_IMPLEMENTATION -DSTB_IMAGE_RESIZE_IMPLEMENTATION -DSTB_IMAGE_WRITE_IMPLEMENTATION -x c $^ -c -o "$@" diff --git a/cpi2png.c b/cpi2png.c index b599272..9038073 100644 --- a/cpi2png.c +++ b/cpi2png.c @@ -1,14 +1,108 @@ -// x-run: make cpi2png +// x-run: make test-cpi2png +#include +#include +#include #include - -#define STB_IMAGE_IMPLEMENTATION #include -#define STB_IMAGE_WRITE_IMPLEMENTATION #include +#include +#include "cc-common.h" + +bool read_varint(FILE *fp, unsigned int *out); int main(int argc, char **argv) { + if (argc < 3) { + fprintf(stderr, "Usage: %s [input.cpi] [output.png]\n", argv[0]); + } + FILE *fp_in = fopen(argv[1], "rb"); + assert(fp_in != NULL && "Failed to open input file"); + + unsigned char header[4]; + + unsigned char version = 0; + assert(fread(header, 1, 4, fp_in) == 4 && "Failed to read header: not enough bytes"); + if (0 == memcmp(header, "CCPI", 4)) { // Original CCPI (CPIv0) + printf("CPIv0 (old header)\n"); + } else if (0 == memcmp(header, "CPI", 3)) { // Newer CCPI (CPIvX) + version = header[3]; + } else { + assert(false && "Not a CPI/CCPI image: invalid header"); + } + + if (version & 0x80) { + fprintf(stderr, "Draft version: 0x%02x may not be supported properly! Here be dragons!\n", version); + } + + unsigned int width = 0, height = 0; + + if (version == 0) { + width = fgetc(fp_in); + height = fgetc(fp_in); + (void)fgetc(fp_in); // XXX: ignore scale + } else if (version == 1) { + assert(read_varint(fp_in, &width) && "Failed to read width varint"); + assert(read_varint(fp_in, &height) && "Failed to read height varint"); + } else { + assert(false && "Failed to read size: unsupported version"); + } + + union color *canvas = malloc(width * height * 8 * 11 * sizeof(union color)); + + + // XXX: may change in future when we introduce variable-size palettes + // though, it may never change, if I'm being honest. Why would I choose + // worse image quality with less colors when I can use all of them? + union color *colors = calloc(16, sizeof(union color)); + + // NOTE: our `union color` type is 4 bytes long, while palette stored in the + // file itself uses 3 bytes per color, so we can't just `fread` them at once, + // sadly. + for (int i = 0; i < 16; i++) { + colors[i].rgba.r = fgetc(fp_in); + colors[i].rgba.g = fgetc(fp_in); + colors[i].rgba.b = fgetc(fp_in); + colors[i].rgba.a = 0xff; + } + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + unsigned char sym = fgetc(fp_in); + unsigned char color = fgetc(fp_in); + union color background = colors[color & 0xF]; + union color foreground = colors[color >> 4]; + for (int oy = 0; oy < 11; oy++) { + for (int ox = 0; ox < 8; ox++) { + union color pix = ((0x80 >> ox) & cc_font_atlas[sym][oy]) ? foreground : background; + canvas[ox + (x + (y * 11 + oy) * width) * 8] = pix; + } + } + } + } + + stbi_write_png(argv[2], width * 8, height * 11, 4, canvas, 0); + + free(colors); + free(canvas); + fclose(fp_in); return EXIT_SUCCESS; } + +bool read_varint(FILE *fp, unsigned int *out) { + int position = 0; + + while (true) { + unsigned char curr = fgetc(fp); + *out |= (curr & 0x7F) << position; + + if ((curr & 0x80) == 0) break; + + position += 7; + + if (position >= 32) return false; + } + + return true; +}