void reduce_4 (T const& in, image_data_8 & out, octree<rgb> trees[], unsigned limits[], unsigned levels, std::vector<unsigned> & alpha) { unsigned width = in.width(); unsigned height = in.height(); //unsigned alphaCount[alpha.size()]; std::vector<unsigned> alphaCount(alpha.size()); for(unsigned i=0; i<alpha.size(); i++) { alpha[i] = 0; alphaCount[i] = 0; } for (unsigned y = 0; y < height; ++y) { mapnik::image_data_32::pixel_type const * row = in.getRow(y); mapnik::image_data_8::pixel_type * row_out = out.getRow(y); for (unsigned x = 0; x < width; ++x) { unsigned val = row[x]; mapnik::rgb c(U2RED(val), U2GREEN(val), U2BLUE(val)); byte index = 0; int idx=-1; for(int j=levels-1; j>0; j--){ if (U2ALPHA(val)>=limits[j] && trees[j].colors()>0) { 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; row_out[x>>1] |= index; } } for(unsigned i=0; i<alpha.size(); i++) { if (alphaCount[i]!=0) alpha[i] /= alphaCount[i]; } }
void save_as_png8_hex(T1 & file, T2 const& image, int colors = 256, int compression = Z_DEFAULT_COMPRESSION, int strategy = Z_DEFAULT_STRATEGY, int trans_mode = -1, double gamma = 2.0) { unsigned width = image.width(); unsigned height = image.height(); // structure for color quantization hextree<mapnik::rgba> 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<mapnik::rgba> pal; tree.create_palette(pal); assert(int(pal.size()) <= colors); std::vector<mapnik::rgb> palette; std::vector<unsigned> alphaTable; for(unsigned i=0; i<pal.size(); i++) { palette.push_back(rgb(pal[i].r, pal[i].g, pal[i].b)); alphaTable.push_back(pal[i].a); } save_as_png8<T1, T2, hextree<mapnik::rgba> >(file, image, tree, palette, alphaTable, compression, strategy); }
void save_as_png8_oct(T1 & file, T2 const& image, png_options const& opts) { // 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 const unsigned TRANSPARENCY_LEVELS = (opts.trans_mode==2||opts.trans_mode<0)?MAX_OCTREE_LEVELS:2; unsigned width = image.width(); unsigned height = image.height(); unsigned alphaHist[256];//transparency histogram unsigned semiCount = 0;//sum of semitransparent pixels unsigned meanAlpha = 0; if (opts.trans_mode == 0) { meanAlpha = 255; } else { 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(static_cast<unsigned>(image.get_row(y)[x])); alphaHist[val]++; meanAlpha += val; if (val>0 && val<255) { semiCount++; } } } meanAlpha /= width*height; } // transparency ranges division points unsigned limits[MAX_OCTREE_LEVELS+1]; limits[0] = 0; limits[1] = (opts.trans_mode!=0 && alphaHist[0]>0)?1:0; limits[TRANSPARENCY_LEVELS] = 256; for(unsigned j=2; j<TRANSPARENCY_LEVELS; j++) { limits[j] = limits[1]; } if (opts.trans_mode != 0) { unsigned alphaHistSum = 0; for(unsigned i=1; i<256; i++) { alphaHistSum += alphaHist[i]; for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) { if (alphaHistSum<semiCount*(j)/4) { limits[j] = i; } } } } // avoid too wide full transparent range if (limits[1]>256/(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[MAX_OCTREE_LEVELS]; // count colors if (opts.trans_mode == 0) { for (unsigned j=0; j<TRANSPARENCY_LEVELS; j++) { cols[j] = 0; } cols[TRANSPARENCY_LEVELS-1] = width * height; } else { for (unsigned j=0; j<TRANSPARENCY_LEVELS; j++) { cols[j] = 0; for (unsigned i=limits[j]; i<limits[j+1]; i++) { cols[j] += alphaHist[i]; } } } unsigned divCoef = width*height-cols[0]; if (divCoef==0) { divCoef = 1; } cols[0] = cols[0]>0?1:0; // fully transparent color (one or not at all) if (opts.colors>=64) { // give chance less populated but not empty cols to have at least few colors(12) unsigned minCols = (12+1)*divCoef/(opts.colors-cols[0]); for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) { if (cols[j]>12 && cols[j]<minCols) { divCoef += minCols-cols[j]; cols[j] = minCols; } } } unsigned usedColors = cols[0]; for(unsigned j=1; j<TRANSPARENCY_LEVELS-1; j++) { cols[j] = cols[j]*(opts.colors-cols[0])/divCoef; usedColors += cols[j]; } // use rest for most opaque group of pixels cols[TRANSPARENCY_LEVELS-1] = opts.colors-usedColors; //no transparency if (opts.trans_mode == 0) { limits[1] = 0; cols[0] = 0; cols[1] = opts.colors; } // octree table for separate alpha range with 1-based index (0 is fully transparent: no color) octree<rgb> trees[MAX_OCTREE_LEVELS]; for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) { trees[j].setMaxColors(cols[j]); } for (unsigned y = 0; y < height; ++y) { typename T2::pixel_type const * row = image.get_row(y); for (unsigned x = 0; x < width; ++x) { unsigned val = row[x]; // insert to proper tree based on alpha range 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; } } } } unsigned leftovers = 0; std::vector<rgb> palette; palette.reserve(opts.colors); if (cols[0]) { palette.push_back(rgb(0,0,0)); } for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) { if (cols[j]>0) { if (leftovers>0) { cols[j] += leftovers; trees[j].setMaxColors(cols[j]); leftovers = 0; } std::vector<rgb> pal; trees[j].setOffset( static_cast<unsigned>(palette.size())); trees[j].create_palette(pal); leftovers = cols[j] - static_cast<unsigned>(pal.size()); cols[j] = static_cast<unsigned>(pal.size()); palette.insert(palette.end(), pal.begin(), pal.end()); } } //transparency values per palette index std::vector<unsigned> alphaTable; //alphaTable.resize(palette.size());//allow semitransparency also in almost opaque range if (opts.trans_mode != 0) { alphaTable.resize(palette.size() - cols[TRANSPARENCY_LEVELS-1]); } if (palette.size() > 16 ) { // >16 && <=256 colors -> write 8-bit color depth image_gray8 reduced_image(width,height); reduce_8(image, reduced_image, trees, limits, TRANSPARENCY_LEVELS, alphaTable); save_as_png(file,palette,reduced_image,width,height,8,alphaTable,opts); } else if (palette.size() == 1) { // 1 color image -> write 1-bit color depth PNG unsigned image_width = ((width + 15) >> 3) & ~1U; // 1-bit image, round up to 16-bit boundary unsigned image_height = height; image_gray8 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,opts); }
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<mapnik::rgba> 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<mapnik::rgba> pal; tree.create_palette(pal); assert(int(pal.size()) <= colors); std::vector<mapnik::rgb> palette; std::vector<unsigned> alphaTable; for(unsigned i=0; i<pal.size(); i++) { palette.push_back(rgb(pal[i].r, pal[i].g, pal[i].b)); alphaTable.push_back(pal[i].a); } if (palette.size() > 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); } }