Compare commits

...

8 commits

Author SHA1 Message Date
Daniel Patterson
cdb9bdb392 Remove debugging from test case, fix size check. 2015-06-18 09:01:52 -07:00
Daniel Patterson
f6a858b6c9 Add ability to scale SVGs by specifying different image dimensions. 2015-06-18 08:59:39 -07:00
Daniel Patterson
776436db6e Cleanups. 2015-06-17 21:04:36 -07:00
Daniel Patterson
3059cbf231 Remove debugging outputs, fix missing semicolon. 2015-06-17 20:54:44 -07:00
Daniel Patterson
192495ce21 Fix test cases. 2015-06-17 20:50:01 -07:00
Daniel Patterson
51a50fdf92 Realize template functions so that we can actually link. 2015-06-17 17:16:59 -07:00
Daniel Patterson
fb289417e5 WIP - SVG rasterizer 2015-06-17 16:34:48 -07:00
Daniel Patterson
a4f51e92c9 Function to also parse SVG from iostreams 2015-06-17 15:19:50 -07:00
6 changed files with 384 additions and 0 deletions

View file

@ -30,6 +30,9 @@
#include <mapnik/gradient.hpp> #include <mapnik/gradient.hpp>
#include <mapnik/util/noncopyable.hpp> #include <mapnik/util/noncopyable.hpp>
// boost
#include <boost/iostreams/stream.hpp>
// stl // stl
#include <map> #include <map>
@ -42,6 +45,7 @@ namespace mapnik { namespace svg {
~svg_parser(); ~svg_parser();
void parse(std::string const& filename); void parse(std::string const& filename);
void parse_from_string(std::string const& svg); void parse_from_string(std::string const& svg);
template <typename T> void parse_from_stream(boost::iostreams::stream<T> &svg_stream);
svg_converter_type & path_; svg_converter_type & path_;
bool is_defs_; bool is_defs_;
std::map<std::string, gradient> gradient_map_; std::map<std::string, gradient> gradient_map_;

View file

@ -84,6 +84,8 @@ if '-DHAVE_WEBP' in env['CPPDEFINES']:
lib_env['LIBS'].append('webp') lib_env['LIBS'].append('webp')
enabled_imaging_libraries.append('webp_reader.cpp') enabled_imaging_libraries.append('webp_reader.cpp')
enabled_imaging_libraries.append('svg_reader.cpp')
lib_env['LIBS'].append('xml2') lib_env['LIBS'].append('xml2')
if '-DBOOST_REGEX_HAS_ICU' in env['CPPDEFINES']: if '-DBOOST_REGEX_HAS_ICU' in env['CPPDEFINES']:

View file

@ -25,6 +25,8 @@
#include <mapnik/image_util.hpp> #include <mapnik/image_util.hpp>
#include <mapnik/factory.hpp> #include <mapnik/factory.hpp>
#include <iostream>
namespace mapnik namespace mapnik
{ {
@ -60,6 +62,21 @@ inline boost::optional<std::string> type_from_bytes(char const* data, size_t siz
return result_type("webp"); return result_type("webp");
} }
} }
// NOTE: Limit search to first 300 bytes. Totally arbitrary, but don't
// want to search through whole buffer
const size_t max_svg_search = std::min(static_cast<size_t>(300), size);
for(int i=0; i < static_cast<signed>(max_svg_search)-4; i++)
{
if (data[i] == '<' &&
(data[i+1] == 's' || data[i+1] == 'S') &&
(data[i+2] == 'v' || data[i+2] == 'V') &&
(data[i+3] == 'g' || data[i+3] == 'G'))
{
return result_type("svg");
}
}
return result_type(); return result_type();
} }

View file

@ -41,6 +41,9 @@
#include <boost/spirit/include/phoenix_operator.hpp> #include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/fusion/include/std_pair.hpp> #include <boost/fusion/include/std_pair.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
#include <string> #include <string>
@ -1089,5 +1092,45 @@ void svg_parser::parse_from_string(std::string const& svg)
} }
} }
int _xmlInputReadCallback(void * stream, char * buffer, int len) {
std::istream *s = reinterpret_cast<std::istream *>(stream);
s->read(buffer,len);
return s->gcount();
}
template <typename T>
int _xmlInputCloseCallback(void * stream)
{
boost::iostreams::stream<T> *s = reinterpret_cast<boost::iostreams::stream<T> *>(stream);
try {
s->close();
return 0;
}
catch (...) {
return -1;
}
}
template <typename T>
void svg_parser::parse_from_stream(boost::iostreams::stream<T> &svg_stream)
{
xmlTextReaderPtr reader = xmlReaderForIO(_xmlInputReadCallback,
_xmlInputCloseCallback<T>,
reinterpret_cast<void *>(&svg_stream),
nullptr,
nullptr,
(XML_PARSE_NOBLANKS | XML_PARSE_NOCDATA | XML_PARSE_NOERROR | XML_PARSE_NOWARNING));
if (reader == nullptr)
{
MAPNIK_LOG_ERROR(svg_parser) << "Unable to parse SVG XML";
}
else if (!parse_reader(*this,reader))
{
MAPNIK_LOG_ERROR(svg_parser) << "Unable to parse SVG";
}
}
template void svg_parser::parse_from_stream(boost::iostreams::stream<boost::iostreams::file_source> & svg_stream);
template void svg_parser::parse_from_stream(boost::iostreams::stream<boost::iostreams::array_source> & svg_stream);
}} }}

229
src/svg_reader.cpp Normal file
View file

@ -0,0 +1,229 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2015 Artem Pavlenko
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*****************************************************************************/
// mapnik
#include <mapnik/debug.hpp>
#include <mapnik/image_reader.hpp>
#include <mapnik/marker.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/svg/svg_parser.hpp>
#include <mapnik/svg/svg_storage.hpp>
#include <mapnik/svg/svg_converter.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include <mapnik/svg/svg_path_adapter.hpp>
#include <mapnik/svg/svg_renderer_agg.hpp>
#include <mapnik/svg/svg_path_attributes.hpp>
#include "agg_rasterizer_scanline_aa.h"
#include "agg_basics.h"
#include "agg_rendering_buffer.h"
#include "agg_renderer_base.h"
#include "agg_pixfmt_rgba.h"
#include "agg_scanline_u.h"
#include <libxml/parser.h> // for xmlInitParser(), xmlCleanupParser()
extern "C"
{
#include <png.h>
}
// boost
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-local-typedef"
#include <boost/iostreams/device/file.hpp>
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#pragma GCC diagnostic pop
// stl
#include <cstring>
#include <memory>
#include <algorithm>
namespace mapnik
{
template <typename T>
class svg_reader : public image_reader
{
using source_type = T;
using input_stream = boost::iostreams::stream<source_type>;
private:
source_type source_;
input_stream stream_;
unsigned width_;
unsigned height_;
std::shared_ptr<marker_svg> marker;
public:
explicit svg_reader(std::string const& file_name);
svg_reader(char const* data, std::size_t size);
~svg_reader();
unsigned width() const final;
unsigned height() const final;
inline bool has_alpha() const final { return true; }
boost::optional<box2d<double> > bounding_box() const final;
void read(unsigned x,unsigned y,image_rgba8& image) final;
image_any read(unsigned x, unsigned y, unsigned width, unsigned height) final;
private:
void init();
};
namespace
{
image_reader* create_svg_reader(std::string const& file)
{
return new svg_reader<boost::iostreams::file_source>(file);
}
image_reader* create_svg_reader2(char const * data, std::size_t size)
{
return new svg_reader<boost::iostreams::array_source>(data, size);
}
const bool registered = register_image_reader("svg",create_svg_reader);
const bool registered2 = register_image_reader("svg", create_svg_reader2);
}
template <typename T>
svg_reader<T>::svg_reader(std::string const& file_name)
: source_(file_name,std::ios_base::in | std::ios_base::binary),
stream_(source_),
width_(0),
height_(0)
{
if (!source_.is_open()) throw image_reader_exception("SVG reader: cannot open file '"+ file_name + "'");
if (!stream_) throw image_reader_exception("SVG reader: cannot open file '"+ file_name + "'");
init();
}
template <typename T>
svg_reader<T>::svg_reader(char const* data, std::size_t size)
: source_(data,size),
stream_(source_),
width_(0),
height_(0)
{
if (!stream_) throw image_reader_exception("SVG reader: cannot open image stream");
init();
}
template <typename T>
svg_reader<T>::~svg_reader() {}
template <typename T>
void svg_reader<T>::init()
{
using namespace mapnik::svg;
svg_path_ptr marker_path(std::make_shared<svg_storage_type>());
vertex_stl_adapter<svg_path_storage> stl_storage(marker_path->source());
svg_path_adapter svg_path(stl_storage);
svg_converter_type svg(svg_path, marker_path->attributes());
svg_parser p(svg);
p.parse_from_stream(stream_);
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 = std::make_shared<mapnik::marker_svg>(marker_path);
width_=svg.width();
height_=svg.height();
}
template <typename T>
unsigned svg_reader<T>::width() const
{
return width_;
}
template <typename T>
unsigned svg_reader<T>::height() const
{
return height_;
}
template <typename T>
boost::optional<box2d<double> > svg_reader<T>::bounding_box() const
{
// TODO: does this need to be implemented?
return boost::optional<box2d<double> >();
}
template <typename T>
void svg_reader<T>::read(unsigned x0, unsigned y0, image_rgba8& image)
{
using pixfmt = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt>;
using renderer_solid = agg::renderer_scanline_aa_solid<renderer_base>;
agg::rasterizer_scanline_aa<> ras_ptr;
agg::scanline_u8 sl;
double opacity = 1;
unsigned w = std::min(unsigned(image.width()),width_ - x0);
unsigned h = std::min(unsigned(image.height()),height_ - y0);
// 10 pixel buffer to avoid edge clipping of 100% svg's
agg::rendering_buffer buf(image.bytes(), image.width(), image.height(), image.row_size());
pixfmt pixf(buf);
renderer_base renb(pixf);
mapnik::box2d<double> const& bbox = marker->get_data()->bounding_box();
mapnik::coord<double,2> c = bbox.center();
// center the svg marker on '0,0'
agg::trans_affine mtx = agg::trans_affine_translation(-c.x,-c.y);
// Scale if necessary
mtx.scale((double)w / width_, (double)h / height_);
// render the marker at the center of the marker box
mtx.translate(0.5 * image.width(), 0.5 * image.height());
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_renderer_agg<mapnik::svg::svg_path_adapter,
agg::pod_bvector<mapnik::svg::path_attributes>,
renderer_solid,
agg::pixfmt_rgba32_pre > svg_renderer_this(svg_path,
marker->get_data()->attributes());
svg_renderer_this.render(ras_ptr, sl, renb, mtx, opacity, bbox);
demultiply_alpha(image);
}
template <typename T>
image_any svg_reader<T>::read(unsigned x, unsigned y, unsigned width, unsigned height)
{
image_rgba8 data(width,height);
read(x, y, data);
return image_any(std::move(data));
}
}

89
test/unit/imaging/svg.cpp Normal file
View file

@ -0,0 +1,89 @@
#include "catch.hpp"
// mapnik
#include <mapnik/image_any.hpp>
#include <mapnik/color.hpp>
#include <mapnik/image_view_any.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/image_reader.hpp>
#include <mapnik/image_any.hpp>
#include <iostream>
TEST_CASE("image class svg features") {
SECTION("svg_blank")
{
std::string imagedata = "";
CHECK_THROWS(std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(imagedata.c_str(), imagedata.length())));
}
SECTION("svg_invalid")
{
std::string imagedata = "<svg/svg>";
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(imagedata.c_str(), imagedata.length()));
CHECK(reader.get());
unsigned width = reader->width();
unsigned height = reader->height();
CHECK(width == 0);
CHECK(height == 0);
}
SECTION("svg_empty")
{
std::string imagedata = "<svg></svg>";
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(imagedata.c_str(), imagedata.length()));
CHECK(reader.get());
unsigned width = reader->width();
unsigned height = reader->height();
CHECK(width == 0);
CHECK(height == 0);
}
SECTION("svg_simple")
{
std::string imagedata = "<svg width='100' height='100'><g id='a'><ellipse fill='#FFFFFF' stroke='#000000' stroke-width='4' cx='50' cy='50' rx='25' ry='25'/></g></svg>";
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(imagedata.c_str(), imagedata.length()));
CHECK(reader.get());
unsigned width = reader->width();
unsigned height = reader->height();
CHECK(width == 100);
CHECK(height == 100);
mapnik::image_any im = reader->read(0,0,width,height);
mapnik::image_rgba8 raw = im.get<mapnik::image_rgba8>();
std::string pngdata = mapnik::save_to_string<mapnik::image_rgba8>(raw,"png");
CHECK(pngdata.length() == 1270);
} // END SECTION
SECTION("svg_scaled")
{
std::string imagedata = "<svg width='100' height='100'><g id='a'><ellipse fill='#FFFFFF' stroke='#000000' stroke-width='4' cx='50' cy='50' rx='25' ry='25'/></g></svg>";
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(imagedata.c_str(), imagedata.length()));
CHECK(reader.get());
unsigned width = reader->width();
unsigned height = reader->height();
CHECK(width == 100);
CHECK(height == 100);
mapnik::image_any im = reader->read(0,0,width/2,height/2);
mapnik::image_rgba8 raw = im.get<mapnik::image_rgba8>();
CHECK(im.width() == 50);
CHECK(im.height() == 50);
std::string pngdata = mapnik::save_to_string<mapnik::image_rgba8>(raw,"png");
CHECK(pngdata.length() == 616);
} // END SECTION
} // END TEST CASE