CPI -> PNG renderer

Supports CPIv0 and CPIv1 for now, but other formats are experimental
anyways, so not a big problem
This commit is contained in:
Casey 2024-10-03 20:28:49 +03:00
parent 8589b2a730
commit d4ca60c9f9
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
2 changed files with 105 additions and 5 deletions

View File

@ -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 "$@"

102
cpi2png.c
View File

@ -1,14 +1,108 @@
// x-run: make cpi2png
// x-run: make test-cpi2png
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb/stb_image_write.h>
#include <string.h>
#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;
}