diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a8a48d2c..f6fce3b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ For a complete change history, see the git log. Not yet released +- Added support for overriding fill, stroke, and opacity for svg markers using marker properties + +- Added support for setting opacity dynamically on images in polygon pattern and markers symbolizers + - Added support for filtering on a features geometry type, either `point`, `linestring`, 'polygon`, or `collection` using the expression keyword of `[mapnik::geometry_type]` (#546) diff --git a/bindings/python/mapnik_markers_symbolizer.cpp b/bindings/python/mapnik_markers_symbolizer.cpp index 698569378..9613e7476 100644 --- a/bindings/python/mapnik_markers_symbolizer.cpp +++ b/bindings/python/mapnik_markers_symbolizer.cpp @@ -86,7 +86,6 @@ struct markers_symbolizer_pickle_suite : boost::python::pickle_suite }; - void export_markers_symbolizer() { using namespace boost::python; @@ -115,7 +114,11 @@ void export_markers_symbolizer() .add_property("opacity", &markers_symbolizer::get_opacity, &markers_symbolizer::set_opacity, - "Set/get the text opacity") + "Set/get the overall opacity") + .add_property("fill_opacity", + &markers_symbolizer::get_fill_opacity, + &markers_symbolizer::set_fill_opacity, + "Set/get the fill opacity") .add_property("ignore_placement", &markers_symbolizer::get_ignore_placement, &markers_symbolizer::set_ignore_placement) diff --git a/bindings/python/mapnik_python.cpp b/bindings/python/mapnik_python.cpp index b86a7e33d..883946427 100644 --- a/bindings/python/mapnik_python.cpp +++ b/bindings/python/mapnik_python.cpp @@ -614,6 +614,7 @@ BOOST_PYTHON_MODULE(_mapnik) python_optional(); python_optional(); python_optional(); + python_optional(); python_optional(); python_optional(); register_ptr_to_python(); diff --git a/include/mapnik/marker_helpers.hpp b/include/mapnik/marker_helpers.hpp index efc565fc2..4d7a2be78 100644 --- a/include/mapnik/marker_helpers.hpp +++ b/include/mapnik/marker_helpers.hpp @@ -38,24 +38,54 @@ bool push_explicit_style(Attr const& src, Attr & dst, markers_symbolizer const& { boost::optional const& strk = sym.get_stroke(); boost::optional const& fill = sym.get_fill(); - if (strk || fill) + boost::optional const& opacity = sym.get_opacity(); + boost::optional const& fill_opacity = sym.get_fill_opacity(); + if (strk || fill || opacity || fill_opacity) { for(unsigned i = 0; i < src.size(); ++i) { mapnik::svg::path_attributes attr = src[i]; - if (strk && attr.stroke_flag) + if (attr.stroke_flag) { - attr.stroke_width = strk->get_width(); - color const& s_color = strk->get_color(); - attr.stroke_color = agg::rgba(s_color.red()/255.0,s_color.green()/255.0, - s_color.blue()/255.0,(s_color.alpha()*strk->get_opacity())/255.0); + // TODO - stroke attributes need to be boost::optional + // for this to work properly + if (strk) + { + attr.stroke_width = strk->get_width(); + color const& s_color = strk->get_color(); + attr.stroke_color = agg::rgba(s_color.red()/255.0, + s_color.green()/255.0, + s_color.blue()/255.0, + s_color.alpha()/255.0); + } + if (opacity) + { + attr.stroke_opacity = *opacity; + } + else if (strk) + { + attr.stroke_opacity = strk->get_opacity(); + } } - if (fill && attr.fill_flag) + if (attr.fill_flag) { - color const& f_color = *fill; - attr.fill_color = agg::rgba(f_color.red()/255.0,f_color.green()/255.0, - f_color.blue()/255.0,(f_color.alpha()*sym.get_opacity())/255.0); + if (fill) + { + color const& f_color = *fill; + attr.fill_color = agg::rgba(f_color.red()/255.0, + f_color.green()/255.0, + f_color.blue()/255.0, + f_color.alpha()/255.0); + } + if (opacity) + { + attr.fill_opacity = *opacity; + } + else if (fill_opacity) + { + attr.fill_opacity = *fill_opacity; + } } dst.push_back(attr); } diff --git a/include/mapnik/markers_symbolizer.hpp b/include/mapnik/markers_symbolizer.hpp index e8ba4cd0e..d11cf6f8f 100644 --- a/include/mapnik/markers_symbolizer.hpp +++ b/include/mapnik/markers_symbolizer.hpp @@ -65,8 +65,12 @@ public: double get_spacing() const; void set_max_error(double max_error); double get_max_error() const; + void set_opacity(float opacity); + boost::optional get_opacity() const; void set_fill(color const& fill); boost::optional get_fill() const; + void set_fill_opacity(float opacity); + boost::optional get_fill_opacity() const; void set_stroke(stroke const& stroke); boost::optional get_stroke() const; void set_marker_placement(marker_placement_e marker_p); @@ -79,6 +83,8 @@ private: double spacing_; double max_error_; boost::optional fill_; + boost::optional fill_opacity_; + boost::optional opacity_; boost::optional stroke_; marker_placement_e marker_p_; }; diff --git a/src/agg/process_markers_symbolizer.cpp b/src/agg/process_markers_symbolizer.cpp index d57b22b11..46d76fc70 100644 --- a/src/agg/process_markers_symbolizer.cpp +++ b/src/agg/process_markers_symbolizer.cpp @@ -64,7 +64,6 @@ struct vector_markers_rasterizer_dispatch typedef agg::comp_op_adaptor_rgba_pre blender_type; // comp blender typedef agg::pixfmt_custom_blend_rgba pixfmt_comp_type; typedef agg::renderer_base renderer_base; - typedef agg::renderer_scanline_aa_solid renderer_type; vector_markers_rasterizer_dispatch(BufferType & image_buffer, SvgRenderer & svg_renderer, @@ -105,7 +104,7 @@ struct vector_markers_rasterizer_dispatch if (sym_.get_allow_overlap() || detector_.has_placement(transformed_bbox)) { - svg_renderer_.render(ras_, sl_, renb_, matrix, sym_.get_opacity(), bbox_); + svg_renderer_.render(ras_, sl_, renb_, matrix, 1, bbox_); if (!sym_.get_ignore_placement()) detector_.insert(transformed_bbox); @@ -123,7 +122,7 @@ struct vector_markers_rasterizer_dispatch agg::trans_affine matrix = marker_trans_; matrix.rotate(angle); matrix.translate(x, y); - svg_renderer_.render(ras_, sl_, renb_, matrix, sym_.get_opacity(), bbox_); + svg_renderer_.render(ras_, sl_, renb_, matrix, 1, bbox_); } } } @@ -146,7 +145,6 @@ void render_raster_marker(Rasterizer & ras, RendererBuffer & renb, agg::scanline_u8 & sl, image_data_32 const& src, agg::trans_affine const& marker_tr, double opacity) { - double width = src.width(); double height = src.height(); double p[8]; @@ -180,10 +178,19 @@ void render_raster_marker(Rasterizer & ras, RendererBuffer & renb, typedef agg::span_interpolator_linear interpolator_type; typedef agg::span_image_filter_rgba_2x2 span_gen_type; + typedef agg::order_rgba order_type; + typedef agg::pixel32_type pixel_type; + typedef agg::comp_op_adaptor_rgba_pre blender_type; // comp blender + typedef agg::pixfmt_custom_blend_rgba pixfmt_comp_type; + typedef agg::renderer_base renderer_base; + 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); - agg::render_scanlines_aa(ras, sl, renb, sa, sg); + renderer_type rp(renb,sa, sg, unsigned(opacity*255)); + agg::render_scanlines(ras, sl, rp); } template @@ -195,7 +202,6 @@ struct raster_markers_rasterizer_dispatch typedef agg::comp_op_adaptor_rgba_pre blender_type; // comp blender typedef agg::pixfmt_custom_blend_rgba pixfmt_comp_type; typedef agg::renderer_base renderer_base; - typedef agg::renderer_scanline_aa_solid renderer_type; raster_markers_rasterizer_dispatch(BufferType & image_buffer, Rasterizer & ras, @@ -236,8 +242,9 @@ struct raster_markers_rasterizer_dispatch detector_.has_placement(transformed_bbox)) { + float opacity = sym_.get_opacity() ? *sym_.get_opacity() : 1; render_raster_marker(ras_, renb_, sl_, src_, - matrix, sym_.get_opacity()); + matrix, opacity); if (!sym_.get_ignore_placement()) detector_.insert(transformed_bbox); } @@ -254,8 +261,9 @@ struct raster_markers_rasterizer_dispatch agg::trans_affine matrix = marker_trans_; matrix.rotate(angle); matrix.translate(x,y); + float opacity = sym_.get_opacity() ? *sym_.get_opacity() : 1; render_raster_marker(ras_, renb_, sl_, src_, - matrix, sym_.get_opacity()); + matrix, opacity); } } } @@ -284,7 +292,6 @@ void agg_renderer::process(markers_symbolizer const& sym, typedef agg::comp_op_adaptor_rgba_pre blender_type; // comp blender typedef agg::pixfmt_custom_blend_rgba pixfmt_comp_type; typedef agg::renderer_base renderer_base; - typedef agg::renderer_scanline_aa_solid renderer_type; typedef label_collision_detector4 detector_type; typedef boost::mpl::vector conv_types; @@ -311,6 +318,8 @@ void agg_renderer::process(markers_symbolizer const& sym, if ((*mark)->is_vector()) { + typedef agg::renderer_scanline_aa_solid renderer_type; + using namespace mapnik::svg; boost::optional marker = (*mark)->get_vector_data(); diff --git a/src/cairo_renderer.cpp b/src/cairo_renderer.cpp index 9529c9d69..d9a01d4c7 100644 --- a/src/cairo_renderer.cpp +++ b/src/cairo_renderer.cpp @@ -1452,7 +1452,7 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, if (sym.get_allow_overlap() || detector_.has_placement(extent)) { - render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h) ,**mark, tr, sym.get_opacity()); + render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h) ,**mark, tr, 1); if (!sym.get_ignore_placement()) detector_.insert(extent); @@ -1471,7 +1471,7 @@ void cairo_renderer_base::process(markers_symbolizer const& sym, while (placement.get_point(x, y, angle)) { agg::trans_affine matrix = recenter * tr * agg::trans_affine_rotation(angle) * agg::trans_affine_translation(x, y); - render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h), **mark, matrix, sym.get_opacity(),false); + render_marker(pixel_position(x - 0.5 * w, y - 0.5 * h), **mark, matrix, 1,false); } } context.fill(); diff --git a/src/grid/process_markers_symbolizer.cpp b/src/grid/process_markers_symbolizer.cpp index c3a81468d..edbad9852 100644 --- a/src/grid/process_markers_symbolizer.cpp +++ b/src/grid/process_markers_symbolizer.cpp @@ -140,7 +140,7 @@ void grid_renderer::process(markers_symbolizer const& sym, detector_.has_placement(transformed_bbox)) { placed = true; - svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, sym.get_opacity(), bbox); + svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, 1, bbox); if (!sym.get_ignore_placement()) detector_.insert(transformed_bbox); } @@ -166,7 +166,7 @@ void grid_renderer::process(markers_symbolizer const& sym, agg::trans_affine matrix = marker_trans; matrix.rotate(angle); matrix.translate(x, y); - svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, sym.get_opacity(), bbox); + svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, 1, bbox); } } else @@ -186,7 +186,7 @@ void grid_renderer::process(markers_symbolizer const& sym, agg::trans_affine matrix = marker_trans; matrix.rotate(angle); matrix.translate(x, y); - svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, sym.get_opacity(), bbox); + svg_renderer.render_id(*ras_ptr, sl, renb, feature.id(), matrix, 1, bbox); } } } diff --git a/src/load_map.cpp b/src/load_map.cpp index 7349d0f8e..315e85e9a 100644 --- a/src/load_map.cpp +++ b/src/load_map.cpp @@ -1013,9 +1013,13 @@ void map_parser::parse_markers_symbolizer(rule & rule, xml_node const& node) sym.set_filename(expr); } + // overall opacity - impacts both fill and stroke, like svg optional opacity = node.get_opt_attr("opacity"); if (opacity) sym.set_opacity(*opacity); + optional fill_opacity = node.get_opt_attr("fill-opacity"); + if (fill_opacity) sym.set_fill_opacity(*fill_opacity); + optional image_transform_wkt = node.get_opt_attr("transform"); if (image_transform_wkt) { diff --git a/src/markers_symbolizer.cpp b/src/markers_symbolizer.cpp index c494bf7fc..5ea1c4624 100644 --- a/src/markers_symbolizer.cpp +++ b/src/markers_symbolizer.cpp @@ -74,6 +74,8 @@ markers_symbolizer::markers_symbolizer(markers_symbolizer const& rhs) spacing_(rhs.spacing_), max_error_(rhs.max_error_), fill_(rhs.fill_), + fill_opacity_(rhs.fill_opacity_), + opacity_(rhs.opacity_), stroke_(rhs.stroke_), marker_p_(rhs.marker_p_) {} @@ -117,6 +119,16 @@ double markers_symbolizer::get_max_error() const return max_error_; } +void markers_symbolizer::set_opacity(float opacity) +{ + opacity_ = opacity; +} + +boost::optional markers_symbolizer::get_opacity() const +{ + return opacity_; +} + void markers_symbolizer::set_fill(color const& fill) { fill_ = fill; @@ -127,6 +139,16 @@ boost::optional markers_symbolizer::get_fill() const return fill_; } +void markers_symbolizer::set_fill_opacity(float opacity) +{ + fill_opacity_ = opacity; +} + +boost::optional markers_symbolizer::get_fill_opacity() const +{ + return fill_opacity_; +} + void markers_symbolizer::set_width(expression_ptr const& width) { width_ = width; diff --git a/src/save_map.cpp b/src/save_map.cpp index 232aba2a7..f572c8fc0 100644 --- a/src/save_map.cpp +++ b/src/save_map.cpp @@ -283,6 +283,10 @@ public: { set_attr( sym_node, "fill", sym.get_fill() ); } + if (sym.get_fill_opacity() != dfl.get_fill_opacity() || explicit_defaults_) + { + set_attr( sym_node, "fill-opacity", sym.get_fill_opacity() ); + } if (sym.get_opacity() != dfl.get_opacity() || explicit_defaults_) { set_attr( sym_node, "opacity", sym.get_opacity() ); diff --git a/tests/data/svg/octocat.svg b/tests/data/svg/octocat.svg new file mode 100644 index 000000000..1f883261b --- /dev/null +++ b/tests/data/svg/octocat.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/python_tests/object_test.py b/tests/python_tests/object_test.py index 7806ada66..3213ca86e 100644 --- a/tests/python_tests/object_test.py +++ b/tests/python_tests/object_test.py @@ -126,7 +126,8 @@ def test_pointsymbolizer_init(): def test_markersymbolizer_init(): p = mapnik.MarkersSymbolizer() eq_(p.allow_overlap, False) - eq_(p.opacity,1) + eq_(p.opacity,None) + eq_(p.fill_opacity,None) eq_(p.filename,'shape://ellipse') eq_(p.placement,mapnik.marker_placement.POINT_PLACEMENT) eq_(p.fill,None) @@ -154,9 +155,11 @@ def test_markersymbolizer_init(): p.fill = mapnik.Color('white') p.allow_overlap = True p.opacity = 0.5 + p.fill_opacity = .01 eq_(p.allow_overlap, True) eq_(p.opacity, 0.5) + eq_(p.fill_opacity, 0.5) # PointSymbolizer missing image file