BuildingSymbolizer: rewrite rendering algorithm

- cache transformed vertices for multiple passes

- perform each rendering stage on the whole (Multi)Polygon:
    1. stroke ground outline
    2. fill wall strips
    3. stroke and fill roof

- don't render back walls if roof and wall fills are opaque
This commit is contained in:
Mickey Rose 2018-08-06 20:42:57 +02:00
parent c617b8763a
commit 874b8f531a
9 changed files with 489 additions and 165 deletions

View file

@ -306,6 +306,7 @@ public:
void line_to(double x, double y);
void rectangle(double x, double y, double w, double h);
void stroke();
void stroke_preserve();
void fill();
void paint();
void paint(double opacity);

View file

@ -60,6 +60,10 @@ public:
: type_(type)
{}
// underlying container access
container_type const* operator->() const { return &cont_; }
container_type* operator->() { return &cont_; }
types type() const
{
return static_cast<types>(type_ & types::Polygon);

View file

@ -0,0 +1,121 @@
/*****************************************************************************
*
* This file is part of Mapnik (c++ mapping toolkit)
*
* Copyright (C) 2018 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_RENDERER_COMMON_AGG_BUILDING_SYMBOLIZER_CONTEXT_HPP
#define MAPNIK_RENDERER_COMMON_AGG_BUILDING_SYMBOLIZER_CONTEXT_HPP
// mapnik
#include <mapnik/color.hpp>
#include <mapnik/util/noncopyable.hpp>
// agg
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_vcgen_stroke.h"
#pragma GCC diagnostic pop
namespace mapnik { namespace detail {
template <typename Derived, typename Rasterizer>
struct agg_building_symbolizer_context : util::noncopyable
{
agg::vcgen_stroke stroke_gen;
Rasterizer & ras;
explicit agg_building_symbolizer_context(Rasterizer & r)
: ras(r) {}
Derived & derived()
{
return static_cast<Derived&>(*this);
}
void set_color(color const& c)
{
// to be overridden in Derived
}
void fill() { derived().render_scanlines(ras); }
void fill_from(double x, double y) { ras.move_to_d(x, y); }
void fill_to(double x, double y) { ras.line_to_d(x, y); }
void fill_close() { ras.close_polygon(); }
void stroke()
{
stroke_gen.rewind(0);
ras.reset();
ras.add_path(stroke_gen);
derived().render_scanlines(ras);
}
void stroke_from(double x, double y)
{
stroke_gen.remove_all();
stroke_gen.add_vertex(x, y, agg::path_cmd_move_to);
}
void stroke_to(double x, double y)
{
stroke_gen.add_vertex(x, y, agg::path_cmd_line_to);
}
void stroke_close()
{
stroke_gen.add_vertex(0, 0, agg::path_cmd_end_poly |
agg::path_flags_close);
}
template <typename VertexSource>
void stroke_preserve(VertexSource & vs, unsigned path_id = 0)
{
vs.rewind(path_id);
for (;;)
{
double x, y;
unsigned cmd = vs.vertex(&x, &y);
if (agg::is_move_to(cmd))
{
stroke_from(x, y);
}
else if (agg::is_vertex(cmd))
{
stroke_to(x, y);
}
else if (agg::is_close(cmd))
{
stroke_close();
stroke();
}
else if (agg::is_stop(cmd))
{
break;
}
}
// simulate the "preserve" part
ras.reset();
ras.add_path(vs, path_id);
}
};
}} // namespace mapnik::detail
#endif // MAPNIK_RENDERER_COMMON_AGG_BUILDING_SYMBOLIZER_CONTEXT_HPP

View file

@ -30,26 +30,22 @@
#include <mapnik/path.hpp>
#include <mapnik/transform_path_adapter.hpp>
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_conv_transform.h"
#pragma GCC diagnostic pop
namespace mapnik {
struct render_building_symbolizer
{
using vertex_adapter_type = geometry::polygon_vertex_adapter<double>;
using transform_path_type = transform_path_adapter<view_transform, vertex_adapter_type>;
using roof_type = agg::conv_transform<transform_path_type>;
private:
using size_t = std::size_t;
using uint8_t = std::uint8_t;
static constexpr uint8_t SEG_MOVETO_LHR = SEG_MOVETO | 0x10;
static constexpr uint8_t SEG_MOVETO_RHR = SEG_MOVETO | 0x20;
renderer_common const& rencom_;
double const height_;
bool render_back_side_ = false;
path_type roof_vertices_{path_type::types::Polygon};
public:
@ -71,6 +67,21 @@ public:
// colors are not always needed so they're not extracted here
}
bool has_transparent_fill() const
{
return (fill_color.alpha_ & wall_fill_color.alpha_) != 255;
}
bool render_back_side() const
{
return render_back_side_;
}
void render_back_side(bool value)
{
render_back_side_ = value;
}
void setup_colors(building_symbolizer const& sym,
feature_impl const& feature)
{
@ -124,81 +135,258 @@ public:
safe_mul(base_stroke_color.alpha_, stroke_opacity);
}
template <typename F1, typename F2, typename F3>
template <typename Context>
void apply(feature_impl const& feature,
proj_transform const& prj_trans,
F1 face_func, F2 frame_func, F3 roof_func)
Context & painter)
{
auto const& geom = feature.get_geometry();
roof_vertices_->clear();
if (geom.is<geometry::polygon<double>>())
{
auto const& poly = geom.get<geometry::polygon<double>>();
vertex_adapter_type va(poly);
transform_path_type transformed(rencom_.t_, va, prj_trans);
make_building(transformed, face_func, frame_func, roof_func);
render_ground(poly, prj_trans, painter);
}
else if (geom.is<geometry::multi_polygon<double>>())
{
auto const& multi_poly = geom.get<geometry::multi_polygon<double>>();
for (auto const& poly : multi_poly)
{
vertex_adapter_type va(poly);
transform_path_type transformed(rencom_.t_, va, prj_trans);
make_building(transformed, face_func, frame_func, roof_func);
render_ground(poly, prj_trans, painter);
}
}
if (roof_vertices_.size() > 0)
{
render_walls(painter);
render_roof(painter);
}
}
private:
template <typename F>
void render_face(double x0, double y0, double x, double y, F & face_func, path_type & frame)
{
path_type faces(path_type::types::Polygon);
faces.move_to(x0, y0);
faces.line_to(x, y);
faces.line_to(x, y - height_);
faces.line_to(x0, y0 - height_);
face_func(faces, wall_fill_color);
frame.move_to(x0, y0);
frame.line_to(x, y);
frame.line_to(x, y - height_);
frame.line_to(x0, y0 - height_);
bool is_visible(int orientation) const
{
return orientation > 0 || (orientation < 0 && render_back_side_);
}
template <typename Geom, typename F1, typename F2, typename F3>
void make_building(Geom & poly, F1 & face_func, F2 & frame_func, F3 & roof_func)
template <typename T, typename Context>
void render_ground(geometry::polygon<T> const& poly,
proj_transform const& prj_trans,
Context & painter)
{
using vertex_adapter_type = geometry::polygon_vertex_adapter<T>;
using transform_path_type = transform_path_adapter<view_transform, vertex_adapter_type>;
vertex_adapter_type va(poly);
transform_path_type transformed(rencom_.t_, va, prj_trans);
painter.set_color(base_stroke_color);
paint_outline(transformed, painter);
}
template <typename Context>
void render_roof(Context & painter) const
{
vertex_adapter va(roof_vertices_);
painter.set_color(stroke_color);
painter.stroke_preserve(va);
painter.set_color(fill_color);
painter.fill();
}
template <typename Context>
void render_walls(Context & painter)
{
size_t const size = roof_vertices_.size();
size_t strip_begin = size;
size_t wrap_begin = size;
size_t wrap_end = size;
double x = 0;
double y = 0;
int poly_orientation = 0;
int strip_orientation = 0;
int wrap_orientation = 0;
painter.set_color(wall_fill_color);
for (size_t i = 0; i < size; ++i)
{
double x0 = x;
unsigned cmd = roof_vertices_->get_vertex(i, &x, &y);
// SEG_LINETO is the most likely command
// SEG_CLOSE contains coordinates from previous MOVETO
if (cmd == SEG_LINETO || cmd == SEG_CLOSE)
{
double dx = (x - x0) * poly_orientation;
int wall_orientation = (dx < 0 ? -1 : dx > 0 ? 1 : 0);
if (wall_orientation && wall_orientation != strip_orientation)
{
if (wrap_orientation == 0)
{
wrap_orientation = strip_orientation;
wrap_end = i;
}
else
{
paint_wall(strip_orientation,
strip_begin, i, i, i, painter);
}
strip_orientation = wall_orientation;
strip_begin = i - 1;
}
if (cmd == SEG_CLOSE)
{
if (wrap_orientation != strip_orientation)
{
paint_wall(wrap_orientation,
wrap_begin, wrap_end, i, i, painter);
wrap_end = wrap_begin;
}
paint_wall(strip_orientation, strip_begin, i + 1,
wrap_begin, wrap_end, painter);
}
}
else if (cmd == SEG_MOVETO_LHR || cmd == SEG_MOVETO_RHR)
{
poly_orientation = (cmd == SEG_MOVETO_RHR ? -1 : 1);
strip_orientation = 0;
strip_begin = size;
wrap_orientation = 0;
wrap_begin = i;
wrap_end = i;
// restore plain SEG_MOVETO command for `render_roof`
roof_vertices_->set_command(i, SEG_MOVETO);
}
}
}
template <typename VertexSource, typename Context>
void paint_outline(VertexSource & poly, Context & painter)
{
path_type frame(path_type::types::LineString);
double ring_begin_x, ring_begin_y;
double x0 = 0;
double y0 = 0;
double x, y;
double area = 0;
size_t ring_begin = roof_vertices_.size();
uint8_t ring_begin_cmd = SEG_MOVETO;
// stroke ground outline, collect transformed points in roof_vertices_
// and figure out exterior ring orientation along the way
poly.rewind(0);
for (unsigned cm = poly.vertex(&x, &y); cm != SEG_END; cm = poly.vertex(&x, &y))
while (unsigned cmd = poly.vertex(&x, &y))
{
if (cm == SEG_MOVETO)
if (cmd == SEG_LINETO) // the most likely command
{
ring_begin_x = x;
ring_begin_y = y;
if (ring_begin_cmd == SEG_MOVETO) // exterior ring
{
double ax = x0 - ring_begin_x;
double ay = y0 - ring_begin_y;
double bx = x - ring_begin_x;
double by = y - ring_begin_y;
area += ax * by - bx * ay;
}
roof_vertices_.line_to(x, y - height_);
painter.stroke_to(x, y);
x0 = x;
y0 = y;
}
else if (cm == SEG_LINETO)
else if (cmd == SEG_MOVETO)
{
render_face(x0, y0, x, y, face_func, frame);
ring_begin = roof_vertices_.size();
ring_begin_x = x0 = x;
ring_begin_y = y0 = y;
roof_vertices_.move_to(x, y - height_);
painter.stroke_from(x, y);
}
else if (cm == SEG_CLOSE)
else if (cmd == SEG_CLOSE)
{
render_face(x0, y0, ring_begin_x, ring_begin_y, face_func, frame);
if (ring_begin_cmd == SEG_MOVETO) // exterior ring
{
// because in screen coordinates `y` grows downward,
// negative area means counter-clockwise orientation
// (left-hand-rule, inside is on the left)
ring_begin_cmd = (area < 0 ? SEG_MOVETO_LHR : SEG_MOVETO_RHR);
}
if (ring_begin < roof_vertices_.size())
{
// modify SEG_MOVETO command so that `render_walls` knows
// how front-side and back-side walls are oriented
roof_vertices_->set_command(ring_begin, ring_begin_cmd);
}
painter.stroke_close();
painter.stroke();
x0 = ring_begin_x;
y0 = ring_begin_y;
// add close path command including coordinates;
// we utilize these when rendering walls
roof_vertices_.push_vertex(x0, y0 - height_, SEG_CLOSE);
}
x0 = x;
y0 = y;
}
}
template <typename Context>
void paint_wall(int orientation,
size_t begin1, size_t end1,
size_t begin2, size_t end2,
Context & painter) const
{
if (is_visible(orientation))
{
paint_wall_faces(begin1, end1, begin2, end2, painter);
}
}
template <typename Context>
void paint_wall_faces(size_t begin1, size_t end1,
size_t begin2, size_t end2,
Context & painter) const
{
if (begin1 >= end1)
return;
double x, y;
roof_vertices_->get_vertex(begin1, &x, &y);
painter.fill_from(x, y);
double x0 = x;
for (size_t i = begin1; ++i < end1; )
{
roof_vertices_->get_vertex(i, &x, &y);
painter.fill_to(x, y);
}
for (size_t i = begin2; i < end2; ++i)
{
roof_vertices_->get_vertex(i, &x, &y);
painter.fill_to(x, y);
}
frame_func(frame, wall_fill_color);
double sx = stroke_width * (x < x0 ? -0.5 : 0.5);
painter.fill_to(x + sx, y);
painter.fill_to(x + sx, y + height_);
painter.fill_to(x, y + height_);
agg::trans_affine_translation tr(0, -height_);
roof_type roof(poly, tr);
roof_func(roof, fill_color);
for (size_t i = end2; i-- > begin2; )
{
roof_vertices_->get_vertex(i, &x, &y);
painter.fill_to(x, y + height_);
}
for (size_t i = end1; i-- > begin1; )
{
roof_vertices_->get_vertex(i, &x, &y);
painter.fill_to(x, y + height_);
}
painter.fill_to(x - sx, y + height_);
painter.fill_to(x - sx, y);
painter.fill_to(x, y);
painter.fill_close();
painter.fill();
}
static void safe_mul(uint8_t & v, double opacity)

View file

@ -83,6 +83,12 @@ public:
::operator delete(vertices_);
}
}
void clear()
{
pos_ = 0;
}
size_type size() const
{
return pos_;

View file

@ -21,7 +21,6 @@
*****************************************************************************/
// mapnik
#include <mapnik/make_unique.hpp>
#include <mapnik/image_any.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/agg_renderer.hpp>
@ -30,12 +29,10 @@
#include <mapnik/symbolizer.hpp>
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/expression.hpp>
#include <mapnik/renderer_common/agg_building_symbolizer_context.hpp>
#include <mapnik/renderer_common/process_building_symbolizer.hpp>
// stl
#include <deque>
#include <memory>
// agg
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_basics.h"
@ -45,26 +42,49 @@
#include "agg_rasterizer_scanline_aa.h"
#include "agg_scanline_u.h"
#include "agg_renderer_scanline.h"
#include "agg_conv_stroke.h"
#pragma GCC diagnostic pop
namespace mapnik
namespace mapnik {
namespace { // (local)
struct agg_building_context
: detail::agg_building_symbolizer_context<agg_building_context, rasterizer>
{
using base = detail::agg_building_symbolizer_context<agg_building_context, rasterizer>;
using pixfmt_type = agg::pixfmt_rgba32_pre;
using renderer_base = agg::renderer_base<pixfmt_type>;
using renderer_type = agg::renderer_scanline_aa_solid<renderer_base>;
pixfmt_type pixf_;
renderer_base renb_;
renderer_type ren_;
agg::scanline_u8 sl_;
agg_building_context(agg::rendering_buffer & buf, rasterizer & ras)
: base(ras), pixf_(buf), renb_(pixf_), ren_(renb_) {}
void render_scanlines(rasterizer & ras)
{
agg::render_scanlines(ras, sl_, ren_);
}
void set_color(color const& c)
{
ren_.color(agg::rgba8_pre(c.red(), c.green(), c.blue(), c.alpha()));
}
};
} // namespace mapnik::(local)
template <typename T0,typename T1>
void agg_renderer<T0,T1>::process(building_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
using ren_base = agg::renderer_base<agg::pixfmt_rgba32_pre>;
using renderer = agg::renderer_scanline_aa_solid<ren_base>;
buffer_type & current_buffer = buffers_.top().get();
agg::rendering_buffer buf(current_buffer.bytes(), current_buffer.width(), current_buffer.height(), current_buffer.row_size());
agg::pixfmt_rgba32_pre pixf(buf);
ren_base renb(pixf);
renderer ren(renb);
agg::scanline_u8 sl;
agg_building_context ctx{buf, *ras_ptr};
render_building_symbolizer rebus{sym, feature, common_};
double gamma = get<value_double, keys::gamma>(sym, feature, common_.vars_);
@ -76,36 +96,12 @@ void agg_renderer<T0,T1>::process(building_symbolizer const& sym,
gamma_ = gamma;
}
rebus.setup_colors(sym, feature);
ctx.stroke_gen.width(rebus.stroke_width);
ctx.stroke_gen.line_join(agg::round_join);
rebus.apply(
feature, prj_trans,
[&](path_type const& faces, color const& c)
{
vertex_adapter va(faces);
ras_ptr->reset();
ras_ptr->add_path(va);
ren.color(agg::rgba8_pre(c.red(), c.green(), c.blue(), c.alpha()));
agg::render_scanlines(*ras_ptr, sl, ren);
},
[&](path_type const& frame, color const& c)
{
vertex_adapter va(frame);
agg::conv_stroke<vertex_adapter> stroke(va);
stroke.width(rebus.stroke_width);
stroke.miter_limit(1.0);
ras_ptr->reset();
ras_ptr->add_path(stroke);
ren.color(agg::rgba8_pre(c.red(), c.green(), c.blue(), c.alpha()));
agg::render_scanlines(*ras_ptr, sl, ren);
},
[&](render_building_symbolizer::roof_type & roof, color const& c)
{
ras_ptr->reset();
ras_ptr->add_path(roof);
ren.color(agg::rgba8_pre(c.red(), c.green(), c.blue(), c.alpha()));
agg::render_scanlines(*ras_ptr, sl, ren);
});
rebus.setup_colors(sym, feature);
rebus.render_back_side(rebus.has_transparent_fill());
rebus.apply(feature, prj_trans, ctx);
}
template void agg_renderer<image_rgba8>::process(building_symbolizer const&,

View file

@ -292,6 +292,12 @@ void cairo_context::stroke()
check_object_status_and_throw_exception(*this);
}
void cairo_context::stroke_preserve()
{
cairo_stroke_preserve(cairo_.get());
check_object_status_and_throw_exception(*this);
}
void cairo_context::fill()
{
cairo_fill(cairo_.get());

View file

@ -23,20 +23,46 @@
#if defined(HAVE_CAIRO)
// mapnik
#include <mapnik/make_unique.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/proj_transform.hpp>
#include <mapnik/cairo/cairo_renderer.hpp>
#include <mapnik/util/noncopyable.hpp>
// mapnik symbolizer generics
#include <mapnik/renderer_common/process_building_symbolizer.hpp>
// stl
#include <cmath>
#include <memory>
namespace mapnik {
namespace mapnik
namespace { // (local)
struct cairo_building_context : util::noncopyable
{
cairo_context & cairo_;
explicit cairo_building_context(cairo_context & context)
: cairo_(context) {}
void set_color(color const& c) { cairo_.set_color(c); }
void fill() { cairo_.fill(); }
void fill_from(double x, double y) { cairo_.move_to(x, y); }
void fill_to(double x, double y) { cairo_.line_to(x, y); }
void fill_close() { cairo_.close_path(); }
void stroke() { cairo_.stroke(); }
void stroke_from(double x, double y) { cairo_.move_to(x, y); }
void stroke_to(double x, double y) { cairo_.line_to(x, y); }
void stroke_close() { cairo_.close_path(); }
template <typename VertexSource>
void stroke_preserve(VertexSource & vs, unsigned path_id = 0)
{
cairo_.add_path(vs, path_id);
cairo_.stroke_preserve();
}
};
} // namespace mapnik::(local)
template <typename T>
void cairo_renderer<T>::process(building_symbolizer const& sym,
@ -44,37 +70,17 @@ void cairo_renderer<T>::process(building_symbolizer const& sym,
proj_transform const& prj_trans)
{
cairo_save_restore guard(context_);
cairo_building_context ctx{context_};
render_building_symbolizer rebus{sym, feature, common_};
composite_mode_e comp_op = get<composite_mode_e, keys::comp_op>(sym, feature, common_.vars_);
context_.set_operator(comp_op);
context_.set_line_width(rebus.stroke_width);
context_.set_line_join(ROUND_JOIN);
rebus.setup_colors(sym, feature);
rebus.apply(
feature, prj_trans,
[&](path_type const& faces, color const& c)
{
vertex_adapter va(faces);
context_.set_color(c);
context_.add_path(va);
context_.fill();
},
[&](path_type const& frame, color const& c)
{
vertex_adapter va(frame);
context_.set_color(c);
context_.set_line_width(rebus.stroke_width);
context_.set_miter_limit(1.0);
context_.add_path(va);
context_.stroke();
},
[&](render_building_symbolizer::roof_type & roof, color const& c)
{
context_.set_color(c);
context_.add_path(roof);
context_.fill();
});
rebus.render_back_side(rebus.has_transparent_fill());
rebus.apply(feature, prj_trans, ctx);
}
template void cairo_renderer<cairo_ptr>::process(building_symbolizer const&,

View file

@ -23,7 +23,6 @@
#if defined(GRID_RENDERER)
// mapnik
#include <mapnik/make_unique.hpp>
#include <mapnik/feature.hpp>
#include <mapnik/grid/grid_rasterizer.hpp>
#include <mapnik/grid/grid_renderer.hpp>
@ -32,69 +31,66 @@
#include <mapnik/expression_evaluator.hpp>
#include <mapnik/expression.hpp>
#include <mapnik/symbolizer.hpp>
#include <mapnik/renderer_common/agg_building_symbolizer_context.hpp>
#include <mapnik/renderer_common/process_building_symbolizer.hpp>
// stl
#include <deque>
#include <memory>
// agg
#pragma GCC diagnostic push
#include <mapnik/warning_ignore_agg.hpp>
#include "agg_rasterizer_scanline_aa.h"
#include "agg_renderer_scanline.h"
#include "agg_scanline_bin.h"
#include "agg_conv_stroke.h"
#pragma GCC diagnostic pop
namespace mapnik
namespace mapnik {
namespace { // (local)
struct grid_building_context
: detail::agg_building_symbolizer_context<grid_building_context, grid_rasterizer>
{
using base = detail::agg_building_symbolizer_context<grid_building_context, grid_rasterizer>;
using renderer_base = grid_renderer_base_type;
using renderer_type = agg::renderer_scanline_bin_solid<renderer_base>;
using pixfmt_type = typename renderer_base::pixfmt_type;
using color_type = typename renderer_base::pixfmt_type::color_type;
pixfmt_type pixf_;
renderer_base renb_;
renderer_type ren_;
agg::scanline_bin sl_;
grid_building_context(grid_rendering_buffer & buf, grid_rasterizer & ras)
: base(ras), pixf_(buf), renb_(pixf_), ren_(renb_) {}
void render_scanlines(grid_rasterizer & ras)
{
agg::render_scanlines(ras, sl_, ren_);
}
void set_feature(value_integer id)
{
ren_.color(color_type(id));
}
};
} // namespace mapnik::(local)
template <typename T>
void grid_renderer<T>::process(building_symbolizer const& sym,
mapnik::feature_impl & feature,
proj_transform const& prj_trans)
{
using pixfmt_type = typename grid_renderer_base_type::pixfmt_type;
using color_type = typename grid_renderer_base_type::pixfmt_type::color_type;
using renderer_type = agg::renderer_scanline_bin_solid<grid_renderer_base_type>;
agg::scanline_bin sl;
grid_rendering_buffer buf(pixmap_.raw_data(), common_.width_, common_.height_, common_.width_);
pixfmt_type pixf(buf);
grid_renderer_base_type renb(pixf);
renderer_type ren(renb);
grid_building_context ctx{buf, *ras_ptr};
render_building_symbolizer rebus{sym, feature, common_};
rebus.apply(
feature, prj_trans,
[&](path_type const& faces, color const& )
{
vertex_adapter va(faces);
ras_ptr->reset();
ras_ptr->add_path(va);
ren.color(color_type(feature.id()));
agg::render_scanlines(*ras_ptr, sl, ren);
},
[&](path_type const& frame, color const& )
{
vertex_adapter va(frame);
agg::conv_stroke<vertex_adapter> stroke(va);
stroke.width(rebus.stroke_width);
stroke.miter_limit(1.0);
ras_ptr->reset();
ras_ptr->add_path(stroke);
ren.color(color_type(feature.id()));
agg::render_scanlines(*ras_ptr, sl, ren);
},
[&](render_building_symbolizer::roof_type & roof, color const& )
{
ras_ptr->reset();
ras_ptr->add_path(roof);
ren.color(color_type(feature.id()));
agg::render_scanlines(*ras_ptr, sl, ren);
});
ctx.stroke_gen.width(rebus.stroke_width);
ctx.stroke_gen.line_join(agg::round_join);
ctx.set_feature(feature.id());
rebus.render_back_side(false);
rebus.apply(feature, prj_trans, ctx);
pixmap_.add_feature(feature);
}