1*e47783fdSXin Li // Copyright 2013 Google Inc. All Rights Reserved.
2*e47783fdSXin Li //
3*e47783fdSXin Li // Licensed under the Apache License, Version 2.0 (the "License");
4*e47783fdSXin Li // you may not use this file except in compliance with the License.
5*e47783fdSXin Li // You may obtain a copy of the License at
6*e47783fdSXin Li //
7*e47783fdSXin Li // http://www.apache.org/licenses/LICENSE-2.0
8*e47783fdSXin Li //
9*e47783fdSXin Li // Unless required by applicable law or agreed to in writing, software
10*e47783fdSXin Li // distributed under the License is distributed on an "AS IS" BASIS,
11*e47783fdSXin Li // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*e47783fdSXin Li // See the License for the specific language governing permissions and
13*e47783fdSXin Li // limitations under the License.
14*e47783fdSXin Li //
15*e47783fdSXin Li // Author: [email protected] (Lode Vandevenne)
16*e47783fdSXin Li // Author: [email protected] (Jyrki Alakuijala)
17*e47783fdSXin Li
18*e47783fdSXin Li // Command line tool to recompress and optimize PNG images, using zopflipng_lib.
19*e47783fdSXin Li
20*e47783fdSXin Li #include <stdlib.h>
21*e47783fdSXin Li #include <stdio.h>
22*e47783fdSXin Li
23*e47783fdSXin Li #include "lodepng/lodepng.h"
24*e47783fdSXin Li #include "lodepng/lodepng_util.h"
25*e47783fdSXin Li #include "zopflipng_lib.h"
26*e47783fdSXin Li
27*e47783fdSXin Li // Returns directory path (including last slash) in dir, filename without
28*e47783fdSXin Li // extension in file, extension (including the dot) in ext
GetFileNameParts(const std::string & filename,std::string * dir,std::string * file,std::string * ext)29*e47783fdSXin Li void GetFileNameParts(const std::string& filename,
30*e47783fdSXin Li std::string* dir, std::string* file, std::string* ext) {
31*e47783fdSXin Li size_t npos = (size_t)(-1);
32*e47783fdSXin Li size_t slashpos = filename.find_last_of("/\\");
33*e47783fdSXin Li std::string nodir;
34*e47783fdSXin Li if (slashpos == npos) {
35*e47783fdSXin Li *dir = "";
36*e47783fdSXin Li nodir = filename;
37*e47783fdSXin Li } else {
38*e47783fdSXin Li *dir = filename.substr(0, slashpos + 1);
39*e47783fdSXin Li nodir = filename.substr(slashpos + 1);
40*e47783fdSXin Li }
41*e47783fdSXin Li size_t dotpos = nodir.find_last_of('.');
42*e47783fdSXin Li if (dotpos == (size_t)(-1)) {
43*e47783fdSXin Li *file = nodir;
44*e47783fdSXin Li *ext = "";
45*e47783fdSXin Li } else {
46*e47783fdSXin Li *file = nodir.substr(0, dotpos);
47*e47783fdSXin Li *ext = nodir.substr(dotpos);
48*e47783fdSXin Li }
49*e47783fdSXin Li }
50*e47783fdSXin Li
51*e47783fdSXin Li // Returns whether the file exists and we have read permissions.
FileExists(const std::string & filename)52*e47783fdSXin Li bool FileExists(const std::string& filename) {
53*e47783fdSXin Li FILE* file = fopen(filename.c_str(), "rb");
54*e47783fdSXin Li if (file) {
55*e47783fdSXin Li fclose(file);
56*e47783fdSXin Li return true;
57*e47783fdSXin Li }
58*e47783fdSXin Li return false;
59*e47783fdSXin Li }
60*e47783fdSXin Li
61*e47783fdSXin Li // Returns the size of the file, if it exists and we have read permissions.
GetFileSize(const std::string & filename)62*e47783fdSXin Li size_t GetFileSize(const std::string& filename) {
63*e47783fdSXin Li size_t size;
64*e47783fdSXin Li FILE* file = fopen(filename.c_str(), "rb");
65*e47783fdSXin Li if (!file) return 0;
66*e47783fdSXin Li fseek(file , 0 , SEEK_END);
67*e47783fdSXin Li size = static_cast<size_t>(ftell(file));
68*e47783fdSXin Li fclose(file);
69*e47783fdSXin Li return size;
70*e47783fdSXin Li }
71*e47783fdSXin Li
ShowHelp()72*e47783fdSXin Li void ShowHelp() {
73*e47783fdSXin Li printf("ZopfliPNG, a Portable Network Graphics (PNG) image optimizer.\n"
74*e47783fdSXin Li "\n"
75*e47783fdSXin Li "Usage: zopflipng [options]... infile.png outfile.png\n"
76*e47783fdSXin Li " zopflipng [options]... --prefix=[fileprefix] [files.png]...\n"
77*e47783fdSXin Li "\n"
78*e47783fdSXin Li "If the output file exists, it is considered a result from a"
79*e47783fdSXin Li " previous run and not overwritten if its filesize is smaller.\n"
80*e47783fdSXin Li "\n"
81*e47783fdSXin Li "Options:\n"
82*e47783fdSXin Li "-m: compress more: use more iterations (depending on file size)\n"
83*e47783fdSXin Li "--prefix=[fileprefix]: Adds a prefix to output filenames. May also"
84*e47783fdSXin Li " contain a directory path. When using a prefix, multiple input files"
85*e47783fdSXin Li " can be given and the output filenames are generated with the"
86*e47783fdSXin Li " prefix\n"
87*e47783fdSXin Li " If --prefix is specified without value, 'zopfli_' is used.\n"
88*e47783fdSXin Li " If input file names contain the prefix, they are not processed but"
89*e47783fdSXin Li " considered as output from previous runs. This is handy when using"
90*e47783fdSXin Li " *.png wildcard expansion with multiple runs.\n"
91*e47783fdSXin Li "-y: do not ask about overwriting files.\n"
92*e47783fdSXin Li "--lossy_transparent: remove colors behind alpha channel 0. No visual"
93*e47783fdSXin Li " difference, removes hidden information.\n"
94*e47783fdSXin Li "--lossy_8bit: convert 16-bit per channel image to 8-bit per"
95*e47783fdSXin Li " channel.\n"
96*e47783fdSXin Li "-d: dry run: don't save any files, just see the console output"
97*e47783fdSXin Li " (e.g. for benchmarking)\n"
98*e47783fdSXin Li "--always_zopflify: always output the image encoded by Zopfli, even if"
99*e47783fdSXin Li " it's bigger than the original, for benchmarking the algorithm. Not"
100*e47783fdSXin Li " good for real optimization.\n"
101*e47783fdSXin Li "-q: use quick, but not very good, compression"
102*e47783fdSXin Li " (e.g. for only trying the PNG filter and color types)\n"
103*e47783fdSXin Li "--iterations=[number]: number of iterations, more iterations makes it"
104*e47783fdSXin Li " slower but provides slightly better compression. Default: 15 for"
105*e47783fdSXin Li " small files, 5 for large files.\n"
106*e47783fdSXin Li "--splitting=[0-3]: ignored, left for backwards compatibility\n"
107*e47783fdSXin Li "--filters=[types]: filter strategies to try:\n"
108*e47783fdSXin Li " 0-4: give all scanlines PNG filter type 0-4\n"
109*e47783fdSXin Li " m: minimum sum\n"
110*e47783fdSXin Li " e: entropy\n"
111*e47783fdSXin Li " p: predefined (keep from input, this likely overlaps another"
112*e47783fdSXin Li " strategy)\n"
113*e47783fdSXin Li " b: brute force (experimental)\n"
114*e47783fdSXin Li " By default, if this argument is not given, one that is most likely"
115*e47783fdSXin Li " the best for this image is chosen by trying faster compression with"
116*e47783fdSXin Li " each type.\n"
117*e47783fdSXin Li " If this argument is used, all given filter types"
118*e47783fdSXin Li " are tried with slow compression and the best result retained. A good"
119*e47783fdSXin Li " set of filters to try is --filters=0me.\n"
120*e47783fdSXin Li "--keepchunks=nAME,nAME,...: keep metadata chunks with these names"
121*e47783fdSXin Li " that would normally be removed, e.g. tEXt,zTXt,iTXt,gAMA, ... \n"
122*e47783fdSXin Li " Due to adding extra data, this increases the result size. Keeping"
123*e47783fdSXin Li " bKGD or sBIT chunks may cause additional worse compression due to"
124*e47783fdSXin Li " forcing a certain color type, it is advised to not keep these for"
125*e47783fdSXin Li " web images because web browsers do not use these chunks. By default"
126*e47783fdSXin Li " ZopfliPNG only keeps (and losslessly modifies) the following chunks"
127*e47783fdSXin Li " because they are essential: IHDR, PLTE, tRNS, IDAT and IEND.\n"
128*e47783fdSXin Li "--keepcolortype: Keep original color type (RGB, RGBA, gray,"
129*e47783fdSXin Li " gray+alpha or palette) and bit depth of the PNG.\n"
130*e47783fdSXin Li " This results in a loss of compression opportunities, e.g. it will no"
131*e47783fdSXin Li " longer convert a 4-channel RGBA image to 2-channel gray+alpha if the"
132*e47783fdSXin Li " image only had translucent gray pixels.\n"
133*e47783fdSXin Li " May be useful if a device does not support decoding PNGs of a"
134*e47783fdSXin Li " particular color type.\n"
135*e47783fdSXin Li "\n"
136*e47783fdSXin Li "Usage examples:\n"
137*e47783fdSXin Li "Optimize a file and overwrite if smaller: zopflipng infile.png"
138*e47783fdSXin Li " outfile.png\n"
139*e47783fdSXin Li "Compress more: zopflipng -m infile.png outfile.png\n"
140*e47783fdSXin Li "Optimize multiple files: zopflipng --prefix a.png b.png c.png\n"
141*e47783fdSXin Li "Compress really good and trying all filter strategies: zopflipng"
142*e47783fdSXin Li " --iterations=500 --filters=01234mepb --lossy_8bit"
143*e47783fdSXin Li " --lossy_transparent infile.png outfile.png\n");
144*e47783fdSXin Li }
145*e47783fdSXin Li
PrintSize(const char * label,size_t size)146*e47783fdSXin Li void PrintSize(const char* label, size_t size) {
147*e47783fdSXin Li printf("%s: %d (%dK)\n", label, (int) size, (int) size / 1024);
148*e47783fdSXin Li }
149*e47783fdSXin Li
PrintResultSize(const char * label,size_t oldsize,size_t newsize)150*e47783fdSXin Li void PrintResultSize(const char* label, size_t oldsize, size_t newsize) {
151*e47783fdSXin Li printf("%s: %d (%dK). Percentage of original: %.3f%%\n",
152*e47783fdSXin Li label, (int) newsize, (int) newsize / 1024, newsize * 100.0 / oldsize);
153*e47783fdSXin Li }
154*e47783fdSXin Li
main(int argc,char * argv[])155*e47783fdSXin Li int main(int argc, char *argv[]) {
156*e47783fdSXin Li if (argc < 2) {
157*e47783fdSXin Li ShowHelp();
158*e47783fdSXin Li return 0;
159*e47783fdSXin Li }
160*e47783fdSXin Li
161*e47783fdSXin Li ZopfliPNGOptions png_options;
162*e47783fdSXin Li
163*e47783fdSXin Li // cmd line options
164*e47783fdSXin Li bool always_zopflify = false; // overwrite file even if we have bigger result
165*e47783fdSXin Li bool yes = false; // do not ask to overwrite files
166*e47783fdSXin Li bool dryrun = false; // never save anything
167*e47783fdSXin Li
168*e47783fdSXin Li std::string user_out_filename; // output filename if no prefix is used
169*e47783fdSXin Li bool use_prefix = false;
170*e47783fdSXin Li std::string prefix = "zopfli_"; // prefix for output filenames
171*e47783fdSXin Li
172*e47783fdSXin Li std::vector<std::string> files;
173*e47783fdSXin Li for (int i = 1; i < argc; i++) {
174*e47783fdSXin Li std::string arg = argv[i];
175*e47783fdSXin Li if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') {
176*e47783fdSXin Li for (size_t pos = 1; pos < arg.size(); pos++) {
177*e47783fdSXin Li char c = arg[pos];
178*e47783fdSXin Li if (c == 'y') {
179*e47783fdSXin Li yes = true;
180*e47783fdSXin Li } else if (c == 'd') {
181*e47783fdSXin Li dryrun = true;
182*e47783fdSXin Li } else if (c == 'm') {
183*e47783fdSXin Li png_options.num_iterations *= 4;
184*e47783fdSXin Li png_options.num_iterations_large *= 4;
185*e47783fdSXin Li } else if (c == 'q') {
186*e47783fdSXin Li png_options.use_zopfli = false;
187*e47783fdSXin Li } else if (c == 'h') {
188*e47783fdSXin Li ShowHelp();
189*e47783fdSXin Li return 0;
190*e47783fdSXin Li } else {
191*e47783fdSXin Li printf("Unknown flag: %c\n", c);
192*e47783fdSXin Li return 0;
193*e47783fdSXin Li }
194*e47783fdSXin Li }
195*e47783fdSXin Li } else if (arg[0] == '-' && arg.size() > 1 && arg[1] == '-') {
196*e47783fdSXin Li size_t eq = arg.find('=');
197*e47783fdSXin Li std::string name = arg.substr(0, eq);
198*e47783fdSXin Li std::string value = eq >= arg.size() - 1 ? "" : arg.substr(eq + 1);
199*e47783fdSXin Li int num = atoi(value.c_str());
200*e47783fdSXin Li if (name == "--always_zopflify") {
201*e47783fdSXin Li always_zopflify = true;
202*e47783fdSXin Li } else if (name == "--verbose") {
203*e47783fdSXin Li png_options.verbose = true;
204*e47783fdSXin Li } else if (name == "--lossy_transparent") {
205*e47783fdSXin Li png_options.lossy_transparent = true;
206*e47783fdSXin Li } else if (name == "--lossy_8bit") {
207*e47783fdSXin Li png_options.lossy_8bit = true;
208*e47783fdSXin Li } else if (name == "--iterations") {
209*e47783fdSXin Li if (num < 1) num = 1;
210*e47783fdSXin Li png_options.num_iterations = num;
211*e47783fdSXin Li png_options.num_iterations_large = num;
212*e47783fdSXin Li } else if (name == "--splitting") {
213*e47783fdSXin Li // ignored
214*e47783fdSXin Li } else if (name == "--filters") {
215*e47783fdSXin Li for (size_t j = 0; j < value.size(); j++) {
216*e47783fdSXin Li ZopfliPNGFilterStrategy strategy = kStrategyZero;
217*e47783fdSXin Li char f = value[j];
218*e47783fdSXin Li switch (f) {
219*e47783fdSXin Li case '0': strategy = kStrategyZero; break;
220*e47783fdSXin Li case '1': strategy = kStrategyOne; break;
221*e47783fdSXin Li case '2': strategy = kStrategyTwo; break;
222*e47783fdSXin Li case '3': strategy = kStrategyThree; break;
223*e47783fdSXin Li case '4': strategy = kStrategyFour; break;
224*e47783fdSXin Li case 'm': strategy = kStrategyMinSum; break;
225*e47783fdSXin Li case 'e': strategy = kStrategyEntropy; break;
226*e47783fdSXin Li case 'p': strategy = kStrategyPredefined; break;
227*e47783fdSXin Li case 'b': strategy = kStrategyBruteForce; break;
228*e47783fdSXin Li default:
229*e47783fdSXin Li printf("Unknown filter strategy: %c\n", f);
230*e47783fdSXin Li return 1;
231*e47783fdSXin Li }
232*e47783fdSXin Li png_options.filter_strategies.push_back(strategy);
233*e47783fdSXin Li // Enable auto filter strategy only if no user-specified filter is
234*e47783fdSXin Li // given.
235*e47783fdSXin Li png_options.auto_filter_strategy = false;
236*e47783fdSXin Li }
237*e47783fdSXin Li } else if (name == "--keepchunks") {
238*e47783fdSXin Li bool correct = true;
239*e47783fdSXin Li if ((value.size() + 1) % 5 != 0) correct = false;
240*e47783fdSXin Li for (size_t i = 0; i + 4 <= value.size() && correct; i += 5) {
241*e47783fdSXin Li png_options.keepchunks.push_back(value.substr(i, 4));
242*e47783fdSXin Li if (i > 4 && value[i - 1] != ',') correct = false;
243*e47783fdSXin Li }
244*e47783fdSXin Li if (!correct) {
245*e47783fdSXin Li printf("Error: keepchunks format must be like for example:\n"
246*e47783fdSXin Li " --keepchunks=gAMA,cHRM,sRGB,iCCP\n");
247*e47783fdSXin Li return 0;
248*e47783fdSXin Li }
249*e47783fdSXin Li } else if (name == "--keepcolortype") {
250*e47783fdSXin Li png_options.keep_colortype = true;
251*e47783fdSXin Li } else if (name == "--prefix") {
252*e47783fdSXin Li use_prefix = true;
253*e47783fdSXin Li if (!value.empty()) prefix = value;
254*e47783fdSXin Li } else if (name == "--help") {
255*e47783fdSXin Li ShowHelp();
256*e47783fdSXin Li return 0;
257*e47783fdSXin Li } else {
258*e47783fdSXin Li printf("Unknown flag: %s\n", name.c_str());
259*e47783fdSXin Li return 0;
260*e47783fdSXin Li }
261*e47783fdSXin Li } else {
262*e47783fdSXin Li files.push_back(argv[i]);
263*e47783fdSXin Li }
264*e47783fdSXin Li }
265*e47783fdSXin Li
266*e47783fdSXin Li if (!use_prefix) {
267*e47783fdSXin Li if (files.size() == 2) {
268*e47783fdSXin Li // The second filename is the output instead of an input if no prefix is
269*e47783fdSXin Li // given.
270*e47783fdSXin Li user_out_filename = files[1];
271*e47783fdSXin Li files.resize(1);
272*e47783fdSXin Li } else {
273*e47783fdSXin Li printf("Please provide one input and output filename\n\n");
274*e47783fdSXin Li ShowHelp();
275*e47783fdSXin Li return 0;
276*e47783fdSXin Li }
277*e47783fdSXin Li }
278*e47783fdSXin Li
279*e47783fdSXin Li size_t total_in_size = 0;
280*e47783fdSXin Li // Total output size, taking input size if the input file was smaller
281*e47783fdSXin Li size_t total_out_size = 0;
282*e47783fdSXin Li // Total output size that zopfli produced, even if input was smaller, for
283*e47783fdSXin Li // benchmark information
284*e47783fdSXin Li size_t total_out_size_zopfli = 0;
285*e47783fdSXin Li size_t total_errors = 0;
286*e47783fdSXin Li size_t total_files = 0;
287*e47783fdSXin Li size_t total_files_smaller = 0;
288*e47783fdSXin Li size_t total_files_saved = 0;
289*e47783fdSXin Li size_t total_files_equal = 0;
290*e47783fdSXin Li
291*e47783fdSXin Li for (size_t i = 0; i < files.size(); i++) {
292*e47783fdSXin Li if (use_prefix && files.size() > 1) {
293*e47783fdSXin Li std::string dir, file, ext;
294*e47783fdSXin Li GetFileNameParts(files[i], &dir, &file, &ext);
295*e47783fdSXin Li // avoid doing filenames which were already output by this so that you
296*e47783fdSXin Li // don't get zopfli_zopfli_zopfli_... files after multiple runs.
297*e47783fdSXin Li if (file.find(prefix) == 0) continue;
298*e47783fdSXin Li }
299*e47783fdSXin Li
300*e47783fdSXin Li total_files++;
301*e47783fdSXin Li
302*e47783fdSXin Li printf("Optimizing %s\n", files[i].c_str());
303*e47783fdSXin Li std::vector<unsigned char> image;
304*e47783fdSXin Li unsigned w, h;
305*e47783fdSXin Li std::vector<unsigned char> origpng;
306*e47783fdSXin Li unsigned error;
307*e47783fdSXin Li lodepng::State inputstate;
308*e47783fdSXin Li std::vector<unsigned char> resultpng;
309*e47783fdSXin Li
310*e47783fdSXin Li error = lodepng::load_file(origpng, files[i]);
311*e47783fdSXin Li if (!error) {
312*e47783fdSXin Li error = ZopfliPNGOptimize(origpng, png_options,
313*e47783fdSXin Li png_options.verbose, &resultpng);
314*e47783fdSXin Li }
315*e47783fdSXin Li
316*e47783fdSXin Li if (error) {
317*e47783fdSXin Li if (error == 1) {
318*e47783fdSXin Li printf("Decoding error\n");
319*e47783fdSXin Li } else {
320*e47783fdSXin Li printf("Decoding error %u: %s\n", error, lodepng_error_text(error));
321*e47783fdSXin Li }
322*e47783fdSXin Li }
323*e47783fdSXin Li
324*e47783fdSXin Li // Verify result, check that the result causes no decoding errors
325*e47783fdSXin Li if (!error) {
326*e47783fdSXin Li error = lodepng::decode(image, w, h, resultpng);
327*e47783fdSXin Li if (!error) {
328*e47783fdSXin Li std::vector<unsigned char> origimage;
329*e47783fdSXin Li unsigned origw, origh;
330*e47783fdSXin Li lodepng::decode(origimage, origw, origh, origpng);
331*e47783fdSXin Li if (origw != w || origh != h || origimage.size() != image.size()) {
332*e47783fdSXin Li error = 1;
333*e47783fdSXin Li } else {
334*e47783fdSXin Li for (size_t i = 0; i < image.size(); i += 4) {
335*e47783fdSXin Li bool same_alpha = image[i + 3] == origimage[i + 3];
336*e47783fdSXin Li bool same_rgb =
337*e47783fdSXin Li (png_options.lossy_transparent && image[i + 3] == 0) ||
338*e47783fdSXin Li (image[i + 0] == origimage[i + 0] &&
339*e47783fdSXin Li image[i + 1] == origimage[i + 1] &&
340*e47783fdSXin Li image[i + 2] == origimage[i + 2]);
341*e47783fdSXin Li if (!same_alpha || !same_rgb) {
342*e47783fdSXin Li error = 1;
343*e47783fdSXin Li break;
344*e47783fdSXin Li }
345*e47783fdSXin Li }
346*e47783fdSXin Li }
347*e47783fdSXin Li }
348*e47783fdSXin Li if (error) {
349*e47783fdSXin Li printf("Error: verification of result failed, keeping original."
350*e47783fdSXin Li " Error: %u.\n", error);
351*e47783fdSXin Li // Reset the error to 0, instead set output back to the original. The
352*e47783fdSXin Li // input PNG is valid, zopfli failed on it so treat as if it could not
353*e47783fdSXin Li // make it smaller.
354*e47783fdSXin Li error = 0;
355*e47783fdSXin Li resultpng = origpng;
356*e47783fdSXin Li }
357*e47783fdSXin Li }
358*e47783fdSXin Li
359*e47783fdSXin Li if (error) {
360*e47783fdSXin Li total_errors++;
361*e47783fdSXin Li } else {
362*e47783fdSXin Li size_t origsize = origpng.size();
363*e47783fdSXin Li size_t resultsize = resultpng.size();
364*e47783fdSXin Li
365*e47783fdSXin Li if (!png_options.keepchunks.empty()) {
366*e47783fdSXin Li std::vector<std::string> names;
367*e47783fdSXin Li std::vector<size_t> sizes;
368*e47783fdSXin Li lodepng::getChunkInfo(names, sizes, resultpng);
369*e47783fdSXin Li for (size_t i = 0; i < names.size(); i++) {
370*e47783fdSXin Li if (names[i] == "bKGD" || names[i] == "sBIT") {
371*e47783fdSXin Li printf("Forced to keep original color type due to keeping bKGD or"
372*e47783fdSXin Li " sBIT chunk. Try without --keepchunks for better"
373*e47783fdSXin Li " compression.\n");
374*e47783fdSXin Li break;
375*e47783fdSXin Li }
376*e47783fdSXin Li }
377*e47783fdSXin Li }
378*e47783fdSXin Li
379*e47783fdSXin Li PrintSize("Input size", origsize);
380*e47783fdSXin Li PrintResultSize("Result size", origsize, resultsize);
381*e47783fdSXin Li if (resultsize < origsize) {
382*e47783fdSXin Li printf("Result is smaller\n");
383*e47783fdSXin Li } else if (resultsize == origsize) {
384*e47783fdSXin Li printf("Result has exact same size\n");
385*e47783fdSXin Li } else {
386*e47783fdSXin Li printf(always_zopflify
387*e47783fdSXin Li ? "Original was smaller\n"
388*e47783fdSXin Li : "Preserving original PNG since it was smaller\n");
389*e47783fdSXin Li }
390*e47783fdSXin Li
391*e47783fdSXin Li std::string out_filename = user_out_filename;
392*e47783fdSXin Li if (use_prefix) {
393*e47783fdSXin Li std::string dir, file, ext;
394*e47783fdSXin Li GetFileNameParts(files[i], &dir, &file, &ext);
395*e47783fdSXin Li out_filename = dir + prefix + file + ext;
396*e47783fdSXin Li }
397*e47783fdSXin Li bool different_output_name = out_filename != files[i];
398*e47783fdSXin Li
399*e47783fdSXin Li total_in_size += origsize;
400*e47783fdSXin Li total_out_size_zopfli += resultpng.size();
401*e47783fdSXin Li if (resultpng.size() < origsize) total_files_smaller++;
402*e47783fdSXin Li else if (resultpng.size() == origsize) total_files_equal++;
403*e47783fdSXin Li
404*e47783fdSXin Li if (!always_zopflify && resultpng.size() >= origsize) {
405*e47783fdSXin Li // Set output file to input since zopfli didn't improve it.
406*e47783fdSXin Li resultpng = origpng;
407*e47783fdSXin Li }
408*e47783fdSXin Li
409*e47783fdSXin Li bool already_exists = FileExists(out_filename);
410*e47783fdSXin Li size_t origoutfilesize = GetFileSize(out_filename);
411*e47783fdSXin Li
412*e47783fdSXin Li // When using a prefix, and the output file already exist, assume it's
413*e47783fdSXin Li // from a previous run. If that file is smaller, it may represent a
414*e47783fdSXin Li // previous run with different parameters that gave a smaller PNG image.
415*e47783fdSXin Li // This also applies when not using prefix but same input as output file.
416*e47783fdSXin Li // In that case, do not overwrite it. This behaviour can be removed by
417*e47783fdSXin Li // adding the always_zopflify flag.
418*e47783fdSXin Li bool keep_earlier_output_file = already_exists &&
419*e47783fdSXin Li resultpng.size() >= origoutfilesize && !always_zopflify &&
420*e47783fdSXin Li (use_prefix || !different_output_name);
421*e47783fdSXin Li
422*e47783fdSXin Li if (keep_earlier_output_file) {
423*e47783fdSXin Li // An output file from a previous run is kept, add that files' size
424*e47783fdSXin Li // to the output size statistics.
425*e47783fdSXin Li total_out_size += origoutfilesize;
426*e47783fdSXin Li if (use_prefix) {
427*e47783fdSXin Li printf(resultpng.size() == origoutfilesize
428*e47783fdSXin Li ? "File not written because a previous run was as good.\n"
429*e47783fdSXin Li : "File not written because a previous run was better.\n");
430*e47783fdSXin Li }
431*e47783fdSXin Li } else {
432*e47783fdSXin Li bool confirmed = true;
433*e47783fdSXin Li if (!yes && !dryrun && already_exists) {
434*e47783fdSXin Li printf("File %s exists, overwrite? (y/N) ", out_filename.c_str());
435*e47783fdSXin Li char answer = 0;
436*e47783fdSXin Li // Read the first character, the others and enter with getchar.
437*e47783fdSXin Li while (int input = getchar()) {
438*e47783fdSXin Li if (input == '\n' || input == EOF) break;
439*e47783fdSXin Li else if (!answer) answer = input;
440*e47783fdSXin Li }
441*e47783fdSXin Li confirmed = answer == 'y' || answer == 'Y';
442*e47783fdSXin Li }
443*e47783fdSXin Li if (confirmed) {
444*e47783fdSXin Li if (!dryrun) {
445*e47783fdSXin Li if (lodepng::save_file(resultpng, out_filename) != 0) {
446*e47783fdSXin Li printf("Failed to write to file %s\n", out_filename.c_str());
447*e47783fdSXin Li } else {
448*e47783fdSXin Li total_files_saved++;
449*e47783fdSXin Li }
450*e47783fdSXin Li }
451*e47783fdSXin Li total_out_size += resultpng.size();
452*e47783fdSXin Li } else {
453*e47783fdSXin Li // An output file from a previous run is kept, add that files' size
454*e47783fdSXin Li // to the output size statistics.
455*e47783fdSXin Li total_out_size += origoutfilesize;
456*e47783fdSXin Li }
457*e47783fdSXin Li }
458*e47783fdSXin Li }
459*e47783fdSXin Li printf("\n");
460*e47783fdSXin Li }
461*e47783fdSXin Li
462*e47783fdSXin Li if (total_files > 1) {
463*e47783fdSXin Li printf("Summary for all files:\n");
464*e47783fdSXin Li printf("Files tried: %d\n", (int) total_files);
465*e47783fdSXin Li printf("Files smaller: %d\n", (int) total_files_smaller);
466*e47783fdSXin Li if (total_files_equal) {
467*e47783fdSXin Li printf("Files equal: %d\n", (int) total_files_equal);
468*e47783fdSXin Li }
469*e47783fdSXin Li printf("Files saved: %d\n", (int) total_files_saved);
470*e47783fdSXin Li if (total_errors) printf("Errors: %d\n", (int) total_errors);
471*e47783fdSXin Li PrintSize("Total input size", total_in_size);
472*e47783fdSXin Li PrintResultSize("Total output size", total_in_size, total_out_size);
473*e47783fdSXin Li PrintResultSize("Benchmark result size",
474*e47783fdSXin Li total_in_size, total_out_size_zopfli);
475*e47783fdSXin Li }
476*e47783fdSXin Li
477*e47783fdSXin Li if (dryrun) printf("No files were written because dry run was specified\n");
478*e47783fdSXin Li
479*e47783fdSXin Li return total_errors;
480*e47783fdSXin Li }
481