diff --git a/include/mapnik/marker.hpp b/include/mapnik/marker.hpp index aac1a3660..d9c1fe970 100644 --- a/include/mapnik/marker.hpp +++ b/include/mapnik/marker.hpp @@ -102,7 +102,7 @@ public: if (is_bitmap()) return (*bitmap_data_)->width(); else if (is_vector()) - return (*vector_data_)->bounding_box().width(); + return (*vector_data_)->width(); return 0; } inline double height() const @@ -110,7 +110,7 @@ public: if (is_bitmap()) return (*bitmap_data_)->height(); else if (is_vector()) - return (*vector_data_)->bounding_box().height(); + return (*vector_data_)->height(); return 0; } diff --git a/include/mapnik/marker_helpers.hpp b/include/mapnik/marker_helpers.hpp index 707fbf2ab..88c6377e6 100644 --- a/include/mapnik/marker_helpers.hpp +++ b/include/mapnik/marker_helpers.hpp @@ -48,6 +48,7 @@ #include "agg_image_accessors.h" #include "agg_pixfmt_rgba.h" #include "agg_span_image_filter_rgba.h" +#include "agg_span_interpolator_linear.h" // boost #include @@ -69,7 +70,8 @@ struct vector_markers_rasterizer_dispatch agg::trans_affine const& marker_trans, markers_symbolizer const& sym, Detector & detector, - double scale_factor) + double scale_factor, + bool snap_to_pixels) : buf_(render_buffer), pixf_(buf_), renb_(pixf_), @@ -79,7 +81,8 @@ struct vector_markers_rasterizer_dispatch marker_trans_(marker_trans), sym_(sym), detector_(detector), - scale_factor_(scale_factor) + scale_factor_(scale_factor), + snap_to_pixels_(snap_to_pixels) { pixf_.comp_op(static_cast(sym_.comp_op())); } @@ -116,6 +119,12 @@ struct vector_markers_rasterizer_dispatch if (sym_.get_allow_overlap() || detector_.has_placement(transformed_bbox)) { + if (snap_to_pixels_) + { + // https://github.com/mapnik/mapnik/issues/1316 + matrix.tx = std::floor(matrix.tx+.5); + matrix.ty = std::floor(matrix.ty+.5); + } svg_renderer_.render(ras_, sl_, renb_, matrix, sym_.get_opacity(), bbox_); if (!sym_.get_ignore_placement()) { @@ -153,6 +162,7 @@ private: markers_symbolizer const& sym_; Detector & detector_; double scale_factor_; + bool snap_to_pixels_; }; template @@ -171,7 +181,8 @@ struct raster_markers_rasterizer_dispatch agg::trans_affine const& marker_trans, markers_symbolizer const& sym, Detector & detector, - double scale_factor) + double scale_factor, + bool snap_to_pixels) : buf_(render_buffer), pixf_(buf_), renb_(pixf_), @@ -180,7 +191,8 @@ struct raster_markers_rasterizer_dispatch marker_trans_(marker_trans), sym_(sym), detector_(detector), - scale_factor_(scale_factor) + scale_factor_(scale_factor), + snap_to_pixels_(snap_to_pixels) { pixf_.comp_op(static_cast(sym_.comp_op())); } @@ -245,41 +257,66 @@ struct raster_markers_rasterizer_dispatch void render_raster_marker(agg::trans_affine const& marker_tr, double opacity) { + typedef agg::pixfmt_rgba32_pre pixfmt_pre; double width = src_.width(); double height = src_.height(); - double p[8]; - p[0] = 0; p[1] = 0; - p[2] = width; p[3] = 0; - p[4] = width; p[5] = height; - p[6] = 0; p[7] = height; - marker_tr.transform(&p[0], &p[1]); - marker_tr.transform(&p[2], &p[3]); - marker_tr.transform(&p[4], &p[5]); - marker_tr.transform(&p[6], &p[7]); - ras_.move_to_d(p[0],p[1]); - ras_.line_to_d(p[2],p[3]); - ras_.line_to_d(p[4],p[5]); - ras_.line_to_d(p[6],p[7]); - agg::span_allocator sa; - agg::image_filter_bilinear filter_kernel; - agg::image_filter_lut filter(filter_kernel, false); - agg::rendering_buffer marker_buf((unsigned char *)src_.getBytes(), - src_.width(), - src_.height(), - src_.width()*4); - agg::pixfmt_rgba32_pre pixf(marker_buf); - typedef agg::image_accessor_clone img_accessor_type; - typedef agg::span_interpolator_linear interpolator_type; - typedef agg::span_image_filter_rgba_2x2 span_gen_type; - typedef agg::renderer_scanline_aa_alpha, - span_gen_type> renderer_type; - img_accessor_type ia(pixf); - interpolator_type interpolator(agg::trans_affine(p, 0, 0, width, height) ); - span_gen_type sg(ia, interpolator, filter); - renderer_type rp(renb_,sa, sg, unsigned(opacity*255)); - agg::render_scanlines(ras_, sl_, rp); + if (std::fabs(1.0 - scale_factor_) < 0.001 + && (std::fabs(1.0 - marker_tr.sx) < agg::affine_epsilon) + && (std::fabs(0.0 - marker_tr.shy) < agg::affine_epsilon) + && (std::fabs(0.0 - marker_tr.shx) < agg::affine_epsilon) + && (std::fabs(1.0 - marker_tr.sy) < agg::affine_epsilon)) + { + agg::rendering_buffer src_buffer((unsigned char *)src_.getBytes(),src_.width(),src_.height(),src_.width() * 4); + pixfmt_pre pixf_mask(src_buffer); + renb_.blend_from(pixf_mask, + 0, + std::floor(marker_tr.tx + .5), + std::floor(marker_tr.ty + .5), + unsigned(255*sym_.get_opacity())); + } + else + { + typedef agg::image_accessor_clone img_accessor_type; + typedef agg::span_interpolator_linear<> interpolator_type; + //typedef agg::span_image_filter_rgba_2x2 span_gen_type; + typedef agg::span_image_resample_rgba_affine span_gen_type; + typedef agg::renderer_scanline_aa_alpha, + span_gen_type> renderer_type; + + double p[8]; + p[0] = 0; p[1] = 0; + p[2] = width; p[3] = 0; + p[4] = width; p[5] = height; + p[6] = 0; p[7] = height; + marker_tr.transform(&p[0], &p[1]); + marker_tr.transform(&p[2], &p[3]); + marker_tr.transform(&p[4], &p[5]); + marker_tr.transform(&p[6], &p[7]); + agg::span_allocator sa; + agg::image_filter_lut filter; + filter.calculate(agg::image_filter_bilinear(), true); + agg::rendering_buffer marker_buf((unsigned char *)src_.getBytes(), + src_.width(), + src_.height(), + src_.width()*4); + pixfmt_pre pixf(marker_buf); + img_accessor_type ia(pixf); + agg::trans_affine final_tr(p, 0, 0, width, height); + if (snap_to_pixels_) + { + final_tr.tx = std::floor(final_tr.tx+.5); + final_tr.ty = std::floor(final_tr.ty+.5); + } + interpolator_type interpolator(final_tr); + span_gen_type sg(ia, interpolator, filter); + renderer_type rp(renb_,sa, sg, unsigned(opacity*255)); + ras_.move_to_d(p[0],p[1]); + ras_.line_to_d(p[2],p[3]); + ras_.line_to_d(p[4],p[5]); + ras_.line_to_d(p[6],p[7]); + agg::render_scanlines(ras_, sl_, rp); + } } private: @@ -293,6 +330,7 @@ private: markers_symbolizer const& sym_; Detector & detector_; double scale_factor_; + bool snap_to_pixels_; }; @@ -327,6 +365,8 @@ void build_ellipse(T const& sym, mapnik::feature_impl const& feature, svg_storag styled_svg.pop_attr(); double lox,loy,hix,hiy; styled_svg.bounding_rect(&lox, &loy, &hix, &hiy); + styled_svg.set_dimensions(width,height); + marker_ellipse.set_dimensions(width,height); marker_ellipse.set_bounding_box(lox,loy,hix,hiy); } @@ -381,7 +421,11 @@ bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const& } template -void setup_transform_scaling(agg::trans_affine & tr, box2d const& bbox, mapnik::feature_impl const& feature, T const& sym) +void setup_transform_scaling(agg::trans_affine & tr, + double svg_width, + double svg_height, + mapnik::feature_impl const& feature, + T const& sym) { double width = 0; double height = 0; @@ -396,18 +440,18 @@ void setup_transform_scaling(agg::trans_affine & tr, box2d const& bbox, if (width > 0 && height > 0) { - double sx = width/bbox.width(); - double sy = height/bbox.height(); + double sx = width/svg_width; + double sy = height/svg_height; tr *= agg::trans_affine_scaling(sx,sy); } else if (width > 0) { - double sx = width/bbox.width(); + double sx = width/svg_width; tr *= agg::trans_affine_scaling(sx); } else if (height > 0) { - double sy = height/bbox.height(); + double sy = height/svg_height; tr *= agg::trans_affine_scaling(sy); } } diff --git a/include/mapnik/svg/svg_converter.hpp b/include/mapnik/svg/svg_converter.hpp index d67c6c2ca..d0e7dfcb5 100644 --- a/include/mapnik/svg/svg_converter.hpp +++ b/include/mapnik/svg/svg_converter.hpp @@ -308,6 +308,22 @@ public: agg::bounding_rect(trans, *this, 0, attributes_.size(), x1, y1, x2, y2); } + void set_dimensions(double w, double h) + { + svg_width_ = w; + svg_height_ = h; + } + + double width() + { + return svg_width_; + } + + double height() + { + return svg_height_; + } + VertexSource & storage() { return source_; @@ -333,6 +349,8 @@ private: AttributeSource & attributes_; AttributeSource attr_stack_; agg::trans_affine transform_; + double svg_width_; + double svg_height_; }; diff --git a/include/mapnik/svg/svg_parser.hpp b/include/mapnik/svg/svg_parser.hpp index 68f151bf2..333949ec4 100644 --- a/include/mapnik/svg/svg_parser.hpp +++ b/include/mapnik/svg/svg_parser.hpp @@ -51,6 +51,7 @@ namespace mapnik { namespace svg { void start_element(xmlTextReaderPtr reader); void end_element(xmlTextReaderPtr reader); void parse_path(xmlTextReaderPtr reader); + void parse_dimensions(xmlTextReaderPtr reader); void parse_polygon(xmlTextReaderPtr reader); void parse_polyline(xmlTextReaderPtr reader); void parse_line(xmlTextReaderPtr reader); diff --git a/include/mapnik/svg/svg_storage.hpp b/include/mapnik/svg/svg_storage.hpp index a0827972a..89e6ebca4 100644 --- a/include/mapnik/svg/svg_storage.hpp +++ b/include/mapnik/svg/svg_storage.hpp @@ -34,7 +34,9 @@ template class svg_storage : mapnik::noncopyable { public: - svg_storage() {} + svg_storage() : + svg_width_(0), + svg_height_(0) {} VertexSource & source() // FIXME!! make const { @@ -61,11 +63,29 @@ public: return bounding_box_; } + double width() const + { + return svg_width_; + } + + double height() const + { + return svg_height_; + } + + void set_dimensions(double w, double h) + { + svg_width_ = w; + svg_height_ = h; + } + private: VertexSource source_; AttributeSource attributes_; box2d bounding_box_; + double svg_width_; + double svg_height_; }; diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index ed1a0ea92..d001fbbd7 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -74,6 +74,9 @@ void agg_renderer::process(markers_symbolizer const& sym, std::string filename = path_processor_type::evaluate(*sym.get_filename(), feature); + // https://github.com/mapnik/mapnik/issues/1316 + bool snap_pixels = !mapnik::marker_cache::instance().is_uri(filename); + if (!filename.empty()) { boost::optional mark = mapnik::marker_cache::instance().find(filename, true); @@ -120,8 +123,15 @@ void agg_renderer::process(markers_symbolizer const& sym, agg::trans_affine_translation recenter(-center.x, -center.y); agg::trans_affine marker_trans = recenter * tr; buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4); - dispatch_type rasterizer_dispatch(render_buffer,svg_renderer,*ras_ptr, - bbox, marker_trans, sym, *detector_, scale_factor_); + dispatch_type rasterizer_dispatch(render_buffer, + svg_renderer, + *ras_ptr, + bbox, + marker_trans, + sym, + *detector_, + scale_factor_, + 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_); @@ -142,7 +152,7 @@ void agg_renderer::process(markers_symbolizer const& sym, else { box2d const& bbox = (*mark)->bounding_box(); - setup_transform_scaling(tr, bbox, feature, sym); + setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym); evaluate_transform(tr, feature, sym.get_image_transform()); coord2d center = bbox.center(); agg::trans_affine_translation recenter(-center.x, -center.y); @@ -153,8 +163,15 @@ void agg_renderer::process(markers_symbolizer const& sym, bool result = push_explicit_style( (*stock_vector_marker)->attributes(), attributes, sym); svg_renderer_type svg_renderer(svg_path, result ? attributes : (*stock_vector_marker)->attributes()); buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4); - dispatch_type rasterizer_dispatch(render_buffer,svg_renderer,*ras_ptr, - bbox, marker_trans, sym, *detector_, scale_factor_); + dispatch_type rasterizer_dispatch(render_buffer, + svg_renderer, + *ras_ptr, + bbox, + marker_trans, + sym, + *detector_, + scale_factor_, + 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_); @@ -176,7 +193,7 @@ void agg_renderer::process(markers_symbolizer const& sym, else // raster markers { box2d const& bbox = (*mark)->bounding_box(); - setup_transform_scaling(tr, bbox, feature, sym); + setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym); evaluate_transform(tr, feature, sym.get_image_transform()); coord2d center = bbox.center(); agg::trans_affine_translation recenter(-center.x, -center.y); @@ -184,8 +201,14 @@ void agg_renderer::process(markers_symbolizer const& sym, boost::optional marker = (*mark)->get_bitmap_data(); typedef raster_markers_rasterizer_dispatch dispatch_type; buf_type render_buffer(current_buffer_->raw_data(), width_, height_, width_ * 4); - dispatch_type rasterizer_dispatch(render_buffer,*ras_ptr, **marker, - marker_trans, sym, *detector_, scale_factor_); + dispatch_type rasterizer_dispatch(render_buffer, + *ras_ptr, + **marker, + marker_trans, + sym, + *detector_, + scale_factor_, + 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_); diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 4a4f29a39..f3cec1438 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1160,7 +1160,7 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, agg::trans_affine geom_tr; evaluate_transform(geom_tr, feature, sym.get_transform()); box2d const& bbox = (*mark)->bounding_box(); - setup_transform_scaling(tr, bbox, feature, sym); + setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym); evaluate_transform(tr, feature, sym.get_image_transform()); if ((*mark)->is_vector()) diff --git a/src/grid/process_markers_symbolizer.cpp b/src/grid/process_markers_symbolizer.cpp index 1cf09ead5..256a8753f 100644 --- a/src/grid/process_markers_symbolizer.cpp +++ b/src/grid/process_markers_symbolizer.cpp @@ -167,7 +167,7 @@ void grid_renderer::process(markers_symbolizer const& sym, else { box2d const& bbox = (*mark)->bounding_box(); - setup_transform_scaling(tr, bbox, feature, sym); + setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym); evaluate_transform(tr, feature, sym.get_image_transform()); // TODO - clamping to >= 4 pixels coord2d center = bbox.center(); @@ -210,7 +210,7 @@ void grid_renderer::process(markers_symbolizer const& sym, else // raster markers { box2d const& bbox = (*mark)->bounding_box(); - setup_transform_scaling(tr, bbox, feature, sym); + setup_transform_scaling(tr, bbox.width(), bbox.height(), feature, sym); evaluate_transform(tr, feature, sym.get_image_transform()); // - clamp sizes to > 4 pixels of interactivity coord2d center = bbox.center(); diff --git a/src/marker_cache.cpp b/src/marker_cache.cpp index 526fffb6c..d483d7b47 100644 --- a/src/marker_cache.cpp +++ b/src/marker_cache.cpp @@ -151,6 +151,7 @@ boost::optional marker_cache::find(std::string const& uri, double lox,loy,hix,hiy; svg.bounding_rect(&lox, &loy, &hix, &hiy); marker_path->set_bounding_box(lox,loy,hix,hiy); + marker_path->set_dimensions(svg.width(),svg.height()); marker_ptr mark(boost::make_shared(marker_path)); result.reset(mark); if (update_cache) diff --git a/src/svg/svg_parser.cpp b/src/svg/svg_parser.cpp index 5ec93e07e..ed6b23979 100644 --- a/src/svg/svg_parser.cpp +++ b/src/svg/svg_parser.cpp @@ -265,6 +265,10 @@ void svg_parser::start_element(xmlTextReaderPtr reader) { parse_ellipse(reader); } + else if (xmlStrEqual(name, BAD_CAST "svg")) + { + parse_dimensions(reader); + } #ifdef MAPNIK_LOG else if (!xmlStrEqual(name, BAD_CAST "svg")) { @@ -451,6 +455,28 @@ void svg_parser::parse_attr(xmlTextReaderPtr reader) } xmlTextReaderMoveToElement(reader); } + +void svg_parser::parse_dimensions(xmlTextReaderPtr reader) +{ + xmlChar *value; + double width = 0; + double height = 0; + value = xmlTextReaderGetAttribute(reader, BAD_CAST "width"); + if (value) + { + width = parse_double((const char*)value); + xmlFree(value); + } + xmlChar *value2; + value2 = xmlTextReaderGetAttribute(reader, BAD_CAST "width"); + if (value2) + { + height = parse_double((const char*)value2); + xmlFree(value2); + } + path_.set_dimensions(width,height); + +} void svg_parser::parse_path(xmlTextReaderPtr reader) { xmlChar *value; diff --git a/tests/visual_tests/compare.py b/tests/visual_tests/compare.py index de406298b..a3415a216 100644 --- a/tests/visual_tests/compare.py +++ b/tests/visual_tests/compare.py @@ -73,12 +73,12 @@ def compare_grids(actual, expected, threshold=0, alpha=True): errors.append((None, actual, expected)) return -1 equal = (im1 == im2) - diff = 0 # TODO - real diffing if not equal: - errors.append((1, actual, expected)) + errors.append((9999, actual, expected)) + return 9999 passed += 1 - return diff + return 0 def summary(generate=False): global errors