Created
April 19, 2021 21:36
-
-
Save MCJack123/4eb02b57de9fcb9c6e62b9b459ada8e6 to your computer and use it in GitHub Desktop.
Render CraftOS-PC raw terminal packets to PNG (requires png++)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compile together with https://github.com/MCJack123/craftos2/blob/master/src/font.c | |
#include <iostream> | |
#include <sstream> | |
#include <algorithm> | |
#include <array> | |
#include <numeric> | |
#include <png++/png.hpp> | |
extern "C" { | |
struct font_image { | |
unsigned int width; | |
unsigned int height; | |
unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ | |
unsigned char pixel_data[128 * 175 * 2 + 1]; | |
}; | |
extern struct font_image font_image; | |
} | |
struct Rect { | |
int x; | |
int y; | |
int w; | |
int h; | |
}; | |
static constexpr unsigned fontWidth = 6; | |
static constexpr unsigned fontHeight = 9; | |
static constexpr unsigned fontScale = 2; | |
static constexpr unsigned charScale = 2; | |
static constexpr unsigned dpiScale = 1; | |
static constexpr unsigned charWidth = fontWidth * 2/fontScale * charScale; | |
static constexpr unsigned charHeight = fontHeight * 2/fontScale * charScale; | |
static unsigned short width; | |
static unsigned short height; | |
static png::image<png::rgb_pixel> font_img(font_image.width, font_image.height); | |
static void blit(const png::image<png::rgb_pixel>& src, Rect srcrect, png::image<png::rgb_pixel>& dest, Rect destrect, png::rgb_pixel colormod) { | |
const int xratio = destrect.w / srcrect.w; | |
const int yratio = destrect.h / srcrect.h; | |
for (int y = 0; y < destrect.h; y += yratio) | |
for (int i = 0; i < yratio; i++) | |
for (int x = 0; x < destrect.w; x += xratio) | |
for (int j = 0; j < xratio; j++) | |
if (src.get_pixel(srcrect.x + (x / xratio), srcrect.y + (y / yratio)).red > 64) | |
dest.set_pixel(destrect.x + x + j, destrect.y + y + i, colormod); | |
} | |
static void fill(png::image<png::rgb_pixel>& img, Rect rect, png::rgb_pixel color) { | |
for (int y = rect.y; y < rect.y + rect.h; y++) | |
for (int x = rect.x; x < rect.x + rect.w; x++) | |
img.set_pixel(x, y, color); | |
} | |
static void drawChar(png::image<png::rgb_pixel>& img, unsigned char c, int x, int y, png::rgb_pixel fg, png::rgb_pixel bg, bool transparent) { | |
Rect srcrect = { | |
(int)(((fontWidth + 2))*(c & 0x0F)+1), | |
(int)(((fontHeight + 2))*(c >> 4)+1), | |
(int)(fontWidth), | |
(int)(fontHeight) | |
}; | |
Rect destrect = { | |
(int)(x * charWidth * dpiScale + 2 * charScale * dpiScale), | |
(int)(y * charHeight * dpiScale + 2 * charScale * dpiScale), | |
(int)(fontWidth * charScale * dpiScale), | |
(int)(fontHeight * charScale * dpiScale) | |
}; | |
if (!transparent) fill(img, destrect, bg); | |
if (c != ' ' && c != '\0') blit(font_img, srcrect, img, destrect, fg); | |
} | |
inline png::rgb_pixel fromrgb16(unsigned short val) { | |
return png::rgb_pixel((val >> 8) & 0xF8, (val >> 7) & 0xFC, (val & 0x1F) << 3); | |
} | |
static std::string base64_decode(const std::string &in) { | |
std::string out; | |
std::vector<int> T(256,-1); | |
for (int i=0; i<64; i++) T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i; | |
int val=0, valb=-8; | |
for (unsigned char c : in) { | |
if (T[c] == -1) break; | |
val = (val << 6) + T[c]; | |
valb += 6; | |
if (valb >= 0) { | |
out.push_back(char((val>>valb)&0xFF)); | |
valb -= 8; | |
} | |
} | |
return out; | |
} | |
static std::array<unsigned int, 256> generate_crc_lookup_table() noexcept { | |
auto const reversed_polynomial = unsigned int{0xEDB88320uL}; | |
struct byte_checksum { | |
unsigned n = 0; | |
unsigned int operator()() noexcept { | |
auto checksum = static_cast<unsigned int>(n++); | |
for (auto i = 0; i < 8; ++i) | |
checksum = (checksum >> 1) ^ ((checksum & 0x1u) ? reversed_polynomial : 0); | |
return checksum; | |
} | |
}; | |
auto table = std::array<unsigned int, 256>{}; | |
std::generate(table.begin(), table.end(), byte_checksum{}); | |
return table; | |
} | |
template <typename InputIterator> | |
static unsigned int crc(InputIterator first, InputIterator last) { | |
static auto const table = generate_crc_lookup_table(); | |
return unsigned int{0xFFFFFFFFuL} & ~std::accumulate(first, last, | |
~unsigned int{0} & unsigned int{0xFFFFFFFFuL}, | |
[](unsigned int checksum, unsigned char value) | |
{ return table[(checksum ^ value) & 0xFFu] ^ (checksum >> 8); }); | |
} | |
int main(int argc, const char * argv[]) { | |
for (int y = 0; y < font_image.height; y++) | |
for (int x = 0; x < font_image.width; x++) | |
font_img.set_pixel(x, y, fromrgb16(((unsigned short*)font_image.pixel_data)[y*font_image.width+x])); | |
std::string data; | |
std::getline(std::cin, data); | |
if (data.substr(0, 4) != "!CPC") { | |
std::cerr << "Invalid format packet\n"; | |
return 2; | |
} | |
int packet_size = std::stoi(data.substr(4, 4), nullptr, 16); | |
std::string payload = base64_decode(data.substr(8, packet_size)); | |
unsigned int this_checksum = crc(data.begin() + 8, data.begin() + 8 + packet_size); | |
unsigned int expected_checksum = std::stoul(data.substr(packet_size + 8, 8), nullptr, 16); | |
if (this_checksum != expected_checksum) { | |
std::cerr << "Invalid checksum (expected " << expected_checksum << ", got " << this_checksum << ")\n"; | |
return 2; | |
} | |
std::stringstream ss(payload); | |
unsigned char type = ss.get(); | |
ss.get(); | |
if (type != 0) { | |
std::cerr << "Not a display packet\n"; | |
return 2; | |
} | |
int gfxmode = ss.get(); | |
if (gfxmode > 2) { | |
std::cerr << "Unknown graphics mode\n"; | |
return 2; | |
} | |
bool cursor = ss.get(); | |
unsigned short cursorX, cursorY; | |
ss.read((char*)&width, 2); | |
ss.read((char*)&height, 2); | |
ss.read((char*)&cursorX, 2); | |
ss.read((char*)&cursorY, 2); | |
ss.get(); // ignoring grayscale | |
ss.get(); ss.get(); ss.get(); | |
png::rgb_pixel palette[256]; | |
png::image<png::rgb_pixel> img(width * 12 + 8, height * 18 + 8); | |
unsigned char * pixels = new unsigned char[width*height*54]; | |
unsigned char * screen = new unsigned char[width*height]; | |
unsigned char * colors = new unsigned char[width*height]; | |
if (gfxmode) { | |
unsigned char c, n; | |
int total = 0; | |
while (total < width * height) { | |
c = ss.get(); | |
n = ss.get(); | |
memset(screen + total, c, n); | |
total += n; | |
} | |
} else { | |
unsigned char c, n; | |
int total = 0; | |
while (total < width * height) { | |
c = ss.get(); | |
n = ss.get(); | |
memset(screen + total, c, n); | |
total += n; | |
} | |
total = 0; | |
while (total < width * height) { | |
c = ss.get(); | |
n = ss.get(); | |
memset(colors + total, c, n); | |
total += n; | |
} | |
} | |
for (int i = 0; i < (gfxmode == 2 ? 256 : 16); i++) { | |
palette[i].red = ss.get(); | |
palette[i].green = ss.get(); | |
palette[i].blue = ss.get(); | |
} | |
Rect br = {0, 0, width * 12 + 8, height * 18 + 8}; | |
fill(img, br, palette[15]); | |
if (gfxmode) { | |
for (int y = 0; y < height * 9; y++) { | |
for (int x = 0; x < width * 6; x++) { | |
Rect r = {x * 2 + 4, y * 2 + 4, 2, 2}; | |
fill(img, r, palette[pixels[y*width*6+x]]); | |
} | |
} | |
} else { | |
for (int y = 0; y < height; y++) | |
for (int x = 0; x < width; x++) | |
drawChar(img, screen[y*width+x], x, y, palette[colors[y*width+x] & 15], palette[colors[y*width+x] >> 4], false); | |
if (cursor) drawChar(img, '_', cursorX, cursorY, palette[15], palette[0], true); | |
} | |
delete[] pixels; | |
delete[] colors; | |
delete[] screen; | |
if (argc > 1) img.write(argv[1]); | |
else img.write_stream(std::cout); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment