Compress-Zopfli
view release on metacpan or search on metacpan
zopflib/src/zopflipng/zopflipng_lib.cc view on Meta::CPAN
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: lode.vandevenne@gmail.com (Lode Vandevenne)
// Author: jyrki.alakuijala@gmail.com (Jyrki Alakuijala)
// See zopflipng_lib.h
#include "zopflipng_lib.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <set>
#include <vector>
#include "lodepng/lodepng.h"
#include "lodepng/lodepng_util.h"
#include "../zopfli/deflate.h"
ZopfliPNGOptions::ZopfliPNGOptions()
: verbose(false)
, lossy_transparent(false)
, lossy_8bit(false)
, auto_filter_strategy(true)
, use_zopfli(true)
, num_iterations(15)
, num_iterations_large(5)
, block_split_strategy(1) {
}
// Deflate compressor passed as fuction pointer to LodePNG to have it use Zopfli
// as its compression backend.
unsigned CustomPNGDeflate(unsigned char** out, size_t* outsize,
const unsigned char* in, size_t insize,
const LodePNGCompressSettings* settings) {
const ZopfliPNGOptions* png_options =
static_cast<const ZopfliPNGOptions*>(settings->custom_context);
unsigned char bp = 0;
ZopfliOptions options;
ZopfliInitOptions(&options);
options.verbose = png_options->verbose;
options.numiterations = insize < 200000
? png_options->num_iterations : png_options->num_iterations_large;
ZopfliDeflate(&options, 2 /* Dynamic */, 1, in, insize, &bp, out, outsize);
return 0; // OK
}
// Returns 32-bit integer value for RGBA color.
static unsigned ColorIndex(const unsigned char* color) {
return color[0] + 256u * color[1] + 65536u * color[2] + 16777216u * color[3];
}
// Counts amount of colors in the image, up to 257. If transparent_counts_as_one
// is enabled, any color with alpha channel 0 is treated as a single color with
// index 0.
void CountColors(std::set<unsigned>* unique,
const unsigned char* image, unsigned w, unsigned h,
bool transparent_counts_as_one) {
unique->clear();
for (size_t i = 0; i < w * h; i++) {
unsigned index = ColorIndex(&image[i * 4]);
if (transparent_counts_as_one && image[i * 4 + 3] == 0) index = 0;
unique->insert(index);
if (unique->size() > 256) break;
}
}
// Remove RGB information from pixels with alpha=0
void LossyOptimizeTransparent(lodepng::State* inputstate, unsigned char* image,
unsigned w, unsigned h) {
// First check if we want to preserve potential color-key background color,
// or instead use the last encountered RGB value all the time to save bytes.
bool key = true;
for (size_t i = 0; i < w * h; i++) {
if (image[i * 4 + 3] > 0 && image[i * 4 + 3] < 255) {
key = false;
break;
}
}
std::set<unsigned> count; // Color count, up to 257.
CountColors(&count, image, w, h, true);
// If true, means palette is possible so avoid using different RGB values for
// the transparent color.
bool palette = count.size() <= 256;
// Choose the color key or first initial background color.
int r = 0, g = 0, b = 0;
if (key || palette) {
for (size_t i = 0; i < w * h; i++) {
if (image[i * 4 + 3] == 0) {
// Use RGB value of first encountered transparent pixel. This can be
// used as a valid color key, or in case of palette ensures a color
// existing in the input image palette is used.
r = image[i * 4 + 0];
g = image[i * 4 + 1];
b = image[i * 4 + 2];
break;
}
}
}
for (size_t i = 0; i < w * h; i++) {
// if alpha is 0, alter the RGB value to a possibly more efficient one.
if (image[i * 4 + 3] == 0) {
image[i * 4 + 0] = r;
image[i * 4 + 1] = g;
image[i * 4 + 2] = b;
} else {
if (!key && !palette) {
// Use the last encountered RGB value if no key or palette is used: that
// way more values can be 0 thanks to the PNG filter types.
r = image[i * 4 + 0];
g = image[i * 4 + 1];
b = image[i * 4 + 2];
}
}
}
// If there are now less colors, update palette of input image to match this.
if (palette && inputstate->info_png.color.palettesize > 0) {
CountColors(&count, image, w, h, false);
if (count.size() < inputstate->info_png.color.palettesize) {
std::vector<unsigned char> palette_out;
unsigned char* palette_in = inputstate->info_png.color.palette;
for (size_t i = 0; i < inputstate->info_png.color.palettesize; i++) {
if (count.count(ColorIndex(&palette_in[i * 4])) != 0) {
palette_out.push_back(palette_in[i * 4 + 0]);
palette_out.push_back(palette_in[i * 4 + 1]);
palette_out.push_back(palette_in[i * 4 + 2]);
palette_out.push_back(palette_in[i * 4 + 3]);
}
}
inputstate->info_png.color.palettesize = palette_out.size() / 4;
for (size_t i = 0; i < palette_out.size(); i++) {
palette_in[i] = palette_out[i];
}
}
}
}
// Tries to optimize given a single PNG filter strategy.
// Returns 0 if ok, other value for error
unsigned 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) {
unsigned error = 0;
zopflib/src/zopflipng/zopflipng_lib.cc view on Meta::CPAN
kStrategyZero, kStrategyOne, kStrategyTwo, kStrategyThree, kStrategyFour,
kStrategyMinSum, kStrategyEntropy, kStrategyPredefined, kStrategyBruteForce
};
bool strategy_enable[kNumFilterStrategies] = {
false, false, false, false, false, false, false, false, false
};
std::string strategy_name[kNumFilterStrategies] = {
"zero", "one", "two", "three", "four",
"minimum sum", "entropy", "predefined", "brute force"
};
for (size_t i = 0; i < png_options.filter_strategies.size(); i++) {
strategy_enable[png_options.filter_strategies[i]] = true;
}
std::vector<unsigned char> image;
unsigned w, h;
unsigned error;
lodepng::State inputstate;
error = lodepng::decode(image, w, h, inputstate, origpng);
bool keep_colortype = false;
if (!png_options.keepchunks.empty()) {
// If the user wants to keep the non-essential chunks bKGD or sBIT, the
// input color type has to be kept since the chunks format depend on it.
// This may severely hurt compression if it is not an ideal color type.
// Ideally these chunks should not be kept for web images. Handling of bKGD
// chunks could be improved by changing its color type but not done yet due
// to its additional complexity, for sBIT such improvement is usually not
// possible.
std::set<std::string> keepchunks;
ChunksToKeep(origpng, png_options.keepchunks, &keepchunks);
keep_colortype = keepchunks.count("bKGD") || keepchunks.count("sBIT");
if (keep_colortype && verbose) {
printf("Forced to keep original color type due to keeping bKGD or sBIT"
" chunk.\n");
}
}
if (error) {
if (verbose) {
if (error == 1) {
printf("Decoding error\n");
} else {
printf("Decoding error %u: %s\n", error, lodepng_error_text(error));
}
}
return error;
}
bool bit16 = false; // Using 16-bit per channel raw image
if (inputstate.info_png.color.bitdepth == 16 &&
(keep_colortype || !png_options.lossy_8bit)) {
// Decode as 16-bit
image.clear();
error = lodepng::decode(image, w, h, origpng, LCT_RGBA, 16);
bit16 = true;
}
if (!error) {
// If lossy_transparent, remove RGB information from pixels with alpha=0
if (png_options.lossy_transparent && !bit16) {
LossyOptimizeTransparent(&inputstate, &image[0], w, h);
}
if (png_options.auto_filter_strategy) {
error = AutoChooseFilterStrategy(image, w, h, inputstate, bit16,
keep_colortype, origpng,
/* Don't try brute force */
kNumFilterStrategies - 1,
filterstrategies, strategy_enable);
}
}
if (!error) {
size_t bestsize = 0;
for (int i = 0; i < kNumFilterStrategies; i++) {
if (!strategy_enable[i]) continue;
std::vector<unsigned char> temp;
error = TryOptimize(image, w, h, inputstate, bit16, keep_colortype,
origpng, filterstrategies[i], true /* use_zopfli */,
windowsize, &png_options, &temp);
if (!error) {
if (verbose) {
printf("Filter strategy %s: %d bytes\n",
strategy_name[i].c_str(), (int) temp.size());
}
if (bestsize == 0 || temp.size() < bestsize) {
bestsize = temp.size();
(*resultpng).swap(temp); // Store best result so far in the output.
}
}
}
if (!png_options.keepchunks.empty()) {
KeepChunks(origpng, png_options.keepchunks, resultpng);
}
}
return error;
}
extern "C" void CZopfliPNGSetDefaults(CZopfliPNGOptions* png_options) {
memset(png_options, 0, sizeof(*png_options));
// Constructor sets the defaults
ZopfliPNGOptions opts;
png_options->lossy_transparent = opts.lossy_transparent;
png_options->lossy_8bit = opts.lossy_8bit;
png_options->auto_filter_strategy = opts.auto_filter_strategy;
png_options->use_zopfli = opts.use_zopfli;
png_options->num_iterations = opts.num_iterations;
png_options->num_iterations_large = opts.num_iterations_large;
png_options->block_split_strategy = opts.block_split_strategy;
}
extern "C" int CZopfliPNGOptimize(const unsigned char* origpng,
const size_t origpng_size,
const CZopfliPNGOptions* png_options,
int verbose,
unsigned char** resultpng,
size_t* resultpng_size) {
ZopfliPNGOptions opts;
// Copy over to the C++-style struct
opts.lossy_transparent = !!png_options->lossy_transparent;
opts.lossy_8bit = !!png_options->lossy_8bit;
opts.auto_filter_strategy = !!png_options->auto_filter_strategy;
opts.use_zopfli = !!png_options->use_zopfli;
opts.num_iterations = png_options->num_iterations;
opts.num_iterations_large = png_options->num_iterations_large;
opts.block_split_strategy = png_options->block_split_strategy;
for (int i = 0; i < png_options->num_filter_strategies; i++) {
opts.filter_strategies.push_back(png_options->filter_strategies[i]);
}
for (int i = 0; i < png_options->num_keepchunks; i++) {
opts.keepchunks.push_back(png_options->keepchunks[i]);
}
const std::vector<unsigned char> origpng_cc(origpng, origpng + origpng_size);
std::vector<unsigned char> resultpng_cc;
int ret = ZopfliPNGOptimize(origpng_cc, opts, !!verbose, &resultpng_cc);
if (ret) {
return ret;
}
*resultpng_size = resultpng_cc.size();
*resultpng = (unsigned char*) malloc(resultpng_cc.size());
if (!(*resultpng)) {
return ENOMEM;
}
memcpy(*resultpng,
reinterpret_cast<unsigned char*>(&resultpng_cc[0]),
resultpng_cc.size());
return 0;
}
( run in 2.144 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )