aboutsummaryrefslogtreecommitdiff
path: root/src/zopflipng/zopflipng_lib.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/zopflipng/zopflipng_lib.cc')
-rw-r--r--[-rwxr-xr-x]src/zopflipng/zopflipng_lib.cc213
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;
+}