/***************************************************************************** * * This file is part of Mapnik (c++ mapping toolkit) * * Copyright (C) 2021 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MAPNIK_DISABLE_WARNING_PUSH #include #include "agg_rendering_buffer.h" #include "agg_pixfmt_rgba.h" #include "agg_color_rgba.h" #include "agg_scanline_u.h" #include "agg_image_filters.h" #include "agg_span_allocator.h" #include "agg_image_accessors.h" #include "agg_span_image_filter_rgba.h" MAPNIK_DISABLE_WARNING_POP // stl #include namespace mapnik { template agg_renderer::agg_renderer(Map const& m, T0& pixmap, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m, scale_factor) , buffers_() , internal_buffers_(m.width(), m.height()) , inflated_buffer_() , ras_ptr(std::make_unique()) , gamma_method_(gamma_method_enum::GAMMA_POWER) , gamma_(1.0) , common_(m, attributes(), offset_x, offset_y, m.width(), m.height(), scale_factor) { setup(m, pixmap); } template agg_renderer::agg_renderer(Map const& m, request const& req, attributes const& vars, T0& pixmap, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m, scale_factor) , buffers_() , internal_buffers_(req.width(), req.height()) , inflated_buffer_() , ras_ptr(std::make_unique()) , gamma_method_(gamma_method_enum::GAMMA_POWER) , gamma_(1.0) , common_(m, req, vars, offset_x, offset_y, req.width(), req.height(), scale_factor) { setup(m, pixmap); } template agg_renderer::agg_renderer(Map const& m, T0& pixmap, std::shared_ptr detector, double scale_factor, unsigned offset_x, unsigned offset_y) : feature_style_processor(m, scale_factor) , buffers_() , internal_buffers_(m.width(), m.height()) , inflated_buffer_() , ras_ptr(std::make_unique()) , gamma_method_(gamma_method_enum::GAMMA_POWER) , gamma_(1.0) , common_(m, attributes(), offset_x, offset_y, m.width(), m.height(), scale_factor, detector) { setup(m, pixmap); } template struct setup_agg_bg_visitor { setup_agg_bg_visitor(buffer_type& pixmap, renderer_common const& common, composite_mode_e mode, double opacity) : pixmap_(pixmap) , common_(common) , mode_(mode) , opacity_(opacity) {} void operator()(marker_null const&) const {} void operator()(marker_svg const&) const {} void operator()(marker_rgba8 const& marker) const { mapnik::image_rgba8 const& bg_image = marker.get_data(); std::size_t w = bg_image.width(); std::size_t h = bg_image.height(); if (w > 0 && h > 0) { // repeat background-image both vertically and horizontally std::size_t x_steps = static_cast(std::ceil(common_.width_ / double(w))); std::size_t y_steps = static_cast(std::ceil(common_.height_ / double(h))); for (std::size_t x = 0; x < x_steps; ++x) { for (std::size_t y = 0; y < y_steps; ++y) { composite(pixmap_, bg_image, mode_, opacity_, x * w, y * h); } } } } private: buffer_type& pixmap_; renderer_common const& common_; composite_mode_e mode_; double opacity_; }; template void agg_renderer::setup(Map const& m, buffer_type& pixmap) { buffers_.emplace(pixmap); mapnik::set_premultiplied_alpha(pixmap, true); auto&& bg = m.background(); if (bg) { if (bg->alpha() < 255) { mapnik::color bg_color = *bg; bg_color.premultiply(); mapnik::fill(pixmap, bg_color); } else { mapnik::color bg_color = *bg; bg_color.set_premultiplied(true); mapnik::fill(pixmap, bg_color); } } auto&& image_filename = m.background_image(); if (image_filename) { // NOTE: marker_cache returns premultiplied image, if needed std::shared_ptr bg_marker = mapnik::marker_cache::instance().find(*image_filename, true); setup_agg_bg_visitor visitor(pixmap, common_, m.background_image_comp_op(), m.background_image_opacity()); util::apply_visitor(visitor, *bg_marker); } MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: Scale=" << m.scale(); } template agg_renderer::~agg_renderer() {} template void agg_renderer::start_map_processing(Map const& map) { MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: Start map processing bbox=" << map.get_current_extent(); ras_ptr->clip_box(0, 0, common_.width_, common_.height_); } template void agg_renderer::end_map_processing(Map const& map) { mapnik::demultiply_alpha(buffers_.top().get()); MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: End map processing"; } template void agg_renderer::start_layer_processing(layer const& lay, box2d const& query_extent) { MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: Start processing layer=" << lay.name(); MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: -- datasource=" << lay.datasource().get(); MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: -- query_extent=" << query_extent; if (lay.clear_label_cache()) { common_.detector_->clear(); } common_.query_extent_ = query_extent; auto&& maximum_extent = lay.maximum_extent(); if (maximum_extent) { common_.query_extent_.clip(*maximum_extent); } if (lay.comp_op() || lay.get_opacity() < 1.0) { buffers_.emplace(internal_buffers_.push()); set_premultiplied_alpha(buffers_.top().get(), true); } else { buffers_.emplace(buffers_.top().get()); } } template void agg_renderer::end_layer_processing(layer const& lyr) { MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: End layer processing"; buffer_type& current_buffer = buffers_.top().get(); buffers_.pop(); buffer_type& previous_buffer = buffers_.top().get(); if (¤t_buffer != &previous_buffer) { composite_mode_e comp_op = lyr.comp_op() ? *lyr.comp_op() : src_over; composite(previous_buffer, current_buffer, comp_op, lyr.get_opacity(), 0, 0); internal_buffers_.pop(); } } template void agg_renderer::start_style_processing(feature_type_style const& st) { MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: Start processing style"; if (st.comp_op() || st.image_filters().size() > 0 || st.get_opacity() < 1) { if (st.image_filters_inflate()) { int radius = 0; mapnik::filter::filter_radius_visitor visitor(radius); for (mapnik::filter::filter_type const& filter_tag : st.image_filters()) { util::apply_visitor(visitor, filter_tag); } radius *= common_.scale_factor_; if (radius > common_.t_.offset()) { common_.t_.set_offset(radius); } int offset = common_.t_.offset(); unsigned target_width = common_.width_ + (offset * 2); unsigned target_height = common_.height_ + (offset * 2); ras_ptr->clip_box(-int(offset * 2), -int(offset * 2), target_width, target_height); if (!inflated_buffer_ || (inflated_buffer_->width() < target_width || inflated_buffer_->height() < target_height)) { inflated_buffer_ = std::make_unique(target_width, target_height); } else { mapnik::fill(*inflated_buffer_, 0); // fill with transparent colour } buffers_.emplace(*inflated_buffer_); } else { buffers_.emplace(internal_buffers_.push()); common_.t_.set_offset(0); ras_ptr->clip_box(0, 0, common_.width_, common_.height_); } set_premultiplied_alpha(buffers_.top().get(), true); } else { common_.t_.set_offset(0); ras_ptr->clip_box(0, 0, common_.width_, common_.height_); buffers_.emplace(buffers_.top().get()); } } template void agg_renderer::end_style_processing(feature_type_style const& st) { buffer_type& current_buffer = buffers_.top().get(); buffers_.pop(); buffer_type& previous_buffer = buffers_.top().get(); if (¤t_buffer != &previous_buffer) { bool blend_from = false; if (st.image_filters().size() > 0) { blend_from = true; mapnik::filter::filter_visitor visitor(current_buffer, common_.scale_factor_); for (mapnik::filter::filter_type const& filter_tag : st.image_filters()) { util::apply_visitor(visitor, filter_tag); } mapnik::premultiply_alpha(current_buffer); } if (st.comp_op()) { composite(previous_buffer, current_buffer, *st.comp_op(), st.get_opacity(), -common_.t_.offset(), -common_.t_.offset()); } else if (blend_from || st.get_opacity() < 1.0) { composite(previous_buffer, current_buffer, src_over, st.get_opacity(), -common_.t_.offset(), -common_.t_.offset()); } if (internal_buffers_.in_range() && ¤t_buffer == &internal_buffers_.top()) { internal_buffers_.pop(); } } if (st.direct_image_filters().size() > 0) { // apply any 'direct' image filters mapnik::filter::filter_visitor visitor(previous_buffer, common_.scale_factor_); for (mapnik::filter::filter_type const& filter_tag : st.direct_image_filters()) { util::apply_visitor(visitor, filter_tag); } mapnik::premultiply_alpha(previous_buffer); } MAPNIK_LOG_DEBUG(agg_renderer) << "agg_renderer: End processing style"; } template struct agg_render_marker_visitor { agg_render_marker_visitor(renderer_common& common, buffer_type& current_buffer, std::unique_ptr const& ras_ptr, gamma_method_enum& gamma_method, double& gamma, pixel_position const& pos, agg::trans_affine const& tr, double opacity, composite_mode_e comp_op) : common_(common) , current_buffer_(current_buffer) , ras_ptr_(ras_ptr) , gamma_method_(gamma_method) , gamma_(gamma) , pos_(pos) , tr_(tr) , opacity_(opacity) , comp_op_(comp_op) {} void operator()(marker_null const&) const {} void operator()(marker_svg const& marker) const { using color_type = agg::rgba8; using order_type = agg::order_rgba; using blender_type = agg::comp_op_adaptor_rgba_pre; // comp blender using pixfmt_comp_type = agg::pixfmt_custom_blend_rgba; using renderer_base = agg::renderer_base; using renderer_type = agg::renderer_scanline_aa_solid; ras_ptr_->reset(); if (gamma_method_ != gamma_method_enum::GAMMA_POWER || gamma_ != 1.0) { ras_ptr_->gamma(agg::gamma_power()); gamma_method_ = gamma_method_enum::GAMMA_POWER; gamma_ = 1.0; } agg::scanline_u8 sl; agg::rendering_buffer buf(current_buffer_.bytes(), current_buffer_.width(), current_buffer_.height(), current_buffer_.row_size()); pixfmt_comp_type pixf(buf); pixf.comp_op(static_cast(comp_op_)); renderer_base renb(pixf); box2d const& bbox = marker.get_data()->bounding_box(); coord c = bbox.center(); // center the svg marker on '0,0' agg::trans_affine mtx = agg::trans_affine_translation(-c.x, -c.y); // apply symbol transformation to get to map space mtx *= tr_; mtx *= agg::trans_affine_scaling(common_.scale_factor_); // render the marker at the center of the marker box mtx.translate(pos_.x, pos_.y); svg::vertex_stl_adapter stl_storage(marker.get_data()->source()); svg_path_adapter svg_path(stl_storage); svg::renderer_agg svg_renderer( svg_path, marker.get_data()->svg_group()); // https://github.com/mapnik/mapnik/issues/1316 // https://github.com/mapnik/mapnik/issues/1866 mtx.tx = std::floor(mtx.tx + .5); mtx.ty = std::floor(mtx.ty + .5); svg_renderer.render(*ras_ptr_, sl, renb, mtx, opacity_, bbox); } void operator()(marker_rgba8 const& marker) const { using color_type = agg::rgba8; using order_type = agg::order_rgba; using blender_type = agg::comp_op_adaptor_rgba_pre; // comp blender using pixfmt_comp_type = agg::pixfmt_custom_blend_rgba; using renderer_base = agg::renderer_base; ras_ptr_->reset(); if (gamma_method_ != gamma_method_enum::GAMMA_POWER || gamma_ != 1.0) { ras_ptr_->gamma(agg::gamma_power()); gamma_method_ = gamma_method_enum::GAMMA_POWER; gamma_ = 1.0; } agg::scanline_u8 sl; agg::rendering_buffer buf(current_buffer_.bytes(), current_buffer_.width(), current_buffer_.height(), current_buffer_.row_size()); pixfmt_comp_type pixf(buf); pixf.comp_op(static_cast(comp_op_)); renderer_base renb(pixf); double width = marker.width(); double height = marker.height(); if (std::fabs(1.0 - common_.scale_factor_) < 0.001 && (std::fabs(1.0 - tr_.sx) < agg::affine_epsilon) && (std::fabs(0.0 - tr_.shy) < agg::affine_epsilon) && (std::fabs(0.0 - tr_.shx) < agg::affine_epsilon) && (std::fabs(1.0 - tr_.sy) < agg::affine_epsilon)) { double cx = 0.5 * width; double cy = 0.5 * height; composite(current_buffer_, marker.get_data(), comp_op_, opacity_, std::floor(pos_.x - cx + .5), std::floor(pos_.y - cy + .5)); } else { double p[8]; double x0 = pos_.x - 0.5 * width; double y0 = pos_.y - 0.5 * height; p[0] = x0; p[1] = y0; p[2] = x0 + width; p[3] = y0; p[4] = x0 + width; p[5] = y0 + height; p[6] = x0; p[7] = y0 + height; agg::trans_affine marker_tr; marker_tr *= agg::trans_affine_translation(-pos_.x, -pos_.y); marker_tr *= tr_; marker_tr *= agg::trans_affine_scaling(common_.scale_factor_); marker_tr *= agg::trans_affine_translation(pos_.x, pos_.y); marker_tr.transform(&p[0], &p[1]); marker_tr.transform(&p[2], &p[3]); marker_tr.transform(&p[4], &p[5]); marker_tr.transform(&p[6], &p[7]); ras_ptr_->move_to_d(p[0], p[1]); ras_ptr_->line_to_d(p[2], p[3]); ras_ptr_->line_to_d(p[4], p[5]); ras_ptr_->line_to_d(p[6], p[7]); agg::span_allocator sa; agg::image_filter_bilinear filter_kernel; agg::image_filter_lut filter(filter_kernel, false); buffer_type const& src = marker.get_data(); agg::rendering_buffer marker_buf((unsigned char*)src.bytes(), src.width(), src.height(), src.row_size()); agg::pixfmt_rgba32_pre marker_pixf(marker_buf); using img_accessor_type = agg::image_accessor_clone; using interpolator_type = agg::span_interpolator_linear; using span_gen_type = agg::span_image_filter_rgba_2x2; using renderer_type = agg::renderer_scanline_aa_alpha, span_gen_type>; img_accessor_type ia(marker_pixf); agg::trans_affine final_tr(p, 0, 0, width, height); final_tr.tx = std::floor(final_tr.tx + .5); final_tr.ty = std::floor(final_tr.ty + .5); interpolator_type interpolator(final_tr); span_gen_type sg(ia, interpolator, filter); renderer_type rp(renb, sa, sg, unsigned(opacity_ * 255)); agg::render_scanlines(*ras_ptr_, sl, rp); } } private: renderer_common& common_; buffer_type& current_buffer_; std::unique_ptr const& ras_ptr_; gamma_method_enum& gamma_method_; double& gamma_; pixel_position const& pos_; agg::trans_affine const& tr_; double opacity_; composite_mode_e comp_op_; }; template void agg_renderer::render_marker(pixel_position const& pos, marker const& marker, agg::trans_affine const& tr, double opacity, composite_mode_e comp_op) { agg_render_marker_visitor visitor(common_, buffers_.top().get(), ras_ptr, gamma_method_, gamma_, pos, tr, opacity, comp_op); util::apply_visitor(visitor, marker); } template bool agg_renderer::painted() { return buffers_.top().get().painted(); } template void agg_renderer::painted(bool painted) { buffers_.top().get().painted(painted); } template void agg_renderer::debug_draw_box(box2d const& box, double x, double y, double angle) { buffer_type& current_buffer = buffers_.top().get(); agg::rendering_buffer buf(current_buffer.bytes(), current_buffer.width(), current_buffer.height(), current_buffer.row_size()); debug_draw_box(buf, box, x, y, angle); } template template void agg_renderer::debug_draw_box(R& buf, box2d const& box, double x, double y, double angle) { using pixfmt = agg::pixfmt_rgba32_pre; using renderer_base = agg::renderer_base; using renderer_type = agg::renderer_scanline_aa_solid; agg::scanline_p8 sl_line; pixfmt pixf(buf); renderer_base renb(pixf); renderer_type ren(renb); // compute transformation matrix agg::trans_affine tr = agg::trans_affine_rotation(angle).translate(x, y); // prepare path agg::path_storage pbox; pbox.start_new_path(); pbox.move_to(box.minx(), box.miny()); pbox.line_to(box.maxx(), box.miny()); pbox.line_to(box.maxx(), box.maxy()); pbox.line_to(box.minx(), box.maxy()); pbox.line_to(box.minx(), box.miny()); // prepare stroke with applied transformation using conv_transform = agg::conv_transform; using conv_stroke = agg::conv_stroke; conv_transform tbox(pbox, tr); conv_stroke sbox(tbox); sbox.generator().width(1.0 * common_.scale_factor_); // render the outline ras_ptr->reset(); ras_ptr->add_path(sbox); ren.color(agg::rgba8_pre(0x33, 0x33, 0xff, 0xcc)); // blue is fine agg::render_scanlines(*ras_ptr, sl_line, ren); } template void agg_renderer::draw_geo_extent(box2d const& extent, mapnik::color const& color) { box2d box = common_.t_.forward(extent); double x0 = box.minx(); double x1 = box.maxx(); double y0 = box.miny(); double y1 = box.maxy(); unsigned rgba = color.rgba(); for (double x = x0; x < x1; x++) { mapnik::set_pixel(buffers_.top().get(), x, y0, rgba); mapnik::set_pixel(buffers_.top().get(), x, y1, rgba); } for (double y = y0; y < y1; y++) { mapnik::set_pixel(buffers_.top().get(), x0, y, rgba); mapnik::set_pixel(buffers_.top().get(), x1, y, rgba); } } template class agg_renderer; template void agg_renderer::debug_draw_box(agg::rendering_buffer& buf, box2d const& box, double x, double y, double angle); } // namespace mapnik