From 0224ce3019ba813d0d88e6e648e31160179b97e4 Mon Sep 17 00:00:00 2001 From: Dane Springmeyer Date: Thu, 3 Oct 2013 16:37:09 -0700 Subject: [PATCH] add support for seamless blurring - closes #1478 --- CHANGELOG.md | 2 + include/mapnik/agg_renderer.hpp | 17 +++++++- include/mapnik/ctrans.hpp | 20 +++++++-- include/mapnik/image_filter.hpp | 15 +++++++ src/agg/agg_renderer.cpp | 41 ++++++++++++++---- src/agg/process_line_pattern_symbolizer.cpp | 6 +-- src/agg/process_line_symbolizer.cpp | 8 ++-- src/agg/process_markers_symbolizer.cpp | 6 +-- .../process_polygon_pattern_symbolizer.cpp | 5 ++- src/agg/process_polygon_symbolizer.cpp | 2 +- src/agg/process_shield_symbolizer.cpp | 2 +- src/agg/process_text_symbolizer.cpp | 2 +- ...ng-tiled-0,1-512-512-1.0-agg-reference.png | Bin 7897 -> 7892 bytes ...ng-tiled-0,1-512-512-2.0-agg-reference.png | Bin 7897 -> 7892 bytes 14 files changed, 98 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f41b5fdb2..c609312ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ Released ... Summary: TODO +- Added support for more seamless blurring by rendering to a larger internal image to avoid edge effects (#1478) + - Fixed rendering of large shapes at high zoom levels, which might dissapear due to integer overflow. This bug was previously fixable when geometries were clipped, but would, until now, re-appear if clipping was turned off for a symbolizer (#2000) diff --git a/include/mapnik/agg_renderer.hpp b/include/mapnik/agg_renderer.hpp index be248ea94..49275d668 100644 --- a/include/mapnik/agg_renderer.hpp +++ b/include/mapnik/agg_renderer.hpp @@ -137,6 +137,21 @@ public: return scale_factor_; } + inline box2d clipping_extent() const + { + if (t_.offset() > 0) + { + box2d box = query_extent_; + double scale = static_cast(query_extent_.width())/static_cast(width_); + // 3 is used here because at least 3 was needed for the 'style-level-compositing-tiled-0,1' visual test to pass + // TODO - add more tests to hone in on a more robust # + scale *= t_.offset()*3; + box.pad(scale); + return box; + } + return query_extent_; + } + protected: template void debug_draw_box(R& buf, box2d const& extent, @@ -149,11 +164,11 @@ private: buffer_type & pixmap_; boost::shared_ptr internal_buffer_; mutable buffer_type * current_buffer_; + CoordTransform t_; mutable bool style_level_compositing_; unsigned width_; unsigned height_; double scale_factor_; - CoordTransform t_; freetype_engine font_engine_; face_manager font_manager_; boost::shared_ptr detector_; diff --git a/include/mapnik/ctrans.hpp b/include/mapnik/ctrans.hpp index dfe1fcd5b..f02ab48b4 100644 --- a/include/mapnik/ctrans.hpp +++ b/include/mapnik/ctrans.hpp @@ -123,6 +123,7 @@ private: box2d extent_; double offset_x_; double offset_y_; + int offset_; double sx_; double sy_; @@ -134,6 +135,7 @@ public: extent_(extent), offset_x_(offset_x), offset_y_(offset_y), + offset_(0), sx_(1.0), sy_(1.0) { @@ -143,6 +145,16 @@ public: sy_ = static_cast(height_) / extent_.height(); } + inline int offset() const + { + return offset_; + } + + inline void set_offset(int offset) + { + offset_ = offset; + } + inline double offset_x() const { return offset_x_; @@ -175,14 +187,14 @@ public: inline void forward(double *x, double *y) const { - *x = (*x - extent_.minx()) * sx_ - offset_x_; - *y = (extent_.maxy() - *y) * sy_ - offset_y_; + *x = (*x - extent_.minx()) * sx_ - (offset_x_ - offset_); + *y = (extent_.maxy() - *y) * sy_ - (offset_y_ - offset_); } inline void backward(double *x, double *y) const { - *x = extent_.minx() + (*x + offset_x_) / sx_; - *y = extent_.maxy() - (*y + offset_y_) / sy_; + *x = extent_.minx() + (*x + (offset_x_ - offset_)) / sx_; + *y = extent_.maxy() - (*y + (offset_y_ - offset_)) / sy_; } inline coord2d& forward(coord2d& c) const diff --git a/include/mapnik/image_filter.hpp b/include/mapnik/image_filter.hpp index ab322bb75..af51482da 100644 --- a/include/mapnik/image_filter.hpp +++ b/include/mapnik/image_filter.hpp @@ -775,6 +775,21 @@ struct filter_visitor : boost::static_visitor Src & src_; }; +struct filter_radius_visitor : boost::static_visitor +{ + int & radius_; + filter_radius_visitor(int & radius) + : radius_(radius) {} + template + void operator () (T const& /*filter*/) {} + + void operator () (agg_stack_blur const& op) + { + if (op.rx > radius_) radius_ = op.rx; + if (op.ry > radius_) radius_ = op.ry; + } +}; + }} #endif // MAPNIK_IMAGE_FILTER_HPP diff --git a/src/agg/agg_renderer.cpp b/src/agg/agg_renderer.cpp index 0c3367496..3747d853e 100644 --- a/src/agg/agg_renderer.cpp +++ b/src/agg/agg_renderer.cpp @@ -70,11 +70,11 @@ agg_renderer::agg_renderer(Map const& m, T & pixmap, double scale_factor, uns pixmap_(pixmap), internal_buffer_(), current_buffer_(&pixmap), + t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y), style_level_compositing_(false), width_(pixmap_.width()), height_(pixmap_.height()), scale_factor_(scale_factor), - t_(m.width(),m.height(),m.get_current_extent(),offset_x,offset_y), font_engine_(), font_manager_(font_engine_), detector_(boost::make_shared(box2d(-m.buffer_size(), -m.buffer_size(), m.width() + m.buffer_size() ,m.height() + m.buffer_size()))), @@ -92,11 +92,11 @@ agg_renderer::agg_renderer(Map const& m, request const& req, T & pixmap, doub pixmap_(pixmap), internal_buffer_(), current_buffer_(&pixmap), + t_(req.width(),req.height(),req.extent(),offset_x,offset_y), style_level_compositing_(false), width_(pixmap_.width()), height_(pixmap_.height()), scale_factor_(scale_factor), - t_(req.width(),req.height(),req.extent(),offset_x,offset_y), font_engine_(), font_manager_(font_engine_), detector_(boost::make_shared(box2d(-req.buffer_size(), -req.buffer_size(), req.width() + req.buffer_size() ,req.height() + req.buffer_size()))), @@ -115,11 +115,11 @@ agg_renderer::agg_renderer(Map const& m, T & pixmap, boost::shared_ptr::start_style_processing(feature_type_style const& st) if (style_level_compositing_) { - if (!internal_buffer_) + int radius = 0; + mapnik::filter::filter_radius_visitor visitor(radius); + BOOST_FOREACH(mapnik::filter::filter_type const& filter_tag, st.image_filters()) { - internal_buffer_ = boost::make_shared(pixmap_.width(),pixmap_.height()); + boost::apply_visitor(visitor, filter_tag); + } + if (radius > t_.offset()) + { + t_.set_offset(radius); + } + int offset = t_.offset(); + unsigned target_width = width_; + unsigned target_height = height_; + target_width = width_ + (offset * 2); + target_height = height_ + (offset * 2); + ras_ptr->clip_box(-int(offset*2),-int(offset*2),target_width,target_height); + if (!internal_buffer_ || + (internal_buffer_->width() < target_width || + internal_buffer_->height() < target_height)) + { + internal_buffer_ = boost::make_shared(target_width,target_height); } else { @@ -250,6 +268,8 @@ void agg_renderer::start_style_processing(feature_type_style const& st) } else { + t_.set_offset(0); + ras_ptr->clip_box(0,0,width_,height_); current_buffer_ = &pixmap_; } } @@ -269,14 +289,19 @@ void agg_renderer::end_style_processing(feature_type_style const& st) boost::apply_visitor(visitor, filter_tag); } } - if (st.comp_op()) { - composite(pixmap_.data(),current_buffer_->data(), *st.comp_op(), st.get_opacity(), 0, 0, false); + composite(pixmap_.data(), current_buffer_->data(), + *st.comp_op(), st.get_opacity(), + -t_.offset(), + -t_.offset(), false); } else if (blend_from || st.get_opacity() < 1) { - composite(pixmap_.data(),current_buffer_->data(), src_over, st.get_opacity(), 0, 0, false); + composite(pixmap_.data(), current_buffer_->data(), + src_over, st.get_opacity(), + -t_.offset(), + -t_.offset(), false); } } // apply any 'direct' image filters diff --git a/src/agg/process_line_pattern_symbolizer.cpp b/src/agg/process_line_pattern_symbolizer.cpp index 75cc54775..c79410fb2 100644 --- a/src/agg/process_line_pattern_symbolizer.cpp +++ b/src/agg/process_line_pattern_symbolizer.cpp @@ -127,7 +127,7 @@ void agg_renderer::process(line_pattern_symbolizer const& sym, agg::trans_affine tr; evaluate_transform(tr, feature, sym.get_transform()); - box2d clipping_extent = query_extent_; + box2d clip_box = clipping_extent(); if (sym.clip()) { double padding = (double)(query_extent_.width()/pixmap_.width()); @@ -137,13 +137,13 @@ void agg_renderer::process(line_pattern_symbolizer const& sym, if (std::fabs(sym.offset()) > 0) padding *= std::fabs(sym.offset()) * 1.2; padding *= scale_factor_; - clipping_extent.pad(padding); + clip_box.pad(padding); } typedef boost::mpl::vector conv_types; vertex_converter, rasterizer_type, line_pattern_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(clipping_extent,ras,sym,t_,prj_trans,tr,scale_factor_); + converter(clip_box,ras,sym,t_,prj_trans,tr,scale_factor_); if (sym.clip()) converter.set(); //optional clip (default: true) converter.set(); //always transform diff --git a/src/agg/process_line_symbolizer.cpp b/src/agg/process_line_symbolizer.cpp index bf7dcd2fb..b4290ffce 100644 --- a/src/agg/process_line_symbolizer.cpp +++ b/src/agg/process_line_symbolizer.cpp @@ -92,7 +92,7 @@ void agg_renderer::process(line_symbolizer const& sym, agg::trans_affine tr; evaluate_transform(tr, feature, sym.get_transform()); - box2d clipping_extent = query_extent_; + box2d clip_box = clipping_extent(); if (sym.clip()) { double padding = (double)(query_extent_.width()/pixmap_.width()); @@ -102,7 +102,7 @@ void agg_renderer::process(line_symbolizer const& sym, if (std::fabs(sym.offset()) > 0) padding *= std::fabs(sym.offset()) * 1.2; padding *= scale_factor_; - clipping_extent.pad(padding); + clip_box.pad(padding); // debugging //box2d inverse = query_extent_; //inverse.pad(-padding); @@ -121,7 +121,7 @@ void agg_renderer::process(line_symbolizer const& sym, vertex_converter, rasterizer_type, line_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(clipping_extent,ras,sym,t_,prj_trans,tr,scale_factor_); + converter(clip_box,ras,sym,t_,prj_trans,tr,scale_factor_); if (sym.clip()) converter.set(); // optional clip (default: true) converter.set(); // always transform if (std::fabs(sym.offset()) > 0.0) converter.set(); // parallel offset @@ -141,7 +141,7 @@ void agg_renderer::process(line_symbolizer const& sym, { vertex_converter, rasterizer, line_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(clipping_extent,*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); + converter(clip_box,*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); if (sym.clip()) converter.set(); // optional clip (default: true) converter.set(); // always transform diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index 2e8b5469d..91f6ada6e 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -139,7 +139,7 @@ void agg_renderer::process(markers_symbolizer const& sym, snap_pixels); vertex_converter, dispatch_type, markers_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); + converter(clipping_extent(), rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) { eGeomType type = feature.paths()[0].type(); @@ -179,7 +179,7 @@ void agg_renderer::process(markers_symbolizer const& sym, snap_pixels); vertex_converter, dispatch_type, markers_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); + converter(clipping_extent(), rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) { eGeomType type = feature.paths()[0].type(); @@ -216,7 +216,7 @@ void agg_renderer::process(markers_symbolizer const& sym, true /*snap rasters no matter what*/); vertex_converter, dispatch_type, markers_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_, rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); + converter(clipping_extent(), rasterizer_dispatch, sym,t_,prj_trans,tr,scale_factor_); if (sym.clip() && feature.paths().size() > 0) // optional clip (default: true) { diff --git a/src/agg/process_polygon_pattern_symbolizer.cpp b/src/agg/process_polygon_pattern_symbolizer.cpp index 71e5b0def..95ff2e056 100644 --- a/src/agg/process_polygon_pattern_symbolizer.cpp +++ b/src/agg/process_polygon_pattern_symbolizer.cpp @@ -91,6 +91,7 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, boost::optional pat = (*marker)->get_bitmap_data(); if (!pat) return; + box2d clip_box = clipping_extent(); typedef agg::rgba8 color; typedef agg::order_rgba order; @@ -131,7 +132,7 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, if (feature.num_geometries() > 0) { clipped_geometry_type clipped(feature.get_geometry(0)); - clipped.clip_box(query_extent_.minx(),query_extent_.miny(),query_extent_.maxx(),query_extent_.maxy()); + clipped.clip_box(clip_box.minx(),clip_box.miny(),clip_box.maxx(),clip_box.maxy()); path_type path(t_,clipped,prj_trans); path.vertex(&x0,&y0); } @@ -150,7 +151,7 @@ void agg_renderer::process(polygon_pattern_symbolizer const& sym, typedef boost::mpl::vector conv_types; vertex_converter, rasterizer, polygon_pattern_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_,*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); + converter(clip_box,*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); if (prj_trans.equal() && sym.clip()) converter.set(); //optional clip (default: true) converter.set(); //always transform diff --git a/src/agg/process_polygon_symbolizer.cpp b/src/agg/process_polygon_symbolizer.cpp index 25e2f09a2..e22f042f4 100644 --- a/src/agg/process_polygon_symbolizer.cpp +++ b/src/agg/process_polygon_symbolizer.cpp @@ -62,7 +62,7 @@ void agg_renderer::process(polygon_symbolizer const& sym, typedef boost::mpl::vector conv_types; vertex_converter, rasterizer, polygon_symbolizer, CoordTransform, proj_transform, agg::trans_affine, conv_types> - converter(query_extent_,*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); + converter(clipping_extent(),*ras_ptr,sym,t_,prj_trans,tr,scale_factor_); if (prj_trans.equal() && sym.clip()) converter.set(); //optional clip (default: true) converter.set(); //always transform diff --git a/src/agg/process_shield_symbolizer.cpp b/src/agg/process_shield_symbolizer.cpp index 753dccd14..8e09a4020 100644 --- a/src/agg/process_shield_symbolizer.cpp +++ b/src/agg/process_shield_symbolizer.cpp @@ -45,7 +45,7 @@ void agg_renderer::process(shield_symbolizer const& sym, width_, height_, scale_factor_, t_, font_manager_, *detector_, - query_extent_); + clipping_extent()); text_renderer ren(*current_buffer_, font_manager_, diff --git a/src/agg/process_text_symbolizer.cpp b/src/agg/process_text_symbolizer.cpp index 0ee0e9559..dc47b9882 100644 --- a/src/agg/process_text_symbolizer.cpp +++ b/src/agg/process_text_symbolizer.cpp @@ -41,7 +41,7 @@ void agg_renderer::process(text_symbolizer const& sym, width_,height_, scale_factor_, t_, font_manager_, *detector_, - query_extent_); + clipping_extent()); text_renderer ren(*current_buffer_, font_manager_, diff --git a/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-1.0-agg-reference.png b/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-1.0-agg-reference.png index 2ecb9eeb58de781283d378552ac771725c76cfe3..e1f8f18dcbb5719b7786b5ba6f182884ea8b0348 100644 GIT binary patch literal 7892 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 diff --git a/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-2.0-agg-reference.png b/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-2.0-agg-reference.png index 2ecb9eeb58de781283d378552ac771725c76cfe3..e1f8f18dcbb5719b7786b5ba6f182884ea8b0348 100644 GIT binary patch literal 7892 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