Example #1
0
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);
    }
}
Example #2
0
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);
}