xref: /aosp_15_r20/external/zopfli/src/zopflipng/zopflipng_lib.cc (revision e47783fd9ac7e78d0523d35be12ee382df490d63)
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: [email protected] (Lode Vandevenne)
16 // Author: [email protected] (Jyrki Alakuijala)
17 
18 // See zopflipng_lib.h
19 
20 #include "zopflipng_lib.h"
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <set>
27 #include <vector>
28 
29 #include "lodepng/lodepng.h"
30 #include "lodepng/lodepng_util.h"
31 #include "../zopfli/deflate.h"
32 
ZopfliPNGOptions()33 ZopfliPNGOptions::ZopfliPNGOptions()
34   : verbose(false)
35   , lossy_transparent(false)
36   , lossy_8bit(false)
37   , auto_filter_strategy(true)
38   , keep_colortype(false)
39   , use_zopfli(true)
40   , num_iterations(15)
41   , num_iterations_large(5)
42   , block_split_strategy(1) {
43 }
44 
45 // Deflate compressor passed as fuction pointer to LodePNG to have it use Zopfli
46 // as its compression backend.
CustomPNGDeflate(unsigned char ** out,size_t * outsize,const unsigned char * in,size_t insize,const LodePNGCompressSettings * settings)47 unsigned CustomPNGDeflate(unsigned char** out, size_t* outsize,
48                           const unsigned char* in, size_t insize,
49                           const LodePNGCompressSettings* settings) {
50   const ZopfliPNGOptions* png_options =
51       static_cast<const ZopfliPNGOptions*>(settings->custom_context);
52   unsigned char bp = 0;
53   ZopfliOptions options;
54   ZopfliInitOptions(&options);
55 
56   options.verbose = png_options->verbose;
57   options.numiterations = insize < 200000
58       ? png_options->num_iterations : png_options->num_iterations_large;
59 
60   ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize);
61 
62   return 0;  // OK
63 }
64 
65 // Returns 32-bit integer value for RGBA color.
ColorIndex(const unsigned char * color)66 static unsigned ColorIndex(const unsigned char* color) {
67   return color[0] + 256u * color[1] + 65536u * color[2] + 16777216u * color[3];
68 }
69 
70 // Counts amount of colors in the image, up to 257. If transparent_counts_as_one
71 // is enabled, any color with alpha channel 0 is treated as a single color with
72 // index 0.
CountColors(std::set<unsigned> * unique,const unsigned char * image,unsigned w,unsigned h,bool transparent_counts_as_one)73 void CountColors(std::set<unsigned>* unique,
74                  const unsigned char* image, unsigned w, unsigned h,
75                  bool transparent_counts_as_one) {
76   unique->clear();
77   for (size_t i = 0; i < w * h; i++) {
78     unsigned index = ColorIndex(&image[i * 4]);
79     if (transparent_counts_as_one && image[i * 4 + 3] == 0) index = 0;
80     unique->insert(index);
81     if (unique->size() > 256) break;
82   }
83 }
84 
85 // Remove RGB information from pixels with alpha=0
LossyOptimizeTransparent(lodepng::State * inputstate,unsigned char * image,unsigned w,unsigned h)86 void LossyOptimizeTransparent(lodepng::State* inputstate, unsigned char* image,
87     unsigned w, unsigned h) {
88   // First check if we want to preserve potential color-key background color,
89   // or instead use the last encountered RGB value all the time to save bytes.
90   bool key = true;
91   for (size_t i = 0; i < w * h; i++) {
92     if (image[i * 4 + 3] > 0 && image[i * 4 + 3] < 255) {
93       key = false;
94       break;
95     }
96   }
97   std::set<unsigned> count;  // Color count, up to 257.
98   CountColors(&count, image, w, h, true);
99   // If true, means palette is possible so avoid using different RGB values for
100   // the transparent color.
101   bool palette = count.size() <= 256;
102 
103   // Choose the color key or first initial background color.
104   int r = 0, g = 0, b = 0;
105   if (key || palette) {
106     for (size_t i = 0; i < w * h; i++) {
107       if (image[i * 4 + 3] == 0) {
108         // Use RGB value of first encountered transparent pixel. This can be
109         // used as a valid color key, or in case of palette ensures a color
110         // existing in the input image palette is used.
111         r = image[i * 4 + 0];
112         g = image[i * 4 + 1];
113         b = image[i * 4 + 2];
114         break;
115       }
116     }
117   }
118 
119   for (size_t i = 0; i < w * h; i++) {
120     // if alpha is 0, alter the RGB value to a possibly more efficient one.
121     if (image[i * 4 + 3] == 0) {
122       image[i * 4 + 0] = r;
123       image[i * 4 + 1] = g;
124       image[i * 4 + 2] = b;
125     } else {
126       if (!key && !palette) {
127         // Use the last encountered RGB value if no key or palette is used: that
128         // way more values can be 0 thanks to the PNG filter types.
129         r = image[i * 4 + 0];
130         g = image[i * 4 + 1];
131         b = image[i * 4 + 2];
132       }
133     }
134   }
135 
136   // If there are now less colors, update palette of input image to match this.
137   if (palette && inputstate->info_png.color.palettesize > 0) {
138     CountColors(&count, image, w, h, false);
139     if (count.size() < inputstate->info_png.color.palettesize) {
140       std::vector<unsigned char> palette_out;
141       unsigned char* palette_in = inputstate->info_png.color.palette;
142       for (size_t i = 0; i < inputstate->info_png.color.palettesize; i++) {
143         if (count.count(ColorIndex(&palette_in[i * 4])) != 0) {
144           palette_out.push_back(palette_in[i * 4 + 0]);
145           palette_out.push_back(palette_in[i * 4 + 1]);
146           palette_out.push_back(palette_in[i * 4 + 2]);
147           palette_out.push_back(palette_in[i * 4 + 3]);
148         }
149       }
150       inputstate->info_png.color.palettesize = palette_out.size() / 4;
151       for (size_t i = 0; i < palette_out.size(); i++) {
152         palette_in[i] = palette_out[i];
153       }
154     }
155   }
156 }
157 
158 // Tries to optimize given a single PNG filter strategy.
159 // Returns 0 if ok, other value for error
TryOptimize(const std::vector<unsigned char> & image,unsigned w,unsigned h,const lodepng::State & inputstate,bool bit16,bool keep_colortype,const std::vector<unsigned char> & origfile,ZopfliPNGFilterStrategy filterstrategy,bool use_zopfli,int windowsize,const ZopfliPNGOptions * png_options,std::vector<unsigned char> * out)160 unsigned TryOptimize(
161     const std::vector<unsigned char>& image, unsigned w, unsigned h,
162     const lodepng::State& inputstate, bool bit16, bool keep_colortype,
163     const std::vector<unsigned char>& origfile,
164     ZopfliPNGFilterStrategy filterstrategy,
165     bool use_zopfli, int windowsize, const ZopfliPNGOptions* png_options,
166     std::vector<unsigned char>* out) {
167   unsigned error = 0;
168 
169   lodepng::State state;
170   state.encoder.zlibsettings.windowsize = windowsize;
171   if (use_zopfli && png_options->use_zopfli) {
172     state.encoder.zlibsettings.custom_deflate = CustomPNGDeflate;
173     state.encoder.zlibsettings.custom_context = png_options;
174   }
175 
176   if (keep_colortype) {
177     state.encoder.auto_convert = 0;
178     lodepng_color_mode_copy(&state.info_png.color, &inputstate.info_png.color);
179   }
180   if (inputstate.info_png.color.colortype == LCT_PALETTE) {
181     // Make it preserve the original palette order
182     lodepng_color_mode_copy(&state.info_raw, &inputstate.info_png.color);
183     state.info_raw.colortype = LCT_RGBA;
184     state.info_raw.bitdepth = 8;
185   }
186   if (bit16) {
187     state.info_raw.bitdepth = 16;
188   }
189 
190   state.encoder.filter_palette_zero = 0;
191 
192   std::vector<unsigned char> filters;
193   switch (filterstrategy) {
194     case kStrategyZero:
195       state.encoder.filter_strategy = LFS_ZERO;
196       break;
197     case kStrategyMinSum:
198       state.encoder.filter_strategy = LFS_MINSUM;
199       break;
200     case kStrategyEntropy:
201       state.encoder.filter_strategy = LFS_ENTROPY;
202       break;
203     case kStrategyBruteForce:
204       state.encoder.filter_strategy = LFS_BRUTE_FORCE;
205       break;
206     case kStrategyOne:
207       state.encoder.filter_strategy = LFS_ONE;
208       break;
209     case kStrategyTwo:
210       state.encoder.filter_strategy = LFS_TWO;
211       break;
212     case kStrategyThree:
213       state.encoder.filter_strategy = LFS_THREE;
214       break;
215     case kStrategyFour:
216       state.encoder.filter_strategy = LFS_FOUR;
217       break;
218     case kStrategyPredefined:
219       lodepng::getFilterTypes(filters, origfile);
220       if (filters.size() != h) return 1;  // Error getting filters
221       state.encoder.filter_strategy = LFS_PREDEFINED;
222       state.encoder.predefined_filters = &filters[0];
223       break;
224     default:
225       break;
226   }
227 
228   state.encoder.add_id = false;
229   state.encoder.text_compression = 1;
230 
231   error = lodepng::encode(*out, image, w, h, state);
232 
233   // For very small output, also try without palette, it may be smaller thanks
234   // to no palette storage overhead.
235   if (!error && out->size() < 4096 && !keep_colortype) {
236     if (lodepng::getPNGHeaderInfo(*out).color.colortype == LCT_PALETTE) {
237       LodePNGColorStats stats;
238       lodepng_color_stats_init(&stats);
239       lodepng_compute_color_stats(&stats, &image[0], w, h, &state.info_raw);
240       // Too small for tRNS chunk overhead.
241       if (w * h <= 16 && stats.key) stats.alpha = 1;
242       state.encoder.auto_convert = 0;
243       state.info_png.color.colortype = (stats.alpha ? LCT_RGBA : LCT_RGB);
244       state.info_png.color.bitdepth = 8;
245       state.info_png.color.key_defined = (stats.key && !stats.alpha);
246       if (state.info_png.color.key_defined) {
247         state.info_png.color.key_defined = 1;
248         state.info_png.color.key_r = (stats.key_r & 255u);
249         state.info_png.color.key_g = (stats.key_g & 255u);
250         state.info_png.color.key_b = (stats.key_b & 255u);
251       }
252 
253       std::vector<unsigned char> out2;
254       error = lodepng::encode(out2, image, w, h, state);
255       if (out2.size() < out->size()) out->swap(out2);
256     }
257   }
258 
259   if (error) {
260     printf("Encoding error %u: %s\n", error, lodepng_error_text(error));
261     return error;
262   }
263 
264   return 0;
265 }
266 
267 // Use fast compression to check which PNG filter strategy gives the smallest
268 // output. This allows to then do the slow and good compression only on that
269 // filter type.
AutoChooseFilterStrategy(const std::vector<unsigned char> & image,unsigned w,unsigned h,const lodepng::State & inputstate,bool bit16,bool keep_colortype,const std::vector<unsigned char> & origfile,int numstrategies,ZopfliPNGFilterStrategy * strategies,bool * enable)270 unsigned AutoChooseFilterStrategy(const std::vector<unsigned char>& image,
271                                   unsigned w, unsigned h,
272                                   const lodepng::State& inputstate,
273                                   bool bit16, bool keep_colortype,
274                                   const std::vector<unsigned char>& origfile,
275                                   int numstrategies,
276                                   ZopfliPNGFilterStrategy* strategies,
277                                   bool* enable) {
278   std::vector<unsigned char> out;
279   size_t bestsize = 0;
280   int bestfilter = 0;
281 
282   // A large window size should still be used to do the quick compression to
283   // try out filter strategies: which filter strategy is the best depends
284   // largely on the window size, the closer to the actual used window size the
285   // better.
286   int windowsize = 8192;
287 
288   for (int i = 0; i < numstrategies; i++) {
289     out.clear();
290     unsigned error = TryOptimize(image, w, h, inputstate, bit16, keep_colortype,
291                                  origfile, strategies[i], false, windowsize, 0,
292                                  &out);
293     if (error) return error;
294     if (bestsize == 0 || out.size() < bestsize) {
295       bestsize = out.size();
296       bestfilter = i;
297     }
298   }
299 
300   for (int i = 0; i < numstrategies; i++) {
301     enable[i] = (i == bestfilter);
302   }
303 
304   return 0;  /* OK */
305 }
306 
307 // Outputs the intersection of keepnames and non-essential chunks which are in
308 // the PNG image.
ChunksToKeep(const std::vector<unsigned char> & origpng,const std::vector<std::string> & keepnames,std::set<std::string> * result)309 void ChunksToKeep(const std::vector<unsigned char>& origpng,
310                   const std::vector<std::string>& keepnames,
311                   std::set<std::string>* result) {
312   std::vector<std::string> names[3];
313   std::vector<std::vector<unsigned char> > chunks[3];
314 
315   lodepng::getChunks(names, chunks, origpng);
316 
317   for (size_t i = 0; i < 3; i++) {
318     for (size_t j = 0; j < names[i].size(); j++) {
319       for (size_t k = 0; k < keepnames.size(); k++) {
320         if (keepnames[k] == names[i][j]) {
321           result->insert(names[i][j]);
322         }
323       }
324     }
325   }
326 }
327 
328 // Keeps chunks with given names from the original png by literally copying them
329 // into the new png
KeepChunks(const std::vector<unsigned char> & origpng,const std::vector<std::string> & keepnames,std::vector<unsigned char> * png)330 void KeepChunks(const std::vector<unsigned char>& origpng,
331                 const std::vector<std::string>& keepnames,
332                 std::vector<unsigned char>* png) {
333   std::vector<std::string> names[3];
334   std::vector<std::vector<unsigned char> > chunks[3];
335 
336   lodepng::getChunks(names, chunks, origpng);
337   std::vector<std::vector<unsigned char> > keepchunks[3];
338 
339   // There are 3 distinct locations in a PNG file for chunks: between IHDR and
340   // PLTE, between PLTE and IDAT, and between IDAT and IEND. Keep each chunk at
341   // its corresponding location in the new PNG.
342   for (size_t i = 0; i < 3; i++) {
343     for (size_t j = 0; j < names[i].size(); j++) {
344       for (size_t k = 0; k < keepnames.size(); k++) {
345         if (keepnames[k] == names[i][j]) {
346           keepchunks[i].push_back(chunks[i][j]);
347         }
348       }
349     }
350   }
351 
352   lodepng::insertChunks(*png, keepchunks);
353 }
354 
ZopfliPNGOptimize(const std::vector<unsigned char> & origpng,const ZopfliPNGOptions & png_options,bool verbose,std::vector<unsigned char> * resultpng)355 int ZopfliPNGOptimize(const std::vector<unsigned char>& origpng,
356     const ZopfliPNGOptions& png_options,
357     bool verbose,
358     std::vector<unsigned char>* resultpng) {
359   // Use the largest possible deflate window size
360   int windowsize = 32768;
361 
362   ZopfliPNGFilterStrategy filterstrategies[kNumFilterStrategies] = {
363     kStrategyZero, kStrategyOne, kStrategyTwo, kStrategyThree, kStrategyFour,
364     kStrategyMinSum, kStrategyEntropy, kStrategyPredefined, kStrategyBruteForce
365   };
366   bool strategy_enable[kNumFilterStrategies] = {
367     false, false, false, false, false, false, false, false, false
368   };
369   std::string strategy_name[kNumFilterStrategies] = {
370     "zero", "one", "two", "three", "four",
371     "minimum sum", "entropy", "predefined", "brute force"
372   };
373   for (size_t i = 0; i < png_options.filter_strategies.size(); i++) {
374     strategy_enable[png_options.filter_strategies[i]] = true;
375   }
376 
377   std::vector<unsigned char> image;
378   unsigned w, h;
379   unsigned error;
380   lodepng::State inputstate;
381   error = lodepng::decode(image, w, h, inputstate, origpng);
382 
383   bool keep_colortype = png_options.keep_colortype;
384 
385   if (!png_options.keepchunks.empty()) {
386     // If the user wants to keep the non-essential chunks bKGD or sBIT, the
387     // input color type has to be kept since the chunks format depend on it.
388     // This may severely hurt compression if it is not an ideal color type.
389     // Ideally these chunks should not be kept for web images. Handling of bKGD
390     // chunks could be improved by changing its color type but not done yet due
391     // to its additional complexity, for sBIT such improvement is usually not
392     // possible.
393     std::set<std::string> keepchunks;
394     ChunksToKeep(origpng, png_options.keepchunks, &keepchunks);
395     if (keepchunks.count("bKGD") || keepchunks.count("sBIT")) {
396       if (!keep_colortype && verbose) {
397         printf("Forced to keep original color type due to keeping bKGD or sBIT"
398                " chunk.\n");
399       }
400       keep_colortype = true;
401     }
402   }
403 
404   if (error) {
405     if (verbose) {
406       if (error == 1) {
407         printf("Decoding error\n");
408       } else {
409         printf("Decoding error %u: %s\n", error, lodepng_error_text(error));
410       }
411     }
412     return error;
413   }
414 
415   bool bit16 = false;  // Using 16-bit per channel raw image
416   if (inputstate.info_png.color.bitdepth == 16 &&
417       (keep_colortype || !png_options.lossy_8bit)) {
418     // Decode as 16-bit
419     image.clear();
420     error = lodepng::decode(image, w, h, origpng, LCT_RGBA, 16);
421     bit16 = true;
422   }
423 
424   if (!error) {
425     // If lossy_transparent, remove RGB information from pixels with alpha=0
426     if (png_options.lossy_transparent && !bit16) {
427       LossyOptimizeTransparent(&inputstate, &image[0], w, h);
428     }
429 
430     if (png_options.auto_filter_strategy) {
431       error = AutoChooseFilterStrategy(image, w, h, inputstate, bit16,
432                                        keep_colortype, origpng,
433                                        /* Don't try brute force */
434                                        kNumFilterStrategies - 1,
435                                        filterstrategies, strategy_enable);
436     }
437   }
438 
439   if (!error) {
440     size_t bestsize = 0;
441 
442     for (int i = 0; i < kNumFilterStrategies; i++) {
443       if (!strategy_enable[i]) continue;
444 
445       std::vector<unsigned char> temp;
446       error = TryOptimize(image, w, h, inputstate, bit16, keep_colortype,
447                           origpng, filterstrategies[i], true /* use_zopfli */,
448                           windowsize, &png_options, &temp);
449       if (!error) {
450         if (verbose) {
451           printf("Filter strategy %s: %d bytes\n",
452                  strategy_name[i].c_str(), (int) temp.size());
453         }
454         if (bestsize == 0 || temp.size() < bestsize) {
455           bestsize = temp.size();
456           (*resultpng).swap(temp);  // Store best result so far in the output.
457         }
458       }
459     }
460 
461     if (!png_options.keepchunks.empty()) {
462       KeepChunks(origpng, png_options.keepchunks, resultpng);
463     }
464   }
465 
466   return error;
467 }
468 
CZopfliPNGSetDefaults(CZopfliPNGOptions * png_options)469 extern "C" void CZopfliPNGSetDefaults(CZopfliPNGOptions* png_options) {
470 
471   memset(png_options, 0, sizeof(*png_options));
472   // Constructor sets the defaults
473   ZopfliPNGOptions opts;
474 
475   png_options->lossy_transparent    = opts.lossy_transparent;
476   png_options->lossy_8bit           = opts.lossy_8bit;
477   png_options->auto_filter_strategy = opts.auto_filter_strategy;
478   png_options->use_zopfli           = opts.use_zopfli;
479   png_options->num_iterations       = opts.num_iterations;
480   png_options->num_iterations_large = opts.num_iterations_large;
481   png_options->block_split_strategy = opts.block_split_strategy;
482 }
483 
CZopfliPNGOptimize(const unsigned char * origpng,const size_t origpng_size,const CZopfliPNGOptions * png_options,int verbose,unsigned char ** resultpng,size_t * resultpng_size)484 extern "C" int CZopfliPNGOptimize(const unsigned char* origpng,
485                                   const size_t origpng_size,
486                                   const CZopfliPNGOptions* png_options,
487                                   int verbose,
488                                   unsigned char** resultpng,
489                                   size_t* resultpng_size) {
490   ZopfliPNGOptions opts;
491 
492   // Copy over to the C++-style struct
493   opts.lossy_transparent    = !!png_options->lossy_transparent;
494   opts.lossy_8bit           = !!png_options->lossy_8bit;
495   opts.auto_filter_strategy = !!png_options->auto_filter_strategy;
496   opts.use_zopfli           = !!png_options->use_zopfli;
497   opts.num_iterations       = png_options->num_iterations;
498   opts.num_iterations_large = png_options->num_iterations_large;
499   opts.block_split_strategy = png_options->block_split_strategy;
500 
501   for (int i = 0; i < png_options->num_filter_strategies; i++) {
502     opts.filter_strategies.push_back(png_options->filter_strategies[i]);
503   }
504 
505   for (int i = 0; i < png_options->num_keepchunks; i++) {
506     opts.keepchunks.push_back(png_options->keepchunks[i]);
507   }
508 
509   const std::vector<unsigned char> origpng_cc(origpng, origpng + origpng_size);
510   std::vector<unsigned char> resultpng_cc;
511 
512   int ret = ZopfliPNGOptimize(origpng_cc, opts, !!verbose, &resultpng_cc);
513   if (ret) {
514     return ret;
515   }
516 
517   *resultpng_size = resultpng_cc.size();
518   *resultpng      = (unsigned char*) malloc(resultpng_cc.size());
519   if (!(*resultpng)) {
520     return ENOMEM;
521   }
522 
523   memcpy(*resultpng,
524          reinterpret_cast<unsigned char*>(&resultpng_cc[0]),
525          resultpng_cc.size());
526 
527   return 0;
528 }
529