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/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_;
|
||||||
|
|
|
@ -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']:
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
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