diff --git a/CHANGELOG.md b/CHANGELOG.md index bd83af63f..08afb87cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ Released: - `mapnik.Image` - fixed copy semantics implementation for internal buffer - JSON parsing: unified error_handler across all grammars - Improved unit test coverage + - Raster scaling: fixed nodata handling, acurracy when working with small floats and clipping floats by \[0; 255\] (https://github.com/mapnik/mapnik/pull/3147) ## 3.0.8 diff --git a/include/mapnik/image_scaling.hpp b/include/mapnik/image_scaling.hpp index ad4954eb1..2f156fb73 100644 --- a/include/mapnik/image_scaling.hpp +++ b/include/mapnik/image_scaling.hpp @@ -67,7 +67,8 @@ MAPNIK_DECL void scale_image_agg(T & target, T const& source, double image_ratio_y, double x_off_f, double y_off_f, - double filter_factor); + double filter_factor, + boost::optional const & nodata_value); } #endif // MAPNIK_IMAGE_SCALING_HPP diff --git a/include/mapnik/image_scaling_traits.hpp b/include/mapnik/image_scaling_traits.hpp index e2d0220ff..69eec5a05 100644 --- a/include/mapnik/image_scaling_traits.hpp +++ b/include/mapnik/image_scaling_traits.hpp @@ -23,6 +23,9 @@ #ifndef MAPNIK_IMAGE_SCALING_TRAITS_HPP #define MAPNIK_IMAGE_SCALING_TRAITS_HPP +// mapnik +#include + // agg #include "agg_image_accessors.h" #include "agg_pixfmt_rgba.h" @@ -45,7 +48,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_rgba_nn; - using span_image_resample_affine = agg::span_image_resample_rgba_affine; + using span_image_resample_affine = span_image_resample_rgba_affine; }; @@ -57,7 +60,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -68,7 +71,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -79,7 +82,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -90,7 +93,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -101,7 +104,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -112,7 +115,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -123,7 +126,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -134,7 +137,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -145,7 +148,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template <> @@ -156,7 +159,7 @@ struct agg_scaling_traits using interpolator_type = agg::span_interpolator_linear<>; using img_src_type = agg::image_accessor_clone; using span_image_filter = agg::span_image_filter_gray_nn; - using span_image_resample_affine = agg::span_image_resample_gray_affine; + using span_image_resample_affine = span_image_resample_gray_affine; }; template diff --git a/include/mapnik/renderer_common/process_raster_symbolizer.hpp b/include/mapnik/renderer_common/process_raster_symbolizer.hpp index 5646785ef..f8ce32d84 100644 --- a/include/mapnik/renderer_common/process_raster_symbolizer.hpp +++ b/include/mapnik/renderer_common/process_raster_symbolizer.hpp @@ -77,7 +77,7 @@ struct image_dispatcher if (need_scaling_) { image_rgba8 data_out(width_, height_, true, true); - scale_image_agg(data_out, data_in, method_, scale_x_, scale_y_, 0.0, 0.0, filter_factor_); + scale_image_agg(data_out, data_in, method_, scale_x_, scale_y_, 0.0, 0.0, filter_factor_, nodata_); composite_(data_out, comp_op_, opacity_, start_x_, start_y_); } else @@ -95,7 +95,7 @@ struct image_dispatcher if (need_scaling_) { image_type data_out(width_, height_); - scale_image_agg(data_out, data_in, method_, scale_x_, scale_y_, 0.0, 0.0, filter_factor_); + scale_image_agg(data_out, data_in, method_, scale_x_, scale_y_, 0.0, 0.0, filter_factor_, nodata_); if (colorizer) colorizer->colorize(dst, data_out, nodata_, feature_); } else @@ -157,7 +157,7 @@ struct image_warp_dispatcher void operator() (image_rgba8 const& data_in) const { image_rgba8 data_out(width_, height_, true, true); - warp_image(data_out, data_in, prj_trans_, target_ext_, source_ext_, offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_); + warp_image(data_out, data_in, prj_trans_, target_ext_, source_ext_, offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_, nodata_); composite_(data_out, comp_op_, opacity_, start_x_, start_y_); } @@ -167,7 +167,7 @@ struct image_warp_dispatcher using image_type = T; image_type data_out(width_, height_); if (nodata_) data_out.set(*nodata_); - warp_image(data_out, data_in, prj_trans_, target_ext_, source_ext_, offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_); + warp_image(data_out, data_in, prj_trans_, target_ext_, source_ext_, offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_, nodata_); image_rgba8 dst(width_, height_); raster_colorizer_ptr colorizer = get(sym_, keys::colorizer); if (colorizer) colorizer->colorize(dst, data_out, nodata_, feature_); diff --git a/include/mapnik/span_image_filter.h b/include/mapnik/span_image_filter.h new file mode 100644 index 000000000..cd843010b --- /dev/null +++ b/include/mapnik/span_image_filter.h @@ -0,0 +1,178 @@ +/***************************************************************************** + * + * 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 + * + *****************************************************************************/ + +#ifndef MAPNIK_SPAN_IMAGE_FILTER_INCLUDED +#define MAPNIK_SPAN_IMAGE_FILTER_INCLUDED + +#include "agg_span_image_filter_gray.h" +#include "agg_span_image_filter_rgba.h" + +#include + +namespace mapnik +{ + +template +class span_image_resample_gray_affine : public agg::span_image_resample_affine +{ +public: + using source_type = Source; + using color_type = typename source_type::color_type; + using base_type = agg::span_image_resample_affine; + using interpolator_type = typename base_type::interpolator_type; + using value_type = typename color_type::value_type; + using long_type = typename color_type::long_type; + + enum base_scale_e + { + downscale_shift = agg::image_filter_shift, + base_mask = color_type::base_mask + }; + + span_image_resample_gray_affine(source_type & src, + interpolator_type & inter, + agg::image_filter_lut const & filter, + boost::optional const & nodata_value) : + base_type(src, inter, filter), + nodata_value_(nodata_value) + { } + + void generate(color_type* span, int x, int y, unsigned len) + { + base_type::interpolator().begin(x + base_type::filter_dx_dbl(), + y + base_type::filter_dy_dbl(), len); + + long_type fg; + + int diameter = base_type::filter().diameter(); + int filter_scale = diameter << agg::image_subpixel_shift; + int radius_x = (diameter * base_type::m_rx) >> 1; + int radius_y = (diameter * base_type::m_ry) >> 1; + int len_x_lr = + (diameter * base_type::m_rx + agg::image_subpixel_mask) >> + agg::image_subpixel_shift; + + const agg::int16* weight_array = base_type::filter().weight_array(); + + do + { + base_type::interpolator().coordinates(&x, &y); + + int src_x = x >> agg::image_subpixel_shift; + int src_y = y >> agg::image_subpixel_shift; + const value_type* pix = reinterpret_cast(base_type::source().span(src_x, src_y, 0)); + if (nodata_value_ && *nodata_value_ == *pix) + { + span->v = *nodata_value_; + span->a = base_mask; + ++span; + ++base_type::interpolator(); + continue; + } + + x += base_type::filter_dx_int() - radius_x; + y += base_type::filter_dy_int() - radius_y; + + fg = 0; + + int y_lr = y >> agg::image_subpixel_shift; + int y_hr = ((agg::image_subpixel_mask - (y & agg::image_subpixel_mask)) * + base_type::m_ry_inv) >> + agg::image_subpixel_shift; + int total_weight = 0; + int x_lr = x >> agg::image_subpixel_shift; + int x_hr = ((agg::image_subpixel_mask - (x & agg::image_subpixel_mask)) * + base_type::m_rx_inv) >> + agg::image_subpixel_shift; + + int x_hr2 = x_hr; + const value_type* fg_ptr = reinterpret_cast(base_type::source().span(x_lr, y_lr, len_x_lr)); + for(;;) + { + int weight_y = weight_array[y_hr]; + x_hr = x_hr2; + for(;;) + { + int weight = (weight_y * weight_array[x_hr] + + agg::image_filter_scale) >> + downscale_shift; + if (!nodata_value_ || *nodata_value_ != *fg_ptr) + { + fg += *fg_ptr * weight; + total_weight += weight; + } + x_hr += base_type::m_rx_inv; + if (x_hr >= filter_scale) break; + fg_ptr = reinterpret_cast(base_type::source().next_x()); + } + y_hr += base_type::m_ry_inv; + if (y_hr >= filter_scale) break; + fg_ptr = reinterpret_cast(base_type::source().next_y()); + } + + fg /= total_weight; + if (fg < std::numeric_limits::min()) + { + span->v = std::numeric_limits::min(); + } + else if (fg > std::numeric_limits::max()) + { + span->v = std::numeric_limits::max(); + } + else + { + span->v = static_cast(fg); + } + span->a = base_mask; + + ++span; + ++base_type::interpolator(); + } while(--len); + } + +private: + boost::optional nodata_value_; +}; + +template +class span_image_resample_rgba_affine : public agg::span_image_resample_rgba_affine +{ +public: + using source_type = Source; + using color_type = typename source_type::color_type; + using order_type = typename source_type::order_type; + using base_type = agg::span_image_resample_rgba_affine; + using interpolator_type = typename base_type::interpolator_type; + using value_type = typename color_type::value_type; + using long_type = typename color_type::long_type; + + span_image_resample_rgba_affine(source_type & src, + interpolator_type & inter, + agg::image_filter_lut const & filter, + boost::optional const & nodata_value) : + agg::span_image_resample_rgba_affine(src, inter, filter) + { } +}; + +} + +#endif diff --git a/include/mapnik/warp.hpp b/include/mapnik/warp.hpp index 0bf3332b6..6d314977a 100644 --- a/include/mapnik/warp.hpp +++ b/include/mapnik/warp.hpp @@ -44,7 +44,8 @@ MAPNIK_DECL void reproject_and_scale_raster(raster & target, template MAPNIK_DECL void warp_image (T & target, T const& source, proj_transform const& prj_trans, box2d const& target_ext, box2d const& source_ext, - double offset_x, double offset_y, unsigned mesh_size, scaling_method_e scaling_method, double filter_factor); + double offset_x, double offset_y, unsigned mesh_size, scaling_method_e scaling_method, double filter_factor, + boost::optional const & nodata_value); } #endif // MAPNIK_WARP_HPP diff --git a/src/grid/grid_renderer.cpp b/src/grid/grid_renderer.cpp index 93afb5b03..33d10ee6a 100644 --- a/src/grid/grid_renderer.cpp +++ b/src/grid/grid_renderer.cpp @@ -197,12 +197,13 @@ struct grid_render_marker_visitor else { image_rgba8 target(data.width(), data.height()); + boost::optional nodata; mapnik::scale_image_agg(target, data, SCALING_NEAR, 1, 1, - 0.0, 0.0, 1.0); // TODO: is 1.0 a valid default here, and do we even care in grid_renderer what the image looks like? + 0.0, 0.0, 1.0, nodata); // TODO: is 1.0 a valid default here, and do we even care in grid_renderer what the image looks like? pixmap_.set_rectangle(feature_.id(), target, boost::math::iround(pos_.x - cx), boost::math::iround(pos_.y - cy)); diff --git a/src/image_scaling.cpp b/src/image_scaling.cpp index 63bb88c08..f0cc450e6 100644 --- a/src/image_scaling.cpp +++ b/src/image_scaling.cpp @@ -24,8 +24,6 @@ #include #include #include -// does not handle alpha correctly -//#include // boost #pragma GCC diagnostic push @@ -100,7 +98,7 @@ boost::optional scaling_method_to_string(scaling_method_e scaling_m template void scale_image_agg(T & target, T const& source, scaling_method_e scaling_method, double image_ratio_x, double image_ratio_y, double x_off_f, double y_off_f, - double filter_factor) + double filter_factor, boost::optional const & nodata_value) { // "the image filters should work namely in the premultiplied color space" // http://old.nabble.com/Re:--AGG--Basic-image-transformations-p1110665.html @@ -158,42 +156,46 @@ void scale_image_agg(T & target, T const& source, scaling_method_e scaling_metho using span_gen_type = typename detail::agg_scaling_traits::span_image_resample_affine; agg::image_filter_lut filter; detail::set_scaling_method(filter, scaling_method, filter_factor); - span_gen_type sg(img_src, interpolator, filter); + boost::optional nodata; + if (nodata_value) + { + nodata = nodata_value; + } + span_gen_type sg(img_src, interpolator, filter, nodata); agg::render_scanlines_aa(ras, sl, rb_dst_pre, sa, sg); } - } template MAPNIK_DECL void scale_image_agg(image_rgba8 &, image_rgba8 const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray8 &, image_gray8 const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray8s &, image_gray8s const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray16 &, image_gray16 const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray16s &, image_gray16s const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray32 &, image_gray32 const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray32s &, image_gray32s const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray32f &, image_gray32f const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray64 &, image_gray64 const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray64s &, image_gray64s const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); template MAPNIK_DECL void scale_image_agg(image_gray64f &, image_gray64f const&, scaling_method_e, - double, double , double, double , double); + double, double , double, double , double, boost::optional const &); } diff --git a/src/warp.cpp b/src/warp.cpp index 572974834..49085a0fb 100644 --- a/src/warp.cpp +++ b/src/warp.cpp @@ -51,7 +51,8 @@ namespace mapnik { template MAPNIK_DECL void warp_image (T & target, T const& source, proj_transform const& prj_trans, box2d const& target_ext, box2d const& source_ext, - double offset_x, double offset_y, unsigned mesh_size, scaling_method_e scaling_method, double filter_factor) + double offset_x, double offset_y, unsigned mesh_size, scaling_method_e scaling_method, double filter_factor, + boost::optional const & nodata_value) { using image_type = T; using pixel_type = typename image_type::pixel_type; @@ -147,7 +148,12 @@ MAPNIK_DECL void warp_image (T & target, T const& source, proj_transform const& using span_gen_type = typename detail::agg_scaling_traits::span_image_resample_affine; agg::image_filter_lut filter; detail::set_scaling_method(filter, scaling_method, filter_factor); - span_gen_type sg(ia, interpolator, filter); + boost::optional nodata; + if (nodata_value) + { + nodata = nodata_value; + } + span_gen_type sg(ia, interpolator, filter, nodata); agg::render_scanlines_bin(rasterizer, scanline, rb, sa, sg); } } @@ -162,7 +168,8 @@ struct warp_image_visitor { warp_image_visitor (raster & target_raster, proj_transform const& prj_trans, box2d const& source_ext, double offset_x, double offset_y, unsigned mesh_size, - scaling_method_e scaling_method, double filter_factor) + scaling_method_e scaling_method, double filter_factor, + boost::optional const & nodata_value) : target_raster_(target_raster), prj_trans_(prj_trans), source_ext_(source_ext), @@ -170,7 +177,9 @@ struct warp_image_visitor offset_y_(offset_y), mesh_size_(mesh_size), scaling_method_(scaling_method), - filter_factor_(filter_factor) {} + filter_factor_(filter_factor), + nodata_value_(nodata_value) + {} void operator() (image_null const&) {} @@ -183,7 +192,7 @@ struct warp_image_visitor { image_type & target = util::get(target_raster_.data_); warp_image (target, source, prj_trans_, target_raster_.ext_, source_ext_, - offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_); + offset_x_, offset_y_, mesh_size_, scaling_method_, filter_factor_, nodata_value_); } } @@ -195,6 +204,7 @@ struct warp_image_visitor unsigned mesh_size_; scaling_method_e scaling_method_; double filter_factor_; + boost::optional const & nodata_value_; }; } @@ -203,24 +213,25 @@ void reproject_and_scale_raster(raster & target, raster const& source, proj_transform const& prj_trans, double offset_x, double offset_y, unsigned mesh_size, - scaling_method_e scaling_method) + scaling_method_e scaling_method, + boost::optional const & nodata_value) { detail::warp_image_visitor warper(target, prj_trans, source.ext_, offset_x, offset_y, mesh_size, - scaling_method, source.get_filter_factor()); + scaling_method, source.get_filter_factor(), nodata_value); util::apply_visitor(warper, source.data_); } template MAPNIK_DECL void warp_image (image_rgba8&, image_rgba8 const&, proj_transform const&, - box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double); + box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double, boost::optional const &); template MAPNIK_DECL void warp_image (image_gray8&, image_gray8 const&, proj_transform const&, - box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double); + box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double, boost::optional const &); template MAPNIK_DECL void warp_image (image_gray16&, image_gray16 const&, proj_transform const&, - box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double); + box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double, boost::optional const &); template MAPNIK_DECL void warp_image (image_gray32f&, image_gray32f const&, proj_transform const&, - box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double); + box2d const&, box2d const&, double, double, unsigned, scaling_method_e, double, boost::optional const &); }// namespace mapnik