From 2eec236fc21f0c2058ec7f38d709ee7ebb964e22 Mon Sep 17 00:00:00 2001 From: Artem Pavlenko Date: Sun, 29 Mar 2009 11:05:20 +0000 Subject: [PATCH] + applied "hill shading" patch from Marcin Rudowski a) Raster opacity b) Raster merging modes (TODO:add more modes, consider agg impl) c) Raster scaling algos: fast,bilinear,bilinear8 (TODO: add alpha support in bilinear8) d) improvements to png256 *Great work, thanks!* --- include/mapnik/filter_parser.hpp | 2 +- include/mapnik/graphics.hpp | 132 +++++++++++++++++++++++++++ include/mapnik/image_util.hpp | 132 +++++++++++++++++++++++++++ include/mapnik/octree.hpp | 27 +++++- include/mapnik/raster_symbolizer.hpp | 36 +++++++- src/agg_renderer.cpp | 44 ++++++++- src/cairo_renderer.cpp | 15 ++- src/load_map.cpp | 56 +++++++++++- src/save_map.cpp | 18 +++- src/unicode.cpp | 3 - 10 files changed, 448 insertions(+), 17 deletions(-) diff --git a/include/mapnik/filter_parser.hpp b/include/mapnik/filter_parser.hpp index b345f9ef0..72e135970 100644 --- a/include/mapnik/filter_parser.hpp +++ b/include/mapnik/filter_parser.hpp @@ -38,7 +38,7 @@ // boost -#ifdef MAPNIK_DEBUG +#ifdef MAPNIK_XML_DEBUG #define BOOST_SPIRIT_DEBUG #endif diff --git a/include/mapnik/graphics.hpp b/include/mapnik/graphics.hpp index dfe5c9103..a08eedfd9 100644 --- a/include/mapnik/graphics.hpp +++ b/include/mapnik/graphics.hpp @@ -234,6 +234,138 @@ namespace mapnik } } } + + struct Multiply + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = r1*r0/255; + g1 = g1*g0/255; + b1 = b1*b0/255; + } + }; + struct Multiply2 + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = r1*r0/128; + if (r1>255) r1=255; + g1 = g1*g0/128; + if (g1>255) g1=255; + b1 = b1*b0/128; + if (b1>255) b1=255; + } + }; + struct Divide + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = r0*256/(r1+1); + g1 = g0*256/(g1+1); + b1 = b0*256/(b1+1); + } + }; + struct Divide2 + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = r0*128/(r1+1); + g1 = g0*128/(g1+1); + b1 = b0*128/(b1+1); + } + }; + struct Screen + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = 255 - (255-r0)*(255-r1)/255; + g1 = 255 - (255-g0)*(255-g1)/255; + b1 = 255 - (255-b0)*(255-b1)/255; + } + }; + struct HardLight + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = (r1>128)?255-(255-r0)*(255-2*(r1-128))/256:r0*r1*2/256; + g1 = (g1>128)?255-(255-g0)*(255-2*(g1-128))/256:g0*g1*2/256; + b1 = (b1>128)?255-(255-b0)*(255-2*(b1-128))/256:b0*b1*2/256; + } + }; + struct MergeGrain + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = (r1+r0>128)?r1+r0-128:0; + if (r1>255) r1=255; + g1 = (g1+g0>128)?g1+g0-128:0; + if (g1>255) g1=255; + b1 = (b1+b0>128)?b1+b0-128:0; + if (b1>255) b1=255; + } + }; + struct MergeGrain2 + { + inline static void mergeRGB(unsigned const &r0, unsigned const &g0, unsigned const &b0, + unsigned &r1, unsigned &g1, unsigned &b1) + { + r1 = (2*r1+r0>256)?2*r1+r0-256:0; + if (r1>255) r1=255; + g1 = (2*g1+g0>256)?2*g1+g0-256:0; + if (g1>255) g1=255; + b1 = (2*b1+b0>256)?2*b1+b0-256:0; + if (b1>255) b1=255; + } + }; + + template + inline void merge_rectangle(ImageData32 const& data, unsigned x0, unsigned y0, float opacity) + { + Envelope ext0(0,0,width_,height_); + Envelope ext1(x0,y0,x0 + data.width(),y0 + data.height()); + + if (ext0.intersects(ext1)) + { + Envelope box = ext0.intersect(ext1); + for (int y = box.miny(); y < box.maxy(); ++y) + { + unsigned int* row_to = data_.getRow(y); + unsigned int const * row_from = data.getRow(y-y0); + for (int x = box.minx(); x < box.maxx(); ++x) + { + unsigned rgba0 = row_to[x]; + unsigned rgba1 = row_from[x-x0]; + unsigned a1 = int( ((rgba1 >> 24) & 0xff) * opacity ); + if (a1 == 0) continue; + unsigned r1 = rgba1 & 0xff; + unsigned g1 = (rgba1 >> 8 ) & 0xff; + unsigned b1 = (rgba1 >> 16) & 0xff; + + unsigned a0 = (rgba0 >> 24) & 0xff; + unsigned r0 = rgba0 & 0xff ; + unsigned g0 = (rgba0 >> 8 ) & 0xff; + unsigned b0 = (rgba0 >> 16) & 0xff; + + unsigned a = (a1 * 255 + (255 - a1) * a0 + 127)/255; + + MergeMethod::mergeRGB(r0,g0,b0,r1,g1,b1); + + r0 = (r1*a1 + (((255 - a1) * a0 + 127)/255) * r0 + 127)/a; + g0 = (g1*a1 + (((255 - a1) * a0 + 127)/255) * g0 + 127)/a; + b0 = (b1*a1 + (((255 - a1) * a0 + 127)/255) * b0 + 127)/a; + + row_to[x] = (a << 24)| (b0 << 16) | (g0 << 8) | (r0) ; + } + } + } + } }; } #endif //GRAPHICS_HPP diff --git a/include/mapnik/image_util.hpp b/include/mapnik/image_util.hpp index a1e41ad2e..83c14162d 100644 --- a/include/mapnik/image_util.hpp +++ b/include/mapnik/image_util.hpp @@ -272,6 +272,138 @@ namespace mapnik { } } + template + inline void scale_image_bilinear (Image& target,const Image& source) + { + + int source_width=source.width(); + int source_height=source.height(); + + int target_width=target.width(); + int target_height=target.height(); + + if (source_width<1 || source_height<1 || + target_width<1 || target_height<1) return; + int x=0,y=0,xs=0,ys=0; + int tw2 = target_width/2; + int th2 = target_height/2; + + + for (y=0;y=source_height) + ys1--; + unsigned yprt = y*source_height%target_height; + unsigned yprt1 = target_height-yprt; + for (x=0;x=target_width || source_height>=target_height){ + target(x,y)=source(xs,ys); + continue; + } + unsigned xprt = x*source_width%target_width; + unsigned xprt1 = target_width-xprt; + int xs1 = xs+1; + if (xs1>=source_width) + xs1--; + + unsigned a = source(xs,ys); + unsigned b = source(xs1,ys); + unsigned c = source(xs,ys1); + unsigned d = source(xs1,ys1); + unsigned out=0; + unsigned t = 0; + + for(int i=0; i<4; i++){ + unsigned p,r,s; + // X axis + p = a&0xff; + r = b&0xff; + if (p!=r) + r = (r*xprt+p*xprt1+tw2)/target_width; + p = c&0xff; + s = d&0xff; + if (p!=s) + s = (s*xprt+p*xprt1+tw2)/target_width; + // Y axis + if (r!=s) + r = (s*yprt+r*yprt1+th2)/target_height; + // channel up + out |= r << t; + t += 8; + a >>= 8; + b >>= 8; + c >>= 8; + d >>= 8; + } + target(x,y)=out; + } + } + } + + template + inline void scale_image_bilinear8 (Image& target,const Image& source) + { + + int source_width=source.width(); + int source_height=source.height(); + + int target_width=target.width(); + int target_height=target.height(); + + if (source_width<1 || source_height<1 || + target_width<1 || target_height<1) return; + int x=0,y=0,xs=0,ys=0; + int tw2 = target_width/2; + int th2 = target_height/2; + + + for (y=0;y=source_height) + ys1--; + unsigned yprt = y*source_height%target_height; + unsigned yprt1 = target_height-yprt; + for (x=0;x=target_width || source_height>=target_height){ + target(x,y)=source(xs,ys); + continue; + } + unsigned xprt = x*source_width%target_width; + unsigned xprt1 = target_width-xprt; + int xs1 = xs+1; + if (xs1>=source_width) + xs1--; + + unsigned a = source(xs,ys); + unsigned b = source(xs1,ys); + unsigned c = source(xs,ys1); + unsigned d = source(xs1,ys1); + unsigned p,r,s; + // X axis + p = a&0xff; + r = b&0xff; + if (p!=r) + r = (r*xprt+p*xprt1+tw2)/target_width; + p = c&0xff; + s = d&0xff; + if (p!=s) + s = (s*xprt+p*xprt1+tw2)/target_width; + // Y axis + if (r!=s) + r = (s*yprt+r*yprt1+th2)/target_height; + target(x,y)=(0xff<<24) | (r<<16) | (r<<8) | r; + } + } + } + inline MAPNIK_DECL void save_to_file (Image32 const& image, std::string const& file, std::string const& type) diff --git a/include/mapnik/octree.hpp b/include/mapnik/octree.hpp index 66056228d..da136383a 100644 --- a/include/mapnik/octree.hpp +++ b/include/mapnik/octree.hpp @@ -51,9 +51,10 @@ namespace mapnik { struct RGBPolicy { const static unsigned MAX_LEVELS = 6; + const static unsigned MIN_LEVELS = 3; inline static unsigned index_from_level(unsigned level, rgb const& c) { - unsigned shift = (MAX_LEVELS + 1) - level; + unsigned shift = 7 - level; return (((c.r >> shift) & 1) << 2) | (((c.g >> shift) & 1) << 1) | ((c.b >> shift) & 1); @@ -71,6 +72,7 @@ namespace mapnik { blues(0), count(0), count2(0), + children_count(0), index(0) { memset(&children_[0],0,sizeof(children_)); @@ -90,6 +92,7 @@ namespace mapnik { unsigned blues; unsigned count; unsigned count2; + uint8_t children_count; byte index; }; struct node_cmp @@ -136,6 +139,7 @@ namespace mapnik { unsigned idx = InsertPolicy::index_from_level(level,data); if (cur_node->children_[idx] == 0) { + cur_node->children_count++; cur_node->children_[idx] = new node(); if (level < leaf_level_-1) { @@ -187,13 +191,30 @@ namespace mapnik { if ( reducible_[leaf_level_-1].size() == 0) return; - typename std::deque::iterator pos = reducible_[leaf_level_-1].begin(); + // select best of all reducible: + unsigned red_idx = leaf_level_-1; + unsigned bestv = (*reducible_[red_idx].begin())->count2; + for(unsigned i=red_idx; i>=InsertPolicy::MIN_LEVELS; i--) if (!reducible_[i].empty()){ + node *nd = *reducible_[i].begin(); + unsigned gch = 0; + for(unsigned idx=0; idx<8; idx++){ + if (nd->children_[idx]) + gch += nd->children_[idx]->children_count; + } + if (gch==0 && nd->count2count2; + red_idx = i; + } + } + + typename std::deque::iterator pos = reducible_[red_idx].begin(); node * cur_node = *pos; unsigned num_children = 0; for (unsigned idx=0; idx < 8; ++idx) { if (cur_node->children_[idx] != 0) { + cur_node->children_count++; ++num_children; cur_node->reds += cur_node->children_[idx]->reds; cur_node->greens += cur_node->children_[idx]->greens; @@ -203,7 +224,7 @@ namespace mapnik { } } - reducible_[leaf_level_-1].erase(pos); + reducible_[red_idx].erase(pos); if (num_children > 0 ) { colors_ -= (num_children - 1); diff --git a/include/mapnik/raster_symbolizer.hpp b/include/mapnik/raster_symbolizer.hpp index c77bdb12c..560450dd7 100644 --- a/include/mapnik/raster_symbolizer.hpp +++ b/include/mapnik/raster_symbolizer.hpp @@ -28,7 +28,41 @@ namespace mapnik { - struct MAPNIK_DECL raster_symbolizer { /* TODO */}; + struct MAPNIK_DECL raster_symbolizer { + explicit raster_symbolizer() + : mode_("normal"), + scaling_("fast"), + opacity_(1.0) {} + + std::string const& get_mode() const + { + return mode_; + } + void set_mode(std::string const& mode) + { + mode_ = mode; + } + std::string const& get_scaling() const + { + return scaling_; + } + void set_scaling(std::string const& scaling) + { + scaling_ = scaling; + } + void set_opacity(float opacity) + { + opacity_ = opacity; + } + float get_opacity() const + { + return opacity_; + } + private: + std::string mode_; + std::string scaling_; + float opacity_; + }; } #endif //RASTER_SYMBOLIZER_HPP diff --git a/src/agg_renderer.cpp b/src/agg_renderer.cpp index da552db83..875aaa9c9 100644 --- a/src/agg_renderer.cpp +++ b/src/agg_renderer.cpp @@ -660,20 +660,56 @@ namespace mapnik } template - void agg_renderer::process(raster_symbolizer const&, + void agg_renderer::process(raster_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { - // TODO -- at the moment raster_symbolizer is an empty class - // used for type dispatching, but we can have some fancy raster - // processing in a future (filters??). Just copy raster into pixmap for now. raster_ptr const& raster=feature.get_raster(); if (raster) { Envelope ext=t_.forward(raster->ext_); ImageData32 target(int(ext.width() + 0.5),int(ext.height() + 0.5)); + + if (sym.get_scaling() == "fast"){ + scale_image(target,raster->data_); + } else if (sym.get_scaling() == "bilinear"){ + scale_image_bilinear(target,raster->data_); + } else if (sym.get_scaling() == "bilinear8"){ + scale_image_bilinear8(target,raster->data_); + } else { scale_image(target,raster->data_); + } + + if (sym.get_mode() == "normal"){ + if (sym.get_opacity() == 1.0){ pixmap_.set_rectangle(int(ext.minx()),int(ext.miny()),target); + } else { + pixmap_.set_rectangle_alpha2(target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } + } else if (sym.get_mode() == "grain_merge"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "grain_merge2"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "multiply"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "multiply2"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "divide"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "divide2"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "screen"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else if (sym.get_mode() == "hard_light"){ + pixmap_.template merge_rectangle (target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } else { + if (sym.get_opacity() == 1.0){ + pixmap_.set_rectangle(int(ext.minx()),int(ext.miny()),target); + } else { + pixmap_.set_rectangle_alpha2(target,int(ext.minx()),int(ext.miny()), sym.get_opacity()); + } + } + // TODO: other modes? (add,diff,sub,...) } } diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 6d34f1b67..36f89b312 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -961,7 +961,7 @@ namespace mapnik } } - void cairo_renderer_base::process(raster_symbolizer const&, + void cairo_renderer_base::process(raster_symbolizer const& sym, Feature const& feature, proj_transform const& prj_trans) { @@ -973,10 +973,21 @@ namespace mapnik { Envelope ext = t_.forward(raster->ext_); ImageData32 target(int(ext.width() + 0.5), int(ext.height() + 0.5)); + //TODO -- use cairo matrix transformation for scaling + if (sym.get_scaling() == "fast"){ scale_image(target, raster->data_); + } else if (sym.get_scaling() == "bilinear"){ + scale_image_bilinear(target,raster->data_); + } else if (sym.get_scaling() == "bilinear8"){ + scale_image_bilinear8(target,raster->data_); + } else { + scale_image(target,raster->data_); + } + cairo_context context(context_); - context.add_image(int(ext.minx()), int(ext.miny()), target); + //TODO -- support for advanced image merging + context.add_image(int(ext.minx()), int(ext.miny()), target, sym.get_opacity()); } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 8820c397f..b4c0a8ceb 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -81,6 +81,7 @@ namespace mapnik void parse_line_symbolizer( rule_type & rule, ptree const & sym); void parse_polygon_symbolizer( rule_type & rule, ptree const & sym); void parse_building_symbolizer( rule_type & rule, ptree const & sym ); + void parse_raster_symbolizer( rule_type & rule, ptree const & sym ); void parse_markers_symbolizer( rule_type & rule, ptree const & sym ); void ensure_font_face( const std::string & face_name ); @@ -529,7 +530,7 @@ namespace mapnik } else if ( sym.first == "RasterSymbolizer") { - rule.append(raster_symbolizer()); + parse_raster_symbolizer( rule, sym.second ); } else if ( sym.first == "MarkersSymbolizer") { @@ -1187,6 +1188,59 @@ namespace mapnik } } + void map_parser::parse_raster_symbolizer( rule_type & rule, ptree const & sym ) + { + try + { + raster_symbolizer raster_sym; + + ptree::const_iterator cssIter = sym.begin(); + ptree::const_iterator endCss = sym.end(); + + for(; cssIter != endCss; ++cssIter) + { + ptree::value_type const& css_tag = *cssIter; + ptree const & css = cssIter->second; + + if (css_tag.first == "CssParameter") + { + std::string css_name = get_attr(css, "name"); + if (css_name == "mode") + { + raster_sym.set_mode(get_css(css, css_name)); + } + else if (css_name == "scaling") + { + raster_sym.set_scaling(get_css(css, css_name)); + } + else if (css_name == "opacity") + { + float opacity = get_css(css, css_name); + raster_sym.set_opacity(opacity); + } + else + { + throw config_error(std::string("Failed to parse unknown CSS ") + + "parameter '" + css_name + "'"); + } + } + else if (css_tag.first != "" && + css_tag.first != "" ) + { + throw config_error(std::string("Unknown child node. ") + + "Expected 'CssParameter' but got '" + css_tag.first + "'"); + } + } + rule.append(raster_sym); + } + catch (const config_error & ex) + { + ex.append_context("in RasterSymbolizer"); + throw; + } + } + + void map_parser::ensure_font_face( const std::string & face_name ) { if ( ! font_manager_.get_face( face_name ) ) diff --git a/src/save_map.cpp b/src/save_map.cpp index 7f14a92cc..4e41f6eaf 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -137,8 +137,22 @@ namespace mapnik void operator () ( const raster_symbolizer & sym ) { - rule_.push_back( - ptree::value_type("RasterSymbolizer", ptree())); + ptree & sym_node = rule_.push_back( + ptree::value_type("RasterSymbolizer", ptree()))->second; + raster_symbolizer dfl; + + if ( sym.get_mode() != dfl.get_mode() ) + { + set_css( sym_node, "mode", sym.get_mode() ); + } + if ( sym.get_scaling() != dfl.get_scaling() ) + { + set_css( sym_node, "scaling", sym.get_scaling() ); + } + if ( sym.get_opacity() != dfl.get_opacity() ) + { + set_css( sym_node, "opacity", sym.get_opacity() ); + } } void operator () ( const shield_symbolizer & sym ) diff --git a/src/unicode.cpp b/src/unicode.cpp index 174401c11..874943d33 100644 --- a/src/unicode.cpp +++ b/src/unicode.cpp @@ -165,9 +165,6 @@ namespace mapnik { : ok_(false), conv_(0) { -#ifdef MAPNIK_DEBUG - std::cerr << "ENCODING = " << encoding << "\n"; -#endif //#ifndef WORDS_BIGENDIAN // desc_ = iconv_open("UCS-4LE",encoding.c_str());