File | Deflate | zstd | Ratio (zstd/Deflate) |
---|---|---|---|
airplane.png | 450502 | 423563 | 0.940 |
arctichare.png | 264422 | 250521 | 0.947 |
baboon.png | 637192 | 627087 | 0.984 |
barbara.png | 185727 | 178611 | 0.962 |
boat.png | 177762 | 167658 | 0.943 |
cat.png | 663451 | 677684 | 1.021 |
fruits.png | 472100 | 448872 | 0.951 |
frymire.png | 252124 | 383891 | 1.523 |
girl.png | 623591 | 629472 | 1.009 |
goldhill.png | 173024 | 162789 | 0.941 |
lena.png | 512673 | 476001 | 0.928 |
monarch.png | 614179 | 623697 | 1.015 |
peppers.png | 538749 | 508034 | 0.943 |
pool.png | 186897 | 184469 | 0.987 |
sails.png | 806850 | 815815 | 1.011 |
serrano.png | 106988 | 162387 | 1.518 |
tulips.png | 679233 | 687236 | 1.012 |
watch.png | 697056 | 756786 | 1.086 |
zelda.png | 139181 | 138849 | 1.000 |
---- | ---- | ---- | ---- |
Total | 8181701 | 8303422 | 1.015 |
Last active
August 2, 2023 23:42
-
-
Save t-mat/03d3a6782a60bfef8239 to your computer and use it in GitHub Desktop.
Experimental zstd-PNG converter
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
#if defined(_MSC_VER) | |
# define _CRT_SECURE_NO_WARNINGS | |
# pragma warning (disable : 4351) | |
#endif | |
#include <stdint.h> | |
#include <string.h> | |
#include <string> | |
#include <vector> | |
#include "zstd.h" | |
#include "zlib.h" | |
using Buf = std::vector<uint8_t>; | |
static const uint8_t pngSig[8] = { | |
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a | |
}; | |
static const uint8_t zspngSig[8] = { | |
'z', 'p', 'n', 'g', 0x0d, 0x0a, 0x1a, 0x0a | |
}; | |
struct PngChunk { | |
PngChunk(const uint8_t* p) { | |
top = p; | |
dataBytes = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; | |
memcpy(type, &p[4], sizeof(type)); | |
data = &p[8]; | |
const auto* q = p + 8 + dataBytes; | |
crc = (q[0] << 24) | (q[1] << 16) | (q[2] << 8) | q[3]; | |
} | |
bool is(const void* p) const { | |
return memcmp(type, p, 4) == 0; | |
} | |
const uint8_t* top {}; | |
uint32_t dataBytes {}; | |
uint8_t type[4] {}; | |
const uint8_t* data {}; | |
uint32_t crc {}; | |
}; | |
Buf pngToZstdpng(const Buf& inpBuf) { | |
Buf buf; | |
std::vector<uint8_t> idatBuf(1024 * 1024 * 128); | |
auto* p = inpBuf.data(); | |
if(memcmp(p, pngSig, sizeof(pngSig)) != 0) { | |
return buf; | |
} | |
p += sizeof(pngSig); | |
for(auto c : zspngSig) { | |
buf.push_back(c); | |
} | |
z_stream zs {}; | |
inflateInit(&zs); | |
auto zStatus = Z_OK; | |
zs.next_out = idatBuf.data(); | |
zs.avail_out = (decltype(zs.avail_out)) idatBuf.size(); | |
for(;;) { | |
PngChunk chunk { p }; | |
p += 8 + chunk.dataBytes + 4; | |
if(! chunk.is("IDAT")) { | |
for(const auto* a = chunk.top; a < p; ++a) { | |
buf.push_back(*a); | |
} | |
if(chunk.is("IEND")) { | |
break; | |
} | |
continue; | |
} | |
// IDAT | |
zs.next_in = (Bytef*) chunk.data; | |
zs.avail_in = chunk.dataBytes; | |
while(zs.avail_in) { | |
zStatus = inflate(&zs, Z_NO_FLUSH); | |
if(zStatus != Z_OK && zStatus != Z_STREAM_END) { | |
break; | |
} | |
if(zs.avail_out == 0 || zStatus == Z_STREAM_END) { | |
const auto idatBytes = zs.next_out - idatBuf.data(); | |
const auto zstdBound = ZSTD_compressBound(idatBytes); | |
std::vector<uint8_t> zstdBuf(zstdBound); | |
const auto zstdBytes = ZSTD_compress(zstdBuf.data(), zstdBuf.size(), idatBuf.data(), idatBytes); | |
buf.push_back((zstdBytes >> 24) & 0xff); | |
buf.push_back((zstdBytes >> 16) & 0xff); | |
buf.push_back((zstdBytes >> 8) & 0xff); | |
buf.push_back((zstdBytes >> 0) & 0xff); | |
buf.push_back('I'); | |
buf.push_back('D'); | |
buf.push_back('A'); | |
buf.push_back('T'); | |
for(auto i = 0u; i < zstdBytes; ++i) { | |
buf.push_back(zstdBuf[i]); | |
} | |
buf.push_back(0); | |
buf.push_back(0); | |
buf.push_back(0); | |
buf.push_back(0); | |
} | |
} | |
} | |
inflateEnd(&zs); | |
return buf; | |
} | |
Buf zstdpngToPng(const Buf& inpBuf) { | |
Buf buf; | |
std::vector<uint8_t> idatBuf(1024 * 1024 * 128); | |
auto* p = inpBuf.data(); | |
if(memcmp(p, zspngSig, sizeof(zspngSig)) != 0) { | |
return buf; | |
} | |
p += sizeof(zspngSig); | |
for(auto c : pngSig) { | |
buf.push_back(c); | |
} | |
for(;;) { | |
PngChunk chunk { p }; | |
p += 8 + chunk.dataBytes + 4; | |
if(! chunk.is("IDAT")) { | |
for(const auto* a = chunk.top; a < p; ++a) { | |
buf.push_back(*a); | |
} | |
if(chunk.is("IEND")) { | |
break; | |
} | |
continue; | |
} | |
// IDAT | |
const auto idatBytes = ZSTD_decompress(idatBuf.data(), idatBuf.size(), chunk.data, chunk.dataBytes); | |
const auto lengthOffset = buf.size(); | |
buf.push_back(0); | |
buf.push_back(0); | |
buf.push_back(0); | |
buf.push_back(0); | |
buf.push_back('I'); | |
buf.push_back('D'); | |
buf.push_back('A'); | |
buf.push_back('T'); | |
// note : This procedure emits single HUGE IDAT. | |
{ | |
std::vector<uint8_t> zsBuf(1024 * 1024 * 128); | |
z_stream zs {}; | |
deflateInit(&zs, Z_BEST_COMPRESSION); | |
zs.next_in = idatBuf.data(); | |
zs.avail_in = (unsigned int) idatBytes; | |
zs.next_out = zsBuf.data(); | |
zs.avail_out = (unsigned int) zsBuf.size(); | |
while(zs.avail_in) { | |
deflate(&zs, Z_NO_FLUSH); | |
} | |
deflate(&zs, Z_FINISH); | |
const auto zsBytes = zsBuf.size() - zs.avail_out; | |
for(auto i = 0u; i < zsBytes; ++i) { | |
buf.push_back(zsBuf[i]); | |
} | |
deflateEnd(&zs); | |
} | |
const auto dataBytes = buf.size() - lengthOffset - 8; | |
const auto crc = crc32(0, &buf[lengthOffset + 4], (unsigned int) dataBytes + 4); | |
buf.push_back((crc >> 24) & 0xff); | |
buf.push_back((crc >> 16) & 0xff); | |
buf.push_back((crc >> 8) & 0xff); | |
buf.push_back((crc >> 0) & 0xff); | |
buf[lengthOffset + 0] = (dataBytes >> 24) & 0xff; | |
buf[lengthOffset + 1] = (dataBytes >> 16) & 0xff; | |
buf[lengthOffset + 2] = (dataBytes >> 8) & 0xff; | |
buf[lengthOffset + 3] = (dataBytes >> 0) & 0xff; | |
} | |
return buf; | |
} | |
std::string replaceExt(const std::string& filename, const std::string& orgExt, const std::string& newExt) { | |
const auto dp = filename.rfind(orgExt); | |
if(dp == std::string::npos || filename.substr(dp) != orgExt) { | |
return filename + newExt; | |
} | |
return filename.substr(0, dp) + newExt; | |
} | |
bool fileExist(const std::string& filename) { | |
if(auto* fp = fopen(filename.c_str(), "rb")) { | |
fclose(fp); | |
return true; | |
} | |
return false; | |
} | |
Buf readFile(const std::string& filename) { | |
Buf buf; | |
if(auto* fp = fopen(filename.c_str(), "rb")) { | |
fseek(fp, 0, SEEK_END); | |
buf.resize(ftell(fp)); | |
rewind(fp); | |
if(buf.size() != fread(buf.data(), 1, buf.size(), fp)) { | |
printf("readFile(%s) failed\n", filename.c_str()); | |
exit(EXIT_FAILURE); | |
} | |
fclose(fp); | |
} | |
return buf; | |
} | |
bool writeFile(const std::string& filename, const Buf& buf) { | |
if(auto* fp = fopen(filename.c_str(), "wb")) { | |
fwrite(buf.data(), 1, buf.size(), fp); | |
fclose(fp); | |
return true; | |
} | |
return false; | |
} | |
struct Opt { | |
enum class Mode { None, Encode, Decode }; | |
Mode mode { Mode::None }; | |
bool overwrite { false }; | |
std::string inpFilename {}; | |
std::string outFilename {}; | |
}; | |
Opt makeOpt(int argc, char* argv[]) { | |
Opt opt; | |
for(int iArg = 1; iArg < argc; ++iArg) { | |
const auto* p = argv[iArg]; | |
if(p[0] == '-' && p[1] == 'c') { | |
opt.mode = Opt::Mode::Encode; | |
} else if(p[0] == '-' && p[1] == 'd') { | |
opt.mode = Opt::Mode::Decode; | |
} else if(p[0] == '-' && p[1] == 'y') { | |
opt.overwrite = true; | |
} else { | |
if(opt.inpFilename.empty()) { | |
opt.inpFilename = p; | |
} else { | |
opt.outFilename = p; | |
} | |
} | |
} | |
if(opt.outFilename.empty()) { | |
if(opt.mode == Opt::Mode::Encode) { | |
opt.outFilename = replaceExt(opt.inpFilename, ".png", ".zspng"); | |
} else { | |
opt.outFilename = replaceExt(opt.inpFilename, ".zspng", ".zs.png"); | |
} | |
} | |
return opt; | |
} | |
int main(int argc, char* argv[]) { | |
const Opt opt = makeOpt(argc, argv); | |
if(argc < 2 || opt.mode == Opt::Mode::None) { | |
printf("Encode : zstd-png -c <input .png> [output .zspng]\n"); | |
printf("Decode : zstd-png -d <input .zspng> [output .zs.png]\n"); | |
return 0; | |
} | |
printf("Input file : %s\n", opt.inpFilename.c_str()); | |
printf("Output file : %s\n", opt.outFilename.c_str()); | |
const auto inpFile = readFile(opt.inpFilename); | |
if(inpFile.empty()) { | |
printf("Input file does not exist\n"); | |
exit(EXIT_FAILURE); | |
} | |
const auto outFile = [&]() { | |
if(opt.mode == Opt::Mode::Encode) { | |
return pngToZstdpng(inpFile); | |
} else { | |
return zstdpngToPng(inpFile); | |
} | |
}(); | |
if(outFile.empty()) { | |
printf("Error\n"); | |
exit(EXIT_FAILURE); | |
} | |
if(! opt.overwrite && fileExist(opt.outFilename)) { | |
printf("Output file already exist.\n"); | |
printf("Please specify '-y' switch if you need force overwrite.\n"); | |
exit(EXIT_FAILURE); | |
} | |
writeFile(opt.outFilename, outFile); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment