diff options
Diffstat (limited to 'src/zopflipng/zopflipng_lib.cc')
-rw-r--r--[-rwxr-xr-x] | src/zopflipng/zopflipng_lib.cc | 213 |
1 files changed, 158 insertions, 55 deletions
diff --git a/src/zopflipng/zopflipng_lib.cc b/src/zopflipng/zopflipng_lib.cc index c310790..15188db 100755..100644 --- a/src/zopflipng/zopflipng_lib.cc +++ b/src/zopflipng/zopflipng_lib.cc @@ -19,7 +19,10 @@ #include "zopflipng_lib.h" +#include <errno.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <set> #include <vector> @@ -28,9 +31,11 @@ #include "../zopfli/deflate.h" ZopfliPNGOptions::ZopfliPNGOptions() - : lossy_transparent(false) + : verbose(false) + , lossy_transparent(false) , lossy_8bit(false) , auto_filter_strategy(true) + , keep_colortype(false) , use_zopfli(true) , num_iterations(15) , num_iterations_large(5) @@ -48,40 +53,18 @@ unsigned CustomPNGDeflate(unsigned char** out, size_t* outsize, ZopfliOptions options; ZopfliInitOptions(&options); + options.verbose = png_options->verbose; options.numiterations = insize < 200000 ? png_options->num_iterations : png_options->num_iterations_large; - if (png_options->block_split_strategy == 3) { - // Try both block splitting first and last. - unsigned char* out2 = 0; - size_t outsize2 = 0; - options.blocksplittinglast = 0; - ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize); - bp = 0; - options.blocksplittinglast = 1; - ZopfliDeflate(&options, 2 /* Dynamic */, 1, - in, insize, &bp, &out2, &outsize2); - - if (outsize2 < *outsize) { - free(*out); - *out = out2; - *outsize = outsize2; - printf("Block splitting last was better\n"); - } else { - free(out2); - } - } else { - if (png_options->block_split_strategy == 0) options.blocksplitting = 0; - options.blocksplittinglast = png_options->block_split_strategy == 2; - ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize); - } + ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize); return 0; // OK } // Returns 32-bit integer value for RGBA color. static unsigned ColorIndex(const unsigned char* color) { - return color[0] + 256u * color[1] + 65536u * color[1] + 16777216u * color[3]; + return color[0] + 256u * color[1] + 65536u * color[2] + 16777216u * color[3]; } // Counts amount of colors in the image, up to 257. If transparent_counts_as_one @@ -128,6 +111,7 @@ void LossyOptimizeTransparent(lodepng::State* inputstate, unsigned char* image, r = image[i * 4 + 0]; g = image[i * 4 + 1]; b = image[i * 4 + 2]; + break; } } } @@ -175,7 +159,7 @@ void LossyOptimizeTransparent(lodepng::State* inputstate, unsigned char* image, // Returns 0 if ok, other value for error unsigned TryOptimize( const std::vector<unsigned char>& image, unsigned w, unsigned h, - const lodepng::State& inputstate, bool bit16, + const lodepng::State& inputstate, bool bit16, bool keep_colortype, const std::vector<unsigned char>& origfile, ZopfliPNGFilterStrategy filterstrategy, bool use_zopfli, int windowsize, const ZopfliPNGOptions* png_options, @@ -189,6 +173,10 @@ unsigned TryOptimize( state.encoder.zlibsettings.custom_context = png_options; } + if (keep_colortype) { + state.encoder.auto_convert = 0; + lodepng_color_mode_copy(&state.info_png.color, &inputstate.info_png.color); + } if (inputstate.info_png.color.colortype == LCT_PALETTE) { // Make it preserve the original palette order lodepng_color_mode_copy(&state.info_raw, &inputstate.info_png.color); @@ -216,16 +204,20 @@ unsigned TryOptimize( state.encoder.filter_strategy = LFS_BRUTE_FORCE; break; case kStrategyOne: + state.encoder.filter_strategy = LFS_ONE; + break; case kStrategyTwo: + state.encoder.filter_strategy = LFS_TWO; + break; case kStrategyThree: + state.encoder.filter_strategy = LFS_THREE; + break; case kStrategyFour: - // Set the filters of all scanlines to that number. - filters.resize(h, filterstrategy); - state.encoder.filter_strategy = LFS_PREDEFINED; - state.encoder.predefined_filters = &filters[0]; + state.encoder.filter_strategy = LFS_FOUR; break; case kStrategyPredefined: lodepng::getFilterTypes(filters, origfile); + if (filters.size() != h) return 1; // Error getting filters state.encoder.filter_strategy = LFS_PREDEFINED; state.encoder.predefined_filters = &filters[0]; break; @@ -240,24 +232,25 @@ unsigned TryOptimize( // For very small output, also try without palette, it may be smaller thanks // to no palette storage overhead. - if (!error && out->size() < 4096) { - lodepng::State teststate; - std::vector<unsigned char> temp; - lodepng::decode(temp, w, h, teststate, *out); - LodePNGColorMode& color = teststate.info_png.color; - if (color.colortype == LCT_PALETTE) { - std::vector<unsigned char> out2; - state.encoder.auto_convert = LAC_ALPHA; - bool grey = true; - for (size_t i = 0; i < color.palettesize; i++) { - if (color.palette[i * 4 + 0] != color.palette[i * 4 + 2] - || color.palette[i * 4 + 1] != color.palette[i * 4 + 2]) { - grey = false; - break; - } + if (!error && out->size() < 4096 && !keep_colortype) { + if (lodepng::getPNGHeaderInfo(*out).color.colortype == LCT_PALETTE) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); + lodepng_compute_color_stats(&stats, &image[0], w, h, &state.info_raw); + // Too small for tRNS chunk overhead. + if (w * h <= 16 && stats.key) stats.alpha = 1; + state.encoder.auto_convert = 0; + state.info_png.color.colortype = (stats.alpha ? LCT_RGBA : LCT_RGB); + state.info_png.color.bitdepth = 8; + state.info_png.color.key_defined = (stats.key && !stats.alpha); + if (state.info_png.color.key_defined) { + state.info_png.color.key_defined = 1; + state.info_png.color.key_r = (stats.key_r & 255u); + state.info_png.color.key_g = (stats.key_g & 255u); + state.info_png.color.key_b = (stats.key_b & 255u); } - if (grey) state.info_png.color.colortype = LCT_GREY_ALPHA; + std::vector<unsigned char> out2; error = lodepng::encode(out2, image, w, h, state); if (out2.size() < out->size()) out->swap(out2); } @@ -276,7 +269,8 @@ unsigned TryOptimize( // filter type. unsigned AutoChooseFilterStrategy(const std::vector<unsigned char>& image, unsigned w, unsigned h, - const lodepng::State& inputstate, bool bit16, + const lodepng::State& inputstate, + bool bit16, bool keep_colortype, const std::vector<unsigned char>& origfile, int numstrategies, ZopfliPNGFilterStrategy* strategies, @@ -293,8 +287,9 @@ unsigned AutoChooseFilterStrategy(const std::vector<unsigned char>& image, for (int i = 0; i < numstrategies; i++) { out.clear(); - unsigned error = TryOptimize(image, w, h, inputstate, bit16, origfile, - strategies[i], false, windowsize, 0, &out); + unsigned error = TryOptimize(image, w, h, inputstate, bit16, keep_colortype, + origfile, strategies[i], false, windowsize, 0, + &out); if (error) return error; if (bestsize == 0 || out.size() < bestsize) { bestsize = out.size(); @@ -309,6 +304,27 @@ unsigned AutoChooseFilterStrategy(const std::vector<unsigned char>& image, return 0; /* OK */ } +// Outputs the intersection of keepnames and non-essential chunks which are in +// the PNG image. +void ChunksToKeep(const std::vector<unsigned char>& origpng, + const std::vector<std::string>& keepnames, + std::set<std::string>* result) { + std::vector<std::string> names[3]; + std::vector<std::vector<unsigned char> > chunks[3]; + + lodepng::getChunks(names, chunks, origpng); + + for (size_t i = 0; i < 3; i++) { + for (size_t j = 0; j < names[i].size(); j++) { + for (size_t k = 0; k < keepnames.size(); k++) { + if (keepnames[k] == names[i][j]) { + result->insert(names[i][j]); + } + } + } + } +} + // Keeps chunks with given names from the original png by literally copying them // into the new png void KeepChunks(const std::vector<unsigned char>& origpng, @@ -364,15 +380,41 @@ int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng, lodepng::State inputstate; error = lodepng::decode(image, w, h, inputstate, origpng); + bool keep_colortype = png_options.keep_colortype; + + if (!png_options.keepchunks.empty()) { + // If the user wants to keep the non-essential chunks bKGD or sBIT, the + // input color type has to be kept since the chunks format depend on it. + // This may severely hurt compression if it is not an ideal color type. + // Ideally these chunks should not be kept for web images. Handling of bKGD + // chunks could be improved by changing its color type but not done yet due + // to its additional complexity, for sBIT such improvement is usually not + // possible. + std::set<std::string> keepchunks; + ChunksToKeep(origpng, png_options.keepchunks, &keepchunks); + if (keepchunks.count("bKGD") || keepchunks.count("sBIT")) { + if (!keep_colortype && verbose) { + printf("Forced to keep original color type due to keeping bKGD or sBIT" + " chunk.\n"); + } + keep_colortype = true; + } + } + if (error) { if (verbose) { - printf("Decoding error %i: %s\n", error, lodepng_error_text(error)); + if (error == 1) { + printf("Decoding error\n"); + } else { + printf("Decoding error %u: %s\n", error, lodepng_error_text(error)); + } } return error; } bool bit16 = false; // Using 16-bit per channel raw image - if (inputstate.info_png.color.bitdepth == 16 && !png_options.lossy_8bit) { + if (inputstate.info_png.color.bitdepth == 16 && + (keep_colortype || !png_options.lossy_8bit)) { // Decode as 16-bit image.clear(); error = lodepng::decode(image, w, h, origpng, LCT_RGBA, 16); @@ -387,7 +429,7 @@ int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng, if (png_options.auto_filter_strategy) { error = AutoChooseFilterStrategy(image, w, h, inputstate, bit16, - origpng, + keep_colortype, origpng, /* Don't try brute force */ kNumFilterStrategies - 1, filterstrategies, strategy_enable); @@ -401,8 +443,8 @@ int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng, if (!strategy_enable[i]) continue; std::vector<unsigned char> temp; - error = TryOptimize(image, w, h, inputstate, bit16, origpng, - filterstrategies[i], true /* use_zopfli */, + error = TryOptimize(image, w, h, inputstate, bit16, keep_colortype, + origpng, filterstrategies[i], true /* use_zopfli */, windowsize, &png_options, &temp); if (!error) { if (verbose) { @@ -423,3 +465,64 @@ int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng, return error; } + +extern "C" void CZopfliPNGSetDefaults(CZopfliPNGOptions* png_options) { + + memset(png_options, 0, sizeof(*png_options)); + // Constructor sets the defaults + ZopfliPNGOptions opts; + + png_options->lossy_transparent = opts.lossy_transparent; + png_options->lossy_8bit = opts.lossy_8bit; + png_options->auto_filter_strategy = opts.auto_filter_strategy; + png_options->use_zopfli = opts.use_zopfli; + png_options->num_iterations = opts.num_iterations; + png_options->num_iterations_large = opts.num_iterations_large; + png_options->block_split_strategy = opts.block_split_strategy; +} + +extern "C" int CZopfliPNGOptimize(const unsigned char* origpng, + const size_t origpng_size, + const CZopfliPNGOptions* png_options, + int verbose, + unsigned char** resultpng, + size_t* resultpng_size) { + ZopfliPNGOptions opts; + + // Copy over to the C++-style struct + opts.lossy_transparent = !!png_options->lossy_transparent; + opts.lossy_8bit = !!png_options->lossy_8bit; + opts.auto_filter_strategy = !!png_options->auto_filter_strategy; + opts.use_zopfli = !!png_options->use_zopfli; + opts.num_iterations = png_options->num_iterations; + opts.num_iterations_large = png_options->num_iterations_large; + opts.block_split_strategy = png_options->block_split_strategy; + + for (int i = 0; i < png_options->num_filter_strategies; i++) { + opts.filter_strategies.push_back(png_options->filter_strategies[i]); + } + + for (int i = 0; i < png_options->num_keepchunks; i++) { + opts.keepchunks.push_back(png_options->keepchunks[i]); + } + + const std::vector<unsigned char> origpng_cc(origpng, origpng + origpng_size); + std::vector<unsigned char> resultpng_cc; + + int ret = ZopfliPNGOptimize(origpng_cc, opts, !!verbose, &resultpng_cc); + if (ret) { + return ret; + } + + *resultpng_size = resultpng_cc.size(); + *resultpng = (unsigned char*) malloc(resultpng_cc.size()); + if (!(*resultpng)) { + return ENOMEM; + } + + memcpy(*resultpng, + reinterpret_cast<unsigned char*>(&resultpng_cc[0]), + resultpng_cc.size()); + + return 0; +} |