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 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]); if (trans_mode==0) val=255; 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] = (alphaHist[0]>0)?1:0; limits[TRANSPARENCY_LEVELS] = 256; unsigned alphaHistSum = 0; for(unsigned j=1; j<TRANSPARENCY_LEVELS; j++) limits[j] = limits[1]; 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 for(unsigned j=1; j<=TRANSPARENCY_LEVELS; j++) { cols[j-1] = 0; for(unsigned i=limits[j-1]; i<limits[j]; i++){ cols[j-1] += 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 (max_colors>=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; 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]*(max_colors-cols[0])/divCoef; usedColors += cols[j]; } // use rest for most opaque group of pixels cols[TRANSPARENCY_LEVELS-1] = max_colors-usedColors; //no transparency if (trans_mode == 0) { limits[1] = 0; cols[0] = 0; cols[1] = max_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.getRow(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(max_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(palette.size()); trees[j].create_palette(pal); assert(pal.size() <= max_colors); leftovers = cols[j]-pal.size(); cols[j] = pal.size(); for(unsigned i=0; i<pal.size(); i++){ palette.push_back(pal[i]); } assert(palette.size() <= 256); } } //transparency values per palette index std::vector<unsigned> alphaTable; //alphaTable.resize(palette.size());//allow semitransparency also in almost opaque range 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, TRANSPARENCY_LEVELS, 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,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, trees, limits, TRANSPARENCY_LEVELS, alphaTable); save_as_png(file,palette,reduced_image,width,height,4,alphaTable); } }
void save_as_png(T1 & file, T2 const& image, png_options const& opts) { if (opts.use_miniz) { MiniZ::PNGWriter writer(opts.compression,opts.strategy); if (opts.trans_mode == 0) { writer.writeIHDR(image.width(), image.height(), 24); writer.writeIDATStripAlpha(image); } else { writer.writeIHDR(image.width(), image.height(), 32); writer.writeIDAT(image); } writer.writeIEND(); writer.toStream(file); return; } png_voidp error_ptr=0; png_structp png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr,0, 0); if (!png_ptr) return; // switch on optimization only if supported #if defined(PNG_LIBPNG_VER) && (PNG_LIBPNG_VER >= 10200) && defined(PNG_MMX_CODE_SUPPORTED) png_uint_32 mask, flags; flags = png_get_asm_flags(png_ptr); mask = png_get_asm_flagmask(PNG_SELECT_READ | PNG_SELECT_WRITE); png_set_asm_flags(png_ptr, flags | mask); #endif png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_NONE); png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr,(png_infopp)0); return; } jmp_buf* jmp_context = (jmp_buf*) png_get_error_ptr(png_ptr); if (jmp_context) { png_destroy_write_struct(&png_ptr, &info_ptr); return; } png_set_write_fn (png_ptr, &file, &write_data<T1>, &flush_data<T1>); png_set_compression_level(png_ptr, opts.compression); png_set_compression_strategy(png_ptr, opts.strategy); png_set_compression_buffer_size(png_ptr, 32768); png_set_IHDR(png_ptr, info_ptr,image.width(),image.height(),8, (opts.trans_mode == 0) ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA,PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); const std::unique_ptr<png_bytep[]> row_pointers(new png_bytep[image.height()]); for (unsigned int i = 0; i < image.height(); i++) { row_pointers[i] = (png_bytep)image.getRow(i); } png_set_rows(png_ptr, info_ptr, row_pointers.get()); png_write_png(png_ptr, info_ptr, (opts.trans_mode == 0) ? PNG_TRANSFORM_STRIP_FILLER_AFTER : PNG_TRANSFORM_IDENTITY, nullptr); png_destroy_write_struct(&png_ptr, &info_ptr); }