inline int import_image(T2 const& im_in, WebPPicture & pic, bool alpha) { image<typename T2::pixel> const& data = im_in.data(); std::size_t width = im_in.width(); std::size_t height = im_in.height(); std::size_t stride = sizeof(typename T2::pixel_type) * width; if (data.width() == width && data.height() == height) { if (alpha) { return WebPPictureImportRGBA(&pic, data.bytes(), static_cast<int>(stride)); } else { #if (WEBP_ENCODER_ABI_VERSION >> 8) >= 1 return WebPPictureImportRGBX(&pic, data.bytes(), static_cast<int>(stride)); #else return WebPPictureImportRGBA(&pic, data.bytes(), static_cast<int>(stride)); #endif } } else { // need to copy: https://github.com/mapnik/mapnik/issues/2024 image_rgba8 im(width,height); for (unsigned y = 0; y < height; ++y) { typename T2::pixel_type const * row_from = im_in.get_row(y); image_rgba8::pixel_type * row_to = im.get_row(y); std::copy(row_from, row_from + width, row_to); } if (alpha) { return WebPPictureImportRGBA(&pic, im.bytes(), static_cast<int>(stride)); } else { #if (WEBP_ENCODER_ABI_VERSION >> 8) >= 1 return WebPPictureImportRGBX(&pic, im.bytes(), static_cast<int>(stride)); #else return WebPPictureImportRGBA(&pic, im.bytes(), static_cast<int>(stride)); #endif } } }
void save_as_jpeg(T1 & file,int quality, T2 const& image) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; int width=image.width(); int height=image.height(); cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); cinfo.dest = (struct jpeg_destination_mgr *)(*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, sizeof(jpeg_detail::dest_mgr)); jpeg_detail::dest_mgr * dest = reinterpret_cast<jpeg_detail::dest_mgr*>(cinfo.dest); dest->pub.init_destination = jpeg_detail::init_destination; dest->pub.empty_output_buffer = jpeg_detail::empty_output_buffer; dest->pub.term_destination = jpeg_detail::term_destination; dest->out = &file; //jpeg_stdio_dest(&cinfo, fp); cinfo.image_width = width; cinfo.image_height = height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults(&cinfo); jpeg_set_quality(&cinfo, quality,1); jpeg_start_compress(&cinfo, 1); JSAMPROW row_pointer[1]; JSAMPLE* row=reinterpret_cast<JSAMPLE*>( ::operator new (sizeof(JSAMPLE) * width*3)); while (cinfo.next_scanline < cinfo.image_height) { const unsigned* imageRow=image.get_row(cinfo.next_scanline); int index=0; for (int i=0;i<width;++i) { row[index++]=(imageRow[i])&0xff; row[index++]=(imageRow[i]>>8)&0xff; row[index++]=(imageRow[i]>>16)&0xff; } row_pointer[0] = &row[0]; (void) jpeg_write_scanlines(&cinfo, row_pointer, 1); } ::operator delete(row); jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); }
void save_as_webp(T1& file, T2 const& image, WebPConfig const& config, bool alpha) { if (WebPValidateConfig(&config) != 1) { throw std::runtime_error("Invalid configuration"); } WebPPicture pic; if (!WebPPictureInit(&pic)) { throw std::runtime_error("version mismatch"); } pic.width = image.width(); pic.height = image.height(); int ok = 0; #if (WEBP_ENCODER_ABI_VERSION >> 8) >= 1 pic.use_argb = !!config.lossless; // lossless fast track if (pic.use_argb) { pic.colorspace = static_cast<WebPEncCSP>(pic.colorspace | WEBP_CSP_ALPHA_BIT); if (WebPPictureAlloc(&pic)) { ok = 1; const int width = pic.width; const int height = pic.height; for (int y = 0; y < height; ++y) { typename T2::pixel_type const * row = image.get_row(y); for (int x = 0; x < width; ++x) { const unsigned rgba = row[x]; unsigned a = (rgba >> 24) & 0xff; unsigned r = rgba & 0xff; unsigned g = (rgba >> 8 ) & 0xff; unsigned b = (rgba >> 16) & 0xff; const uint32_t argb = (a << 24) | (r << 16) | (g << 8) | (b); pic.argb[x + y * pic.argb_stride] = argb; } } }
void save_as_png(T1 & file, T2 const& image, png_options const& opts) { 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,static_cast<png_infopp>(0)); return; } jmp_buf* jmp_context = static_cast<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] = const_cast<png_bytep>(reinterpret_cast<const unsigned char *>(image.get_row(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); }
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); }