add miniz support - closes #1554

This commit is contained in:
Dane Springmeyer 2012-11-04 07:10:05 -05:00
parent f9fa9c2461
commit 5a99d7fbdb
7 changed files with 5392 additions and 56 deletions

View file

@ -8,6 +8,8 @@ For a complete change history, see the git log.
## Future ## Future
- Added alternative PNG/ZLIB implementation (`miniz`) that can be enabled with `e=miniz` (#1554)
- Added support for setting zlib `Z_FIXED` strategy with format string: `png:z=fixed` - Added support for setting zlib `Z_FIXED` strategy with format string: `png:z=fixed`
- Fixed handling of transparency level option in Octree-based PNG encoding (#1556) - Fixed handling of transparency level option in Octree-based PNG encoding (#1556)

View file

@ -0,0 +1,82 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
#ifndef MAPNIK_MINIZ_PNG_HPP
#define MAPNIK_MINIZ_PNG_HPP
// mapnik
#include <mapnik/palette.hpp>
// stl
#include <vector>
#include <iostream>
#include <stdexcept>
/* miniz.c porting issues:
- duplicate symbols in python bindings require moving miniz.c include to just cpp file
- due to http://code.google.com/p/miniz/issues/detail?id=7
- avoiding including miniz.c here requires fwd declaring the two structs below
- being able to fwd declare requires removing typedef from struct declarations in miniz.c
- being able to fwd declare also requires using pointers to structs
*/
// TODO: try using #define MINIZ_HEADER_FILE_ONLY
struct tdefl_output_buffer;
struct tdefl_compressor;
namespace mapnik { namespace MiniZ {
using mapnik::rgb;
class PNGWriter {
public:
PNGWriter(int level, int strategy);
~PNGWriter();
private:
inline void writeUInt32BE(unsigned char *target, unsigned int value);
size_t startChunk(const unsigned char header[], size_t length);
void finishChunk(size_t start);
public:
void writeIHDR(unsigned int width, unsigned int height, unsigned char pixel_depth);
void writePLTE(std::vector<rgb> const& palette);
void writetRNS(std::vector<unsigned> const& alpha);
template<typename T>
void writeIDAT(T const& image);
void writeIEND();
void toStream(std::ostream& stream);
private:
unsigned int flags;
tdefl_compressor *compressor;
tdefl_output_buffer *buffer;
static const unsigned char preamble[];
static const unsigned char IHDR_tpl[];
static const unsigned char PLTE_tpl[];
static const unsigned char tRNS_tpl[];
static const unsigned char IDAT_tpl[];
static const unsigned char IEND_tpl[];
};
}}
#endif // MAPNIK_MINIZ_PNG_HPP

View file

@ -28,6 +28,7 @@
#include <mapnik/palette.hpp> #include <mapnik/palette.hpp>
#include <mapnik/octree.hpp> #include <mapnik/octree.hpp>
#include <mapnik/hextree.hpp> #include <mapnik/hextree.hpp>
#include <mapnik/miniz_png.hpp>
#include <mapnik/image_data.hpp> #include <mapnik/image_data.hpp>
// zlib // zlib
#include <zlib.h> #include <zlib.h>
@ -56,8 +57,23 @@ void flush_data (png_structp png_ptr)
} }
template <typename T1, typename T2> template <typename T1, typename T2>
void save_as_png(T1 & file , T2 const& image, int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY) void save_as_png(T1 & file,
T2 const& image,
int compression = Z_DEFAULT_COMPRESSION,
int strategy = Z_DEFAULT_STRATEGY,
bool use_miniz = false)
{ {
if (use_miniz)
{
MiniZ::PNGWriter writer(compression,strategy);
writer.writeIHDR(image.width(), image.height(), 32);
writer.writeIDAT(image);
writer.writeIEND();
writer.toStream(file);
return;
}
png_voidp error_ptr=0; png_voidp error_ptr=0;
png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
error_ptr,0, 0); error_ptr,0, 0);
@ -127,8 +143,10 @@ void reduce_8 (T const& in, image_data_8 & out, octree<rgb> trees[], unsigned l
mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val));
byte index = 0; byte index = 0;
int idx = -1; int idx = -1;
for(int j=levels-1; j>0; j--){ for(int j=levels-1; j>0; j--)
if (U2ALPHA(val)>=limits[j] && trees[j].colors()>0) { {
if (U2ALPHA(val)>=limits[j] && trees[j].colors()>0)
{
index = idx = trees[j].quantize(c); index = idx = trees[j].quantize(c);
break; break;
} }
@ -173,8 +191,10 @@ void reduce_4 (T const& in, image_data_8 & out, octree<rgb> trees[], unsigned li
mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val));
byte index = 0; byte index = 0;
int idx=-1; int idx=-1;
for(int j=levels-1; j>0; j--){ for(int j=levels-1; j>0; j--)
if (U2ALPHA(val)>=limits[j] && trees[j].colors()>0) { {
if (U2ALPHA(val)>=limits[j] && trees[j].colors()>0)
{
index = idx = trees[j].quantize(c); index = idx = trees[j].quantize(c);
break; break;
} }
@ -210,8 +230,23 @@ void save_as_png(T & file, std::vector<mapnik::rgb> const& palette,
unsigned color_depth, unsigned color_depth,
int compression, int compression,
int strategy, int strategy,
std::vector<unsigned> const&alpha) std::vector<unsigned> const&alpha,
bool use_miniz)
{ {
if (use_miniz)
{
MiniZ::PNGWriter writer(compression,strategy);
// image.width()/height() does not reflect the actual image dimensions; it
// refers to the quantized scanlines.
writer.writeIHDR(width, height, color_depth);
writer.writePLTE(palette);
writer.writetRNS(alpha);
writer.writeIDAT(image);
writer.writeIEND();
writer.toStream(file);
return;
}
png_voidp error_ptr=0; png_voidp error_ptr=0;
png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,
error_ptr,0, 0); error_ptr,0, 0);
@ -277,8 +312,13 @@ void save_as_png(T & file, std::vector<mapnik::rgb> const& palette,
} }
template <typename T1,typename T2> template <typename T1,typename T2>
void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 256, void save_as_png8_oct(T1 & file,
int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY, int trans_mode = -1) T2 const& image,
const unsigned max_colors = 256,
int compression = Z_DEFAULT_COMPRESSION,
int strategy = Z_DEFAULT_STRATEGY,
int trans_mode = -1,
bool use_miniz = false)
{ {
// number of alpha ranges in png8 format; 2 results in smallest image with binary transparency // number of alpha ranges in png8 format; 2 results in smallest image with binary transparency
// 3 is minimum for semitransparency, 4 is recommended, anything else is worse // 3 is minimum for semitransparency, 4 is recommended, anything else is worse
@ -288,11 +328,14 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
unsigned alphaHist[256];//transparency histogram unsigned alphaHist[256];//transparency histogram
unsigned semiCount = 0;//sum of semitransparent pixels unsigned semiCount = 0;//sum of semitransparent pixels
unsigned meanAlpha = 0; unsigned meanAlpha = 0;
for(int i=0; i<256; i++){ for(int i=0; i<256; i++)
{
alphaHist[i] = 0; alphaHist[i] = 0;
} }
for (unsigned y = 0; y < height; ++y){ for (unsigned y = 0; y < height; ++y)
for (unsigned x = 0; x < width; ++x){ {
for (unsigned x = 0; x < width; ++x)
{
unsigned val = U2ALPHA((unsigned)image.getRow(y)[x]); unsigned val = U2ALPHA((unsigned)image.getRow(y)[x]);
if (trans_mode==0) if (trans_mode==0)
val=255; val=255;
@ -312,9 +355,11 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
unsigned alphaHistSum = 0; unsigned alphaHistSum = 0;
for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++)
limits[j] = limits[1]; limits[j] = limits[1];
for(unsigned i=1; i<256; i++){ for(unsigned i=1; i<256; i++)
{
alphaHistSum += alphaHist[i]; alphaHistSum += alphaHist[i];
for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++){ for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++)
{
if (alphaHistSum<semiCount*(j)/4) if (alphaHistSum<semiCount*(j)/4)
limits[j] = i; limits[j] = i;
} }
@ -325,15 +370,18 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
// avoid too wide full opaque range // avoid too wide full opaque range
if (limits[TRANSPARENCY_LEVELS-1]<212) if (limits[TRANSPARENCY_LEVELS-1]<212)
limits[TRANSPARENCY_LEVELS-1]=212; limits[TRANSPARENCY_LEVELS-1]=212;
if (TRANSPARENCY_LEVELS==2) { if (TRANSPARENCY_LEVELS==2)
{
limits[1]=127; limits[1]=127;
} }
// estimated number of colors from palette assigned to chosen ranges // estimated number of colors from palette assigned to chosen ranges
unsigned cols[MAX_OCTREE_LEVELS]; unsigned cols[MAX_OCTREE_LEVELS];
// count colors // count colors
for(unsigned j=1; j<=TRANSPARENCY_LEVELS; j++) { for(unsigned j=1; j<=TRANSPARENCY_LEVELS; j++)
{
cols[j-1] = 0; cols[j-1] = 0;
for(unsigned i=limits[j-1]; i<limits[j]; i++){ for(unsigned i=limits[j-1]; i<limits[j]; i++)
{
cols[j-1] += alphaHist[i]; cols[j-1] += alphaHist[i];
} }
} }
@ -342,16 +390,22 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
if (divCoef==0) divCoef = 1; if (divCoef==0) divCoef = 1;
cols[0] = cols[0]>0?1:0; // fully transparent color (one or not at all) cols[0] = cols[0]>0?1:0; // fully transparent color (one or not at all)
if (max_colors>=64) { if (max_colors>=64)
{
// give chance less populated but not empty cols to have at least few colors(12) // give chance less populated but not empty cols to have at least few colors(12)
unsigned minCols = (12+1)*divCoef/(max_colors-cols[0]); unsigned minCols = (12+1)*divCoef/(max_colors-cols[0]);
for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) if (cols[j]>12 && cols[j]<minCols) { for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++)
{
if (cols[j]>12 && cols[j]<minCols)
{
divCoef += minCols-cols[j]; divCoef += minCols-cols[j];
cols[j] = minCols; cols[j] = minCols;
} }
} }
}
unsigned usedColors = cols[0]; unsigned usedColors = cols[0];
for(unsigned j=1; j<TRANSPARENCY_LEVELS-1; j++){ for(unsigned j=1; j<TRANSPARENCY_LEVELS-1; j++)
{
cols[j] = cols[j]*(max_colors-cols[0])/divCoef; cols[j] = cols[j]*(max_colors-cols[0])/divCoef;
usedColors += cols[j]; usedColors += cols[j];
} }
@ -378,8 +432,10 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
unsigned val = row[x]; unsigned val = row[x];
// insert to proper tree based on alpha range // insert to proper tree based on alpha range
for(unsigned j=TRANSPARENCY_LEVELS-1; j>0; j--){ for(unsigned j=TRANSPARENCY_LEVELS-1; j>0; j--)
if (cols[j]>0 && U2ALPHA(val)>=limits[j]) { {
if (cols[j]>0 && U2ALPHA(val)>=limits[j])
{
trees[j].insert(mapnik::rgb(U2RED(val), U2GREEN(val), U2BLUE(val))); trees[j].insert(mapnik::rgb(U2RED(val), U2GREEN(val), U2BLUE(val)));
break; break;
} }
@ -390,11 +446,16 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
std::vector<rgb> palette; std::vector<rgb> palette;
palette.reserve(max_colors); palette.reserve(max_colors);
if (cols[0]) if (cols[0])
{
palette.push_back(rgb(0,0,0)); palette.push_back(rgb(0,0,0));
}
for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) { for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++)
if (cols[j]>0) { {
if (leftovers>0) { if (cols[j]>0)
{
if (leftovers>0)
{
cols[j] += leftovers; cols[j] += leftovers;
trees[j].setMaxColors(cols[j]); trees[j].setMaxColors(cols[j]);
leftovers = 0; leftovers = 0;
@ -405,7 +466,8 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
assert(pal.size() <= max_colors); assert(pal.size() <= max_colors);
leftovers = cols[j]-pal.size(); leftovers = cols[j]-pal.size();
cols[j] = pal.size(); cols[j] = pal.size();
for(unsigned i=0; i<pal.size(); i++){ for(unsigned i=0; i<pal.size(); i++)
{
palette.push_back(pal[i]); palette.push_back(pal[i]);
} }
assert(palette.size() <= 256); assert(palette.size() <= 256);
@ -416,14 +478,16 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
std::vector<unsigned> alphaTable; std::vector<unsigned> alphaTable;
//alphaTable.resize(palette.size());//allow semitransparency also in almost opaque range //alphaTable.resize(palette.size());//allow semitransparency also in almost opaque range
if (trans_mode != 0) if (trans_mode != 0)
{
alphaTable.resize(palette.size() - cols[TRANSPARENCY_LEVELS-1]); alphaTable.resize(palette.size() - cols[TRANSPARENCY_LEVELS-1]);
}
if (palette.size() > 16 ) if (palette.size() > 16 )
{ {
// >16 && <=256 colors -> write 8-bit color depth // >16 && <=256 colors -> write 8-bit color depth
image_data_8 reduced_image(width,height); image_data_8 reduced_image(width,height);
reduce_8(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable); reduce_8(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable);
save_as_png(file,palette,reduced_image,width,height,8,compression,strategy,alphaTable); save_as_png(file,palette,reduced_image,width,height,8,compression,strategy,alphaTable,use_miniz);
} }
else if (palette.size() == 1) else if (palette.size() == 1)
{ {
@ -432,11 +496,12 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
unsigned image_height = height; unsigned image_height = height;
image_data_8 reduced_image(image_width,image_height); image_data_8 reduced_image(image_width,image_height);
reduce_1(image,reduced_image,trees, limits, alphaTable); reduce_1(image,reduced_image,trees, limits, alphaTable);
if (meanAlpha<255 && cols[0]==0) { if (meanAlpha<255 && cols[0]==0)
{
alphaTable.resize(1); alphaTable.resize(1);
alphaTable[0] = meanAlpha; alphaTable[0] = meanAlpha;
} }
save_as_png(file,palette,reduced_image,width,height,1,compression,strategy,alphaTable); save_as_png(file,palette,reduced_image,width,height,1,compression,strategy,alphaTable,use_miniz);
} }
else else
{ {
@ -445,15 +510,20 @@ void save_as_png8_oct(T1 & file, T2 const& image, const unsigned max_colors = 25
unsigned image_height = height; unsigned image_height = height;
image_data_8 reduced_image(image_width,image_height); image_data_8 reduced_image(image_width,image_height);
reduce_4(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable); reduce_4(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable);
save_as_png(file,palette,reduced_image,width,height,4,compression,strategy,alphaTable); save_as_png(file,palette,reduced_image,width,height,4,compression,strategy,alphaTable,use_miniz);
} }
} }
template <typename T1, typename T2, typename T3> template <typename T1, typename T2, typename T3>
void save_as_png8(T1 & file, T2 const& image, T3 const & tree, void save_as_png8(T1 & file,
std::vector<mapnik::rgb> const& palette, std::vector<unsigned> const& alphaTable, T2 const& image,
int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY) T3 const & tree,
std::vector<mapnik::rgb> const& palette,
std::vector<unsigned> const& alphaTable,
int compression = Z_DEFAULT_COMPRESSION,
int strategy = Z_DEFAULT_STRATEGY,
bool use_miniz = false)
{ {
unsigned width = image.width(); unsigned width = image.width();
unsigned height = image.height(); unsigned height = image.height();
@ -462,18 +532,16 @@ void save_as_png8(T1 & file, T2 const& image, T3 const & tree,
{ {
// >16 && <=256 colors -> write 8-bit color depth // >16 && <=256 colors -> write 8-bit color depth
image_data_8 reduced_image(width, height); image_data_8 reduced_image(width, height);
for (unsigned y = 0; y < height; ++y) for (unsigned y = 0; y < height; ++y)
{ {
mapnik::image_data_32::pixel_type const * row = image.getRow(y); mapnik::image_data_32::pixel_type const * row = image.getRow(y);
mapnik::image_data_8::pixel_type * row_out = reduced_image.getRow(y); mapnik::image_data_8::pixel_type * row_out = reduced_image.getRow(y);
for (unsigned x = 0; x < width; ++x) for (unsigned x = 0; x < width; ++x)
{ {
row_out[x] = tree.quantize(row[x]); row_out[x] = tree.quantize(row[x]);
} }
} }
save_as_png(file, palette, reduced_image, width, height, 8, compression, strategy, alphaTable); save_as_png(file, palette, reduced_image, width, height, 8, compression, strategy, alphaTable, use_miniz);
} }
else if (palette.size() == 1) else if (palette.size() == 1)
{ {
@ -482,7 +550,7 @@ void save_as_png8(T1 & file, T2 const& image, T3 const & tree,
unsigned image_height = height; unsigned image_height = height;
image_data_8 reduced_image(image_width, image_height); image_data_8 reduced_image(image_width, image_height);
reduced_image.set(0); reduced_image.set(0);
save_as_png(file, palette, reduced_image, width, height, 1, compression, strategy, alphaTable); save_as_png(file, palette, reduced_image, width, height, 1, compression, strategy, alphaTable, use_miniz);
} }
else else
{ {
@ -504,14 +572,19 @@ void save_as_png8(T1 & file, T2 const& image, T3 const & tree,
row_out[x>>1] |= index; row_out[x>>1] |= index;
} }
} }
save_as_png(file, palette, reduced_image, width, height, 4, compression, strategy, alphaTable); save_as_png(file, palette, reduced_image, width, height, 4, compression, strategy, alphaTable, use_miniz);
} }
} }
template <typename T1,typename T2> template <typename T1,typename T2>
void save_as_png8_hex(T1 & file, T2 const& image, int colors = 256, void save_as_png8_hex(T1 & file,
int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY, T2 const& image,
int trans_mode = -1, double gamma = 2.0) int colors = 256,
int compression = Z_DEFAULT_COMPRESSION,
int strategy = Z_DEFAULT_STRATEGY,
int trans_mode = -1,
double gamma = 2.0,
bool use_miniz = false)
{ {
unsigned width = image.width(); unsigned width = image.width();
unsigned height = image.height(); unsigned height = image.height();
@ -546,14 +619,18 @@ void save_as_png8_hex(T1 & file, T2 const& image, int colors = 256,
alphaTable.push_back(pal[i].a); alphaTable.push_back(pal[i].a);
} }
save_as_png8<T1, T2, hextree<mapnik::rgba> >(file, image, tree, palette, alphaTable, compression, strategy); save_as_png8<T1, T2, hextree<mapnik::rgba> >(file, image, tree, palette, alphaTable, compression, strategy, use_miniz);
} }
template <typename T1, typename T2> template <typename T1, typename T2>
void save_as_png8_pal(T1 & file, T2 const& image, rgba_palette const& pal, void save_as_png8_pal(T1 & file,
int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY) T2 const& image,
rgba_palette const& pal,
int compression = Z_DEFAULT_COMPRESSION,
int strategy = Z_DEFAULT_STRATEGY,
bool use_miniz = false)
{ {
save_as_png8<T1, T2, rgba_palette>(file, image, pal, pal.palette(), pal.alphaTable(), compression, strategy); save_as_png8<T1, T2, rgba_palette>(file, image, pal, pal.palette(), pal.alphaTable(), compression, strategy, use_miniz);
} }
} }

View file

@ -100,6 +100,7 @@ else: # unix, non-macos
source = Split( source = Split(
""" """
miniz_png.cpp
color.cpp color.cpp
css_color_grammar.cpp css_color_grammar.cpp
conversions.cpp conversions.cpp

View file

@ -112,7 +112,8 @@ void handle_png_options(std::string const& type,
int * strategy, int * strategy,
int * trans_mode, int * trans_mode,
double * gamma, double * gamma,
bool * use_octree) bool * use_octree,
bool * use_miniz)
{ {
if (type == "png" || type == "png24" || type == "png32") if (type == "png" || type == "png24" || type == "png32")
{ {
@ -139,6 +140,10 @@ void handle_png_options(std::string const& type,
{ {
*use_octree = true; *use_octree = true;
} }
else if (t == "e=miniz")
{
*use_miniz = true;
}
else if (boost::algorithm::starts_with(t, "c=")) else if (boost::algorithm::starts_with(t, "c="))
{ {
if (*colors < 0) if (*colors < 0)
@ -174,9 +179,9 @@ void handle_png_options(std::string const& type,
*/ */
if (!mapnik::util::string2int(t.substr(2),*compression) if (!mapnik::util::string2int(t.substr(2),*compression)
|| *compression < Z_DEFAULT_COMPRESSION || *compression < Z_DEFAULT_COMPRESSION
|| *compression > Z_BEST_COMPRESSION) || *compression > 10) // use 10 here rather than Z_BEST_COMPRESSION (9) to allow for MZ_UBER_COMPRESSION
{ {
throw ImageWriterException("invalid compression parameter: " + t.substr(2) + " (only -1 through 9 are valid)"); throw ImageWriterException("invalid compression parameter: " + t.substr(2) + " (only -1 through 10 are valid)");
} }
} }
else if (boost::algorithm::starts_with(t, "s=")) else if (boost::algorithm::starts_with(t, "s="))
@ -208,6 +213,10 @@ void handle_png_options(std::string const& type,
} }
} }
} }
if ((*use_miniz == false) && *compression > Z_BEST_COMPRESSION)
{
throw ImageWriterException("invalid compression value: (only -1 through 9 are valid)");
}
} }
} }
@ -229,6 +238,7 @@ void save_to_stream(T const& image,
int trans_mode = -1; int trans_mode = -1;
double gamma = -1; double gamma = -1;
bool use_octree = true; bool use_octree = true;
bool use_miniz = false;
handle_png_options(t, handle_png_options(t,
&colors, &colors,
@ -236,16 +246,25 @@ void save_to_stream(T const& image,
&strategy, &strategy,
&trans_mode, &trans_mode,
&gamma, &gamma,
&use_octree); &use_octree,
&use_miniz);
if (palette.valid()) if (palette.valid())
save_as_png8_pal(stream, image, palette, compression, strategy); {
save_as_png8_pal(stream, image, palette, compression, strategy, use_miniz);
}
else if (colors < 0) else if (colors < 0)
save_as_png(stream, image, compression, strategy); {
save_as_png(stream, image, compression, strategy, use_miniz);
}
else if (use_octree) else if (use_octree)
save_as_png8_oct(stream, image, colors, compression, strategy, trans_mode); {
save_as_png8_oct(stream, image, colors, compression, strategy, trans_mode, use_miniz);
}
else else
save_as_png8_hex(stream, image, colors, compression, strategy, trans_mode, gamma); {
save_as_png8_hex(stream, image, colors, compression, strategy, trans_mode, gamma, use_miniz);
}
} }
else if (boost::algorithm::starts_with(t, "tif")) else if (boost::algorithm::starts_with(t, "tif"))
{ {
@ -280,6 +299,7 @@ void save_to_stream(T const& image,
int trans_mode = -1; int trans_mode = -1;
double gamma = -1; double gamma = -1;
bool use_octree = true; bool use_octree = true;
bool use_miniz = false;
handle_png_options(t, handle_png_options(t,
&colors, &colors,
@ -287,14 +307,21 @@ void save_to_stream(T const& image,
&strategy, &strategy,
&trans_mode, &trans_mode,
&gamma, &gamma,
&use_octree); &use_octree,
&use_miniz);
if (colors < 0) if (colors < 0)
save_as_png(stream, image, compression, strategy); {
save_as_png(stream, image, compression, strategy, use_miniz);
}
else if (use_octree) else if (use_octree)
save_as_png8_oct(stream, image, colors, compression, strategy, trans_mode); {
save_as_png8_oct(stream, image, colors, compression, strategy, trans_mode, use_miniz);
}
else else
save_as_png8_hex(stream, image, colors, compression, strategy, trans_mode, gamma); {
save_as_png8_hex(stream, image, colors, compression, strategy, trans_mode, gamma, use_miniz);
}
} }
else if (boost::algorithm::starts_with(t, "tif")) else if (boost::algorithm::starts_with(t, "tif"))
{ {

4834
src/miniz.c Normal file

File diff suppressed because it is too large Load diff

313
src/miniz_png.cpp Normal file
View file

@ -0,0 +1,313 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2012 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/palette.hpp>
#include <mapnik/miniz_png.hpp>
#include <mapnik/image_data.hpp>
#include <mapnik/image_view.hpp>
// miniz
#define MINIZ_NO_ARCHIVE_APIS
#define MINIZ_NO_STDIO
#define MINIZ_NO_ZLIB_COMPATIBLE_NAMES
#include "miniz.c"
// zlib
#include <zlib.h>
// stl
#include <vector>
#include <iostream>
#include <stdexcept>
namespace mapnik { namespace MiniZ {
PNGWriter::PNGWriter(int level, int strategy)
{
buffer = NULL;
compressor = NULL;
if (level == -1)
{
level = MZ_DEFAULT_LEVEL; // 6
}
else if (level < 0 || level > 10)
{
throw std::runtime_error("compression level must be between 0 and 10");
}
flags = s_tdefl_num_probes[level]| (level <= 3) ? TDEFL_GREEDY_PARSING_FLAG : 0 | TDEFL_WRITE_ZLIB_HEADER;
if (strategy == Z_FILTERED) flags |= TDEFL_FILTER_MATCHES;
else if (strategy == Z_HUFFMAN_ONLY) flags &= ~TDEFL_MAX_PROBES_MASK;
else if (strategy == Z_RLE) flags |= TDEFL_RLE_MATCHES;
else if (strategy == Z_FIXED) flags |= TDEFL_FORCE_ALL_STATIC_BLOCKS;
buffer = (tdefl_output_buffer *)MZ_MALLOC(sizeof(tdefl_output_buffer));
if (buffer == NULL)
{
throw std::bad_alloc();
}
buffer->m_pBuf = NULL;
buffer->m_capacity = 8192;
buffer->m_expandable = MZ_TRUE;
buffer->m_pBuf = (mz_uint8 *)MZ_MALLOC(buffer->m_capacity);
if (buffer->m_pBuf == NULL)
{
throw std::bad_alloc();
}
compressor = (tdefl_compressor *)MZ_MALLOC(sizeof(tdefl_compressor));
if (compressor == NULL)
{
throw std::bad_alloc();
}
// Reset output buffer.
buffer->m_size = 0;
tdefl_init(compressor, tdefl_output_buffer_putter, buffer, flags);
// Write preamble.
mz_bool status = tdefl_output_buffer_putter(preamble, 8, buffer);
if (status != MZ_TRUE)
{
throw std::bad_alloc();
}
}
PNGWriter::~PNGWriter()
{
if (compressor)
{
MZ_FREE(compressor);
}
if (buffer)
{
if (buffer->m_pBuf)
{
MZ_FREE(buffer->m_pBuf);
}
MZ_FREE(buffer);
}
}
inline void PNGWriter::writeUInt32BE(mz_uint8 *target, mz_uint32 value)
{
target[0] = (value >> 24) & 0xFF;
target[1] = (value >> 16) & 0xFF;
target[2] = (value >> 8) & 0xFF;
target[3] = value & 0xFF;
}
size_t PNGWriter::startChunk(const mz_uint8 header[], size_t length)
{
size_t start = buffer->m_size;
mz_bool status = tdefl_output_buffer_putter(header, length, buffer);
if (status != MZ_TRUE)
{
throw std::bad_alloc();
}
return start;
}
void PNGWriter::finishChunk(size_t start)
{
// Write chunk length at the beginning of the chunk.
size_t payloadLength = buffer->m_size - start - 4 - 4;
writeUInt32BE(buffer->m_pBuf + start, payloadLength);
// Write CRC32 checksum. Don't include the 4-byte length, but /do/ include
// the 4-byte chunk name.
mz_uint32 crc = mz_crc32(MZ_CRC32_INIT, buffer->m_pBuf + start + 4, payloadLength + 4);
mz_uint8 checksum[] = { crc >> 24, crc >> 16, crc >> 8, crc };
mz_bool status = tdefl_output_buffer_putter(checksum, 4, buffer);
if (status != MZ_TRUE)
{
throw std::bad_alloc();
}
}
void PNGWriter::writeIHDR(mz_uint32 width, mz_uint32 height, mz_uint8 pixel_depth)
{
// Write IHDR chunk.
size_t IHDR = startChunk(IHDR_tpl, 21);
writeUInt32BE(buffer->m_pBuf + IHDR + 8, width);
writeUInt32BE(buffer->m_pBuf + IHDR + 12, height);
if (pixel_depth == 32)
{
// Alpha full color image.
buffer->m_pBuf[IHDR + 16] = 8; // bit depth
buffer->m_pBuf[IHDR + 17] = 6; // color type (6 == true color with alpha)
}
else if (pixel_depth == 24)
{
// Full color image.
buffer->m_pBuf[IHDR + 16] = 8; // bit depth
buffer->m_pBuf[IHDR + 17] = 2; // color type (2 == true color without alpha)
}
else
{
// Paletted image.
buffer->m_pBuf[IHDR + 16] = pixel_depth; // bit depth
buffer->m_pBuf[IHDR + 17] = 3; // color type (3 == indexed color)
}
buffer->m_pBuf[IHDR + 18] = 0; // compression method
buffer->m_pBuf[IHDR + 19] = 0; // filter method
buffer->m_pBuf[IHDR + 20] = 0; // interlace method
finishChunk(IHDR);
}
void PNGWriter::writePLTE(std::vector<rgb> const& palette)
{
// Write PLTE chunk.
size_t PLTE = startChunk(PLTE_tpl, 8);
const mz_uint8 *colors = reinterpret_cast<const mz_uint8 *>(&palette[0]);
mz_bool status = tdefl_output_buffer_putter(colors, palette.size() * 3, buffer);
if (status != MZ_TRUE)
{
throw std::bad_alloc();
}
finishChunk(PLTE);
}
void PNGWriter::writetRNS(std::vector<unsigned> const& alpha)
{
if (alpha.size() == 0) return;
std::vector<unsigned char> transparency(alpha.size());
unsigned char transparencySize = 0; // Stores position of biggest to nonopaque value.
for(unsigned i = 0; i < alpha.size(); i++)
{
transparency[i] = alpha[i];
if (alpha[i] < 255)
{
transparencySize = i + 1;
}
}
if (transparencySize > 0)
{
// Write tRNS chunk.
size_t tRNS = startChunk(tRNS_tpl, 8);
mz_bool status = tdefl_output_buffer_putter(&transparency[0], transparencySize, buffer);
if (status != MZ_TRUE)
{
throw std::bad_alloc();
}
finishChunk(tRNS);
}
}
template<typename T>
void PNGWriter::writeIDAT(T const& image)
{
// Write IDAT chunk.
size_t IDAT = startChunk(IDAT_tpl, 8);
mz_uint8 filter_type = 0;
tdefl_status status;
int bytes_per_pixel = sizeof(typename T::pixel_type);
int stride = image.width() * bytes_per_pixel;
for (unsigned int y = 0; y < image.height(); y++)
{
// Write filter_type
status = tdefl_compress_buffer(compressor, &filter_type, 1, TDEFL_NO_FLUSH);
if (status != TDEFL_STATUS_OKAY)
{
throw std::runtime_error("failed to compress image");
}
// Write scanline
status = tdefl_compress_buffer(compressor, (mz_uint8 *)image.getRow(y), stride, TDEFL_NO_FLUSH);
if (status != TDEFL_STATUS_OKAY)
{
throw std::runtime_error("failed to compress image");
}
}
status = tdefl_compress_buffer(compressor, NULL, 0, TDEFL_FINISH);
if (status != TDEFL_STATUS_DONE)
{
throw std::runtime_error("failed to compress image");
}
finishChunk(IDAT);
}
void PNGWriter::writeIEND()
{
// Write IEND chunk.
size_t IEND = startChunk(IEND_tpl, 8);
finishChunk(IEND);
}
void PNGWriter::toStream(std::ostream& stream)
{
stream.write((char *)buffer->m_pBuf, buffer->m_size);
}
const mz_uint8 PNGWriter::preamble[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
};
const mz_uint8 PNGWriter::IHDR_tpl[] = {
0x00, 0x00, 0x00, 0x0D, // chunk length
'I', 'H', 'D', 'R', // "IHDR"
0x00, 0x00, 0x00, 0x00, // image width (4 bytes)
0x00, 0x00, 0x00, 0x00, // image height (4 bytes)
0x00, // bit depth (1 byte)
0x00, // color type (1 byte)
0x00, // compression method (1 byte), has to be 0
0x00, // filter method (1 byte)
0x00 // interlace method (1 byte)
};
const mz_uint8 PNGWriter::PLTE_tpl[] = {
0x00, 0x00, 0x00, 0x00, // chunk length
'P', 'L', 'T', 'E' // "IDAT"
};
const mz_uint8 PNGWriter::tRNS_tpl[] = {
0x00, 0x00, 0x00, 0x00, // chunk length
't', 'R', 'N', 'S' // "IDAT"
};
const mz_uint8 PNGWriter::IDAT_tpl[] = {
0x00, 0x00, 0x00, 0x00, // chunk length
'I', 'D', 'A', 'T' // "IDAT"
};
const mz_uint8 PNGWriter::IEND_tpl[] = {
0x00, 0x00, 0x00, 0x00, // chunk length
'I', 'E', 'N', 'D' // "IEND"
};
template void PNGWriter::writeIDAT<image_data_8>(image_data_8 const& image);
template void PNGWriter::writeIDAT<image_view<image_data_8> >(image_view<image_data_8> const& image);
template void PNGWriter::writeIDAT<image_data_32>(image_data_32 const& image);
template void PNGWriter::writeIDAT<image_view<image_data_32> >(image_view<image_data_32> const& image);
}}