2024-10-03 20:28:49 +03:00
|
|
|
// x-run: make test-cpi2png
|
2024-10-03 18:57:44 +03:00
|
|
|
|
2024-10-03 20:28:49 +03:00
|
|
|
#include <assert.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdio.h>
|
2024-10-03 18:57:44 +03:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stb/stb_image.h>
|
|
|
|
#include <stb/stb_image_write.h>
|
2024-10-03 20:28:49 +03:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "cc-common.h"
|
2024-10-03 18:57:44 +03:00
|
|
|
|
2024-10-03 20:28:49 +03:00
|
|
|
bool read_varint(FILE *fp, unsigned int *out);
|
2024-10-03 18:57:44 +03:00
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
2024-10-03 20:28:49 +03:00
|
|
|
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)
|
2024-10-03 20:39:04 +03:00
|
|
|
version = 0;
|
2024-10-03 20:28:49 +03:00
|
|
|
} 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));
|
2024-10-03 18:57:44 +03:00
|
|
|
|
2024-10-03 20:28:49 +03:00
|
|
|
// 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);
|
2024-10-03 18:57:44 +03:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
2024-10-03 20:28:49 +03:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|