Skip to content

Instantly share code, notes, and snippets.

@Talon1024
Created September 27, 2017 23:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Talon1024/45170eb0170bc5084d6c421622efd9ad to your computer and use it in GitHub Desktop.
Save Talon1024/45170eb0170bc5084d6c421622efd9ad to your computer and use it in GitHub Desktop.
Convert CEL files to PNG
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
extern "C" {
#include "png.h"
}
struct Colour {
std::uint8_t red;
std::uint8_t green;
std::uint8_t blue;
};
struct CELImage {
std::uint16_t width;
std::uint16_t height;
std::uint16_t unknown1;
std::uint16_t unknown2;
std::uint16_t bitdepth;
std::uint32_t length;
std::uint16_t palColours = 256;
Colour* palette;
std::uint8_t* pixelData;
};
// Main program I/O functions
CELImage* readCelImage(const char* celFname);
char* getPngFname(const char* celFname);
bool writeCelToPng(CELImage* cel, const char* fname);
// Misc functions
void usage();
// File I/O helpers
std::uint16_t byteSwap16(std::uint16_t toSwap);
std::uint16_t readUInt16LE(FILE* fp);
std::uint32_t byteSwap32(std::uint32_t toSwap);
std::uint32_t readUInt32LE(FILE* fp);
int main(int argc, char** argv) {
if (argc > 1) {
for (int i = 1; i < argc; i++) {
CELImage* cel = readCelImage(argv[i]);
if ( cel != nullptr ) {
char* pngFile = getPngFname(argv[i]);
if (writeCelToPng(cel, pngFile)) {
std::puts("Success!\n");
return 0;
} else {
return 1;
}
std::free(pngFile);
delete[] cel->palette;
delete[] cel->pixelData;
delete cel;
return 0;
} else {
return 1;
}
}
} else {
usage();
return 0;
}
}
void usage() {
std::puts(
"CEL to PNG by Kevin Caccamo\n"
"Version 1.0\n"
"\n"
"Usage: celtopng *.CEL..\n"
"\n"
"Converts each CEL to a PNG.");
}
std::uint16_t byteSwap16(std::uint16_t toSwap) {
std::uint16_t temp = (toSwap >> 8) & 0x00ff;
temp |= (toSwap << 8) & 0xff00;
return temp;
}
std::uint16_t readUInt16LE(FILE* fp) {
std::uint16_t buf;
std::fread(&buf, 2, 1, fp);
#if __BYTE_ORDER == __BIG_ENDIAN
buf = byteSwap16(buf);
#endif
return buf;
}
std::uint32_t byteSwap32(std::uint32_t toSwap) {
std::uint32_t temp = (toSwap >> 16) & 0x000000ff;
temp |= (toSwap >> 8) & 0x0000ff00;
temp |= (toSwap << 8) & 0x00ff0000;
temp |= (toSwap << 16) & 0xff000000;
return temp;
}
std::uint32_t readUInt32LE(FILE* fp) {
std::uint32_t buf;
std::fread(&buf, 4, 1, fp);
#if __BYTE_ORDER == __BIG_ENDIAN
buf = byteSwap32(buf);
#endif
return buf;
}
CELImage* readCelImage(const char* celFname) {
FILE* celp = std::fopen(celFname, "rb");
{
std::uint16_t magic = readUInt16LE(celp);
if (magic != 37145) {
std::fclose(celp);
std::fprintf(stderr, "CEL magic number is not 37145.\n");
return nullptr;
}
}
CELImage* cel = new CELImage;
cel->width = readUInt16LE(celp);
cel->height = readUInt16LE(celp);
cel->unknown1 = readUInt16LE(celp);
cel->unknown2 = readUInt16LE(celp);
cel->bitdepth = readUInt16LE(celp);
cel->length = readUInt32LE(celp);
#ifdef DEBUG
std::printf(
"========== Image: %s ==========\n"
"width: %hu\n"
"height: %hu\n"
"unknown1 (offset X?): %hu\n"
"unknown2 (offset Y?): %hu\n"
"bit depth: %hu\n"
"length: %u\n", celFname, cel->width, cel->height,
cel->unknown1, cel->unknown2, cel->bitdepth, cel->length);
if (cel->length < cel->width * cel->height) {
std::puts("Warning! This file may be compressed. View at your own risk!\n");
} else if (cel->length > cel->width * cel->height) {
std::puts("Animation?\n");
}
#endif
std::uint32_t palOffset = 32; // Found this by trial and error.
//if (argv[2] != nullptr) palOffset = std::atoi(argv[2]);
std::fseek(celp, palOffset, SEEK_SET);
//int palBytes = 768;
cel->palColours = 256; // palBytes / 3;
cel->palette = new Colour[cel->palColours];
for (int x = 0; x < cel->palColours; x++) {
cel->palette[x].red = (std::uint8_t) std::fgetc(celp) << 2; // << 2 because I took a screenshot of WC2 in DOSBox, and noticed the difference in colour when I opened the screenshot in GIMP.
cel->palette[x].green = (std::uint8_t) std::fgetc(celp) << 2;
cel->palette[x].blue = (std::uint8_t) std::fgetc(celp) << 2;
}
cel->pixelData = new std::uint8_t[cel->length];
for (int i = 0; i < cel->length; i++) cel->pixelData[i] = (std::uint8_t) std::fgetc(celp);
std::fclose(celp);
return cel;
}
char* getPngFname(const char* celFname) {
// Copy filename string
std::uint32_t celFnameLength = std::strlen(celFname);
char* pngFname = (char*) std::malloc(celFnameLength + 1);
std::strcpy(pngFname, celFname);
pngFname[celFnameLength] = 0;
// Change to working directory
#if defined(_WIN32) || defined(_WIN64)
char dirsep = '\\'; // Windows
#else
char dirsep = '/'; // Unix (Linux/MacOS/BSD)
#endif
char* wdir = std::strrchr(pngFname, dirsep);
if (!wdir) wdir = pngFname;
else wdir += 1;
// Change extension to PNG
char* extn = std::strrchr(pngFname, '.');
std::strncpy(extn+1, "png\0", 4);
return wdir;
}
bool writeCelToPng(CELImage* cel, const char* fname) {
std::printf("Attempting to write to %s\n", fname);
// Init PNG writing
png_structp pngWriter = png_create_write_struct(
png_get_libpng_ver(NULL), NULL, NULL, NULL);
png_infop pngInfo = png_create_info_struct(pngWriter);
if (!pngInfo) {
png_destroy_write_struct(&pngWriter, nullptr);
return false;
}
// libPNG error handler
if (setjmp(png_jmpbuf(pngWriter))) {
png_destroy_write_struct(&pngWriter, nullptr);
return false;
}
FILE* pngFp = std::fopen(fname, "w");
png_init_io(pngWriter, pngFp);
//png_set_write_status_fn(pngWriter, writePngRow);
png_set_IHDR(pngWriter, pngInfo, cel->width, cel->height,
cel->bitdepth, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_compression_level(pngWriter, Z_BEST_COMPRESSION);
// Convert CEL palette to PNG palette
png_colorp celPal = new png_color[cel->palColours];
for (int i = 0; i < cel->palColours; i++) {
celPal[i].red = cel->palette[i].red;
celPal[i].green = cel->palette[i].green;
celPal[i].blue = cel->palette[i].blue;
}
png_byte** pngRows = new png_byte*[cel->height];
for (int i = 0; i < cel->height; i++) {
std::uint8_t* curCelRow = cel->pixelData+(i*cel->width);
pngRows[i] = new png_byte[cel->width];
std::memcpy(pngRows[i], curCelRow, cel->width);
}
png_set_PLTE(pngWriter, pngInfo, celPal, cel->palColours);
png_set_rows(pngWriter, pngInfo, pngRows);
png_write_png(pngWriter, pngInfo, 0, nullptr);
png_destroy_info_struct(pngWriter, &pngInfo);
png_destroy_write_struct(&pngWriter, nullptr);
for (int i = 0; i < cel->height; i++) {
delete[] pngRows[i];
}
delete[] pngRows;
delete[] celPal;
return true;
}
@Talon1024
Copy link
Author

This is for WC2 CEL files. The other CEL files can be converted to PNG using ffmpeg.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment