Compare commits
8 commits
master
...
svg-raster
Author | SHA1 | Date | |
---|---|---|---|
|
cdb9bdb392 | ||
|
f6a858b6c9 | ||
|
776436db6e | ||
|
3059cbf231 | ||
|
192495ce21 | ||
|
51a50fdf92 | ||
|
fb289417e5 | ||
|
a4f51e92c9 |
6 changed files with 384 additions and 0 deletions
|
@ -30,6 +30,9 @@
|
|||
#include <mapnik/gradient.hpp>
|
||||
#include <mapnik/util/noncopyable.hpp>
|
||||
|
||||
// boost
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
|
||||
// stl
|
||||
#include <map>
|
||||
|
||||
|
@ -42,6 +45,7 @@ namespace mapnik { namespace svg {
|
|||
~svg_parser();
|
||||
void parse(std::string const& filename);
|
||||
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_;
|
||||
bool is_defs_;
|
||||
std::map<std::string, gradient> gradient_map_;
|
||||
|
|
|
@ -84,6 +84,8 @@ if '-DHAVE_WEBP' in env['CPPDEFINES']:
|
|||
lib_env['LIBS'].append('webp')
|
||||
enabled_imaging_libraries.append('webp_reader.cpp')
|
||||
|
||||
enabled_imaging_libraries.append('svg_reader.cpp')
|
||||
|
||||
lib_env['LIBS'].append('xml2')
|
||||
|
||||
if '-DBOOST_REGEX_HAS_ICU' in env['CPPDEFINES']:
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/factory.hpp>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace mapnik
|
||||
{
|
||||
|
||||
|
@ -60,6 +62,21 @@ inline boost::optional<std::string> type_from_bytes(char const* data, size_t siz
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@
|
|||
#include <boost/spirit/include/phoenix_operator.hpp>
|
||||
#include <boost/fusion/include/std_pair.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
|
||||
|
||||
#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
229
src/svg_reader.cpp
Normal 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
89
test/unit/imaging/svg.cpp
Normal 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
|
||||
|
Loading…
Reference in a new issue