Skip to content

Instantly share code, notes, and snippets.

@t-mat
Last active August 2, 2023 23:42
Show Gist options
  • Save t-mat/03d3a6782a60bfef8239 to your computer and use it in GitHub Desktop.
Save t-mat/03d3a6782a60bfef8239 to your computer and use it in GitHub Desktop.
Experimental zstd-PNG converter
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
#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