diff --git a/include/mapnik/octree.hpp b/include/mapnik/octree.hpp index d01862a96..632ea84fb 100644 --- a/include/mapnik/octree.hpp +++ b/include/mapnik/octree.hpp @@ -105,6 +105,7 @@ namespace mapnik { std::deque reducible_[InsertPolicy::MAX_LEVELS]; unsigned max_colors_; unsigned colors_; + unsigned offset_; unsigned leaf_level_; bool has_alfa_; @@ -112,6 +113,7 @@ namespace mapnik { explicit octree(unsigned max_colors=256) : max_colors_(max_colors), colors_(0), + offset_(0), leaf_level_(InsertPolicy::MAX_LEVELS), has_alfa_(false), root_(new node()) @@ -119,6 +121,21 @@ namespace mapnik { ~octree() { delete root_;} + void setMaxColors(unsigned max_colors) + { + max_colors_ = max_colors; + } + + void setOffset(unsigned offset) + { + offset_ = offset; + } + + unsigned getOffset() + { + return offset_; + } + void hasAlfa(bool v) { has_alfa_=v; @@ -168,7 +185,7 @@ namespace mapnik { node * cur_node = root_; while (cur_node) { - if (cur_node->count != 0) return cur_node->index; + if (cur_node->count != 0) return cur_node->index + offset_; unsigned idx = InsertPolicy::index_from_level(level,c); cur_node = cur_node->children_[idx]; ++level; @@ -190,22 +207,22 @@ namespace mapnik { void reduce() { + reducible_[0].push_back(root_); + // sort reducible for (unsigned i=0;i= max_colors_ - 1) + while ( colors_ > max_colors_ && colors_ > 1) { while (leaf_level_ >0 && reducible_[leaf_level_-1].size() == 0) { --leaf_level_; } - if (leaf_level_ < 1) continue; - - if ( reducible_[leaf_level_-1].size() == 0) return; + if (leaf_level_ <= 0) return; // select best of all reducible: unsigned red_idx = leaf_level_-1; diff --git a/include/mapnik/png_io.hpp b/include/mapnik/png_io.hpp index b4259179a..1e8c5e595 100644 --- a/include/mapnik/png_io.hpp +++ b/include/mapnik/png_io.hpp @@ -30,6 +30,23 @@ 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 + +#ifdef MAPNIK_BIG_ENDIAN + #define U2RED(x) (((x)>>24)&0xff) + #define U2GREEN(x) (((x)>>16)&0xff) + #define U2BLUE(x) (((x)>>8)&0xff) + #define U2ALPHA(x) ((x)&0xff) +#else + #define U2RED(x) ((x)&0xff) + #define U2GREEN(x) (((x)>>8)&0xff) + #define U2BLUE(x) (((x)>>16)&0xff) + #define U2ALPHA(x) (((x)>>24)&0xff) +#endif + namespace mapnik { template @@ -92,75 +109,109 @@ namespace mapnik { } template - void reduce_8 (T const& in, image_data_8 & out, octree & tree) + void reduce_8 (T const& in, ImageData8 & out, octree trees[], unsigned limits[], std::vector &alpha) { unsigned width = in.width(); unsigned height = in.height(); + + unsigned alphaCount[alpha.size()]; + for(unsigned i=0; i>24)&0xff, (val>>16)&0xff, (val>>8) & 0xff); - byte index = tree.quantize(c); - if (!(val&0x80)) index = 0;//alfa -#else - mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff); - byte index = tree.quantize(c); - if (!((val>>24)&0x80)) index = 0;//alfa -#endif + mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); + byte index = 0; + int idx = -1; + for(int j=TRANSPARENCY_LEVELS-1; j>0; j--){ + if (U2ALPHA(val)>=limits[j]) { + index = idx = trees[j].quantize(c); + break; + } + } + if (idx>=0 && idx<(int)alpha.size()) + { + alpha[idx]+=U2ALPHA(val); + alphaCount[idx]++; + } row_out[x] = index; } } + for(unsigned i=0; i - void reduce_4 (T const& in, image_data_8 & out, octree & tree) + void reduce_4 (T const& in, ImageData8 & out, octree trees[], unsigned limits[], std::vector &alpha) { unsigned width = in.width(); unsigned height = in.height(); + unsigned alphaCount[alpha.size()]; + for(unsigned i=0; i>24)&0xff, (val>>16)&0xff, (val>>8) & 0xff); - byte index = tree.quantize(c); + mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); + byte index = 0; + int idx=-1; + for(int j=TRANSPARENCY_LEVELS-1; j>0; j--){ + if (U2ALPHA(val)>=limits[j]) { + index = idx = trees[j].quantize(c); + break; + } + } + if (idx>=0 && idx<(int)alpha.size()) + { + alpha[idx]+=U2ALPHA(val); + alphaCount[idx]++; + } if (x%2 == 0) index = index<<4; - if (!(val&0x80)) index = 0;//alfa -#else - mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff); - byte index = tree.quantize(c); - if (x%2 == 0) index = index<<4; - if (!((val>>24)&0x80)) index = 0;//alfa -#endif row_out[x>>1] |= index; } } + for(unsigned i=0; i - void reduce_1(T const&, image_data_8 & out, octree &) + void reduce_1(T const&, ImageData8 & out, octree trees[], unsigned limits[], std::vector &alpha) { out.set(0); // only one color!!! } template void save_as_png(T & file, std::vector & palette, - mapnik::image_data_8 const& image, + mapnik::ImageData8 const& image, unsigned width, unsigned height, unsigned color_depth, - bool hasAlfa) + std::vector &alpha) { png_voidp error_ptr=0; png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, @@ -196,11 +247,17 @@ namespace mapnik { png_set_PLTE(png_ptr,info_ptr,reinterpret_cast(&palette[0]),palette.size()); // make transparent lowest indexes, so tRNS is small - if (hasAlfa) + if (alpha.size()>0) { - byte trans[] = {0,0,0,0}; - //png_color_16p unused; - png_set_tRNS(png_ptr, info_ptr, (png_bytep)trans, 1, NULL); + png_byte trans[alpha.size()]; + unsigned alphaSize=0;//truncate to nonopaque values + for(unsigned i=0; i0) + png_set_tRNS(png_ptr, info_ptr, (png_bytep)trans, alphaSize, NULL); } png_write_info(png_ptr, info_ptr); @@ -216,61 +273,152 @@ namespace mapnik { template void save_as_png256(T1 & file, T2 const& image) { - octree tree(256); unsigned width = image.width(); unsigned height = image.height(); + unsigned alphaHist[256];//transparency histogram + unsigned semiCount = 0;//sum of semitransparent pixels + unsigned meanAlpha = 0; + for(int i=0; i<256; i++){ + alphaHist[i] = 0; + } + for (unsigned y = 0; y < height; ++y){ + for (unsigned x = 0; x < width; ++x){ + unsigned val = U2ALPHA((unsigned)image.getRow(y)[x]); + alphaHist[val]++; + meanAlpha += val; + if (val>0 && val<255) + semiCount++; + } + } + meanAlpha /= width*height; + + // transparency ranges division points + unsigned limits[TRANSPARENCY_LEVELS+1]; + limits[0] = 0; + limits[1] = (alphaHist[0]>0)?1:0; + limits[TRANSPARENCY_LEVELS] = 256; + unsigned alphaHistSum = 0; + for(int j=1; j256/(TRANSPARENCY_LEVELS-1)) + limits[1]=256/(TRANSPARENCY_LEVELS-1); + // avoid too wide full opaque range + if (limits[TRANSPARENCY_LEVELS-1]<212) + limits[TRANSPARENCY_LEVELS-1]=212; + if (TRANSPARENCY_LEVELS==2) { + limits[1]=127; + } + // estimated number of colors from palette assigned to chosen ranges + unsigned cols[TRANSPARENCY_LEVELS]; + // count colors + for(int j=1; j<=TRANSPARENCY_LEVELS; j++) { + cols[j-1] = 0; + for(unsigned i=limits[j-1]; i0?1:0; // fully transparent color (one or not at all) + unsigned usedColors = cols[0]; + for(int j=1; j12 && cols[j]<12) + cols[j] = 12; // reserve at least 12 colors to have any effect + usedColors += cols[j]; + } + // use rest for most opaque group of pixels + cols[TRANSPARENCY_LEVELS-1] = 256-usedColors; + + // octree table for separate alpha range with 1-based index (0 is fully transparent: no color) + octree trees[TRANSPARENCY_LEVELS]; + for(int j=1; j>24)&0xff, (val>>16)&0xff, (val>>8) & 0xff)); - } -#else - if ((val>>24)&0x80) - { - tree.insert(mapnik::rgb((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff)); - } -#endif - else - { - tree.hasAlfa(true); + + // insert to proper tree based on alpha range + for(int 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; + } } } } - + unsigned leftovers = 0; std::vector palette; - tree.create_palette(palette); - assert(palette.size() <= 256); + palette.reserve(256); + if (cols[0]) + palette.push_back(rgb(0,0,0)); + + for(int j=1; j0) { + if (leftovers>0) { + cols[j] += leftovers; + trees[j].setMaxColors(cols[j]); + leftovers = 0; + } + std::vector pal; + trees[j].setOffset(palette.size()); + trees[j].create_palette(pal); + assert(pal.size() <= 256); + 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 (palette.size() > 16 ) { // >16 && <=256 colors -> write 8-bit color depth - image_data_8 reduced_image(width,height); - reduce_8(image,reduced_image,tree); - save_as_png(file,palette,reduced_image,width,height,8,tree.hasAlfa()); + ImageData8 reduced_image(width,height); + reduce_8(image,reduced_image,trees, limits, alphaTable); + 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); - reduce_1(image,reduced_image,tree); - save_as_png(file,palette,reduced_image,width,height,1,tree.hasAlfa()); + ImageData8 reduced_image(image_width,image_height); + reduce_1(image,reduced_image,trees, limits, alphaTable); + if (meanAlpha<255 && cols[0]==0) { + alphaTable.resize(1); + alphaTable[0] = meanAlpha; + } + 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); - reduce_4(image,reduced_image,tree); - save_as_png(file,palette,reduced_image,width,height,4,tree.hasAlfa()); + ImageData8 reduced_image(image_width,image_height); + reduce_4(image,reduced_image,trees, limits, alphaTable); + save_as_png(file,palette,reduced_image,width,height,4,alphaTable); } } }