Merge pull request #2930 from mapycz/improve-visual-test-8
Visual tests: add tiled mode
This commit is contained in:
commit
a440a99f56
5 changed files with 190 additions and 103 deletions
|
@ -1,53 +0,0 @@
|
|||
/*****************************************************************************
|
||||
*
|
||||
* 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 COMPARE_IMAGES_HPP
|
||||
#define COMPARE_IMAGES_HPP
|
||||
|
||||
// stl
|
||||
#include <memory>
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_reader.hpp>
|
||||
|
||||
namespace visual_tests
|
||||
{
|
||||
|
||||
template <typename Image>
|
||||
unsigned compare_images(Image const & actual, std::string const & reference)
|
||||
{
|
||||
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(reference, "png"));
|
||||
if (!reader.get())
|
||||
{
|
||||
throw mapnik::image_reader_exception("Failed to load: " + reference);
|
||||
}
|
||||
|
||||
mapnik::image_any ref_image_any = reader->read(0, 0, reader->width(), reader->height());
|
||||
Image const & reference_image = mapnik::util::get<Image>(ref_image_any);
|
||||
|
||||
return mapnik::compare(actual, reference_image, 0, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -35,21 +35,23 @@ namespace visual_tests
|
|||
|
||||
struct map_size
|
||||
{
|
||||
map_size(int _width, int _height) : width(_width), height(_height) { }
|
||||
map_size(std::size_t _width, std::size_t _height) : width(_width), height(_height) { }
|
||||
map_size() { }
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
std::size_t width = 0;
|
||||
std::size_t height = 0;
|
||||
};
|
||||
|
||||
struct config
|
||||
{
|
||||
config() : status(true),
|
||||
scales({ 1.0, 2.0 }),
|
||||
sizes({ { 500, 100 } }) { }
|
||||
sizes({ { 500, 100 } }),
|
||||
tiles({ { 1, 1 } }) { }
|
||||
|
||||
bool status;
|
||||
std::vector<double> scales;
|
||||
std::vector<map_size> sizes;
|
||||
std::vector<map_size> tiles;
|
||||
};
|
||||
|
||||
enum result_state : std::uint8_t
|
||||
|
@ -66,6 +68,7 @@ struct result
|
|||
result_state state;
|
||||
std::string renderer_name;
|
||||
map_size size;
|
||||
map_size tiles;
|
||||
double scale_factor;
|
||||
boost::filesystem::path actual_image_path;
|
||||
boost::filesystem::path reference_image_path;
|
||||
|
|
|
@ -23,15 +23,16 @@
|
|||
#ifndef RENDERER_HPP
|
||||
#define RENDERER_HPP
|
||||
|
||||
#include "compare_images.hpp"
|
||||
|
||||
// stl
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
// mapnik
|
||||
#include <mapnik/map.hpp>
|
||||
#include <mapnik/image_util.hpp>
|
||||
#include <mapnik/image_reader.hpp>
|
||||
#include <mapnik/agg_renderer.hpp>
|
||||
#if defined(GRID_RENDERER)
|
||||
#include <mapnik/grid/grid_renderer.hpp>
|
||||
|
@ -56,10 +57,20 @@ struct renderer_base
|
|||
using image_type = ImageType;
|
||||
|
||||
static constexpr const char * ext = ".png";
|
||||
static constexpr const bool support_tiles = true;
|
||||
|
||||
unsigned compare(image_type const & actual, boost::filesystem::path const& reference) const
|
||||
{
|
||||
return compare_images(actual, reference.string());
|
||||
std::unique_ptr<mapnik::image_reader> reader(mapnik::get_image_reader(reference.string(), "png"));
|
||||
if (!reader.get())
|
||||
{
|
||||
throw mapnik::image_reader_exception("Failed to load: " + reference.string());
|
||||
}
|
||||
|
||||
mapnik::image_any ref_image_any = reader->read(0, 0, reader->width(), reader->height());
|
||||
ImageType const & reference_image = mapnik::util::get<ImageType>(ref_image_any);
|
||||
|
||||
return mapnik::compare(actual, reference_image, 0, true);
|
||||
}
|
||||
|
||||
void save(image_type const & image, boost::filesystem::path const& path) const
|
||||
|
@ -106,6 +117,7 @@ struct svg_renderer : renderer_base<std::string>
|
|||
{
|
||||
static constexpr const char * name = "svg";
|
||||
static constexpr const char * ext = ".svg";
|
||||
static constexpr const bool support_tiles = false;
|
||||
|
||||
image_type render(mapnik::Map const & map, double scale_factor) const
|
||||
{
|
||||
|
@ -191,10 +203,35 @@ struct grid_renderer : renderer_base<mapnik::image_rgba8>
|
|||
};
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
void set_rectangle(T const & src, T & dst, std::size_t x, std::size_t y)
|
||||
{
|
||||
mapnik::box2d<int> ext0(0, 0, dst.width(), dst.height());
|
||||
mapnik::box2d<int> ext1(x, y, x + src.width(), y + src.height());
|
||||
|
||||
if (ext0.intersects(ext1))
|
||||
{
|
||||
mapnik::box2d<int> box = ext0.intersect(ext1);
|
||||
for (std::size_t pix_y = box.miny(); pix_y < static_cast<std::size_t>(box.maxy()); ++pix_y)
|
||||
{
|
||||
typename T::pixel_type * row_to = dst.get_row(pix_y);
|
||||
typename T::pixel_type const * row_from = src.get_row(pix_y - y);
|
||||
|
||||
for (std::size_t pix_x = box.minx(); pix_x < static_cast<std::size_t>(box.maxx()); ++pix_x)
|
||||
{
|
||||
row_to[pix_x] = row_from[pix_x - x];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Renderer>
|
||||
class renderer
|
||||
{
|
||||
public:
|
||||
using renderer_type = Renderer;
|
||||
using image_type = typename Renderer::image_type;
|
||||
|
||||
renderer(boost::filesystem::path const & _output_dir, boost::filesystem::path const & _reference_dir, bool _overwrite)
|
||||
: ren(), output_dir(_output_dir), reference_dir(_reference_dir), overwrite(_overwrite)
|
||||
{
|
||||
|
@ -202,8 +239,42 @@ public:
|
|||
|
||||
result test(std::string const & name, mapnik::Map const & map, double scale_factor) const
|
||||
{
|
||||
typename Renderer::image_type image(ren.render(map, scale_factor));
|
||||
boost::filesystem::path reference = reference_dir / image_file_name(name, map.width(), map.height(), scale_factor, true, Renderer::ext);
|
||||
image_type image(ren.render(map, scale_factor));
|
||||
return report(image, name, { map.width(), map.height() }, { 1, 1 }, scale_factor);
|
||||
}
|
||||
|
||||
result test_tiles(std::string const & name, mapnik::Map & map, map_size const & tiles, double scale_factor) const
|
||||
{
|
||||
image_type image(map.width(), map.height());
|
||||
mapnik::box2d<double> box = map.get_current_extent();
|
||||
map.resize(image.width() / tiles.width, image.height() / tiles.height);
|
||||
double tile_box_width = box.width() / tiles.width;
|
||||
double tile_box_height = box.height() / tiles.height;
|
||||
|
||||
for (std::size_t tile_y = 0; tile_y < tiles.height; tile_y++)
|
||||
{
|
||||
for (std::size_t tile_x = 0; tile_x < tiles.width; tile_x++)
|
||||
{
|
||||
mapnik::box2d<double> tile_box(
|
||||
box.minx() + tile_x * tile_box_width,
|
||||
box.miny() + tile_y * tile_box_height,
|
||||
box.minx() + (tile_x + 1) * tile_box_width,
|
||||
box.miny() + (tile_y + 1) * tile_box_height);
|
||||
map.zoom_to_box(tile_box);
|
||||
image_type tile(ren.render(map, scale_factor));
|
||||
set_rectangle(tile, image, tile_x * tile.width(), (tiles.height - 1 - tile_y) * tile.height());
|
||||
}
|
||||
}
|
||||
return report(image, name, { image.width(), image.height() }, tiles, scale_factor);
|
||||
}
|
||||
|
||||
result report(image_type const & image,
|
||||
std::string const & name,
|
||||
map_size const & size,
|
||||
map_size const & tiles,
|
||||
double scale_factor) const
|
||||
{
|
||||
boost::filesystem::path reference = reference_dir / image_file_name(name, size, tiles, scale_factor, true);
|
||||
bool reference_exists = boost::filesystem::exists(reference);
|
||||
result res;
|
||||
|
||||
|
@ -211,14 +282,15 @@ public:
|
|||
res.name = name;
|
||||
res.renderer_name = Renderer::name;
|
||||
res.scale_factor = scale_factor;
|
||||
res.size = map_size(map.width(), map.height());
|
||||
res.size = size;
|
||||
res.tiles = tiles;
|
||||
res.reference_image_path = reference;
|
||||
res.diff = reference_exists ? ren.compare(image, reference) : 0;
|
||||
|
||||
if (res.diff)
|
||||
{
|
||||
boost::filesystem::create_directories(output_dir);
|
||||
boost::filesystem::path path = output_dir / image_file_name(name, map.width(), map.height(), scale_factor, false, Renderer::ext);
|
||||
boost::filesystem::path path = output_dir / image_file_name(name, size, tiles, scale_factor, false);
|
||||
res.actual_image_path = path;
|
||||
res.state = STATE_FAIL;
|
||||
ren.save(image, path);
|
||||
|
@ -235,16 +307,23 @@ public:
|
|||
|
||||
private:
|
||||
std::string image_file_name(std::string const & test_name,
|
||||
double width,
|
||||
double height,
|
||||
map_size const & size,
|
||||
map_size const & tiles,
|
||||
double scale_factor,
|
||||
bool reference,
|
||||
std::string const& ext) const
|
||||
bool reference) const
|
||||
{
|
||||
std::stringstream s;
|
||||
s << test_name << '-' << width << '-' << height << '-'
|
||||
<< std::fixed << std::setprecision(1) << scale_factor
|
||||
<< '-' << Renderer::name << (reference ? "-reference" : "") << ext;
|
||||
s << test_name << '-' << size.width << '-' << size.height << '-';
|
||||
if (tiles.width > 1 || tiles.height > 1)
|
||||
{
|
||||
s << tiles.width << 'x' << tiles.height << '-';
|
||||
}
|
||||
s << std::fixed << std::setprecision(1) << scale_factor << '-' << Renderer::name;
|
||||
if (reference)
|
||||
{
|
||||
s << "-reference";
|
||||
}
|
||||
s << Renderer::ext;
|
||||
return s.str();
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,12 @@ namespace visual_tests
|
|||
|
||||
void console_report::report(result const & r)
|
||||
{
|
||||
s << '"' << r.name << '-' << r.size.width << '-' << r.size.height << '-' << std::fixed <<
|
||||
std::setprecision(1) << r.scale_factor << "\" with " << r.renderer_name << "... ";
|
||||
s << '"' << r.name << '-' << r.size.width << '-' << r.size.height;
|
||||
if (r.tiles.width > 1 || r.tiles.height > 1)
|
||||
{
|
||||
s << '-' << r.tiles.width << 'x' << r.tiles.height;
|
||||
}
|
||||
s << '-' << std::fixed << std::setprecision(1) << r.scale_factor << "\" with " << r.renderer_name << "... ";
|
||||
|
||||
switch (r.state)
|
||||
{
|
||||
|
|
|
@ -34,21 +34,55 @@ namespace visual_tests
|
|||
class renderer_visitor
|
||||
{
|
||||
public:
|
||||
renderer_visitor(std::string const & name, mapnik::Map const & map, double scale_factor)
|
||||
: name_(name), map_(map), scale_factor_(scale_factor)
|
||||
renderer_visitor(std::string const & name,
|
||||
mapnik::Map & map,
|
||||
map_size const & tiles,
|
||||
double scale_factor,
|
||||
result_list & results,
|
||||
report_type & report)
|
||||
: name_(name),
|
||||
map_(map),
|
||||
tiles_(tiles),
|
||||
scale_factor_(scale_factor),
|
||||
results_(results),
|
||||
report_(report)
|
||||
{
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
result operator()(T const & renderer) const
|
||||
template <typename T, typename std::enable_if<T::renderer_type::support_tiles>::type* = nullptr>
|
||||
void operator()(T const & renderer)
|
||||
{
|
||||
return renderer.test(name_, map_, scale_factor_);
|
||||
result r;
|
||||
if (tiles_.width == 1 && tiles_.height == 1)
|
||||
{
|
||||
r = renderer.test(name_, map_, scale_factor_);
|
||||
}
|
||||
else
|
||||
{
|
||||
r = renderer.test_tiles(name_, map_, tiles_, scale_factor_);
|
||||
}
|
||||
mapnik::util::apply_visitor(report_visitor(r), report_);
|
||||
results_.push_back(std::move(r));
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<!T::renderer_type::support_tiles>::type* = nullptr>
|
||||
void operator()(T const & renderer)
|
||||
{
|
||||
if (tiles_.width == 1 && tiles_.height == 1)
|
||||
{
|
||||
result r = renderer.test(name_, map_, scale_factor_);
|
||||
mapnik::util::apply_visitor(report_visitor(r), report_);
|
||||
results_.push_back(std::move(r));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::string const & name_;
|
||||
mapnik::Map const & map_;
|
||||
mapnik::Map & map_;
|
||||
map_size const & tiles_;
|
||||
double scale_factor_;
|
||||
result_list & results_;
|
||||
report_type & report_;
|
||||
};
|
||||
|
||||
runner::runner(runner::path_type const & styles_dir,
|
||||
|
@ -173,12 +207,12 @@ result_list runner::test_range(files_iterator begin, files_iterator end, std::re
|
|||
|
||||
result_list runner::test_one(runner::path_type const& style_path, config cfg, report_type & report) const
|
||||
{
|
||||
mapnik::Map m(cfg.sizes.front().width, cfg.sizes.front().height);
|
||||
mapnik::Map map(cfg.sizes.front().width, cfg.sizes.front().height);
|
||||
result_list results;
|
||||
|
||||
try
|
||||
{
|
||||
mapnik::load_map(m, style_path.string(), true);
|
||||
mapnik::load_map(map, style_path.string(), true);
|
||||
}
|
||||
catch (std::exception const& ex)
|
||||
{
|
||||
|
@ -191,7 +225,7 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re
|
|||
throw;
|
||||
}
|
||||
|
||||
mapnik::parameters const & params = m.get_extra_parameters();
|
||||
mapnik::parameters const & params = map.get_extra_parameters();
|
||||
|
||||
boost::optional<mapnik::value_integer> status = params.get<mapnik::value_integer>("status", cfg.status);
|
||||
|
||||
|
@ -208,32 +242,52 @@ result_list runner::test_one(runner::path_type const& style_path, config cfg, re
|
|||
parse_map_sizes(*sizes, cfg.sizes);
|
||||
}
|
||||
|
||||
boost::optional<std::string> tiles = params.get<std::string>("tiles");
|
||||
|
||||
if (tiles)
|
||||
{
|
||||
cfg.tiles.clear();
|
||||
parse_map_sizes(*tiles, cfg.tiles);
|
||||
}
|
||||
|
||||
boost::optional<std::string> bbox_string = params.get<std::string>("bbox");
|
||||
mapnik::box2d<double> box;
|
||||
|
||||
if (bbox_string)
|
||||
{
|
||||
box.from_string(*bbox_string);
|
||||
}
|
||||
|
||||
std::string name(style_path.stem().string());
|
||||
|
||||
for (map_size const & size : cfg.sizes)
|
||||
for (auto const & size : cfg.sizes)
|
||||
{
|
||||
m.resize(size.width, size.height);
|
||||
|
||||
boost::optional<std::string> bbox_string = params.get<std::string>("bbox");
|
||||
|
||||
if (bbox_string)
|
||||
for (auto const & scale_factor : cfg.scales)
|
||||
{
|
||||
mapnik::box2d<double> bbox;
|
||||
bbox.from_string(*bbox_string);
|
||||
m.zoom_to_box(bbox);
|
||||
}
|
||||
else
|
||||
{
|
||||
m.zoom_all();
|
||||
}
|
||||
|
||||
for (double const & scale_factor : cfg.scales)
|
||||
{
|
||||
for(auto const& ren : renderers_)
|
||||
for (auto const & tiles_count : cfg.tiles)
|
||||
{
|
||||
result r = mapnik::util::apply_visitor(renderer_visitor(name, m, scale_factor), ren);
|
||||
results.emplace_back(r);
|
||||
mapnik::util::apply_visitor(report_visitor(r), report);
|
||||
if (!tiles_count.width || !tiles_count.height)
|
||||
{
|
||||
throw std::runtime_error("Cannot render zero tiles.");
|
||||
}
|
||||
if (size.width % tiles_count.width || size.height % tiles_count.height)
|
||||
{
|
||||
throw std::runtime_error("Tile size is not an integer.");
|
||||
}
|
||||
|
||||
for (auto const & ren : renderers_)
|
||||
{
|
||||
map.resize(size.width, size.height);
|
||||
if (box.valid())
|
||||
{
|
||||
map.zoom_to_box(box);
|
||||
}
|
||||
else
|
||||
{
|
||||
map.zoom_all();
|
||||
}
|
||||
mapnik::util::apply_visitor(renderer_visitor(name, map, tiles_count, scale_factor, results, report), ren);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue