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 2ecb9eeb5..e1f8f18dc 100644 Binary files a/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-1.0-agg-reference.png and b/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-1.0-agg-reference.png differ 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 2ecb9eeb5..e1f8f18dc 100644 Binary files a/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-2.0-agg-reference.png and b/tests/visual_tests/images/style-level-compositing-tiled-0,1-512-512-2.0-agg-reference.png differ