diff --git a/include/mapnik/geometry_strategy.hpp b/include/mapnik/geometry_strategy.hpp index 209292792..0a322ddc0 100644 --- a/include/mapnik/geometry_strategy.hpp +++ b/include/mapnik/geometry_strategy.hpp @@ -24,6 +24,7 @@ #define MAPNIK_GEOMETRY_STRATEGY_HPP #include +#include namespace mapnik { namespace geometry { @@ -192,6 +193,57 @@ private: double offset_; }; +struct scale_rounding_strategy +{ + scale_rounding_strategy(double scale, double offset = 0) + : scale_(scale), offset_(offset) {} + + template + inline bool apply(P1 const & p1, P2 & p2) const + { + + using p2_type = typename boost::geometry::coordinate_type::type; + double x = (boost::geometry::get<0>(p1) * scale_) + offset_; + double y = (boost::geometry::get<1>(p1) * scale_) + offset_; + try { + boost::geometry::set<0>(p2, util::rounding_cast(x)); + } + catch(boost::numeric::negative_overflow&) + { + boost::geometry::set<0>(p2, std::numeric_limits::min()); + } + catch(boost::numeric::positive_overflow&) + { + boost::geometry::set<0>(p2, std::numeric_limits::max()); + } + try { + boost::geometry::set<1>(p2, util::rounding_cast(y)); + } + catch(boost::numeric::negative_overflow&) + { + boost::geometry::set<1>(p2, std::numeric_limits::min()); + } + catch(boost::numeric::positive_overflow&) + { + boost::geometry::set<1>(p2, std::numeric_limits::max()); + } + return true; + } + + template + inline P2 execute(P1 const& p1, bool & status) const + { + P2 p2; + status = apply(p1, p2); + return p2; + } + +private: + double scale_; + double offset_; +}; + + } // end geometry ns } // end mapnik ns diff --git a/include/mapnik/image_util.hpp b/include/mapnik/image_util.hpp index 8d17e3316..9cabef117 100644 --- a/include/mapnik/image_util.hpp +++ b/include/mapnik/image_util.hpp @@ -216,7 +216,7 @@ MAPNIK_DECL void set_rectangle (T & dst, T const& src, int x = 0, int y = 0); template inline bool check_bounds (T const& data, std::size_t x, std::size_t y) { - return (x < static_cast(data.width()) && y < static_cast(data.height())); + return (x < data.width() && y < data.height()); } // COMPOSITE_PIXEL diff --git a/include/mapnik/util/rounding_cast.hpp b/include/mapnik/util/rounding_cast.hpp new file mode 100644 index 000000000..d67c066cc --- /dev/null +++ b/include/mapnik/util/rounding_cast.hpp @@ -0,0 +1,48 @@ +/***************************************************************************** + * + * 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_UTIL_ROUNDING_CAST_HPP +#define MAPNIK_UTIL_ROUNDING_CAST_HPP + +#include + +namespace mapnik { +namespace util { + +template +inline +Target rounding_cast ( Source arg ) +{ + typedef boost::numeric::conversion_traits Traits ; + + typedef +boost::numeric::converter + > Converter ; + + return Converter::convert(arg); +} + +} // end util ns +} // end mapnik ns + +#endif //MAPNIK_UTIL_ROUNDING_CAST_HPP + diff --git a/src/image_util.cpp b/src/image_util.cpp index fc0e7bdb7..59040e0f7 100644 --- a/src/image_util.cpp +++ b/src/image_util.cpp @@ -632,19 +632,16 @@ struct visitor_set_alpha void operator() (image_rgba8 & data) { using pixel_type = image_rgba8::pixel_type; - pixel_type a1; - try + if (opacity_ > 1.0) { - a1 = numeric_cast(255.0 * opacity_); + opacity_ = 1.0; } - catch(negative_overflow&) + if (opacity_ < 0.0) { - a1 = std::numeric_limits::min(); - } - catch(positive_overflow&) - { - a1 = std::numeric_limits::max(); + opacity_ = 0.0; } + + pixel_type a1 = static_cast(255.0 * opacity_); for (unsigned int y = 0; y < data.height(); ++y) { pixel_type* row_to = data.get_row(y); diff --git a/test/unit/geometry/geometry_strategy_test.cpp b/test/unit/geometry/geometry_strategy_test.cpp index abf116431..124b343bf 100644 --- a/test/unit/geometry/geometry_strategy_test.cpp +++ b/test/unit/geometry/geometry_strategy_test.cpp @@ -88,8 +88,8 @@ SECTION("proj and view strategy") { } { // Test with scaling as well. This would be like projection from 4326 to a vector tile. - mapnik::geometry::scale_strategy ss(16, 0.5); - using sg_type = strategy_group; + mapnik::geometry::scale_rounding_strategy ss(16); + using sg_type = strategy_group; sg_type sg(ps, vs, ss); geometry p1(std::move(point(-97.553098,35.523105))); point r1(938 , 1615); @@ -112,9 +112,130 @@ SECTION("proj and view strategy") { //std::cout << p3.x << " , " << p3.y << std::endl; assert_g_equal(r1, p3); } + { + // Test with scaling + offset as well. This would be like projection from 4326 to a vector tile. + mapnik::geometry::scale_rounding_strategy ss(16, 20); + using sg_type = strategy_group; + sg_type sg(ps, vs, ss); + geometry p1(std::move(point(-97.553098,35.523105))); + point r1(958 , 1635); + geometry p2 = transform(p1, sg); + REQUIRE(p2.is >()); + point p3 = mapnik::util::get >(p2); + //std::cout << p3.x << " , " << p3.y << std::endl; + assert_g_equal(r1, p3); + } + { + // Test the entire scaling plus offset in reverse process in reverse! This would be like converting a vector tile coordinate to 4326. + mapnik::geometry::scale_strategy ss(1.0/16.0, -20.0/16.0); + using sg_type = strategy_group_first; + sg_type sg(ss, uvs, ps_rev); + geometry p1(std::move(point(958 , 1635))); + point r1(-97.5586 , 35.5322); + geometry p2 = transform(p1, sg); + REQUIRE(p2.is >()); + point p3 = mapnik::util::get >(p2); + //std::cout << p3.x << " , " << p3.y << std::endl; + assert_g_equal(r1, p3); + } -} +} // END SECTION -} +SECTION("scaling strategies - double to double") { + using namespace mapnik::geometry; + + { + scale_strategy ss(10.0); + point p(-90.3, 35.5); + point r(-903.0, 355.0); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + scale_strategy ss(0.5, -2.0); + point p(-90.3, 35.5); + point r(-47.15, 15.75); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + // Not the rounding doesn't apply because not casting to ints + scale_rounding_strategy ss(0.5, -2.0); + point p(-90.3, 35.5); + point r(-47.15, 15.75); + point o = transform(p, ss); + assert_g_equal(r, o); + } + +} // END SECTION + +SECTION("scaling strategies - double to int64") { + using namespace mapnik::geometry; + + { + scale_strategy ss(10.0); + point p(-90.31, 35.58); + point r(-903, 355); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + scale_strategy ss(0.5, -2.0); + point p(-90.3, 35.5); + point r(-47, 15); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + scale_rounding_strategy ss(0.5, -2.0); + point p(-90.3, 35.5); + point r(-47, 16); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + // Test underflow and overflow + std::int64_t min = std::numeric_limits::min(); + std::int64_t max = std::numeric_limits::max(); + scale_strategy ss(1.0E100); + point p(-90.3, 35.5); + point r(min, max); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + // Test underflow and overflow + std::int64_t min = std::numeric_limits::min(); + std::int64_t max = std::numeric_limits::max(); + scale_rounding_strategy ss(1.0E100); + point p(-90.3, 35.5); + point r(min, max); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + // Test overrflow and underflow + std::int64_t min = std::numeric_limits::min(); + std::int64_t max = std::numeric_limits::max(); + scale_strategy ss(1.0E100); + point p(90.3, -35.5); + point r(max, min); + point o = transform(p, ss); + assert_g_equal(r, o); + } + { + // Test overflow and underflow + std::int64_t min = std::numeric_limits::min(); + std::int64_t max = std::numeric_limits::max(); + scale_rounding_strategy ss(1.0E100); + point p(90.3, -35.5); + point r(max, min); + point o = transform(p, ss); + assert_g_equal(r, o); + } + +} // END SECTION + +} // END TEST CASE diff --git a/test/unit/imaging/image_view.cpp b/test/unit/imaging/image_view.cpp new file mode 100644 index 000000000..4290ef251 --- /dev/null +++ b/test/unit/imaging/image_view.cpp @@ -0,0 +1,279 @@ +#include "catch.hpp" + +// mapnik +#include +#include +#include +#include + +TEST_CASE("image view") { + +SECTION("test rgba8") { + + mapnik::image_rgba8 im(4,4); + mapnik::color c_red("red"); + mapnik::color c_blue("blue"); + mapnik::color c_green("green"); + mapnik::color c_yellow("yellow"); + mapnik::fill(im, c_red); + // Upper Left 2x2 is blue + mapnik::set_pixel(im, 0, 0, c_blue); + mapnik::set_pixel(im, 0, 1, c_blue); + mapnik::set_pixel(im, 1, 0, c_blue); + mapnik::set_pixel(im, 1, 1, c_blue); + // Upper Right 2x2 is green + mapnik::set_pixel(im, 2, 0, c_green); + mapnik::set_pixel(im, 2, 1, c_green); + mapnik::set_pixel(im, 3, 0, c_green); + mapnik::set_pixel(im, 3, 1, c_green); + // Lower Left 2x2 is yellow + mapnik::set_pixel(im, 0, 2, c_yellow); + mapnik::set_pixel(im, 0, 3, c_yellow); + mapnik::set_pixel(im, 1, 2, c_yellow); + mapnik::set_pixel(im, 1, 3, c_yellow); + + mapnik::image_rgba8 im2(5,5); + mapnik::fill(im2, c_red); + + // Now that we have test data run tests + mapnik::image_view_rgba8 view_all(0,0,4,4,im); + mapnik::image_view_rgba8 view_blue(0,0,2,2,im); + mapnik::image_view_rgba8 view_green(2,0,2,2,im); + mapnik::image_view_rgba8 view_yellow(0,2,2,2,im); + mapnik::image_view_rgba8 view_red(2,2,2,2,im); + mapnik::image_view_rgba8 view_bad(99,99,99,99,im); + const mapnik::image_view_rgba8 view_all_2(0,0,4,4,im2); + + // Check that image_views all have the same underlying data + CHECK(view_all == view_blue); + CHECK(view_all == view_green); + CHECK(view_all == view_yellow); + CHECK(view_all == view_red); + + // Check that view_all and view_all_2 are not the same underlying data + CHECK_FALSE(view_all == view_all_2); + CHECK(view_all < view_all_2); + + // Check that copy constructor works + mapnik::image_view_rgba8 view_all_3(view_all_2); + CHECK(view_all_2 == view_all_3); + + // Check other constructor + mapnik::image_view_rgba8 view_all_4(std::move(view_all_3)); + CHECK(view_all_2 == view_all_4); + + // Check that x offset is correct + CHECK(view_all.x() == 0); + CHECK(view_blue.x() == 0); + CHECK(view_green.x() == 2); + CHECK(view_yellow.x() == 0); + CHECK(view_red.x() == 2); + CHECK(view_bad.x() == 3); + + // Check that y offset is correct + CHECK(view_all.y() == 0); + CHECK(view_blue.y() == 0); + CHECK(view_green.y() == 0); + CHECK(view_yellow.y() == 2); + CHECK(view_red.y() == 2); + CHECK(view_bad.y() == 3); + + // Check that width is correct + CHECK(view_all.width() == 4); + CHECK(view_blue.width() == 2); + CHECK(view_green.width() == 2); + CHECK(view_yellow.width() == 2); + CHECK(view_red.width() == 2); + CHECK(view_bad.width() == 1); + + // Check that height is correct + CHECK(view_all.height() == 4); + CHECK(view_blue.height() == 2); + CHECK(view_green.height() == 2); + CHECK(view_yellow.height() == 2); + CHECK(view_red.height() == 2); + CHECK(view_bad.height() == 1); + + // Check that size is correct + CHECK(view_all.size() == 64); + CHECK(view_blue.size() == 16); + CHECK(view_green.size() == 16); + CHECK(view_yellow.size() == 16); + CHECK(view_red.size() == 16); + + // Check that row_size is correct + CHECK(view_all.row_size() == 16); + CHECK(view_blue.row_size() == 8); + CHECK(view_green.row_size() == 8); + CHECK(view_yellow.row_size() == 8); + CHECK(view_red.row_size() == 8); + + // Check that get_premultiplied is correct + CHECK_FALSE(view_all.get_premultiplied()); + CHECK_FALSE(view_blue.get_premultiplied()); + CHECK_FALSE(view_green.get_premultiplied()); + CHECK_FALSE(view_yellow.get_premultiplied()); + CHECK_FALSE(view_red.get_premultiplied()); + + // Check that operator to retrieve value works properly + CHECK(view_all(0,0) == c_blue.rgba()); + CHECK(view_blue(0,0) == c_blue.rgba()); + CHECK(view_green(0,0) == c_green.rgba()); + CHECK(view_yellow(0,0) == c_yellow.rgba()); + CHECK(view_red.row_size() == 8); + + // Check that offset is correct + CHECK(view_all.get_offset() == 0.0); + CHECK(view_blue.get_offset() == 0.0); + CHECK(view_green.get_offset() == 0.0); + CHECK(view_yellow.get_offset() == 0.0); + CHECK(view_red.get_offset() == 0.0); + + // Check that scaling is correct + CHECK(view_all.get_scaling() == 1.0); + CHECK(view_blue.get_scaling() == 1.0); + CHECK(view_green.get_scaling() == 1.0); + CHECK(view_yellow.get_scaling() == 1.0); + CHECK(view_red.get_scaling() == 1.0); + + // CHECK that image dtype is correct + CHECK(view_all.get_dtype() == mapnik::image_dtype_rgba8); + CHECK(view_blue.get_dtype() == mapnik::image_dtype_rgba8); + CHECK(view_green.get_dtype() == mapnik::image_dtype_rgba8); + CHECK(view_yellow.get_dtype() == mapnik::image_dtype_rgba8); + CHECK(view_red.get_dtype() == mapnik::image_dtype_rgba8); + + unsigned expected_val; + using pixel_type = mapnik::image_view_rgba8::pixel_type; + // Check that all data in the view is correct + // Blue + expected_val = c_blue.rgba(); + for (std::size_t y = 0; y < view_blue.height(); ++y) + { + std::size_t width = view_blue.width(); + pixel_type const* data_1 = view_blue.get_row(y); + pixel_type const* data_2 = view_blue.get_row(y, 1); + for (std::size_t x = 0; x < width; ++x) + { + CHECK(*data_1 == expected_val); + ++data_1; + } + for (std::size_t x = 1; x < width; ++x) + { + CHECK(*data_2 == expected_val); + ++data_2; + } + } + // Green + expected_val = c_green.rgba(); + for (std::size_t y = 0; y < view_green.height(); ++y) + { + std::size_t width = view_green.width(); + pixel_type const* data_1 = view_green.get_row(y); + pixel_type const* data_2 = view_green.get_row(y, 1); + for (std::size_t x = 0; x < width; ++x) + { + CHECK(*data_1 == expected_val); + ++data_1; + } + for (std::size_t x = 1; x < width; ++x) + { + CHECK(*data_2 == expected_val); + ++data_2; + } + } + // Yellow + expected_val = c_yellow.rgba(); + for (std::size_t y = 0; y < view_yellow.height(); ++y) + { + std::size_t width = view_yellow.width(); + pixel_type const* data_1 = view_yellow.get_row(y); + pixel_type const* data_2 = view_yellow.get_row(y, 1); + for (std::size_t x = 0; x < width; ++x) + { + CHECK(*data_1 == expected_val); + ++data_1; + } + for (std::size_t x = 1; x < width; ++x) + { + CHECK(*data_2 == expected_val); + ++data_2; + } + } + // Red + expected_val = c_red.rgba(); + for (std::size_t y = 0; y < view_red.height(); ++y) + { + std::size_t width = view_red.width(); + pixel_type const* data_1 = view_red.get_row(y); + pixel_type const* data_2 = view_red.get_row(y, 1); + for (std::size_t x = 0; x < width; ++x) + { + CHECK(*data_1 == expected_val); + ++data_1; + } + for (std::size_t x = 1; x < width; ++x) + { + CHECK(*data_2 == expected_val); + ++data_2; + } + } + +} // END SECTION + +SECTION("image_view_null") +{ + mapnik::image_view_null view_null; + const mapnik::image_view_null view_null2; + mapnik::image_view_null view_null3(view_null2); + mapnik::image_view_null & view_null4 = view_null3; + + // All nulls are equal + CHECK(view_null == view_null4); + CHECK(view_null == view_null2); + + // No null is greater + CHECK_FALSE(view_null < view_null4); + CHECK_FALSE(view_null < view_null2); + + // Check defaults + CHECK(view_null.x() == 0); + CHECK(view_null.y() == 0); + CHECK(view_null.width() == 0); + CHECK(view_null.height() == 0); + CHECK(view_null.size() == 0); + CHECK(view_null.row_size() == 0); + CHECK(view_null.get_offset() == 0.0); + CHECK(view_null.get_scaling() == 1.0); + CHECK(view_null.get_dtype() == mapnik::image_dtype_null); + CHECK_FALSE(view_null.get_premultiplied()); + + // Should throw if we try to access data. + REQUIRE_THROWS(view_null(0,0)); + + CHECK(view_null.get_row(0) == nullptr); + CHECK(view_null.get_row(0,0) == nullptr); + +} // END SECTION + +SECTION("image view any") +{ + mapnik::image_view_any im_any_null; + CHECK(im_any_null.get_dtype() == mapnik::image_dtype_null); + + mapnik::image_gray8 im(4,4); + mapnik::image_view_gray8 im_view(0,0,4,4,im); + mapnik::image_view_any im_view_any(im_view); + + CHECK(im_view_any.get_dtype() == mapnik::image_dtype_gray8); + CHECK(im_view_any.width() == 4); + CHECK(im_view_any.height() == 4); + CHECK(im_view_any.size() == 16); + CHECK(im_view_any.row_size() == 4); + CHECK_FALSE(im_view_any.get_premultiplied()); + CHECK(im_view_any.get_offset() == 0.0); + CHECK(im_view_any.get_scaling() == 1.0); + +} // END SECTION + +} // END TEST CASE