Merge pull request #4113 from mapnik/svg-fixes

Svg fixes
This commit is contained in:
Artem Pavlenko 2020-01-24 10:19:40 +00:00 committed by GitHub
commit 7e604ed8b0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 43 deletions

View file

@ -22,7 +22,6 @@
#include <math.h> #include <math.h>
#include "agg_bezier_arc.h" #include "agg_bezier_arc.h"
namespace agg namespace agg
{ {
@ -144,7 +143,7 @@ void bezier_arc_svg::init(double x0, double y0,
m_radii_ok = true; m_radii_ok = true;
if(rx < 0.0) rx = -rx; if(rx < 0.0) rx = -rx;
if(ry < 0.0) ry = -rx; if(ry < 0.0) ry = -ry;
// Calculate the middle point between // Calculate the middle point between
// the current and the final points // the current and the final points
@ -178,7 +177,6 @@ void bezier_arc_svg::init(double x0, double y0,
pry = ry * ry; pry = ry * ry;
if(radii_check > 10.0) m_radii_ok = false; if(radii_check > 10.0) m_radii_ok = false;
} }
// Calculate (cx1, cy1) // Calculate (cx1, cy1)
//------------------------ //------------------------
double sign = (large_arc_flag == sweep_flag) ? -1.0 : 1.0; double sign = (large_arc_flag == sweep_flag) ? -1.0 : 1.0;
@ -222,12 +220,12 @@ void bezier_arc_svg::init(double x0, double y0,
if(v < -1.0) v = -1.0; if(v < -1.0) v = -1.0;
if(v > 1.0) v = 1.0; if(v > 1.0) v = 1.0;
double sweep_angle = sign * std::acos(v); double sweep_angle = sign * std::acos(v);
if (std::fabs(sweep_angle) < pi * 1e-6) m_radii_ok = false;
if(!sweep_flag && sweep_angle > 0) if(!sweep_flag && sweep_angle > 0)
{ {
sweep_angle -= pi * 2.0; sweep_angle -= pi * 2.0;
} }
else else if (sweep_flag && sweep_angle < 0)
if (sweep_flag && sweep_angle < 0)
{ {
sweep_angle += pi * 2.0; sweep_angle += pi * 2.0;
} }

View file

@ -444,12 +444,13 @@ void path_adapter<VC>::curve3(double x_to, double y_to)
{ {
double x0; double x0;
double y0; double y0;
if(is_vertex(vertices_.last_vertex(&x0, &y0))) unsigned last_cmd = last_vertex(&x0, &y0);
if (is_vertex(last_cmd))
{ {
double x_ctrl; double x_ctrl;
double y_ctrl; double y_ctrl;
unsigned cmd = vertices_.prev_vertex(&x_ctrl, &y_ctrl); unsigned prev_cmd = prev_vertex(&x_ctrl, &y_ctrl);
if(is_curve(cmd)) if (is_curve(last_cmd) && is_curve(prev_cmd))
{ {
x_ctrl = x0 + x0 - x_ctrl; x_ctrl = x0 + x0 - x_ctrl;
y_ctrl = y0 + y0 - y_ctrl; y_ctrl = y0 + y0 - y_ctrl;
@ -491,9 +492,7 @@ void path_adapter<VC>::curve4_rel(double dx_ctrl1, double dy_ctrl1,
rel_to_abs(&dx_ctrl1, &dy_ctrl1); rel_to_abs(&dx_ctrl1, &dy_ctrl1);
rel_to_abs(&dx_ctrl2, &dy_ctrl2); rel_to_abs(&dx_ctrl2, &dy_ctrl2);
rel_to_abs(&dx_to, &dy_to); rel_to_abs(&dx_to, &dy_to);
vertices_.add_vertex(dx_ctrl1, dy_ctrl1, path_cmd_curve4); curve4(dx_ctrl1, dy_ctrl1, dx_ctrl2, dy_ctrl2, dx_to, dy_to);
vertices_.add_vertex(dx_ctrl2, dy_ctrl2, path_cmd_curve4);
vertices_.add_vertex(dx_to, dy_to, path_cmd_curve4);
} }
//------------------------------------------------------------------------ //------------------------------------------------------------------------
@ -503,12 +502,13 @@ void path_adapter<VC>::curve4(double x_ctrl2, double y_ctrl2,
{ {
double x0; double x0;
double y0; double y0;
if(is_vertex(last_vertex(&x0, &y0))) unsigned last_cmd = last_vertex(&x0, &y0);
if (is_vertex(last_cmd))
{ {
double x_ctrl1; double x_ctrl1;
double y_ctrl1; double y_ctrl1;
unsigned cmd = prev_vertex(&x_ctrl1, &y_ctrl1); unsigned prev_cmd = prev_vertex(&x_ctrl1, &y_ctrl1);
if(is_curve(cmd)) if (is_curve(last_cmd) && is_curve(prev_cmd))
{ {
x_ctrl1 = x0 + x0 - x_ctrl1; x_ctrl1 = x0 + x0 - x_ctrl1;
y_ctrl1 = y0 + y0 - y_ctrl1; y_ctrl1 = y0 + y0 - y_ctrl1;

View file

@ -111,7 +111,8 @@ auto const curve3 = [] (auto const& ctx)
auto const curve3_smooth = [] (auto const& ctx) auto const curve3_smooth = [] (auto const& ctx)
{ {
auto const& attr = _attr(ctx); auto const& attr = _attr(ctx);
extract_path(ctx).curve3(std::get<0>(attr),std::get<1>(attr), extract_path(ctx).curve3(std::get<0>(attr),
std::get<1>(attr),
x3::get<relative_tag>(ctx)); x3::get<relative_tag>(ctx));
}; };

View file

@ -602,7 +602,7 @@ void parse_fill(svg_parser& parser, char const* value)
traverse_tree(parser, gradient_node); traverse_tree(parser, gradient_node);
if (parser.gradient_map_.count(id) > 0) if (parser.gradient_map_.count(id) > 0)
{ {
parser.path_.add_stroke_gradient(parser.gradient_map_[id]); parser.path_.add_fill_gradient(parser.gradient_map_[id]);
} }
else else
{ {
@ -1360,7 +1360,6 @@ void parse_radial_gradient(svg_parser & parser, rapidxml::xml_node<char> const*
} }
} }
parser.gradient_map_[id] = gr; parser.gradient_map_[id] = gr;
//MAPNIK_LOG_DEBUG(svg_parser) << "Found Radial Gradient: " << " " << cx << " " << cy << " " << fx << " " << fy << " " << r;
} }
void parse_linear_gradient(svg_parser & parser, rapidxml::xml_node<char> const* node) void parse_linear_gradient(svg_parser & parser, rapidxml::xml_node<char> const* node)

@ -1 +1 @@
Subproject commit 901ee22d4cdbe1a7caff3f92e10f38ec784b9caa Subproject commit 54a2c2595c81e838810e413d42a57f09fcf41331

View file

@ -161,4 +161,33 @@ TEST_CASE("SVG path parser") {
std::make_tuple(1050, 125, 2)}; std::make_tuple(1050, 125, 2)};
test_path_parser(str, expected); test_path_parser(str, expected);
} }
SECTION("Quadratic Bézier")
{
std::string str = "M200,300 Q400,50 600,300 T1000,300";
std::vector<std::tuple<double,double,unsigned>> expected = {
std::make_tuple(200, 300, 1),
std::make_tuple(400, 50, 3),
std::make_tuple(600, 300, 3),
std::make_tuple(800, 550, 3),
std::make_tuple(1000, 300, 3)};
test_path_parser(str, expected);
}
SECTION("Cubic Bézier")
{
std::string str = "M100,200 C100,100 250,100 250,200S400,300 400,200";
std::vector<std::tuple<double,double,unsigned>> expected = {
std::make_tuple(100, 200, 1),
std::make_tuple(100, 100, 4),
std::make_tuple(250, 100, 4),
std::make_tuple(250, 200, 4),
std::make_tuple(250, 300, 4),
std::make_tuple(400, 300, 4),
std::make_tuple(400, 200, 4)};
test_path_parser(str, expected);
}
} }

View file

@ -24,6 +24,7 @@
#include <sstream> #include <sstream>
#include <vector> #include <vector>
#include <string> #include <string>
#include <cmath>
#include <mapnik/version.hpp> #include <mapnik/version.hpp>
#include <mapnik/debug.hpp> #include <mapnik/debug.hpp>
@ -54,9 +55,11 @@
struct main_marker_visitor struct main_marker_visitor
{ {
main_marker_visitor(std::string const& svg_name, main_marker_visitor(std::string const& svg_name,
double scale_factor,
bool verbose, bool verbose,
bool auto_open) bool auto_open)
: svg_name_(svg_name), : svg_name_(svg_name),
scale_factor_(scale_factor),
verbose_(verbose), verbose_(verbose),
auto_open_(auto_open) {} auto_open_(auto_open) {}
@ -71,17 +74,33 @@ struct main_marker_visitor
double opacity = 1; double opacity = 1;
double w, h; double w, h;
std::tie(w, h) = marker.dimensions(); std::tie(w, h) = marker.dimensions();
double svg_width = w * scale_factor_;
double svg_height = h * scale_factor_;
int output_width = static_cast<int>(std::round(svg_width));
int output_height = static_cast<int>(std::round(svg_height));
if (verbose_) if (verbose_)
{ {
std::clog << "found width of '" << w << "' and height of '" << h << "'\n"; std::clog << "Found width of '" << w << "' and height of '" << h << "'\n";
auto b = marker.bounding_box();
std::clog << "SVG BBOX:" << b << std::endl;
std::clog << "Output image dimensions:[" << output_width << "," << output_height << "]" << std::endl;
} }
mapnik::image_rgba8 im(static_cast<int>(w + 0.5), static_cast<int>(h + 0.5)); mapnik::image_rgba8 im(output_width, output_height, true, true);
agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size()); agg::rendering_buffer buf(im.bytes(), im.width(), im.height(), im.row_size());
pixfmt pixf(buf); pixfmt pixf(buf);
renderer_base renb(pixf); renderer_base renb(pixf);
mapnik::box2d<double> const& bbox = {0, 0, w, h}; mapnik::box2d<double> const& bbox = {0, 0, svg_width, svg_height};
agg::trans_affine mtx = {}; // center the svg marker on '0,0'
agg::trans_affine mtx = agg::trans_affine_translation(-0.5 * w, -0.5 * h);
// Scale the image
mtx.scale(scale_factor_);
// render the marker at the center of the marker box
mtx.translate(0.5 * svg_width, 0.5 * svg_height);
mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(marker.get_data()->source()); mapnik::svg::vertex_stl_adapter<mapnik::svg::svg_path_storage> stl_storage(marker.get_data()->source());
mapnik::svg::svg_path_adapter svg_path(stl_storage); mapnik::svg::svg_path_adapter svg_path(stl_storage);
mapnik::svg::renderer_agg< mapnik::svg::renderer_agg<
@ -124,6 +143,7 @@ struct main_marker_visitor
private: private:
std::string svg_name_; std::string svg_name_;
double scale_factor_ = 1.0;
bool verbose_; bool verbose_;
bool auto_open_; bool auto_open_;
}; };
@ -138,7 +158,8 @@ int main (int argc,char** argv)
int status = 0; int status = 0;
std::vector<std::string> svg_files; std::vector<std::string> svg_files;
mapnik::logger::instance().set_severity(mapnik::logger::error); mapnik::logger::instance().set_severity(mapnik::logger::error);
double scale_factor = 1.0;
std::string usage = "Usage: svg2png [options] <svg-file(s)>";
try try
{ {
po::options_description desc("svg2png utility"); po::options_description desc("svg2png utility");
@ -148,6 +169,7 @@ int main (int argc,char** argv)
("verbose,v","verbose output") ("verbose,v","verbose output")
("open,o","automatically open the file after rendering (os x only)") ("open,o","automatically open the file after rendering (os x only)")
("strict,s","enables strict SVG parsing") ("strict,s","enables strict SVG parsing")
("scale-factor", po::value<double>(), "provide scaling factor (default: 1.0)")
("svg",po::value<std::vector<std::string> >(),"svg file to read") ("svg",po::value<std::vector<std::string> >(),"svg file to read")
; ;
@ -166,6 +188,7 @@ int main (int argc,char** argv)
if (vm.count("help")) if (vm.count("help"))
{ {
std::clog << desc << std::endl; std::clog << desc << std::endl;
std::clog << usage << std::endl;
return 1; return 1;
} }
@ -183,24 +206,26 @@ int main (int argc,char** argv)
{ {
strict = true; strict = true;
} }
if (vm.count("scale-factor"))
{
scale_factor = vm["scale-factor"].as<double>();
}
if (vm.count("svg")) if (vm.count("svg"))
{ {
svg_files=vm["svg"].as< std::vector<std::string> >(); svg_files=vm["svg"].as< std::vector<std::string> >();
} }
else else
{ {
std::clog << "please provide an svg file!" << std::endl; std::clog << usage << std::endl;
return -1; return -1;
} }
std::vector<std::string>::const_iterator itr = svg_files.begin(); std::vector<std::string>::const_iterator itr = svg_files.begin();
if (itr == svg_files.end()) if (itr == svg_files.end())
{ {
std::clog << "no svg files to render" << std::endl; std::clog << usage << std::endl;
return 0; return 0;
} }
while (itr != svg_files.end()) while (itr != svg_files.end())
{ {
std::string svg_name (*itr++); std::string svg_name (*itr++);
@ -209,7 +234,7 @@ int main (int argc,char** argv)
std::clog << "found: " << svg_name << "\n"; std::clog << "found: " << svg_name << "\n";
} }
std::shared_ptr<mapnik::marker const> marker = mapnik::marker_cache::instance().find(svg_name, false, strict); std::shared_ptr<mapnik::marker const> marker = mapnik::marker_cache::instance().find(svg_name, false, strict);
main_marker_visitor visitor(svg_name, verbose, auto_open); main_marker_visitor visitor(svg_name, scale_factor, verbose, auto_open);
status = mapnik::util::apply_visitor(visitor, *marker); status = mapnik::util::apply_visitor(visitor, *marker);
} }
} }