From 58f4431df16925ac028ac790c975b449ca030c7b Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Mon, 10 Dec 2007 19:59:17 +0000 Subject: [PATCH] support for palette based PNGs, user 'png256' as a format parameter (see updated rundemo.py) --- bindings/python/mapnik_image_view.cpp | 6 +- demo/python/rundemo.py | 2 +- include/mapnik/image_reader.hpp | 6 +- include/mapnik/image_view.hpp | 5 +- include/mapnik/octree.hpp | 225 +++++++++++++++++++++++++ plugins/input/gdal/gdal_featureset.cpp | 49 +++--- src/image_reader.cpp | 12 +- src/image_util.cpp | 148 +++++++++++++++- src/symbolizer.cpp | 59 ++++--- 9 files changed, 450 insertions(+), 62 deletions(-) create mode 100644 include/mapnik/octree.hpp diff --git a/bindings/python/mapnik_image_view.cpp b/bindings/python/mapnik_image_view.cpp index 9c7e977ac..7a77406b0 100644 --- a/bindings/python/mapnik_image_view.cpp +++ b/bindings/python/mapnik_image_view.cpp @@ -29,6 +29,9 @@ using mapnik::ImageData32; using mapnik::image_view; using mapnik::save_to_file; +void (*view_to_file1)(std::string const&,std::string const&, image_view const&) = mapnik::save_to_file; +void (*view_to_file2)(std::string const&, image_view const&) = mapnik::save_to_file; + void export_image_view() { using namespace boost::python; @@ -37,5 +40,6 @@ void export_image_view() .def("height",&image_view::height) ; - //def("save_to_file",save_to_file >); + def("save_to_file",view_to_file1); + def("save_to_file",view_to_file2); } diff --git a/demo/python/rundemo.py b/demo/python/rundemo.py index 33deb04f9..4d37f0e7c 100644 --- a/demo/python/rundemo.py +++ b/demo/python/rundemo.py @@ -305,7 +305,7 @@ m.zoom_to_box(Envelope(1405120.04127408,-247003.813399447,1706357.31328276,-2509 # Render two maps, one PNG, one JPEG. -render_to_file(m, 'demo.png', 'png') +render_to_file(m, 'demo.png', 'png256') # save to palette based (max 256 colours) png render_to_file(m, 'demo.jpg', 'jpeg') print """\n\nTwo maps have been rendered in the current directory: diff --git a/include/mapnik/image_reader.hpp b/include/mapnik/image_reader.hpp index 11b16fe49..1d20d55b7 100644 --- a/include/mapnik/image_reader.hpp +++ b/include/mapnik/image_reader.hpp @@ -57,8 +57,10 @@ namespace mapnik virtual ~ImageReader() {} }; - bool register_image_reader(const std::string& type,ImageReader* (*)(const std::string&)); - MAPNIK_DECL ImageReader* get_image_reader(const std::string& type,const std::string& file); + bool register_image_reader(const std::string& type,ImageReader* (*)(const std::string&)); + MAPNIK_DECL ImageReader* get_image_reader(const std::string& file,const std::string& type); + MAPNIK_DECL ImageReader* get_image_reader(const std::string& file); + } #endif //IMAGE_READER_HPP diff --git a/include/mapnik/image_view.hpp b/include/mapnik/image_view.hpp index da6f5a737..51db49a79 100644 --- a/include/mapnik/image_view.hpp +++ b/include/mapnik/image_view.hpp @@ -31,8 +31,9 @@ namespace mapnik { template class image_view { - typedef typename T::pixel_type pixel_type; - public: + public: + typedef typename T::pixel_type pixel_type; + image_view(unsigned x, unsigned y, unsigned width, unsigned height, T const& data) : x_(x), y_(y), diff --git a/include/mapnik/octree.hpp b/include/mapnik/octree.hpp new file mode 100644 index 000000000..3265851cd --- /dev/null +++ b/include/mapnik/octree.hpp @@ -0,0 +1,225 @@ +/***************************************************************************** + * + * This file is part of Mapnik (c++ mapping toolkit) + * + * Copyright (C) 2006 Artem Pavlenko + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + *****************************************************************************/ +//$Id$ + +#ifndef _OCTREE_HPP_ +#define _OCTREE_HPP_ + +#include +#include +#include +#include +#include + +namespace mapnik { + + typedef uint8_t byte ; + struct rgb + { + byte r; + byte g; + byte b; + rgb(byte r_, byte b_, byte g_) + : r(r_), g(g_), b(b_) {} + }; + + struct RGBPolicy + { + const static unsigned MAX_LEVELS = 6; + inline static unsigned index_from_level(unsigned level, rgb const& c) + { + unsigned shift = 7 - level; + return (((c.r >> shift) & 1) << 2) + | (((c.g >> shift) & 1) << 1) + | ((c.b >> shift) & 1); + } + }; + + template + class octree : private boost::noncopyable + { + struct node + { + node () + : next_node_(0), + reds(0), + greens(0), + blues(0), + count(0), + index(0) + { + memset(&children_[0],0,sizeof(children_)); + } + + ~node () + { + for (unsigned i = 0;i < 8; ++i) { + if (children_[i] != 0) delete children_[i],children_[i]=0; + } + } + + bool is_leaf() const { return count != 0; } + node * children_[8]; + node * next_node_; + unsigned reds; + unsigned greens; + unsigned blues; + unsigned count; + uint8_t index; + }; + struct node_cmp + { + bool operator() ( const node * lhs,const node* rhs ) const + { + unsigned left_total=0; + unsigned right_total=0; + for (unsigned i=0; i<8;++i) + { + if (lhs->children_[i]) left_total+=lhs->children_[i]->count; + if (rhs->children_[i]) right_total+=rhs->children_[i]->count; + } + return lhs > rhs; + } + }; + std::set reducible_[InsertPolicy::MAX_LEVELS]; + unsigned max_colors_; + unsigned colors_; + unsigned leaf_level_; + + public: + explicit octree(unsigned max_colors=255) + : max_colors_(max_colors), + colors_(0), + leaf_level_(InsertPolicy::MAX_LEVELS), + root_(new node()) + {} + + ~octree() { delete root_;} + + void insert(T const& data) + { + unsigned level = 0; + node * cur_node = root_; + while (true) { + + if ( cur_node->count > 0 || level == leaf_level_) + { + cur_node->reds += data.r; + cur_node->greens += data.g; + cur_node->blues += data.b; + cur_node->count += 1; + if (cur_node->count == 1) ++colors_; + if (colors_ >= max_colors_ - 1) + { + reduce(); + } + break; + } + + unsigned idx = InsertPolicy::index_from_level(level,data); + if (cur_node->children_[idx] == 0) { + cur_node->children_[idx] = new node(); + if (level < leaf_level_-1) { + reducible_[level+1].insert(cur_node->children_[idx]); + } + } + cur_node = cur_node->children_[idx]; + ++level; + } + } + int quantize(rgb const& c) const + { + unsigned level = 0; + node * cur_node = root_; + while (cur_node) + { + if (cur_node->count != 0) return cur_node->index; + unsigned idx = InsertPolicy::index_from_level(level,c); + cur_node = cur_node->children_[idx]; + ++level; + } + return -1; + } + + void create_palette(std::vector & palette) const + { + palette.reserve(colors_); + create_palette(palette, root_); + } + private: + void reduce() + { + while (leaf_level_ >0 && reducible_[leaf_level_-1].size() == 0) + { + --leaf_level_; + } + + if (leaf_level_ < 1) return; + + typename std::set::iterator pos = reducible_[leaf_level_-1].begin(); + if (pos == reducible_[leaf_level_-1].end()) return; + + node * cur_node = *pos; + unsigned num_children = 0; + for (unsigned idx=0; idx < 8; ++idx) + { + if (cur_node->children_[idx] != 0) + { + ++num_children; + cur_node->reds += cur_node->children_[idx]->reds; + cur_node->greens += cur_node->children_[idx]->greens; + cur_node->blues += cur_node->children_[idx]->blues; + cur_node->count += cur_node->children_[idx]->count; + delete cur_node->children_[idx], cur_node->children_[idx]=0; + } + } + + reducible_[leaf_level_-1].erase(pos); + if (num_children > 0 ) + { + colors_ -= (num_children - 1); + } + } + + void create_palette(std::vector & palette, node * itr) const + { + if (itr->count != 0) + { + unsigned count = itr->count; + palette.push_back(rgb(uint8_t(itr->reds/float(count)), + uint8_t(itr->greens/float(count)), + uint8_t(itr->blues/float(count)))); + itr->index = palette.size() - 1; + } + for (unsigned i=0; i < 8 ;++i) + { + if (itr->children_[i] != 0) + create_palette(palette, itr->children_[i]); + } + + } + private: + node * root_; + }; +} // namespace mapnik + +#endif /* _OCTREE_HPP_ */ diff --git a/plugins/input/gdal/gdal_featureset.cpp b/plugins/input/gdal/gdal_featureset.cpp index 367916652..07df30678 100644 --- a/plugins/input/gdal/gdal_featureset.cpp +++ b/plugins/input/gdal/gdal_featureset.cpp @@ -50,11 +50,16 @@ feature_ptr gdal_featureset::next() GDALRasterBand * grey = 0; for (int i = 0; i < dataset_.GetRasterCount() ;++i) { - GDALRasterBand * band = dataset_.GetRasterBand(i+1); + GDALRasterBand * band = dataset_.GetRasterBand(i+1); + //if (band->GetOverviewCount() > 0) + //{ + // band = band->GetOverview(0); + //} + #ifdef MAPNIK_DEBUG - int bsx,bsy; - band->GetBlockSize(&bsx,&bsy); - std::cout << boost::format("Block=%dx%d Type=%s Color=%s \n") % bsx % bsy + int bsx,bsy; + band->GetBlockSize(&bsx,&bsy); + std::cout << boost::format("Block=%dx%d Type=%s Color=%s \n") % bsx % bsy % GDALGetDataTypeName(band->GetRasterDataType()) % GDALGetColorInterpretationName(band->GetColorInterpretation()); #endif @@ -77,30 +82,30 @@ feature_ptr gdal_featureset::next() grey = band; break; case GCI_PaletteIndex: - { - grey = band; - GDALColorTable *color_table = band->GetColorTable(); + { + grey = band; + GDALColorTable *color_table = band->GetColorTable(); - if ( color_table) - { - int count = color_table->GetColorEntryCount(); + if ( color_table) + { + int count = color_table->GetColorEntryCount(); #ifdef MAPNIK_DEBUG - std::cout << "Color Table count = " << count << "\n";ยด + std::cout << "Color Table count = " << count << "\n"; #endif - for ( int i = 0; i < count; i++ ) - { - const GDALColorEntry *ce = color_table->GetColorEntry ( i ); - if (!ce ) continue; + for ( int i = 0; i < count; i++ ) + { + const GDALColorEntry *ce = color_table->GetColorEntry ( i ); + if (!ce ) continue; #ifdef MAPNIK_DEBUG - std::cout << "color entry RGB (" << ce->c1 <<"," <c2 << "," << ce->c3 << ")\n"; + std::cout << "color entry RGB (" << ce->c1 <<"," <c2 << "," << ce->c3 << ")\n"; #endif - } - } - break; - } + } + } + break; + } case GCI_Undefined: - grey = band; - break; + grey = band; + break; default: break; ; diff --git a/src/image_reader.cpp b/src/image_reader.cpp index 2a161afa1..61b173ddc 100644 --- a/src/image_reader.cpp +++ b/src/image_reader.cpp @@ -22,7 +22,7 @@ //$Id: image_reader.cpp 17 2005-03-08 23:58:43Z pavlenko $ #include - +#include #include namespace mapnik @@ -36,8 +36,14 @@ namespace mapnik return ImageReaderFactory::instance()->register_product(type,fun); } - ImageReader* get_image_reader(const std::string& type,const std::string& file) + ImageReader* get_image_reader(const std::string& filename,const std::string& type) { - return ImageReaderFactory::instance()->create_object(type,file); + return ImageReaderFactory::instance()->create_object(type,filename); } + + ImageReader* get_image_reader(const std::string& filename) + { + std::string type = type_from_filename(filename); + return ImageReaderFactory::instance()->create_object(filename,type); + } } diff --git a/src/image_util.cpp b/src/image_util.cpp index b5a8881c0..d694dd8b9 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // jpeg png extern "C" { @@ -52,6 +53,7 @@ namespace mapnik { //all this should go into image_writer factory if (type=="png") save_as_png(file,image); + else if (type == "png256") save_as_png256(file,image); else if (type=="jpeg") save_as_jpeg(file,85,image); } } @@ -123,6 +125,150 @@ namespace mapnik png_destroy_write_struct(&png_ptr, &info_ptr); } + template + void reduce_8 (T const& in, ImageData8 & out, octree & tree) + { + unsigned width = in.width(); + unsigned height = in.height(); + for (unsigned y = 0; y < height; ++y) + { + mapnik::ImageData32::pixel_type const * row = in.getRow(y); + mapnik::ImageData8::pixel_type * row_out = out.getRow(y); + for (unsigned x = 0; x < width; ++x) + { + unsigned val = row[x]; + mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff); + uint8_t index = tree.quantize(c); + row_out[x] = index; + } + } + } + + template + void reduce_4 (T const& in, ImageData8 & out, octree & tree) + { + unsigned width = in.width(); + unsigned height = in.height(); + + for (unsigned y = 0; y < height; ++y) + { + mapnik::ImageData32::pixel_type const * row = in.getRow(y); + mapnik::ImageData8::pixel_type * row_out = out.getRow(y); + + for (unsigned x = 0; x < width; ++x) + { + unsigned val = row[x]; + mapnik::rgb c((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff); + uint8_t index = tree.quantize(c); + if (x%2 > 0) index = index<<4; + row_out[x>>1] |= index; + } + } + } + + // 1-bit but only one color. + template + void reduce_1(T const&, ImageData8 & out, octree &) + { + out.set(0); // only one color!!! + } + + template + void save_as_png(T & file, std::vector & palette, + mapnik::ImageData8 const& image, + unsigned width, + unsigned height, + unsigned color_depth) + { + 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, 0, 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; + } + if (setjmp(png_jmpbuf(png_ptr))) + { + png_destroy_write_struct(&png_ptr, &info_ptr); + return; + } + png_set_write_fn (png_ptr, &file, &write_data, &flush_data); + + png_set_IHDR(png_ptr, info_ptr,width,height,color_depth, + PNG_COLOR_TYPE_PALETTE,PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT,PNG_FILTER_TYPE_DEFAULT); + + png_set_PLTE(png_ptr,info_ptr,reinterpret_cast(&palette[0]),palette.size()); + + png_write_info(png_ptr, info_ptr); + for (unsigned i=0;i + void save_as_png256(T1 & file, T2 const& image) + { + octree tree(256); + unsigned width = image.width(); + unsigned height = image.height(); + 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::rgb((val)&0xff, (val>>8)&0xff, (val>>16) & 0xff)); + } + } + + std::vector palette; + tree.create_palette(palette); + assert(palette.size() <= 256); + + if (palette.size() > 16 ) + { + // >16 && <=256 colors -> write 8-bit color depth + ImageData8 reduced_image(width,height); + reduce_8(image,reduced_image,tree); + save_as_png(file,palette,reduced_image,width,height,8); + } + 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; + ImageData8 reduced_image(image_width,image_height); + reduce_1(image,reduced_image,tree); + save_as_png(file,palette,reduced_image,width,height,1); + } + else + { + // <=16 colors -> write 4-bit color depth PNG + unsigned image_width = (int(0.5*width) + 3)&~3; + unsigned image_height = height; + ImageData8 reduced_image(image_width,image_height); + reduce_4(image,reduced_image,tree); + save_as_png(file,palette,reduced_image,width,height,4); + } + } #define BUFFER_SIZE 4096 @@ -222,7 +368,7 @@ namespace mapnik template void save_to_file > (std::string const&, std::string const&, image_view const&); - + template void save_to_file > (std::string const&, image_view const&); diff --git a/src/symbolizer.cpp b/src/symbolizer.cpp index c99f1f9c7..56816a6b8 100644 --- a/src/symbolizer.cpp +++ b/src/symbolizer.cpp @@ -29,37 +29,36 @@ namespace mapnik { - symbolizer_with_image::symbolizer_with_image(boost::shared_ptr img) : - image_( img ) {} - - symbolizer_with_image::symbolizer_with_image(std::string const& file, - std::string const& type, unsigned width,unsigned height) - : image_(new ImageData32(width,height)), - image_filename_( file ) - { - std::auto_ptr reader(get_image_reader(type,file)); - if (reader.get()) - reader->read(0,0,*image_); - } - - - symbolizer_with_image::symbolizer_with_image( symbolizer_with_image const& rhs) - : image_(rhs.image_), image_filename_(rhs.image_filename_) {} - - - boost::shared_ptr symbolizer_with_image::get_image() const - { - return image_; - } - void symbolizer_with_image::set_image(boost::shared_ptr symbol) - { - image_ = symbol; - } - const std::string & symbolizer_with_image::get_filename() const - { - return image_filename_; - } + symbolizer_with_image::symbolizer_with_image(boost::shared_ptr img) : + image_( img ) {} + symbolizer_with_image::symbolizer_with_image(std::string const& file, + std::string const& type, unsigned width,unsigned height) + : image_(new ImageData32(width,height)), + image_filename_( file ) + { + std::auto_ptr reader(get_image_reader(type,file)); + if (reader.get()) + reader->read(0,0,*image_); + } + + symbolizer_with_image::symbolizer_with_image( symbolizer_with_image const& rhs) + : image_(rhs.image_), image_filename_(rhs.image_filename_) {} + + + boost::shared_ptr symbolizer_with_image::get_image() const + { + return image_; + } + void symbolizer_with_image::set_image(boost::shared_ptr symbol) + { + image_ = symbol; + } + const std::string & symbolizer_with_image::get_filename() const + { + return image_filename_; + } + } // end of namespace mapnik