aboutsummaryrefslogtreecommitdiff
path: root/src/zopflipng/zopflipng_bin.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/zopflipng/zopflipng_bin.cc')
-rw-r--r--src/zopflipng/zopflipng_bin.cc135
1 files changed, 104 insertions, 31 deletions
diff --git a/src/zopflipng/zopflipng_bin.cc b/src/zopflipng/zopflipng_bin.cc
index 3faea06..ea764b9 100644
--- a/src/zopflipng/zopflipng_bin.cc
+++ b/src/zopflipng/zopflipng_bin.cc
@@ -21,6 +21,7 @@
#include <stdio.h>
#include "lodepng/lodepng.h"
+#include "lodepng/lodepng_util.h"
#include "zopflipng_lib.h"
// Returns directory path (including last slash) in dir, filename without
@@ -47,7 +48,17 @@ void GetFileNameParts(const std::string& filename,
}
}
-// Returns the size of the file
+// Returns whether the file exists and we have read permissions.
+bool FileExists(const std::string& filename) {
+ FILE* file = fopen(filename.c_str(), "rb");
+ if (file) {
+ fclose(file);
+ return true;
+ }
+ return false;
+}
+
+// Returns the size of the file, if it exists and we have read permissions.
size_t GetFileSize(const std::string& filename) {
size_t size;
FILE* file = fopen(filename.c_str(), "rb");
@@ -68,8 +79,7 @@ void ShowHelp() {
" previous run and not overwritten if its filesize is smaller.\n"
"\n"
"Options:\n"
- "-m: compress more: use more iterations (depending on file size) and"
- " use block split strategy 3\n"
+ "-m: compress more: use more iterations (depending on file size)\n"
"--prefix=[fileprefix]: Adds a prefix to output filenames. May also"
" contain a directory path. When using a prefix, multiple input files"
" can be given and the output filenames are generated with the"
@@ -93,8 +103,7 @@ void ShowHelp() {
"--iterations=[number]: number of iterations, more iterations makes it"
" slower but provides slightly better compression. Default: 15 for"
" small files, 5 for large files.\n"
- "--splitting=[0-3]: block split strategy:"
- " 0=none, 1=first, 2=last, 3=try both and take the best\n"
+ "--splitting=[0-3]: ignored, left for backwards compatibility\n"
"--filters=[types]: filter strategies to try:\n"
" 0-4: give all scanlines PNG filter type 0-4\n"
" m: minimum sum\n"
@@ -110,9 +119,19 @@ void ShowHelp() {
" set of filters to try is --filters=0me.\n"
"--keepchunks=nAME,nAME,...: keep metadata chunks with these names"
" that would normally be removed, e.g. tEXt,zTXt,iTXt,gAMA, ... \n"
- " Due to adding extra data, this increases the result size. By default"
- " ZopfliPNG only keeps the following chunks because they are"
- " essential: IHDR, PLTE, tRNS, IDAT and IEND.\n"
+ " Due to adding extra data, this increases the result size. Keeping"
+ " bKGD or sBIT chunks may cause additional worse compression due to"
+ " forcing a certain color type, it is advised to not keep these for"
+ " web images because web browsers do not use these chunks. By default"
+ " ZopfliPNG only keeps (and losslessly modifies) the following chunks"
+ " because they are essential: IHDR, PLTE, tRNS, IDAT and IEND.\n"
+ "--keepcolortype: Keep original color type (RGB, RGBA, gray,"
+ " gray+alpha or palette) and bit depth of the PNG.\n"
+ " This results in a loss of compression opportunities, e.g. it will no"
+ " longer convert a 4-channel RGBA image to 2-channel gray+alpha if the"
+ " image only had translucent gray pixels.\n"
+ " May be useful if a device does not support decoding PNGs of a"
+ " particular color type.\n"
"\n"
"Usage examples:\n"
"Optimize a file and overwrite if smaller: zopflipng infile.png"
@@ -120,8 +139,8 @@ void ShowHelp() {
"Compress more: zopflipng -m infile.png outfile.png\n"
"Optimize multiple files: zopflipng --prefix a.png b.png c.png\n"
"Compress really good and trying all filter strategies: zopflipng"
- " --iterations=500 --splitting=3 --filters=01234mepb"
- " --lossy_8bit --lossy_transparent infile.png outfile.png\n");
+ " --iterations=500 --filters=01234mepb --lossy_8bit"
+ " --lossy_transparent infile.png outfile.png\n");
}
void PrintSize(const char* label, size_t size) {
@@ -151,7 +170,6 @@ int main(int argc, char *argv[]) {
std::string prefix = "zopfli_"; // prefix for output filenames
std::vector<std::string> files;
- std::vector<char> options;
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') {
@@ -164,7 +182,6 @@ int main(int argc, char *argv[]) {
} else if (c == 'm') {
png_options.num_iterations *= 4;
png_options.num_iterations_large *= 4;
- png_options.block_split_strategy = 3;
} else if (c == 'q') {
png_options.use_zopfli = false;
} else if (c == 'h') {
@@ -182,6 +199,8 @@ int main(int argc, char *argv[]) {
int num = atoi(value.c_str());
if (name == "--always_zopflify") {
always_zopflify = true;
+ } else if (name == "--verbose") {
+ png_options.verbose = true;
} else if (name == "--lossy_transparent") {
png_options.lossy_transparent = true;
} else if (name == "--lossy_8bit") {
@@ -191,8 +210,7 @@ int main(int argc, char *argv[]) {
png_options.num_iterations = num;
png_options.num_iterations_large = num;
} else if (name == "--splitting") {
- if (num < 0 || num > 3) num = 1;
- png_options.block_split_strategy = num;
+ // ignored
} else if (name == "--filters") {
for (size_t j = 0; j < value.size(); j++) {
ZopfliPNGFilterStrategy strategy = kStrategyZero;
@@ -228,6 +246,8 @@ int main(int argc, char *argv[]) {
" --keepchunks=gAMA,cHRM,sRGB,iCCP\n");
return 0;
}
+ } else if (name == "--keepcolortype") {
+ png_options.keep_colortype = true;
} else if (name == "--prefix") {
use_prefix = true;
if (!value.empty()) prefix = value;
@@ -287,26 +307,77 @@ int main(int argc, char *argv[]) {
lodepng::State inputstate;
std::vector<unsigned char> resultpng;
- lodepng::load_file(origpng, files[i]);
- error = ZopfliPNGOptimize(origpng, png_options, true, &resultpng);
+ error = lodepng::load_file(origpng, files[i]);
+ if (!error) {
+ error = ZopfliPNGOptimize(origpng, png_options,
+ png_options.verbose, &resultpng);
+ }
if (error) {
- 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));
+ }
}
// Verify result, check that the result causes no decoding errors
if (!error) {
- error = lodepng::decode(image, w, h, inputstate, resultpng);
- if (error) printf("Error: verification of result failed.\n");
+ error = lodepng::decode(image, w, h, resultpng);
+ if (!error) {
+ std::vector<unsigned char> origimage;
+ unsigned origw, origh;
+ lodepng::decode(origimage, origw, origh, origpng);
+ if (origw != w || origh != h || origimage.size() != image.size()) {
+ error = 1;
+ } else {
+ for (size_t i = 0; i < image.size(); i += 4) {
+ bool same_alpha = image[i + 3] == origimage[i + 3];
+ bool same_rgb =
+ (png_options.lossy_transparent && image[i + 3] == 0) ||
+ (image[i + 0] == origimage[i + 0] &&
+ image[i + 1] == origimage[i + 1] &&
+ image[i + 2] == origimage[i + 2]);
+ if (!same_alpha || !same_rgb) {
+ error = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (error) {
+ printf("Error: verification of result failed, keeping original."
+ " Error: %u.\n", error);
+ // Reset the error to 0, instead set output back to the original. The
+ // input PNG is valid, zopfli failed on it so treat as if it could not
+ // make it smaller.
+ error = 0;
+ resultpng = origpng;
+ }
}
if (error) {
- printf("There was an error\n");
total_errors++;
} else {
- size_t origsize = GetFileSize(files[i]);
+ size_t origsize = origpng.size();
size_t resultsize = resultpng.size();
+ if (!png_options.keepchunks.empty()) {
+ std::vector<std::string> names;
+ std::vector<size_t> sizes;
+ lodepng::getChunkInfo(names, sizes, resultpng);
+ for (size_t i = 0; i < names.size(); i++) {
+ if (names[i] == "bKGD" || names[i] == "sBIT") {
+ printf("Forced to keep original color type due to keeping bKGD or"
+ " sBIT chunk. Try without --keepchunks for better"
+ " compression.\n");
+ break;
+ }
+ }
+ }
+
+ PrintSize("Input size", origsize);
+ PrintResultSize("Result size", origsize, resultsize);
if (resultsize < origsize) {
printf("Result is smaller\n");
} else if (resultsize == origsize) {
@@ -316,8 +387,6 @@ int main(int argc, char *argv[]) {
? "Original was smaller\n"
: "Preserving original PNG since it was smaller\n");
}
- PrintSize("Input size", origsize);
- PrintResultSize("Result size", origsize, resultsize);
std::string out_filename = user_out_filename;
if (use_prefix) {
@@ -332,28 +401,29 @@ int main(int argc, char *argv[]) {
if (resultpng.size() < origsize) total_files_smaller++;
else if (resultpng.size() == origsize) total_files_equal++;
- if (!always_zopflify && resultpng.size() > origsize) {
- // Set output file to input since input was smaller.
+ if (!always_zopflify && resultpng.size() >= origsize) {
+ // Set output file to input since zopfli didn't improve it.
resultpng = origpng;
}
+ bool already_exists = FileExists(out_filename);
size_t origoutfilesize = GetFileSize(out_filename);
- bool already_exists = true;
- if (origoutfilesize == 0) already_exists = false;
// When using a prefix, and the output file already exist, assume it's
// from a previous run. If that file is smaller, it may represent a
// previous run with different parameters that gave a smaller PNG image.
+ // This also applies when not using prefix but same input as output file.
// In that case, do not overwrite it. This behaviour can be removed by
// adding the always_zopflify flag.
bool keep_earlier_output_file = already_exists &&
- resultpng.size() >= origoutfilesize && !always_zopflify && use_prefix;
+ resultpng.size() >= origoutfilesize && !always_zopflify &&
+ (use_prefix || !different_output_name);
if (keep_earlier_output_file) {
// An output file from a previous run is kept, add that files' size
// to the output size statistics.
total_out_size += origoutfilesize;
- if (different_output_name) {
+ if (use_prefix) {
printf(resultpng.size() == origoutfilesize
? "File not written because a previous run was as good.\n"
: "File not written because a previous run was better.\n");
@@ -372,8 +442,11 @@ int main(int argc, char *argv[]) {
}
if (confirmed) {
if (!dryrun) {
- lodepng::save_file(resultpng, out_filename);
- total_files_saved++;
+ if (lodepng::save_file(resultpng, out_filename) != 0) {
+ printf("Failed to write to file %s\n", out_filename.c_str());
+ } else {
+ total_files_saved++;
+ }
}
total_out_size += resultpng.size();
} else {