zopfli-bin 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitmodules +3 -0
- data/.testguardrc +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +111 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +39 -0
- data/VERSION +1 -0
- data/ext/Makefile +8 -0
- data/ext/extconf.rb +4 -0
- data/lib/zopfli-bin.rb +5 -0
- data/lib/zopfli/bin.rb +34 -0
- data/test/helper.rb +19 -0
- data/test/test_zopfli-bin.rb +33 -0
- data/vendor/zopfli/CONTRIBUTORS +7 -0
- data/vendor/zopfli/COPYING +201 -0
- data/vendor/zopfli/Makefile +37 -0
- data/vendor/zopfli/README +32 -0
- data/vendor/zopfli/README.zopflipng +35 -0
- data/vendor/zopfli/src/zopfli/blocksplitter.c +342 -0
- data/vendor/zopfli/src/zopfli/blocksplitter.h +77 -0
- data/vendor/zopfli/src/zopfli/cache.c +119 -0
- data/vendor/zopfli/src/zopfli/cache.h +66 -0
- data/vendor/zopfli/src/zopfli/deflate.c +866 -0
- data/vendor/zopfli/src/zopfli/deflate.h +86 -0
- data/vendor/zopfli/src/zopfli/gzip_container.c +117 -0
- data/vendor/zopfli/src/zopfli/gzip_container.h +50 -0
- data/vendor/zopfli/src/zopfli/hash.c +135 -0
- data/vendor/zopfli/src/zopfli/hash.h +70 -0
- data/vendor/zopfli/src/zopfli/katajainen.c +251 -0
- data/vendor/zopfli/src/zopfli/katajainen.h +42 -0
- data/vendor/zopfli/src/zopfli/lz77.c +482 -0
- data/vendor/zopfli/src/zopfli/lz77.h +129 -0
- data/vendor/zopfli/src/zopfli/squeeze.c +546 -0
- data/vendor/zopfli/src/zopfli/squeeze.h +60 -0
- data/vendor/zopfli/src/zopfli/tree.c +101 -0
- data/vendor/zopfli/src/zopfli/tree.h +51 -0
- data/vendor/zopfli/src/zopfli/util.c +213 -0
- data/vendor/zopfli/src/zopfli/util.h +175 -0
- data/vendor/zopfli/src/zopfli/zlib_container.c +79 -0
- data/vendor/zopfli/src/zopfli/zlib_container.h +50 -0
- data/vendor/zopfli/src/zopfli/zopfli.h +97 -0
- data/vendor/zopfli/src/zopfli/zopfli_bin.c +203 -0
- data/vendor/zopfli/src/zopfli/zopfli_lib.c +42 -0
- data/vendor/zopfli/src/zopflipng/lodepng/lodepng.cpp +6260 -0
- data/vendor/zopfli/src/zopflipng/lodepng/lodepng.h +1716 -0
- data/vendor/zopfli/src/zopflipng/lodepng/lodepng_util.cpp +656 -0
- data/vendor/zopfli/src/zopflipng/lodepng/lodepng_util.h +151 -0
- data/vendor/zopfli/src/zopflipng/zopflipng_bin.cc +407 -0
- data/vendor/zopfli/src/zopflipng/zopflipng_lib.cc +425 -0
- data/vendor/zopfli/src/zopflipng/zopflipng_lib.h +79 -0
- data/zopfli-bin.gemspec +119 -0
- metadata +225 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
/*
|
2
|
+
LodePNG Utils
|
3
|
+
|
4
|
+
Copyright (c) 2005-2012 Lode Vandevenne
|
5
|
+
|
6
|
+
This software is provided 'as-is', without any express or implied
|
7
|
+
warranty. In no event will the authors be held liable for any damages
|
8
|
+
arising from the use of this software.
|
9
|
+
|
10
|
+
Permission is granted to anyone to use this software for any purpose,
|
11
|
+
including commercial applications, and to alter it and redistribute it
|
12
|
+
freely, subject to the following restrictions:
|
13
|
+
|
14
|
+
1. The origin of this software must not be misrepresented; you must not
|
15
|
+
claim that you wrote the original software. If you use this software
|
16
|
+
in a product, an acknowledgment in the product documentation would be
|
17
|
+
appreciated but is not required.
|
18
|
+
|
19
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
20
|
+
misrepresented as being the original software.
|
21
|
+
|
22
|
+
3. This notice may not be removed or altered from any source
|
23
|
+
distribution.
|
24
|
+
*/
|
25
|
+
|
26
|
+
/*
|
27
|
+
Extra C++ utilities for LodePNG, for convenience.
|
28
|
+
*/
|
29
|
+
|
30
|
+
#include <string>
|
31
|
+
#include <vector>
|
32
|
+
#include "lodepng.h"
|
33
|
+
|
34
|
+
#pragma once
|
35
|
+
|
36
|
+
namespace lodepng
|
37
|
+
{
|
38
|
+
|
39
|
+
/*
|
40
|
+
Returns info from the header of the PNG by value, purely for convenience.
|
41
|
+
Does NOT check for errors. Returns bogus info if the PNG has an error.
|
42
|
+
Does not require cleanup of allocated memory because no palette or text chunk
|
43
|
+
info is in the LodePNGInfo object after checking only the header of the PNG.
|
44
|
+
*/
|
45
|
+
LodePNGInfo getPNGHeaderInfo(const std::vector<unsigned char>& png);
|
46
|
+
|
47
|
+
/*
|
48
|
+
Get the names and sizes of all chunks in the PNG file.
|
49
|
+
Returns 0 if ok, non-0 if error happened.
|
50
|
+
*/
|
51
|
+
unsigned getChunkInfo(std::vector<std::string>& names, std::vector<size_t>& sizes,
|
52
|
+
const std::vector<unsigned char>& png);
|
53
|
+
|
54
|
+
/*
|
55
|
+
Returns the names and full chunks (including the name and everything else that
|
56
|
+
makes up the chunk) for all chunks except IHDR, PLTE, IDAT and IEND.
|
57
|
+
It separates the chunks into 3 separate lists, representing the chunks between
|
58
|
+
certain critical chunks: 0: IHDR-PLTE, 1: PLTE-IDAT, 2: IDAT-IEND
|
59
|
+
Returns 0 if ok, non-0 if error happened.
|
60
|
+
*/
|
61
|
+
unsigned getChunks(std::vector<std::string> names[3],
|
62
|
+
std::vector<std::vector<unsigned char> > chunks[3],
|
63
|
+
const std::vector<unsigned char>& png);
|
64
|
+
|
65
|
+
/*
|
66
|
+
Inserts chunks into the given png file. The chunks must be fully encoded,
|
67
|
+
including length, type, content and CRC.
|
68
|
+
The array index determines where it goes:
|
69
|
+
0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND.
|
70
|
+
They're appended at the end of those locations within the PNG.
|
71
|
+
Returns 0 if ok, non-0 if error happened.
|
72
|
+
*/
|
73
|
+
unsigned insertChunks(std::vector<unsigned char>& png,
|
74
|
+
const std::vector<std::vector<unsigned char> > chunks[3]);
|
75
|
+
|
76
|
+
/*
|
77
|
+
Get the filtertypes of each scanline in this PNG file.
|
78
|
+
Returns 0 if ok, 1 if PNG decoding error happened.
|
79
|
+
|
80
|
+
For a non-interlaced PNG, it returns one filtertype per scanline, in order.
|
81
|
+
|
82
|
+
For interlaced PNGs, it returns a result as if it's not interlaced. It returns
|
83
|
+
one filtertype per scanline, in order. The values match pass 6 and 7 of the
|
84
|
+
Adam7 interlacing, alternating between the two, so that the values correspond
|
85
|
+
the most to their scanlines.
|
86
|
+
*/
|
87
|
+
unsigned getFilterTypes(std::vector<unsigned char>& filterTypes, const std::vector<unsigned char>& png);
|
88
|
+
|
89
|
+
/*
|
90
|
+
Get the filtertypes of each scanline in every interlace pass this PNG file.
|
91
|
+
Returns 0 if ok, 1 if PNG decoding error happened.
|
92
|
+
|
93
|
+
For a non-interlaced PNG, it returns one filtertype per scanline, in order, in
|
94
|
+
a single std::vector in filterTypes.
|
95
|
+
|
96
|
+
For an interlaced PNG, it returns 7 std::vectors in filterTypes, one for each
|
97
|
+
Adam7 pass. The amount of values per pass can be calculated as follows, where
|
98
|
+
w and h are the size of the image and all divisions are integer divisions:
|
99
|
+
pass 1: (h + 7) / 8
|
100
|
+
pass 2: w <= 4 ? 0 : (h + 7) / 8
|
101
|
+
pass 3: h <= 4 ? 0 : (h + 7) / 8
|
102
|
+
pass 4: w <= 2 ? 0 : (h + 3) / 4
|
103
|
+
pass 5: h <= 2 ? 0 : (h + 3) / 4
|
104
|
+
pass 6: w <= 1 ? 0 : (h + 1) / 2
|
105
|
+
pass 7: h <= 1 ? 0 : (h + 1) / 2
|
106
|
+
*/
|
107
|
+
unsigned getFilterTypesInterlaced(std::vector<std::vector<unsigned char> >& filterTypes,
|
108
|
+
const std::vector<unsigned char>& png);
|
109
|
+
|
110
|
+
/*
|
111
|
+
Returns the value of the i-th pixel in an image with 1, 2, 4 or 8-bit color.
|
112
|
+
E.g. if bits is 4 and i is 5, it returns the 5th nibble (4-bit group), which
|
113
|
+
is the second half of the 3th byte, in big endian (PNG's endian order).
|
114
|
+
*/
|
115
|
+
int getPaletteValue(const unsigned char* data, size_t i, int bits);
|
116
|
+
|
117
|
+
/*
|
118
|
+
The information for extractZlibInfo.
|
119
|
+
*/
|
120
|
+
struct ZlibBlockInfo
|
121
|
+
{
|
122
|
+
int btype; //block type (0-2)
|
123
|
+
size_t compressedbits; //size of compressed block in bits
|
124
|
+
size_t uncompressedbytes; //size of uncompressed block in bytes
|
125
|
+
|
126
|
+
// only filled in for block type 2
|
127
|
+
size_t treebits; //encoded tree size in bits
|
128
|
+
int hlit; //the HLIT value that was filled in for this tree
|
129
|
+
int hdist; //the HDIST value that was filled in for this tree
|
130
|
+
int hclen; //the HCLEN value that was filled in for this tree
|
131
|
+
std::vector<int> clcl; //19 code length code lengths (compressed tree's tree)
|
132
|
+
std::vector<int> treecodes; //N tree codes, with values 0-18. Values 17 or 18 are followed by the repetition value.
|
133
|
+
std::vector<int> litlenlengths; //288 code lengths for lit/len symbols
|
134
|
+
std::vector<int> distlengths; //32 code lengths for dist symbols
|
135
|
+
|
136
|
+
// only filled in for block types 1 or 2
|
137
|
+
std::vector<int> lz77_lcode; //LZ77 codes. 0-255: literals. 256: end symbol. 257-285: length code of length/dist pairs
|
138
|
+
// the next vectors have the same size as lz77_lcode, but an element only has meaningful value if lz77_lcode contains a length code.
|
139
|
+
std::vector<int> lz77_dcode;
|
140
|
+
std::vector<int> lz77_lbits;
|
141
|
+
std::vector<int> lz77_dbits;
|
142
|
+
std::vector<int> lz77_lvalue;
|
143
|
+
std::vector<int> lz77_dvalue;
|
144
|
+
size_t numlit; //number of lit codes in this block
|
145
|
+
size_t numlen; //number of len codes in this block
|
146
|
+
};
|
147
|
+
|
148
|
+
//Extracts all info needed from a PNG file to reconstruct the zlib compression exactly.
|
149
|
+
void extractZlibInfo(std::vector<ZlibBlockInfo>& zlibinfo, const std::vector<unsigned char>& in);
|
150
|
+
|
151
|
+
} // namespace lodepng
|
@@ -0,0 +1,407 @@
|
|
1
|
+
// Copyright 2013 Google Inc. All Rights Reserved.
|
2
|
+
//
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
// you may not use this file except in compliance with the License.
|
5
|
+
// You may obtain a copy of the License at
|
6
|
+
//
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
//
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
// See the License for the specific language governing permissions and
|
13
|
+
// limitations under the License.
|
14
|
+
//
|
15
|
+
// Author: lode.vandevenne@gmail.com (Lode Vandevenne)
|
16
|
+
// Author: jyrki.alakuijala@gmail.com (Jyrki Alakuijala)
|
17
|
+
|
18
|
+
// Command line tool to recompress and optimize PNG images, using zopflipng_lib.
|
19
|
+
|
20
|
+
#include <stdlib.h>
|
21
|
+
#include <stdio.h>
|
22
|
+
|
23
|
+
#include "lodepng/lodepng.h"
|
24
|
+
#include "zopflipng_lib.h"
|
25
|
+
|
26
|
+
// Returns directory path (including last slash) in dir, filename without
|
27
|
+
// extension in file, extension (including the dot) in ext
|
28
|
+
void GetFileNameParts(const std::string& filename,
|
29
|
+
std::string* dir, std::string* file, std::string* ext) {
|
30
|
+
size_t npos = (size_t)(-1);
|
31
|
+
size_t slashpos = filename.find_last_of("/\\");
|
32
|
+
std::string nodir;
|
33
|
+
if (slashpos == npos) {
|
34
|
+
*dir = "";
|
35
|
+
nodir = filename;
|
36
|
+
} else {
|
37
|
+
*dir = filename.substr(0, slashpos + 1);
|
38
|
+
nodir = filename.substr(slashpos + 1);
|
39
|
+
}
|
40
|
+
size_t dotpos = nodir.find_last_of('.');
|
41
|
+
if (dotpos == (size_t)(-1)) {
|
42
|
+
*file = nodir;
|
43
|
+
*ext = "";
|
44
|
+
} else {
|
45
|
+
*file = nodir.substr(0, dotpos);
|
46
|
+
*ext = nodir.substr(dotpos);
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
// Returns the size of the file
|
51
|
+
size_t GetFileSize(const std::string& filename) {
|
52
|
+
size_t size;
|
53
|
+
FILE* file = fopen(filename.c_str(), "rb");
|
54
|
+
if (!file) return 0;
|
55
|
+
fseek(file , 0 , SEEK_END);
|
56
|
+
size = static_cast<size_t>(ftell(file));
|
57
|
+
fclose(file);
|
58
|
+
return size;
|
59
|
+
}
|
60
|
+
|
61
|
+
void ShowHelp() {
|
62
|
+
printf("ZopfliPNG, a Portable Network Graphics (PNG) image optimizer.\n"
|
63
|
+
"\n"
|
64
|
+
"Usage: zopflipng [options]... infile.png outfile.png\n"
|
65
|
+
" zopflipng [options]... --prefix=[fileprefix] [files.png]...\n"
|
66
|
+
"\n"
|
67
|
+
"If the output file exists, it is considered a result from a"
|
68
|
+
" previous run and not overwritten if its filesize is smaller.\n"
|
69
|
+
"\n"
|
70
|
+
"Options:\n"
|
71
|
+
"-m: compress more: use more iterations (depending on file size) and"
|
72
|
+
" use block split strategy 3\n"
|
73
|
+
"--prefix=[fileprefix]: Adds a prefix to output filenames. May also"
|
74
|
+
" contain a directory path. When using a prefix, multiple input files"
|
75
|
+
" can be given and the output filenames are generated with the"
|
76
|
+
" prefix\n"
|
77
|
+
" If --prefix is specified without value, 'zopfli_' is used.\n"
|
78
|
+
" If input file names contain the prefix, they are not processed but"
|
79
|
+
" considered as output from previous runs. This is handy when using"
|
80
|
+
" *.png wildcard expansion with multiple runs.\n"
|
81
|
+
"-y: do not ask about overwriting files.\n"
|
82
|
+
"--lossy_transparent: remove colors behind alpha channel 0. No visual"
|
83
|
+
" difference, removes hidden information.\n"
|
84
|
+
"--lossy_8bit: convert 16-bit per channel image to 8-bit per"
|
85
|
+
" channel.\n"
|
86
|
+
"-d: dry run: don't save any files, just see the console output"
|
87
|
+
" (e.g. for benchmarking)\n"
|
88
|
+
"--always_zopflify: always output the image encoded by Zopfli, even if"
|
89
|
+
" it's bigger than the original, for benchmarking the algorithm. Not"
|
90
|
+
" good for real optimization.\n"
|
91
|
+
"-q: use quick, but not very good, compression"
|
92
|
+
" (e.g. for only trying the PNG filter and color types)\n"
|
93
|
+
"--iterations=[number]: number of iterations, more iterations makes it"
|
94
|
+
" slower but provides slightly better compression. Default: 15 for"
|
95
|
+
" small files, 5 for large files.\n"
|
96
|
+
"--splitting=[0-3]: block split strategy:"
|
97
|
+
" 0=none, 1=first, 2=last, 3=try both and take the best\n"
|
98
|
+
"--filters=[types]: filter strategies to try:\n"
|
99
|
+
" 0-4: give all scanlines PNG filter type 0-4\n"
|
100
|
+
" m: minimum sum\n"
|
101
|
+
" e: entropy\n"
|
102
|
+
" p: predefined (keep from input, this likely overlaps another"
|
103
|
+
" strategy)\n"
|
104
|
+
" b: brute force (experimental)\n"
|
105
|
+
" By default, if this argument is not given, one that is most likely"
|
106
|
+
" the best for this image is chosen by trying faster compression with"
|
107
|
+
" each type.\n"
|
108
|
+
" If this argument is used, all given filter types"
|
109
|
+
" are tried with slow compression and the best result retained. A good"
|
110
|
+
" set of filters to try is --filters=0me.\n"
|
111
|
+
"--keepchunks=nAME,nAME,...: keep metadata chunks with these names"
|
112
|
+
" that would normally be removed, e.g. tEXt,zTXt,iTXt,gAMA, ... \n"
|
113
|
+
" Due to adding extra data, this increases the result size. By default"
|
114
|
+
" ZopfliPNG only keeps the following chunks because they are"
|
115
|
+
" essential: IHDR, PLTE, tRNS, IDAT and IEND.\n"
|
116
|
+
"\n"
|
117
|
+
"Usage examples:\n"
|
118
|
+
"Optimize a file and overwrite if smaller: zopflipng infile.png"
|
119
|
+
" outfile.png\n"
|
120
|
+
"Compress more: zopflipng -m infile.png outfile.png\n"
|
121
|
+
"Optimize multiple files: zopflipng --prefix a.png b.png c.png\n"
|
122
|
+
"Compress really good and trying all filter strategies: zopflipng"
|
123
|
+
" --iterations=500 --splitting=3 --filters=01234mepb"
|
124
|
+
" --lossy_8bit --lossy_transparent infile.png outfile.png\n");
|
125
|
+
}
|
126
|
+
|
127
|
+
void PrintSize(const char* label, size_t size) {
|
128
|
+
printf("%s: %d (%dK)\n", label, (int) size, (int) size / 1024);
|
129
|
+
}
|
130
|
+
|
131
|
+
void PrintResultSize(const char* label, size_t oldsize, size_t newsize) {
|
132
|
+
printf("%s: %d (%dK). Percentage of original: %.3f%%\n",
|
133
|
+
label, (int) newsize, (int) newsize / 1024, newsize * 100.0 / oldsize);
|
134
|
+
}
|
135
|
+
|
136
|
+
int main(int argc, char *argv[]) {
|
137
|
+
if (argc < 2) {
|
138
|
+
ShowHelp();
|
139
|
+
return 0;
|
140
|
+
}
|
141
|
+
|
142
|
+
ZopfliPNGOptions png_options;
|
143
|
+
|
144
|
+
// cmd line options
|
145
|
+
bool always_zopflify = false; // overwrite file even if we have bigger result
|
146
|
+
bool yes = false; // do not ask to overwrite files
|
147
|
+
bool dryrun = false; // never save anything
|
148
|
+
|
149
|
+
std::string user_out_filename; // output filename if no prefix is used
|
150
|
+
bool use_prefix = false;
|
151
|
+
std::string prefix = "zopfli_"; // prefix for output filenames
|
152
|
+
|
153
|
+
std::vector<std::string> files;
|
154
|
+
std::vector<char> options;
|
155
|
+
for (int i = 1; i < argc; i++) {
|
156
|
+
std::string arg = argv[i];
|
157
|
+
if (arg[0] == '-' && arg.size() > 1 && arg[1] != '-') {
|
158
|
+
for (size_t pos = 1; pos < arg.size(); pos++) {
|
159
|
+
char c = arg[pos];
|
160
|
+
if (c == 'y') {
|
161
|
+
yes = true;
|
162
|
+
} else if (c == 'd') {
|
163
|
+
dryrun = true;
|
164
|
+
} else if (c == 'm') {
|
165
|
+
png_options.num_iterations *= 4;
|
166
|
+
png_options.num_iterations_large *= 4;
|
167
|
+
png_options.block_split_strategy = 3;
|
168
|
+
} else if (c == 'q') {
|
169
|
+
png_options.use_zopfli = false;
|
170
|
+
} else if (c == 'h') {
|
171
|
+
ShowHelp();
|
172
|
+
return 0;
|
173
|
+
} else {
|
174
|
+
printf("Unknown flag: %c\n", c);
|
175
|
+
return 0;
|
176
|
+
}
|
177
|
+
}
|
178
|
+
} else if (arg[0] == '-' && arg.size() > 1 && arg[1] == '-') {
|
179
|
+
size_t eq = arg.find('=');
|
180
|
+
std::string name = arg.substr(0, eq);
|
181
|
+
std::string value = eq >= arg.size() - 1 ? "" : arg.substr(eq + 1);
|
182
|
+
int num = atoi(value.c_str());
|
183
|
+
if (name == "--always_zopflify") {
|
184
|
+
always_zopflify = true;
|
185
|
+
} else if (name == "--lossy_transparent") {
|
186
|
+
png_options.lossy_transparent = true;
|
187
|
+
} else if (name == "--lossy_8bit") {
|
188
|
+
png_options.lossy_8bit = true;
|
189
|
+
} else if (name == "--iterations") {
|
190
|
+
if (num < 1) num = 1;
|
191
|
+
png_options.num_iterations = num;
|
192
|
+
png_options.num_iterations_large = num;
|
193
|
+
} else if (name == "--splitting") {
|
194
|
+
if (num < 0 || num > 3) num = 1;
|
195
|
+
png_options.block_split_strategy = num;
|
196
|
+
} else if (name == "--filters") {
|
197
|
+
for (size_t j = 0; j < value.size(); j++) {
|
198
|
+
ZopfliPNGFilterStrategy strategy = kStrategyZero;
|
199
|
+
char f = value[j];
|
200
|
+
switch (f) {
|
201
|
+
case '0': strategy = kStrategyZero; break;
|
202
|
+
case '1': strategy = kStrategyOne; break;
|
203
|
+
case '2': strategy = kStrategyTwo; break;
|
204
|
+
case '3': strategy = kStrategyThree; break;
|
205
|
+
case '4': strategy = kStrategyFour; break;
|
206
|
+
case 'm': strategy = kStrategyMinSum; break;
|
207
|
+
case 'e': strategy = kStrategyEntropy; break;
|
208
|
+
case 'p': strategy = kStrategyPredefined; break;
|
209
|
+
case 'b': strategy = kStrategyBruteForce; break;
|
210
|
+
default:
|
211
|
+
printf("Unknown filter strategy: %c\n", f);
|
212
|
+
return 1;
|
213
|
+
}
|
214
|
+
png_options.filter_strategies.push_back(strategy);
|
215
|
+
// Enable auto filter strategy only if no user-specified filter is
|
216
|
+
// given.
|
217
|
+
png_options.auto_filter_strategy = false;
|
218
|
+
}
|
219
|
+
} else if (name == "--keepchunks") {
|
220
|
+
bool correct = true;
|
221
|
+
if ((value.size() + 1) % 5 != 0) correct = false;
|
222
|
+
for (size_t i = 0; i + 4 <= value.size() && correct; i += 5) {
|
223
|
+
png_options.keepchunks.push_back(value.substr(i, 4));
|
224
|
+
if (i > 4 && value[i - 1] != ',') correct = false;
|
225
|
+
}
|
226
|
+
if (!correct) {
|
227
|
+
printf("Error: keepchunks format must be like for example:\n"
|
228
|
+
" --keepchunks=gAMA,cHRM,sRGB,iCCP\n");
|
229
|
+
return 0;
|
230
|
+
}
|
231
|
+
} else if (name == "--prefix") {
|
232
|
+
use_prefix = true;
|
233
|
+
if (!value.empty()) prefix = value;
|
234
|
+
} else if (name == "--help") {
|
235
|
+
ShowHelp();
|
236
|
+
return 0;
|
237
|
+
} else {
|
238
|
+
printf("Unknown flag: %s\n", name.c_str());
|
239
|
+
return 0;
|
240
|
+
}
|
241
|
+
} else {
|
242
|
+
files.push_back(argv[i]);
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
if (!use_prefix) {
|
247
|
+
if (files.size() == 2) {
|
248
|
+
// The second filename is the output instead of an input if no prefix is
|
249
|
+
// given.
|
250
|
+
user_out_filename = files[1];
|
251
|
+
files.resize(1);
|
252
|
+
} else {
|
253
|
+
printf("Please provide one input and output filename\n\n");
|
254
|
+
ShowHelp();
|
255
|
+
return 0;
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
size_t total_in_size = 0;
|
260
|
+
// Total output size, taking input size if the input file was smaller
|
261
|
+
size_t total_out_size = 0;
|
262
|
+
// Total output size that zopfli produced, even if input was smaller, for
|
263
|
+
// benchmark information
|
264
|
+
size_t total_out_size_zopfli = 0;
|
265
|
+
size_t total_errors = 0;
|
266
|
+
size_t total_files = 0;
|
267
|
+
size_t total_files_smaller = 0;
|
268
|
+
size_t total_files_saved = 0;
|
269
|
+
size_t total_files_equal = 0;
|
270
|
+
|
271
|
+
for (size_t i = 0; i < files.size(); i++) {
|
272
|
+
if (use_prefix && files.size() > 1) {
|
273
|
+
std::string dir, file, ext;
|
274
|
+
GetFileNameParts(files[i], &dir, &file, &ext);
|
275
|
+
// avoid doing filenames which were already output by this so that you
|
276
|
+
// don't get zopfli_zopfli_zopfli_... files after multiple runs.
|
277
|
+
if (file.find(prefix) == 0) continue;
|
278
|
+
}
|
279
|
+
|
280
|
+
total_files++;
|
281
|
+
|
282
|
+
printf("Optimizing %s\n", files[i].c_str());
|
283
|
+
std::vector<unsigned char> image;
|
284
|
+
unsigned w, h;
|
285
|
+
std::vector<unsigned char> origpng;
|
286
|
+
unsigned error;
|
287
|
+
lodepng::State inputstate;
|
288
|
+
std::vector<unsigned char> resultpng;
|
289
|
+
|
290
|
+
lodepng::load_file(origpng, files[i]);
|
291
|
+
error = ZopfliPNGOptimize(origpng, png_options, true, &resultpng);
|
292
|
+
|
293
|
+
if (error) {
|
294
|
+
printf("Decoding error %i: %s\n", error, lodepng_error_text(error));
|
295
|
+
}
|
296
|
+
|
297
|
+
// Verify result, check that the result causes no decoding errors
|
298
|
+
if (!error) {
|
299
|
+
error = lodepng::decode(image, w, h, inputstate, resultpng);
|
300
|
+
if (error) printf("Error: verification of result failed.\n");
|
301
|
+
}
|
302
|
+
|
303
|
+
if (error) {
|
304
|
+
printf("There was an error\n");
|
305
|
+
total_errors++;
|
306
|
+
} else {
|
307
|
+
size_t origsize = GetFileSize(files[i]);
|
308
|
+
size_t resultsize = resultpng.size();
|
309
|
+
|
310
|
+
if (resultsize < origsize) {
|
311
|
+
printf("Result is smaller\n");
|
312
|
+
} else if (resultsize == origsize) {
|
313
|
+
printf("Result has exact same size\n");
|
314
|
+
} else {
|
315
|
+
printf(always_zopflify
|
316
|
+
? "Original was smaller\n"
|
317
|
+
: "Preserving original PNG since it was smaller\n");
|
318
|
+
}
|
319
|
+
PrintSize("Input size", origsize);
|
320
|
+
PrintResultSize("Result size", origsize, resultsize);
|
321
|
+
|
322
|
+
std::string out_filename = user_out_filename;
|
323
|
+
if (use_prefix) {
|
324
|
+
std::string dir, file, ext;
|
325
|
+
GetFileNameParts(files[i], &dir, &file, &ext);
|
326
|
+
out_filename = dir + prefix + file + ext;
|
327
|
+
}
|
328
|
+
bool different_output_name = out_filename != files[i];
|
329
|
+
|
330
|
+
total_in_size += origsize;
|
331
|
+
total_out_size_zopfli += resultpng.size();
|
332
|
+
if (resultpng.size() < origsize) total_files_smaller++;
|
333
|
+
else if (resultpng.size() == origsize) total_files_equal++;
|
334
|
+
|
335
|
+
if (!always_zopflify && resultpng.size() > origsize) {
|
336
|
+
// Set output file to input since input was smaller.
|
337
|
+
resultpng = origpng;
|
338
|
+
}
|
339
|
+
|
340
|
+
size_t origoutfilesize = GetFileSize(out_filename);
|
341
|
+
bool already_exists = true;
|
342
|
+
if (origoutfilesize == 0) already_exists = false;
|
343
|
+
|
344
|
+
// When using a prefix, and the output file already exist, assume it's
|
345
|
+
// from a previous run. If that file is smaller, it may represent a
|
346
|
+
// previous run with different parameters that gave a smaller PNG image.
|
347
|
+
// In that case, do not overwrite it. This behaviour can be removed by
|
348
|
+
// adding the always_zopflify flag.
|
349
|
+
bool keep_earlier_output_file = already_exists &&
|
350
|
+
resultpng.size() >= origoutfilesize && !always_zopflify && use_prefix;
|
351
|
+
|
352
|
+
if (keep_earlier_output_file) {
|
353
|
+
// An output file from a previous run is kept, add that files' size
|
354
|
+
// to the output size statistics.
|
355
|
+
total_out_size += origoutfilesize;
|
356
|
+
if (different_output_name) {
|
357
|
+
printf(resultpng.size() == origoutfilesize
|
358
|
+
? "File not written because a previous run was as good.\n"
|
359
|
+
: "File not written because a previous run was better.\n");
|
360
|
+
}
|
361
|
+
} else {
|
362
|
+
bool confirmed = true;
|
363
|
+
if (!yes && !dryrun && already_exists) {
|
364
|
+
printf("File %s exists, overwrite? (y/N) ", out_filename.c_str());
|
365
|
+
char answer = 0;
|
366
|
+
// Read the first character, the others and enter with getchar.
|
367
|
+
while (int input = getchar()) {
|
368
|
+
if (input == '\n' || input == EOF) break;
|
369
|
+
else if (!answer) answer = input;
|
370
|
+
}
|
371
|
+
confirmed = answer == 'y' || answer == 'Y';
|
372
|
+
}
|
373
|
+
if (confirmed) {
|
374
|
+
if (!dryrun) {
|
375
|
+
lodepng::save_file(resultpng, out_filename);
|
376
|
+
total_files_saved++;
|
377
|
+
}
|
378
|
+
total_out_size += resultpng.size();
|
379
|
+
} else {
|
380
|
+
// An output file from a previous run is kept, add that files' size
|
381
|
+
// to the output size statistics.
|
382
|
+
total_out_size += origoutfilesize;
|
383
|
+
}
|
384
|
+
}
|
385
|
+
}
|
386
|
+
printf("\n");
|
387
|
+
}
|
388
|
+
|
389
|
+
if (total_files > 1) {
|
390
|
+
printf("Summary for all files:\n");
|
391
|
+
printf("Files tried: %d\n", (int) total_files);
|
392
|
+
printf("Files smaller: %d\n", (int) total_files_smaller);
|
393
|
+
if (total_files_equal) {
|
394
|
+
printf("Files equal: %d\n", (int) total_files_equal);
|
395
|
+
}
|
396
|
+
printf("Files saved: %d\n", (int) total_files_saved);
|
397
|
+
if (total_errors) printf("Errors: %d\n", (int) total_errors);
|
398
|
+
PrintSize("Total input size", total_in_size);
|
399
|
+
PrintResultSize("Total output size", total_in_size, total_out_size);
|
400
|
+
PrintResultSize("Benchmark result size",
|
401
|
+
total_in_size, total_out_size_zopfli);
|
402
|
+
}
|
403
|
+
|
404
|
+
if (dryrun) printf("No files were written because dry run was specified\n");
|
405
|
+
|
406
|
+
return total_errors;
|
407
|
+
}
|