Created
September 27, 2017 23:08
-
-
Save Talon1024/45170eb0170bc5084d6c421622efd9ad to your computer and use it in GitHub Desktop.
Convert CEL files to PNG
This file contains 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
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is for WC2 CEL files. The other CEL files can be converted to PNG using ffmpeg.