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