mapnik/include/mapnik/svg/svg_renderer_agg.hpp

495 lines
19 KiB
C++

/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2017 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
*
*****************************************************************************/
#ifndef MAPNIK_SVG_RENDERER_AGG_HPP
#define MAPNIK_SVG_RENDERER_AGG_HPP
// mapnik
#include <mapnik/svg/svg_path_attributes.hpp>
#include <mapnik/gradient.hpp>
#include <mapnik/geometry/box2d.hpp>
#include <mapnik/value/types.hpp>
#include <mapnik/util/noncopyable.hpp>
#if defined(GRID_RENDERER)
#pragma GCC diagnostic push
#include <mapnik/warning_ignore.hpp>
#include <mapnik/grid/grid_pixel.hpp>
#pragma GCC diagnostic pop
#endif
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_path_storage.h"
#include "agg_conv_transform.h"
#include "agg_conv_stroke.h"
#include "agg_conv_contour.h"
#include "agg_conv_curve.h"
#include "agg_conv_dash.h"
#include "agg_color_rgba.h"
#include "agg_bounding_rect.h"
#include "agg_rendering_buffer.h"
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_scanline_p.h"
#include "agg_scanline_bin.h"
#include "agg_renderer_scanline.h"
#include "agg_span_allocator.h"
#include "agg_span_gradient.h"
#include "agg_gradient_lut.h"
#include "agg_gamma_lut.h"
#include "agg_span_interpolator_linear.h"
#pragma GCC diagnostic pop
namespace mapnik {
namespace svg {
// Arbitrary linear gradient specified by two control points. Gradient
// value is taken as the normalised distance along the line segment
// represented by the two points.
class linear_gradient_from_segment
{
public:
linear_gradient_from_segment(double x1, double y1, double x2, double y2) :
x1_(x1*agg::gradient_subpixel_scale),
y1_(y1*agg::gradient_subpixel_scale),
x2_(x2*agg::gradient_subpixel_scale),
y2_(y2*agg::gradient_subpixel_scale)
{
double dx = x2_-x1_;
double dy = y2_-y1_;
length_sqr_ = dx*dx+dy*dy;
}
int calculate(int x, int y, int d) const
{
if (length_sqr_ <= 0)
return 0;
double u = ((x-x1_)*(x2_-x1_) + (y-y1_)*(y2_-y1_))/length_sqr_;
if (u < 0)
u = 0;
else if (u > 1)
u = 1;
return static_cast<int>(u*d);
}
private:
double x1_;
double y1_;
double x2_;
double y2_;
double length_sqr_;
};
template <typename VertexSource, typename AttributeSource, typename ScanlineRenderer, typename PixelFormat>
class renderer_agg : util::noncopyable
{
public:
using curved_type = agg::conv_curve<VertexSource>;
// stroke
using curved_stroked_type = agg::conv_stroke<curved_type>;
using curved_stroked_trans_type = agg::conv_transform<curved_stroked_type>;
// stroke dash-array
using curved_dashed_type = agg::conv_dash<curved_type>;
using curved_dashed_stroked_type = agg::conv_stroke<curved_dashed_type>;
using curved_dashed_stroked_trans_type = agg::conv_transform<curved_dashed_stroked_type>;
// fill
using curved_trans_type = agg::conv_transform<curved_type>;
using curved_trans_contour_type = agg::conv_contour<curved_trans_type>;
// renderer
using renderer_base = agg::renderer_base<PixelFormat>;
using vertex_source_type = VertexSource;
using attribute_source_type = AttributeSource;
renderer_agg(VertexSource & source, AttributeSource const& attributes)
: source_(source),
curved_(source_),
curved_dashed_(curved_),
curved_stroked_(curved_),
curved_dashed_stroked_(curved_dashed_),
attributes_(attributes) {}
template <typename Rasterizer, typename Scanline, typename Renderer>
void render_gradient(Rasterizer& ras,
Scanline& sl,
Renderer& ren,
gradient const& grad,
agg::trans_affine const& mtx,
double opacity,
box2d<double> const& symbol_bbox,
curved_trans_type & curved_trans,
unsigned path_id)
{
using gamma_lut_type = agg::gamma_lut<agg::int8u, agg::int8u>;
using color_func_type = agg::gradient_lut<agg::color_interpolator<agg::rgba8>, 1024>;
using interpolator_type = agg::span_interpolator_linear<>;
using span_allocator_type = agg::span_allocator<agg::rgba8>;
span_allocator_type m_alloc;
color_func_type m_gradient_lut;
gamma_lut_type m_gamma_lut;
double x1,x2,y1,y2,radius;
grad.get_control_points(x1,y1,x2,y2,radius);
m_gradient_lut.remove_all();
for ( mapnik::stop_pair const& st : grad.get_stop_array() )
{
mapnik::color const& stop_color = st.second;
unsigned r = stop_color.red();
unsigned g = stop_color.green();
unsigned b = stop_color.blue();
unsigned a = stop_color.alpha();
m_gradient_lut.add_color(st.first, agg::rgba8_pre(r, g, b, int(a * opacity)));
}
if (m_gradient_lut.build_lut())
{
agg::trans_affine transform = mtx;
transform.invert();
agg::trans_affine tr;
tr = grad.get_transform();
tr.invert();
transform *= tr;
if (grad.get_units() != USER_SPACE_ON_USE)
{
double bx1=symbol_bbox.minx();
double by1=symbol_bbox.miny();
double bx2=symbol_bbox.maxx();
double by2=symbol_bbox.maxy();
if (grad.get_units() == OBJECT_BOUNDING_BOX)
{
bounding_rect_single(curved_trans, path_id, &bx1, &by1, &bx2, &by2);
}
transform.translate(-bx1,-by1);
transform.scale(1.0/(bx2-bx1),1.0/(by2-by1));
}
if (grad.get_gradient_type() == RADIAL)
{
using gradient_adaptor_type = agg::gradient_radial_focus;
using span_gradient_type = agg::span_gradient<agg::rgba8,
interpolator_type,
gradient_adaptor_type,
color_func_type>;
// the agg radial gradient assumes it is centred on 0
transform.translate(-x2,-y2);
// scale everything up since agg turns things into integers a bit too soon
int scaleup=255;
radius *= scaleup;
x1 *= scaleup;
y1 *= scaleup;
x2 *= scaleup;
y2 *= scaleup;
transform.scale(scaleup,scaleup);
interpolator_type span_interpolator(transform);
gradient_adaptor_type gradient_adaptor(radius,(x1-x2),(y1-y2));
span_gradient_type span_gradient(span_interpolator,
gradient_adaptor,
m_gradient_lut,
0, radius);
render_scanlines_aa(ras, sl, ren, m_alloc, span_gradient);
}
else
{
using gradient_adaptor_type = linear_gradient_from_segment;
using span_gradient_type = agg::span_gradient<agg::rgba8,
interpolator_type,
gradient_adaptor_type,
color_func_type>;
// scale everything up since agg turns things into integers a bit too soon
int scaleup=255;
x1 *= scaleup;
y1 *= scaleup;
x2 *= scaleup;
y2 *= scaleup;
transform.scale(scaleup,scaleup);
interpolator_type span_interpolator(transform);
gradient_adaptor_type gradient_adaptor(x1,y1,x2,y2);
span_gradient_type span_gradient(span_interpolator,
gradient_adaptor,
m_gradient_lut,
0, scaleup);
render_scanlines_aa(ras, sl, ren, m_alloc, span_gradient);
}
}
}
template <typename Rasterizer, typename Scanline, typename Renderer>
void render(Rasterizer& ras,
Scanline& sl,
Renderer& ren,
agg::trans_affine const& mtx,
double opacity,
box2d<double> const& symbol_bbox)
{
using namespace agg;
trans_affine transform;
curved_stroked_trans_type curved_stroked_trans(curved_stroked_,transform);
curved_dashed_stroked_trans_type curved_dashed_stroked_trans(curved_dashed_stroked_, transform);
curved_trans_type curved_trans(curved_,transform);
curved_trans_contour_type curved_trans_contour(curved_trans);
curved_trans_contour.auto_detect_orientation(true);
for(unsigned i = 0; i < attributes_.size(); ++i)
{
mapnik::svg::path_attributes const& attr = attributes_[i];
if (!attr.visibility_flag)
continue;
transform = attr.transform;
transform *= mtx;
double scl = transform.scale();
//curved_.approximation_method(curve_inc);
curved_.approximation_scale(scl);
curved_.angle_tolerance(0.0);
typename PixelFormat::color_type color;
if (attr.fill_flag || attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
{
ras.reset();
// https://github.com/mapnik/mapnik/issues/1129
if(std::fabs(curved_trans_contour.width()) <= 1)
{
ras.add_path(curved_trans, attr.index);
}
else
{
curved_trans_contour.miter_limit(attr.miter_limit);
ras.add_path(curved_trans_contour, attr.index);
}
if(attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
{
render_gradient(ras, sl, ren, attr.fill_gradient, transform,
attr.fill_opacity * attr.opacity * opacity, symbol_bbox, curved_trans, attr.index);
}
else
{
ras.filling_rule(attr.even_odd_flag ? fill_even_odd : fill_non_zero);
color = attr.fill_color;
color.opacity(color.opacity() * attr.fill_opacity * attr.opacity * opacity);
ScanlineRenderer ren_s(ren);
color.premultiply();
ren_s.color(color);
render_scanlines(ras, sl, ren_s);
}
}
if (attr.stroke_flag || attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
{
if (attr.dash.size() > 0)
{
curved_dashed_stroked_.width(attr.stroke_width);
curved_dashed_stroked_.line_join(attr.line_join);
curved_dashed_stroked_.line_cap(attr.line_cap);
curved_dashed_stroked_.miter_limit(attr.miter_limit);
curved_dashed_stroked_.inner_join(inner_round);
curved_dashed_stroked_.approximation_scale(scl);
// If the *visual* line width is considerable we
// turn on processing of curve cups.
//---------------------
if (attr.stroke_width * scl > 1.0)
{
curved_.angle_tolerance(0.2);
}
ras.reset();
curved_dashed_.remove_all_dashes();
for (auto d : attr.dash)
{
curved_dashed_.add_dash(std::get<0>(d),std::get<1>(d));
}
curved_dashed_.dash_start(attr.dash_offset);
ras.add_path(curved_dashed_stroked_trans, attr.index);
if (attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
{
render_gradient(ras, sl, ren, attr.stroke_gradient, transform,
attr.stroke_opacity * attr.opacity * opacity, symbol_bbox, curved_trans, attr.index);
}
else
{
ras.filling_rule(fill_non_zero);
color = attr.stroke_color;
color.opacity(color.opacity() * attr.stroke_opacity * attr.opacity * opacity);
ScanlineRenderer ren_s(ren);
color.premultiply();
ren_s.color(color);
render_scanlines(ras, sl, ren_s);
}
}
else
{
curved_stroked_.width(attr.stroke_width);
curved_stroked_.line_join(attr.line_join);
curved_stroked_.line_cap(attr.line_cap);
curved_stroked_.miter_limit(attr.miter_limit);
curved_stroked_.inner_join(inner_round);
curved_stroked_.approximation_scale(scl);
// If the *visual* line width is considerable we
// turn on processing of curve cups.
//---------------------
if (attr.stroke_width * scl > 1.0)
{
curved_.angle_tolerance(0.2);
}
ras.reset();
ras.add_path(curved_stroked_trans, attr.index);
if (attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
{
render_gradient(ras, sl, ren, attr.stroke_gradient, transform,
attr.stroke_opacity * attr.opacity * opacity, symbol_bbox, curved_trans, attr.index);
}
else
{
ras.filling_rule(fill_non_zero);
color = attr.stroke_color;
color.opacity(color.opacity() * attr.stroke_opacity * attr.opacity * opacity);
ScanlineRenderer ren_s(ren);
color.premultiply();
ren_s.color(color);
render_scanlines(ras, sl, ren_s);
}
}
}
}
}
#if defined(GRID_RENDERER)
template <typename Rasterizer, typename Scanline, typename Renderer>
void render_id(Rasterizer& ras,
Scanline& sl,
Renderer& ren,
mapnik::value_integer feature_id,
agg::trans_affine const& mtx,
double /*opacity*/,
box2d<double> const& /*symbol_bbox*/)
{
using namespace agg;
trans_affine transform;
curved_stroked_trans_type curved_stroked_trans(curved_stroked_,transform);
curved_trans_type curved_trans(curved_,transform);
curved_trans_contour_type curved_trans_contour(curved_trans);
curved_trans_contour.auto_detect_orientation(true);
for(unsigned i = 0; i < attributes_.size(); ++i)
{
mapnik::svg::path_attributes const& attr = attributes_[i];
if (!attr.visibility_flag)
continue;
transform = attr.transform;
transform *= mtx;
double scl = transform.scale();
//curved_.approximation_method(curve_inc);
curved_.approximation_scale(scl);
curved_.angle_tolerance(0.0);
typename PixelFormat::color_type color(feature_id);
if (attr.fill_flag || attr.fill_gradient.get_gradient_type() != NO_GRADIENT)
{
ras.reset();
if(std::fabs(curved_trans_contour.width()) <= 1)
{
ras.add_path(curved_trans, attr.index);
}
else
{
curved_trans_contour.miter_limit(attr.miter_limit);
ras.add_path(curved_trans_contour, attr.index);
}
ras.filling_rule(attr.even_odd_flag ? fill_even_odd : fill_non_zero);
ScanlineRenderer ren_s(ren);
ren_s.color(color);
render_scanlines(ras, sl, ren_s);
}
if (attr.stroke_flag || attr.stroke_gradient.get_gradient_type() != NO_GRADIENT)
{
curved_stroked_.width(attr.stroke_width);
//m_curved_stroked.line_join((attr.line_join == miter_join) ? miter_join_round : attr.line_join);
curved_stroked_.line_join(attr.line_join);
curved_stroked_.line_cap(attr.line_cap);
curved_stroked_.miter_limit(attr.miter_limit);
curved_stroked_.inner_join(inner_round);
curved_stroked_.approximation_scale(scl);
// If the *visual* line width is considerable we
// turn on processing of curve cusps.
//---------------------
if (attr.stroke_width * scl > 1.0)
{
curved_.angle_tolerance(0.2);
}
ras.reset();
ras.add_path(curved_stroked_trans, attr.index);
ras.filling_rule(fill_non_zero);
ScanlineRenderer ren_s(ren);
ren_s.color(color);
render_scanlines(ras, sl, ren_s);
}
}
}
#endif
inline VertexSource & source() const { return source_;}
inline AttributeSource const& attributes() const { return attributes_;}
private:
VertexSource & source_;
curved_type curved_;
curved_dashed_type curved_dashed_;
curved_stroked_type curved_stroked_;
curved_dashed_stroked_type curved_dashed_stroked_;
AttributeSource const& attributes_;
};
}}
#endif //MAPNIK_SVG_RENDERER_AGG_HPP