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 (at) gmail.com (Lode Vandevenne) 16 // Author: jyrki.alakuijala (at) gmail.com (Jyrki Alakuijala) 17 18 // See zopflipng_lib.h 19 20 #include "zopflipng_lib.h" 21 22 #include <stdio.h> 23 #include <set> 24 #include <vector> 25 26 #include "lodepng/lodepng.h" 27 #include "lodepng/lodepng_util.h" 28 #include "../zopfli/deflate.h" 29 30 ZopfliPNGOptions::ZopfliPNGOptions() 31 : lossy_transparent(false) 32 , lossy_8bit(false) 33 , auto_filter_strategy(true) 34 , use_zopfli(true) 35 , num_iterations(15) 36 , num_iterations_large(5) 37 , block_split_strategy(1) { 38 } 39 40 // Deflate compressor passed as fuction pointer to LodePNG to have it use Zopfli 41 // as its compression backend. 42 unsigned CustomPNGDeflate(unsigned char** out, size_t* outsize, 43 const unsigned char* in, size_t insize, 44 const LodePNGCompressSettings* settings) { 45 const ZopfliPNGOptions* png_options = 46 static_cast<const ZopfliPNGOptions*>(settings->custom_context); 47 unsigned char bp = 0; 48 ZopfliOptions options; 49 ZopfliInitOptions(&options); 50 51 options.numiterations = insize < 200000 52 ? png_options->num_iterations : png_options->num_iterations_large; 53 54 if (png_options->block_split_strategy == 3) { 55 // Try both block splitting first and last. 56 unsigned char* out2 = 0; 57 size_t outsize2 = 0; 58 options.blocksplittinglast = 0; 59 ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize); 60 bp = 0; 61 options.blocksplittinglast = 1; 62 ZopfliDeflate(&options, 2 /* Dynamic */, 1, 63 in, insize, &bp, &out2, &outsize2); 64 65 if (outsize2 < *outsize) { 66 free(*out); 67 *out = out2; 68 *outsize = outsize2; 69 printf("Block splitting last was better\n"); 70 } else { 71 free(out2); 72 } 73 } else { 74 if (png_options->block_split_strategy == 0) options.blocksplitting = 0; 75 options.blocksplittinglast = png_options->block_split_strategy == 2; 76 ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize); 77 } 78 79 return 0; // OK 80 } 81 82 // Returns 32-bit integer value for RGBA color. 83 static unsigned ColorIndex(const unsigned char* color) { 84 return color[0] + 256u * color[1] + 65536u * color[1] + 16777216u * color[3]; 85 } 86 87 // Counts amount of colors in the image, up to 257. If transparent_counts_as_one 88 // is enabled, any color with alpha channel 0 is treated as a single color with 89 // index 0. 90 void CountColors(std::set<unsigned>* unique, 91 const unsigned char* image, unsigned w, unsigned h, 92 bool transparent_counts_as_one) { 93 unique->clear(); 94 for (size_t i = 0; i < w * h; i++) { 95 unsigned index = ColorIndex(&image[i * 4]); 96 if (transparent_counts_as_one && image[i * 4 + 3] == 0) index = 0; 97 unique->insert(index); 98 if (unique->size() > 256) break; 99 } 100 } 101 102 // Remove RGB information from pixels with alpha=0 103 void LossyOptimizeTransparent(lodepng::State* inputstate, unsigned char* image, 104 unsigned w, unsigned h) { 105 // First check if we want to preserve potential color-key background color, 106 // or instead use the last encountered RGB value all the time to save bytes. 107 bool key = true; 108 for (size_t i = 0; i < w * h; i++) { 109 if (image[i * 4 + 3] > 0 && image[i * 4 + 3] < 255) { 110 key = false; 111 break; 112 } 113 } 114 std::set<unsigned> count; // Color count, up to 257. 115 CountColors(&count, image, w, h, true); 116 // If true, means palette is possible so avoid using different RGB values for 117 // the transparent color. 118 bool palette = count.size() <= 256; 119 120 // Choose the color key or first initial background color. 121 int r = 0, g = 0, b = 0; 122 if (key || palette) { 123 for (size_t i = 0; i < w * h; i++) { 124 if (image[i * 4 + 3] == 0) { 125 // Use RGB value of first encountered transparent pixel. This can be 126 // used as a valid color key, or in case of palette ensures a color 127 // existing in the input image palette is used. 128 r = image[i * 4 + 0]; 129 g = image[i * 4 + 1]; 130 b = image[i * 4 + 2]; 131 } 132 } 133 } 134 135 for (size_t i = 0; i < w * h; i++) { 136 // if alpha is 0, alter the RGB value to a possibly more efficient one. 137 if (image[i * 4 + 3] == 0) { 138 image[i * 4 + 0] = r; 139 image[i * 4 + 1] = g; 140 image[i * 4 + 2] = b; 141 } else { 142 if (!key && !palette) { 143 // Use the last encountered RGB value if no key or palette is used: that 144 // way more values can be 0 thanks to the PNG filter types. 145 r = image[i * 4 + 0]; 146 g = image[i * 4 + 1]; 147 b = image[i * 4 + 2]; 148 } 149 } 150 } 151 152 // If there are now less colors, update palette of input image to match this. 153 if (palette && inputstate->info_png.color.palettesize > 0) { 154 CountColors(&count, image, w, h, false); 155 if (count.size() < inputstate->info_png.color.palettesize) { 156 std::vector<unsigned char> palette_out; 157 unsigned char* palette_in = inputstate->info_png.color.palette; 158 for (size_t i = 0; i < inputstate->info_png.color.palettesize; i++) { 159 if (count.count(ColorIndex(&palette_in[i * 4])) != 0) { 160 palette_out.push_back(palette_in[i * 4 + 0]); 161 palette_out.push_back(palette_in[i * 4 + 1]); 162 palette_out.push_back(palette_in[i * 4 + 2]); 163 palette_out.push_back(palette_in[i * 4 + 3]); 164 } 165 } 166 inputstate->info_png.color.palettesize = palette_out.size() / 4; 167 for (size_t i = 0; i < palette_out.size(); i++) { 168 palette_in[i] = palette_out[i]; 169 } 170 } 171 } 172 } 173 174 // Tries to optimize given a single PNG filter strategy. 175 // Returns 0 if ok, other value for error 176 unsigned TryOptimize( 177 const std::vector<unsigned char>& image, unsigned w, unsigned h, 178 const lodepng::State& inputstate, bool bit16, 179 const std::vector<unsigned char>& origfile, 180 ZopfliPNGFilterStrategy filterstrategy, 181 bool use_zopfli, int windowsize, const ZopfliPNGOptions* png_options, 182 std::vector<unsigned char>* out) { 183 unsigned error = 0; 184 185 lodepng::State state; 186 state.encoder.zlibsettings.windowsize = windowsize; 187 if (use_zopfli && png_options->use_zopfli) { 188 state.encoder.zlibsettings.custom_deflate = CustomPNGDeflate; 189 state.encoder.zlibsettings.custom_context = png_options; 190 } 191 192 if (inputstate.info_png.color.colortype == LCT_PALETTE) { 193 // Make it preserve the original palette order 194 lodepng_color_mode_copy(&state.info_raw, &inputstate.info_png.color); 195 state.info_raw.colortype = LCT_RGBA; 196 state.info_raw.bitdepth = 8; 197 } 198 if (bit16) { 199 state.info_raw.bitdepth = 16; 200 } 201 202 state.encoder.filter_palette_zero = 0; 203 204 std::vector<unsigned char> filters; 205 switch (filterstrategy) { 206 case kStrategyZero: 207 state.encoder.filter_strategy = LFS_ZERO; 208 break; 209 case kStrategyMinSum: 210 state.encoder.filter_strategy = LFS_MINSUM; 211 break; 212 case kStrategyEntropy: 213 state.encoder.filter_strategy = LFS_ENTROPY; 214 break; 215 case kStrategyBruteForce: 216 state.encoder.filter_strategy = LFS_BRUTE_FORCE; 217 break; 218 case kStrategyOne: 219 case kStrategyTwo: 220 case kStrategyThree: 221 case kStrategyFour: 222 // Set the filters of all scanlines to that number. 223 filters.resize(h, filterstrategy); 224 state.encoder.filter_strategy = LFS_PREDEFINED; 225 state.encoder.predefined_filters = &filters[0]; 226 break; 227 case kStrategyPredefined: 228 lodepng::getFilterTypes(filters, origfile); 229 state.encoder.filter_strategy = LFS_PREDEFINED; 230 state.encoder.predefined_filters = &filters[0]; 231 break; 232 default: 233 break; 234 } 235 236 state.encoder.add_id = false; 237 state.encoder.text_compression = 1; 238 239 error = lodepng::encode(*out, image, w, h, state); 240 241 // For very small output, also try without palette, it may be smaller thanks 242 // to no palette storage overhead. 243 if (!error && out->size() < 4096) { 244 lodepng::State teststate; 245 std::vector<unsigned char> temp; 246 lodepng::decode(temp, w, h, teststate, *out); 247 LodePNGColorMode& color = teststate.info_png.color; 248 if (color.colortype == LCT_PALETTE) { 249 std::vector<unsigned char> out2; 250 state.encoder.auto_convert = LAC_ALPHA; 251 bool grey = true; 252 for (size_t i = 0; i < color.palettesize; i++) { 253 if (color.palette[i * 4 + 0] != color.palette[i * 4 + 2] 254 || color.palette[i * 4 + 1] != color.palette[i * 4 + 2]) { 255 grey = false; 256 break; 257 } 258 } 259 if (grey) state.info_png.color.colortype = LCT_GREY_ALPHA; 260 261 error = lodepng::encode(out2, image, w, h, state); 262 if (out2.size() < out->size()) out->swap(out2); 263 } 264 } 265 266 if (error) { 267 printf("Encoding error %u: %s\n", error, lodepng_error_text(error)); 268 return error; 269 } 270 271 return 0; 272 } 273 274 // Use fast compression to check which PNG filter strategy gives the smallest 275 // output. This allows to then do the slow and good compression only on that 276 // filter type. 277 unsigned AutoChooseFilterStrategy(const std::vector<unsigned char>& image, 278 unsigned w, unsigned h, 279 const lodepng::State& inputstate, bool bit16, 280 const std::vector<unsigned char>& origfile, 281 int numstrategies, 282 ZopfliPNGFilterStrategy* strategies, 283 bool* enable) { 284 std::vector<unsigned char> out; 285 size_t bestsize = 0; 286 int bestfilter = 0; 287 288 // A large window size should still be used to do the quick compression to 289 // try out filter strategies: which filter strategy is the best depends 290 // largely on the window size, the closer to the actual used window size the 291 // better. 292 int windowsize = 8192; 293 294 for (int i = 0; i < numstrategies; i++) { 295 out.clear(); 296 unsigned error = TryOptimize(image, w, h, inputstate, bit16, origfile, 297 strategies[i], false, windowsize, 0, &out); 298 if (error) return error; 299 if (bestsize == 0 || out.size() < bestsize) { 300 bestsize = out.size(); 301 bestfilter = i; 302 } 303 } 304 305 for (int i = 0; i < numstrategies; i++) { 306 enable[i] = (i == bestfilter); 307 } 308 309 return 0; /* OK */ 310 } 311 312 // Keeps chunks with given names from the original png by literally copying them 313 // into the new png 314 void KeepChunks(const std::vector<unsigned char>& origpng, 315 const std::vector<std::string>& keepnames, 316 std::vector<unsigned char>* png) { 317 std::vector<std::string> names[3]; 318 std::vector<std::vector<unsigned char> > chunks[3]; 319 320 lodepng::getChunks(names, chunks, origpng); 321 std::vector<std::vector<unsigned char> > keepchunks[3]; 322 323 // There are 3 distinct locations in a PNG file for chunks: between IHDR and 324 // PLTE, between PLTE and IDAT, and between IDAT and IEND. Keep each chunk at 325 // its corresponding location in the new PNG. 326 for (size_t i = 0; i < 3; i++) { 327 for (size_t j = 0; j < names[i].size(); j++) { 328 for (size_t k = 0; k < keepnames.size(); k++) { 329 if (keepnames[k] == names[i][j]) { 330 keepchunks[i].push_back(chunks[i][j]); 331 } 332 } 333 } 334 } 335 336 lodepng::insertChunks(*png, keepchunks); 337 } 338 339 int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng, 340 const ZopfliPNGOptions& png_options, 341 bool verbose, 342 std::vector<unsigned char>* resultpng) { 343 // Use the largest possible deflate window size 344 int windowsize = 32768; 345 346 ZopfliPNGFilterStrategy filterstrategies[kNumFilterStrategies] = { 347 kStrategyZero, kStrategyOne, kStrategyTwo, kStrategyThree, kStrategyFour, 348 kStrategyMinSum, kStrategyEntropy, kStrategyPredefined, kStrategyBruteForce 349 }; 350 bool strategy_enable[kNumFilterStrategies] = { 351 false, false, false, false, false, false, false, false, false 352 }; 353 std::string strategy_name[kNumFilterStrategies] = { 354 "zero", "one", "two", "three", "four", 355 "minimum sum", "entropy", "predefined", "brute force" 356 }; 357 for (size_t i = 0; i < png_options.filter_strategies.size(); i++) { 358 strategy_enable[png_options.filter_strategies[i]] = true; 359 } 360 361 std::vector<unsigned char> image; 362 unsigned w, h; 363 unsigned error; 364 lodepng::State inputstate; 365 error = lodepng::decode(image, w, h, inputstate, origpng); 366 367 if (error) { 368 if (verbose) { 369 printf("Decoding error %i: %s\n", error, lodepng_error_text(error)); 370 } 371 return error; 372 } 373 374 bool bit16 = false; // Using 16-bit per channel raw image 375 if (inputstate.info_png.color.bitdepth == 16 && !png_options.lossy_8bit) { 376 // Decode as 16-bit 377 image.clear(); 378 error = lodepng::decode(image, w, h, origpng, LCT_RGBA, 16); 379 bit16 = true; 380 } 381 382 if (!error) { 383 // If lossy_transparent, remove RGB information from pixels with alpha=0 384 if (png_options.lossy_transparent && !bit16) { 385 LossyOptimizeTransparent(&inputstate, &image[0], w, h); 386 } 387 388 if (png_options.auto_filter_strategy) { 389 error = AutoChooseFilterStrategy(image, w, h, inputstate, bit16, 390 origpng, 391 /* Don't try brute force */ 392 kNumFilterStrategies - 1, 393 filterstrategies, strategy_enable); 394 } 395 } 396 397 if (!error) { 398 size_t bestsize = 0; 399 400 for (int i = 0; i < kNumFilterStrategies; i++) { 401 if (!strategy_enable[i]) continue; 402 403 std::vector<unsigned char> temp; 404 error = TryOptimize(image, w, h, inputstate, bit16, origpng, 405 filterstrategies[i], true /* use_zopfli */, 406 windowsize, &png_options, &temp); 407 if (!error) { 408 if (verbose) { 409 printf("Filter strategy %s: %d bytes\n", 410 strategy_name[i].c_str(), (int) temp.size()); 411 } 412 if (bestsize == 0 || temp.size() < bestsize) { 413 bestsize = temp.size(); 414 (*resultpng).swap(temp); // Store best result so far in the output. 415 } 416 } 417 } 418 419 if (!png_options.keepchunks.empty()) { 420 KeepChunks(origpng, png_options.keepchunks, resultpng); 421 } 422 } 423 424 return error; 425 } 426