diff options
Diffstat (limited to 'src/zopflipng/zopflipng_bin.cc')
-rw-r--r-- | src/zopflipng/zopflipng_bin.cc | 135 |
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 { |