diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index e494dbb73..521815326 100644 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -328,10 +328,20 @@ mapnik2.render(m, im) images_ = [] im.save('demo.png', 'png') # true-colour RGBA images_.append('demo.png') -im.save('demo256.png', 'png256') # save to palette based (max 256 colours) png + +# old behavior, now can do 'png8:c=256' +im.save('demo256.png', 'png256') # save to palette based (max 256 colours) png images_.append('demo256.png') + +im.save('demo64_binary_transparency.png', 'png8:c=64:t=1') +images_.append('demo64_binary_transparency.png') + +im.save('demo128_colors_hextree_no_alpha.png', 'png8:c=100:m=h:t=0') +images_.append('demo128_colors_hextree_no_alpha.png') + im.save('demo_high.jpg', 'jpeg100') images_.append('demo_high.jpg') + im.save('demo_low.jpg', 'jpeg50') images_.append('demo_low.jpg') diff --git a/include/mapnik/octree.hpp b/include/mapnik/octree.hpp index 20159fe6f..67eed7637 100644 --- a/include/mapnik/octree.hpp +++ b/include/mapnik/octree.hpp @@ -43,7 +43,7 @@ namespace mapnik { byte r; byte g; byte b; - rgb(byte r_, byte b_, byte g_) + rgb(byte r_, byte g_, byte b_) : r(r_), g(g_), b(b_) {} }; diff --git a/include/mapnik/png_io.hpp b/include/mapnik/png_io.hpp index 9e6cdb01a..fe55afa1b 100644 --- a/include/mapnik/png_io.hpp +++ b/include/mapnik/png_io.hpp @@ -23,6 +23,7 @@ //$Id$ #include #include +#include #include extern "C" @@ -30,10 +31,7 @@ extern "C" #include } -// TODO - consider exposing this option to user -// static number of alpha ranges in png256 format -// 2 results in smallest image, 3 is minimum for semitransparency, 4 is recommended, anything else is worse -#define TRANSPARENCY_LEVELS 4 +#define MAX_OCTREE_LEVELS 4 #ifdef MAPNIK_BIG_ENDIAN #define U2RED(x) (((x)>>24)&0xff) @@ -62,7 +60,7 @@ namespace mapnik { T * out = static_cast(png_get_io_ptr(png_ptr)); out->flush(); } - + template void save_as_png(T1 & file , T2 const& image) { @@ -109,7 +107,7 @@ namespace mapnik { } template - void reduce_8 (T const& in, image_data_8 & out, octree trees[], unsigned limits[], std::vector & alpha) + void reduce_8 (T const& in, image_data_8 & out, octree trees[], unsigned limits[], unsigned levels, std::vector & alpha) { unsigned width = in.width(); unsigned height = in.height(); @@ -132,7 +130,7 @@ namespace mapnik { mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); byte index = 0; int idx = -1; - for(int j=TRANSPARENCY_LEVELS-1; j>0; j--){ + for(int j=levels-1; j>0; j--){ if (U2ALPHA(val)>=limits[j]) { index = idx = trees[j].quantize(c); break; @@ -154,7 +152,7 @@ namespace mapnik { } template - void reduce_4 (T const& in, image_data_8 & out, octree trees[], unsigned limits[], std::vector & alpha) + void reduce_4 (T const& in, image_data_8 & out, octree trees[], unsigned limits[], unsigned levels, std::vector & alpha) { unsigned width = in.width(); unsigned height = in.height(); @@ -178,7 +176,7 @@ namespace mapnik { mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); byte index = 0; int idx=-1; - for(int j=TRANSPARENCY_LEVELS-1; j>0; j--){ + for(int j=levels-1; j>0; j--){ if (U2ALPHA(val)>=limits[j]) { index = idx = trees[j].quantize(c); break; @@ -274,8 +272,11 @@ namespace mapnik { } template - void save_as_png256(T1 & file, T2 const& image) + void save_as_png256(T1 & file, T2 const& image, const unsigned max_colors = 256, int trans_mode = -1) { + // number of alpha ranges in png256 format; 2 results in smallest image with binary transparency + // 3 is minimum for semitransparency, 4 is recommended, anything else is worse + const unsigned TRANSPARENCY_LEVELS = (trans_mode==2||trans_mode<0)?MAX_OCTREE_LEVELS:2; unsigned width = image.width(); unsigned height = image.height(); unsigned alphaHist[256];//transparency histogram @@ -287,6 +288,8 @@ namespace mapnik { for (unsigned y = 0; y < height; ++y){ for (unsigned x = 0; x < width; ++x){ unsigned val = U2ALPHA((unsigned)image.getRow(y)[x]); + if (trans_mode==0) + val=255; alphaHist[val]++; meanAlpha += val; if (val>0 && val<255) @@ -296,16 +299,16 @@ namespace mapnik { meanAlpha /= width*height; // transparency ranges division points - unsigned limits[TRANSPARENCY_LEVELS+1]; + unsigned limits[MAX_OCTREE_LEVELS+1]; limits[0] = 0; limits[1] = (alphaHist[0]>0)?1:0; limits[TRANSPARENCY_LEVELS] = 256; unsigned alphaHistSum = 0; - for(int j=1; j0?1:0; // fully transparent color (one or not at all) - // give chance less populated but not empty cols to have at least few colors(12) - unsigned minCols = (12+1)*divCoef/(256-cols[0]); - for(int j=1; j12 && cols[j]=64) { + // give chance less populated but not empty cols to have at least few colors(12) + unsigned minCols = (12+1)*divCoef/(max_colors-cols[0]); + for(unsigned j=1; j12 && cols[j] trees[TRANSPARENCY_LEVELS]; - for(int j=1; j trees[MAX_OCTREE_LEVELS]; + for(unsigned j=1; j0; j--){ + for(unsigned j=TRANSPARENCY_LEVELS-1; j>0; j--){ if (cols[j]>0 && U2ALPHA(val)>=limits[j]) { trees[j].insert(mapnik::rgb(U2RED(val), U2GREEN(val), U2BLUE(val))); break; @@ -369,11 +382,11 @@ namespace mapnik { } unsigned leftovers = 0; std::vector palette; - palette.reserve(256); + palette.reserve(max_colors); if (cols[0]) palette.push_back(rgb(0,0,0)); - for(int j=1; j0) { if (leftovers>0) { cols[j] += leftovers; @@ -383,7 +396,7 @@ namespace mapnik { std::vector pal; trees[j].setOffset(palette.size()); trees[j].create_palette(pal); - assert(pal.size() <= 256); + assert(pal.size() <= max_colors); leftovers = cols[j]-pal.size(); cols[j] = pal.size(); for(unsigned i=0; i alphaTable; //alphaTable.resize(palette.size());//allow semitransparency also in almost opaque range - alphaTable.resize(palette.size() - cols[TRANSPARENCY_LEVELS-1]); + if (trans_mode != 0) + alphaTable.resize(palette.size() - cols[TRANSPARENCY_LEVELS-1]); if (palette.size() > 16 ) { // >16 && <=256 colors -> write 8-bit color depth image_data_8 reduced_image(width,height); - reduce_8(image,reduced_image,trees, limits, alphaTable); + reduce_8(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable); save_as_png(file,palette,reduced_image,width,height,8,alphaTable); } else if (palette.size() == 1) @@ -424,8 +438,97 @@ namespace mapnik { unsigned image_width = (int(0.5*width) + 3)&~3; unsigned image_height = height; image_data_8 reduced_image(image_width,image_height); - reduce_4(image,reduced_image,trees, limits, alphaTable); + reduce_4(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable); save_as_png(file,palette,reduced_image,width,height,4,alphaTable); } } + + template + void save_as_png256_hex(T1 & file, T2 const& image, int colors = 256, int trans_mode = -1, double gamma = 2.0) + { + unsigned width = image.width(); + unsigned height = image.height(); + + // structure for color quantization + hextree tree(colors); + if (trans_mode >= 0) + tree.setTransMode(trans_mode); + if (gamma > 0) + tree.setGamma(gamma); + + for (unsigned y = 0; y < height; ++y) + { + typename T2::pixel_type const * row = image.getRow(y); + for (unsigned x = 0; x < width; ++x) + { + unsigned val = row[x]; + tree.insert(mapnik::rgba(U2RED(val), U2GREEN(val), U2BLUE(val), U2ALPHA(val))); + } + } + + //transparency values per palette index + std::vector pal; + tree.create_palette(pal); + assert(pal.size() <= colors); + + std::vector palette; + std::vector alphaTable; + for(unsigned i=0; i 16 ) + { + // >16 && <=256 colors -> write 8-bit color depth + image_data_8 reduced_image(width, height); + + for (unsigned y = 0; y < height; ++y) + { + mapnik::image_data_32::pixel_type const * row = image.getRow(y); + mapnik::image_data_8::pixel_type * row_out = reduced_image.getRow(y); + + for (unsigned x = 0; x < width; ++x) + { + unsigned val = row[x]; + mapnik::rgba c(U2RED(val), U2GREEN(val), U2BLUE(val), U2ALPHA(val)); + row_out[x] = tree.quantize(c); + } + } + save_as_png(file, palette, reduced_image, width, height, 8, alphaTable); + } + else if (palette.size() == 1) + { + // 1 color image -> write 1-bit color depth PNG + unsigned image_width = (int(0.125*width) + 7)&~7; + unsigned image_height = height; + image_data_8 reduced_image(image_width, image_height); + reduced_image.set(0); + save_as_png(file, palette, reduced_image, width, height, 1, alphaTable); + } + else + { + // <=16 colors -> write 4-bit color depth PNG + unsigned image_width = (int(0.5*width) + 3)&~3; + unsigned image_height = height; + image_data_8 reduced_image(image_width, image_height); + for (unsigned y = 0; y < height; ++y) + { + mapnik::image_data_32::pixel_type const * row = image.getRow(y); + mapnik::image_data_8::pixel_type * row_out = reduced_image.getRow(y); + byte index = 0; + + for (unsigned x = 0; x < width; ++x) + { + unsigned val = row[x]; + mapnik::rgba c(U2RED(val), U2GREEN(val), U2BLUE(val), U2ALPHA(val)); + index = tree.quantize(c); + if (x%2 == 0) index = index<<4; + row_out[x>>1] |= index; + } + } + save_as_png(file, palette, reduced_image, width, height, 4, alphaTable); + } + } } diff --git a/src/image_util.cpp b/src/image_util.cpp index 7916b85ca..b7c750705 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -39,6 +39,9 @@ extern "C" #include #endif +#include +#include + // stl #include #include @@ -52,28 +55,7 @@ namespace mapnik std::string const& type) { std::ostringstream ss(std::ios::out|std::ios::binary); - //all this should go into image_writer factory - if (type=="png") save_as_png(ss,image); - else if (type == "png256") save_as_png256(ss,image); - else if (boost::algorithm::istarts_with(type,std::string("jpeg"))) - { - int quality = 85; - try - { - if(type.substr(4).length() != 0) - { - quality = boost::lexical_cast(type.substr(4)); - if(quality<1 || quality>100) - throw ImageWriterException("invalid jpeg quality: " + type.substr(4)); - } - save_as_jpeg(ss,quality,image); - } - catch(boost::bad_lexical_cast &) - { - throw ImageWriterException("invalid jpeg quality: " + type.substr(4)); - } - } - else throw ImageWriterException("unknown file type: " + type); + save_to_stream(image, ss, type); return ss.str(); } @@ -84,10 +66,89 @@ namespace mapnik { std::ofstream file (filename.c_str(), std::ios::out| std::ios::trunc|std::ios::binary); if (file) + { + save_to_stream(image, file, type); + } + else throw ImageWriterException("Could not write file to " + filename ); + } + + template + void save_to_stream(T const& image, + std::ostream & stream, + std::string const& type) + { + if (stream) { //all this should go into image_writer factory - if (type=="png") save_as_png(file,image); - else if (type == "png256") save_as_png256(file,image); + if (type == "png") save_as_png(stream, image); + else if (boost::algorithm::istarts_with(type, std::string("png256")) || + boost::algorithm::istarts_with(type, std::string("png8")) + ) + { + int colors = 256; + int trans_mode = -1; + double gamma = -1; + bool use_octree = true; + if (type.length() > 6){ + boost::char_separator sep(":"); + boost::tokenizer< boost::char_separator > tokens(type, sep); + BOOST_FOREACH(string t, tokens) + { + if (t == "m=h") + { + use_octree = false; + } + if (t == "m=o") + { + use_octree = true; + } + if (boost::algorithm::istarts_with(t,std::string("c="))) + { + try + { + colors = boost::lexical_cast(t.substr(2)); + if (colors < 0 || colors > 256) + throw ImageWriterException("invalid color parameter: " + t.substr(2) + " out of bounds"); + } + catch(boost::bad_lexical_cast &) + { + throw ImageWriterException("invalid color parameter: " + t.substr(2)); + } + } + if (boost::algorithm::istarts_with(t, std::string("t="))) + { + try + { + trans_mode= boost::lexical_cast(t.substr(2)); + if (trans_mode < 0 || trans_mode > 2) + throw ImageWriterException("invalid trans_mode parameter: " + t.substr(2) + " out of bounds"); + } + catch(boost::bad_lexical_cast &) + { + throw ImageWriterException("invalid trans_mode parameter: " + t.substr(2)); + } + } + if (boost::algorithm::istarts_with(t, std::string("g="))) + { + try + { + gamma= boost::lexical_cast(t.substr(2)); + if (gamma < 0) + throw ImageWriterException("invalid gamma parameter: " + t.substr(2) + " out of bounds"); + } + catch(boost::bad_lexical_cast &) + { + throw ImageWriterException("invalid gamma parameter: " + t.substr(2)); + } + } + } + + } + if (use_octree) + save_as_png256(stream, image, colors); + else + save_as_png256_hex(stream, image, colors, trans_mode, gamma); + } else if (boost::algorithm::istarts_with(type,std::string("jpeg"))) { int quality = 85; @@ -99,7 +160,7 @@ namespace mapnik if(quality<0 || quality>100) throw ImageWriterException("invalid jpeg quality: " + type.substr(4) + " out of bounds"); } - save_as_jpeg(file,quality,image); + save_as_jpeg(stream, quality, image); } catch(boost::bad_lexical_cast &) { @@ -108,7 +169,7 @@ namespace mapnik } else throw ImageWriterException("unknown file type: " + type); } - else throw ImageWriterException("Could not write file to " + filename ); + else throw ImageWriterException("Could not write to empty stream" ); }