From 3ae7d3812e3d16884d69f48f7955c60414881e63 Mon Sep 17 00:00:00 2001 From: dmatetelki Date: Wed, 20 Apr 2016 16:42:31 +0200 Subject: [PATCH] refactoring Marching squares: - pulling out PNG reading dependency to readPngToImageMatrix - making it header only - removing debug printing (should move into a separate header) - 2 simple tests --- lib/graph/marching_squares.hpp | 141 ++++++++++++++ lib/qtgraph/marching_squares.cpp | 240 ------------------------ lib/qtgraph/marching_squares.hpp | 28 --- lib/qtgraph/read_png_to_imagematrix.cpp | 27 +++ lib/qtgraph/read_png_to_imagematrix.hpp | 21 +++ test/CMakeLists.txt | 1 + test/graph/test_marching_squares.cpp | 49 +++++ 7 files changed, 239 insertions(+), 268 deletions(-) create mode 100644 lib/graph/marching_squares.hpp delete mode 100644 lib/qtgraph/marching_squares.cpp delete mode 100644 lib/qtgraph/marching_squares.hpp create mode 100644 lib/qtgraph/read_png_to_imagematrix.cpp create mode 100644 lib/qtgraph/read_png_to_imagematrix.hpp create mode 100644 test/graph/test_marching_squares.cpp diff --git a/lib/graph/marching_squares.hpp b/lib/graph/marching_squares.hpp new file mode 100644 index 0000000..8694ef7 --- /dev/null +++ b/lib/graph/marching_squares.hpp @@ -0,0 +1,141 @@ +#ifndef MARCHING_SQUARES_HPP +#define MARCHING_SQUARES_HPP + +#include + + +struct ImageMatrix { + enum CellType { FREE, SOLID, DESTROYABLE }; + + const std::size_t width_; + const std::size_t height_; + const std::vector cells_; + ImageMatrix(std::size_t w, std::size_t h, const std::vector& c) + : width_(w), height_(h), cells_(c) {} +}; + +struct size_t2 { + std::size_t x, y; + constexpr size_t2 (int _x, int _y) : x(_x), y(_y) {} + size_t2 (const size_t2& o) : x(o.x), y(o.y) {} + void operator+=(const size_t2 o) { x += o.x; y += o.y; } + bool operator==(const size_t2 o) const { return x == o.x && y == o.y; } +}; + + +namespace detail { + + int getMaskAt(int x, int y, const ImageMatrix& image_matrix) + { + const std::size_t width = image_matrix.width_; + const std::vector cells = image_matrix.cells_; + + const ImageMatrix::CellType quad[4] = { + cells[(y-1) * width + (x-1)], cells[(y-1) * width + x], // TL T + cells[ y * width + (x-1)], cells[ y * width + x] }; // R X + + const int mask = ((quad[0] == ImageMatrix::FREE) ? 0x0 : 0x1) + | ((quad[1] == ImageMatrix::FREE) ? 0x0 : 0x2) + | ((quad[2] == ImageMatrix::FREE) ? 0x0 : 0x4) + | ((quad[3] == ImageMatrix::FREE) ? 0x0 : 0x8); + + return mask; + } + + void visitPoint(std::size_t x, std::size_t y, + int mask, + std::vector< bool >& visited, + std::vector< std::pair >& lines, + const ImageMatrix& image_matrix) + { + const std::size_t width = image_matrix.width_; + const std::size_t height = image_matrix.height_; + + visited[y * width + x] = true; + + const bool horizontal_top = (mask == 0x7 || mask == 0x2 || mask == 0x6); + const bool horizontal_bottom = (mask == 0x8 || mask == 0x9 || mask == 0xd); + const bool vertical_left = (mask == 0x7 || mask == 0x4 || mask == 0x6); + const bool vertical_right = (mask == 0x8 || mask == 0x9 || mask == 0xb); + const bool horizontal = horizontal_top || horizontal_bottom; + const bool vertical = vertical_left || vertical_right; + + if (!horizontal && !vertical) + return; + + if (horizontal) { + + // go left till possible + size_t2 i(x, y); + while (true) { + i += size_t2(1, 0); + int next_mask = getMaskAt(i.x, i.y, image_matrix); + if (i.x < width && i.y < height && + ( (horizontal_top && next_mask == 0x3) || + (horizontal_bottom && next_mask == 0xc) || + (vertical_left && next_mask == 0x5) || + (vertical_right && next_mask == 0xa) ) && + visited[i.y*width + i.x] == false) + visited[i.y*width + i.x] = true; + else + break; + } + if (i.x != x) { + visited[i.y*width + i.x] = false; + lines.push_back(std::pair(size_t2(x, y), size_t2( i.x, i.y))); + } + } + + if (vertical) { + size_t2 i(x, y); + while (true) { + i += size_t2(0, 1); + int next_mask = getMaskAt(i.x, i.y, image_matrix); + if (i.x < width && i.y < height && + ( (horizontal_top && next_mask == 0x3) || + (horizontal_bottom && next_mask == 0xc) || + (vertical_left && next_mask == 0x5) || + (vertical_right && next_mask == 0xa) ) && + visited[i.y*width + i.x] == false) + visited[i.y*width + i.x] = true; + else + break; + } + if (i.y != y) { + visited[i.y*width + i.x] = false; + lines.push_back(std::pair(size_t2(x, y), size_t2( i.x, i.y))); + } + } + } + + +} // detail namespace + + +std::vector< std::pair > marchingSquares(const ImageMatrix& image_matrix) +{ + const std::size_t width = image_matrix.width_; + const std::size_t height = image_matrix.height_; + + std::vector visited(width * height, false); + std::vector< std::pair > lines; + + size_t2 point(1, 1); + for (point.y = 1 ;point.y < height; ++point.y) { + for (point.x = 1; point.x < width; ++point.x) { + if (visited[point.y * width + point.x] == true) + continue; + + const int mask = detail::getMaskAt(point.x, point.y, image_matrix); + if (mask == 0x0 || mask == 0xf) // empty or full + continue; + + detail::visitPoint(point.x, point.y, mask, visited, lines, image_matrix); + } + } + + return lines; +} + + +#endif // MARCHING_SQUARES_HPP diff --git a/lib/qtgraph/marching_squares.cpp b/lib/qtgraph/marching_squares.cpp deleted file mode 100644 index 1430d31..0000000 --- a/lib/qtgraph/marching_squares.cpp +++ /dev/null @@ -1,240 +0,0 @@ -#include "marching_squares.hpp" - -#include "floats.hpp" - -#include // for strerror needed by png++/error.hpp - -#include - -// #include -#include -#include - -namespace { - - namespace Color { - enum Code { - FG_RED = 31, - FG_GREEN = 32, - FG_BLUE = 34, - FG_DEFAULT = 39, - BG_RED = 41, - BG_GREEN = 42, - BG_BLUE = 44, - BG_DEFAULT = 49 - }; - class Modifier { - Code code; - public: - Modifier(Code pCode) : code(pCode) {} - friend std::ostream& - operator<<(std::ostream& os, const Modifier& mod) { - return os << "\033[" << mod.code << "m"; - } - }; - } - - struct int2 { - int x, y; - constexpr int2 (int _x, int _y) : x(_x), y(_y) {} - int2 (const int2& o) : x(o.x), y(o.y) {} - void operator+=(const int2 o) { x += o.x; y += o.y; } - }; - -#define ASSERT(x) \ - if (!(x)) std::cerr << __FILE__ << ":" << __LINE__ << ":" << __PRETTY_FUNCTION__ << (x) << std::endl; -} - -MarchingSquares::MarchingSquares() - : width_(0) - , height_(0) - , cells_() -{ -} - -void MarchingSquares::ReadImage(const std::string& filename) -{ - png::image image(filename); // throws std_error(filename); - - width_ = image.get_width(); - height_ = image.get_height(); - cells_.reserve(width_ * height_); - - for (size_t y = 0; y < height_; ++y) { - const png::image::row_type& row = image[y]; - for (size_t x = 0; x < width_; ++x) { - CellType c; // map pixel luminance to cell type - if (row[x] < 16) c = SOLID; // black - else if (row[x] >= 240) c = FREE; // white - else c = DESTROYABLE; // everything else - - /// @todo extend image? - // mark borders as SOLID, such that the entities in the world cannot fall off -// if (x == 0 || x == width_ - 1 || y == 0 || y == height_ - 1) -// c = SOLID; - - // mark white "hole" cells sorrounded by 4 black cells as black - if (y > 1 && y < height_-1 && x > 1 && x < width_-1 && - image[y][x] >= 240 && - image[y-1][x] < 240 && - image[y+1][x] < 240 && - image[y][x-1] < 240 && - image[y][x+1] < 240) - c = SOLID; - - /// @note is it too much? - // mark black "lost" cells sorrounded by 4 white cells as white -// if (y > 1 && y < height_-1 && x > 1 && x < width_-1 && -// image[y][x] < 240 && -// (image[y-1][x-1] >= 240 && image[y-1][x] >= 240 && image[y-1][x+1] >= 240 && image[y][x+1] >= 240 && image[y+1][x+1] >= 240 && image[y+1][x] >= 240 && image[y+1][x-1] >= 240 && image[y][x-1] >= 240)) -// c = FREE; - -// // mark black "bump on the wall" cells as white -// if (y > 1 && y < height_-1 && x > 1 && x < width_-1 && -// image[y][x] < 240 && -// ((image[y-1][x-1] < 240 && image[y-1][x] < 240 && image[y-1][x+1] < 240 && image[y][x+1] >= 240 && image[y+1][x+1] >= 240 && image[y+1][x] >= 240 && image[y+1][x-1] >= 240 && image[y][x-1] >= 240) || -// (image[y-1][x-1] >= 240 && image[y-1][x] >= 240 && image[y-1][x+1] < 240 && image[y][x+1] < 240 && image[y+1][x+1] < 240 && image[y+1][x] >= 240 && image[y+1][x-1] >= 240 && image[y][x-1] >= 240) || -// (image[y-1][x-1] >= 240 && image[y-1][x] >= 240 && image[y-1][x+1] >= 240 && image[y][x+1] >= 240 && image[y+1][x+1] < 240 && image[y+1][x] < 240 && image[y+1][x-1] < 240 && image[y][x-1] >= 240) || -// (image[y-1][x-1] < 240 && image[y-1][x] >= 240 && image[y-1][x+1] >= 240 && image[y][x+1] >= 240 && image[y+1][x+1] >= 240 && image[y+1][x] >= 240 && image[y+1][x-1] < 240 && image[y][x-1] < 240))) -// c = FREE; - - cells_.push_back(c); - } - } -} - - -std::vector< std::pair > -MarchingSquares::RunMarchingSquares() { - - std::vector visited(width_*height_, false); - std::vector< std::pair > lines; - - int2 point(1, 1); - for (point.y = 1 ;point.y < (int)height_; ++point.y) { - for (point.x = 1; point.x < (int)width_; ++point.x) { - - const int mask = getMaskAt(point.x, point.y); - - Color::Modifier blue(Color::FG_BLUE); - Color::Modifier def(Color::FG_DEFAULT); - if (mask != 0) - std::cout << std::left << std::setw(3) << mask; - else - std::cout << blue << mask << def << " "; - - if (visited[point.y*width_ + point.x] == true) - continue; - - if (mask == 0x0 || mask == 0xf) // empty or full - continue; - - visitPoint(point.x, point.y, mask, visited, lines); - } - std::cout << std::endl; - } - - return lines; -} - -int MarchingSquares::getMaskAt(int x, int y) const -{ - const CellType quad[4] = { - cells_[(y-1) * width_ + (x-1)], cells_[(y-1) * width_ + x], // TL T - cells_[ y * width_ + (x-1)], cells_[ y * width_ + x] }; // R X - - const int mask = ((quad[0] == FREE) ? 0x0 : 0x1) - | ((quad[1] == FREE) ? 0x0 : 0x2) - | ((quad[2] == FREE) ? 0x0 : 0x4) - | ((quad[3] == FREE) ? 0x0 : 0x8); - - return mask; -} - -void MarchingSquares::visitPoint(int x, int y, int mask, std::vector< bool >& visited, std::vector< std::pair >& lines) const -{ - visited[y*width_ + x] = true; - - const bool horizontal_top = (mask == 0x7 || mask == 0x2 || mask == 0x6); - const bool horizontal_bottom = (mask == 0x8 || mask == 0x9 || mask == 0xd); - const bool vertical_left = (mask == 0x7 || mask == 0x4 || mask == 0x6); - const bool vertical_right = (mask == 0x8 || mask == 0x9 || mask == 0xb); - const bool horizontal = horizontal_top || horizontal_bottom; - const bool vertical = vertical_left || vertical_right; - - if (!horizontal && !vertical) - return; - - if (mask == 0x8 || mask == 0xb) { - int2 i(x, y); - while ((getMaskAt(i.x, i.y+1) == 0xe || getMaskAt(i.x, i.y+1) == 0x6) && - getMaskAt(i.x-1, i.y+1) == 0x8 /*&& - visited[(i.y)*width_ + i.x] == false*/) { - visited[(i.y+1)*width_ + i.x] = true; - visited[(i.y+1)*width_ + i.x-1] = true; - i += int2(-1, 1); - } - if (i.x != x || i.y != y) { - visited[(i.y-1)*width_ + i.x+1] = false; - lines.push_back(std::pair(float2(x, y), float2( i.x, i.y))); - } - } - if (mask == 1) { - int2 i(x, y); - while ((getMaskAt(i.x-1, i.y) == 0x6 || getMaskAt(i.x-1, i.y) == 0x7) && - getMaskAt(i.x-1, i.y+1) == 0x1 /*&& - visited[(i.y)*width_ + i.x] == false*/) { - visited[i.y*width_ + i.x-1] = true; - visited[(i.y+1)*width_ + i.x-1] = true; - i += int2(-1, 1); - } - if (i.x != x || i.y != y) { - visited[(i.y-1)*width_ + i.x+1] = false; - lines.push_back(std::pair(float2(x, y), float2( i.x, i.y))); - } - } - - if (horizontal) { - - // go left till possible - int2 i(x, y); - while (true) { - i += int2(1, 0); - int next_mask = getMaskAt(i.x, i.y); - if (i.x < (int)width_ && i.y < (int)height_ && - ( (horizontal_top && next_mask == 0x3) || - (horizontal_bottom && next_mask == 0xc) || - (vertical_left && next_mask == 0x5) || - (vertical_right && next_mask == 0xa) ) && - visited[i.y*width_ + i.x] == false) - visited[i.y*width_ + i.x] = true; - else - break; - } - if (i.x != x) { - visited[i.y*width_ + i.x] = false; - lines.push_back(std::pair(float2(x, y), float2( i.x, i.y))); - } - } - - if (vertical) { - int2 i(x, y); - while (true) { - i += int2(0, 1); - int next_mask = getMaskAt(i.x, i.y); - if (i.x < (int)width_ && i.y < (int)height_ && - ( (horizontal_top && next_mask == 0x3) || - (horizontal_bottom && next_mask == 0xc) || - (vertical_left && next_mask == 0x5) || - (vertical_right && next_mask == 0xa) ) && - visited[i.y*width_ + i.x] == false) - visited[i.y*width_ + i.x] = true; - else - break; - } - if (i.y != y) { - visited[i.y*width_ + i.x] = false; - lines.push_back(std::pair(float2(x, y), float2( i.x, i.y))); - } - } -} diff --git a/lib/qtgraph/marching_squares.hpp b/lib/qtgraph/marching_squares.hpp deleted file mode 100644 index 13e9985..0000000 --- a/lib/qtgraph/marching_squares.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef WORLD_WORLD_HPP -#define WORLD_WORLD_HPP - -#include -#include - -class float2; - -class MarchingSquares { - -public: - enum CellType { FREE, SOLID, DESTROYABLE }; - - MarchingSquares(); - - void ReadImage(const std::string& filename); - std::vector< std::pair > RunMarchingSquares(); - -private: - int getMaskAt(int x, int y) const; - void visitPoint(int x, int y, int mask, std::vector& visited, std::vector< std::pair >& lines) const; - - std::size_t width_; - std::size_t height_; - std::vector cells_; -}; - -#endif // WORLD_WORLD_HPP diff --git a/lib/qtgraph/read_png_to_imagematrix.cpp b/lib/qtgraph/read_png_to_imagematrix.cpp new file mode 100644 index 0000000..fefaa48 --- /dev/null +++ b/lib/qtgraph/read_png_to_imagematrix.cpp @@ -0,0 +1,27 @@ +#include "read_png_to_imagematrix.hpp" + +#include +#include // for strerror needed by png++/error.hpp + +ImageMatrix readPngToImageMatrix(const std::string& filename) +{ + png::image image(filename); // throws std_error(filename); + + const std::size_t width = image.get_width(); + const std::size_t height = image.get_height(); + std::vector cells(width * height); + + for (std::size_t y = 0; y < height; ++y) { + const png::image::row_type& row = image[y]; + for (std::size_t x = 0; x < width; ++x) { + ImageMatrix::CellType c; // map pixel luminance to cell type + if (row[x] < 16) c = ImageMatrix::SOLID; // black + else if (row[x] >= 240) c = ImageMatrix::FREE; // white + else c = ImageMatrix::DESTROYABLE; // everything else + + cells.push_back(c); + } + } + return ImageMatrix(width, height, cells); +} + diff --git a/lib/qtgraph/read_png_to_imagematrix.hpp b/lib/qtgraph/read_png_to_imagematrix.hpp new file mode 100644 index 0000000..c45c4a7 --- /dev/null +++ b/lib/qtgraph/read_png_to_imagematrix.hpp @@ -0,0 +1,21 @@ +#ifndef READ_PNG_TO_IMAGE_MATRIX_HPP +#define READ_PNG_TO_IMAGE_MATRIX_HPP + +#include +#include + + +struct ImageMatrix { + enum CellType { FREE, SOLID, DESTROYABLE }; + + const std::size_t width_; + const std::size_t height_; + const std::vector cells_; + ImageMatrix(std::size_t w, std::size_t h, const std::vector& c) + : width_(w), height_(h), cells_(c) {} +}; + +ImageMatrix readPngToImageMatrix(const std::string& filename); // throws std_error(filename); + + +#endif // READ_PNG_TO_IMAGE_MATRIX_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a762b1d..16299e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -7,6 +7,7 @@ graph/test_graph.cpp graph/test_priority_queue.cpp graph/test_quad_tree.cpp graph/test_graph_algorithms.cpp +graph/test_marching_squares.cpp test_main.cpp) diff --git a/test/graph/test_marching_squares.cpp b/test/graph/test_marching_squares.cpp new file mode 100644 index 0000000..af61cc7 --- /dev/null +++ b/test/graph/test_marching_squares.cpp @@ -0,0 +1,49 @@ +#include + +#include "../catch.hpp" + +#include "fixture.hpp" + + +TEST_CASE( "Marching squares", "[marching_squares][algorithm]" ) { + + SECTION("empty") { + const ImageMatrix empty(0, 0, std::vector()); + const std::vector< std::pair > result = marchingSquares(empty); + REQUIRE ( result.size() == 0 ); + } + + SECTION("dot") { + const std::vector dot_v { + ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, + ImageMatrix::FREE, ImageMatrix::SOLID, ImageMatrix::FREE, + ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE + }; + const ImageMatrix dot_i(3, 3, dot_v); + const std::vector< std::pair > result = marchingSquares(dot_i); + REQUIRE ( result.size() == 4 ); + + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 1), size_t2(2, 1)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 1), size_t2(1, 2)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(2, 1), size_t2(2, 2)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 2), size_t2(2, 2)) ) != result.end() ); + } + + SECTION("horizontal line") { + const std::vector line_v { + ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, + ImageMatrix::FREE, ImageMatrix::SOLID, ImageMatrix::SOLID, ImageMatrix::SOLID, ImageMatrix::FREE, + ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE, ImageMatrix::FREE + }; + const ImageMatrix line_i(5, 3, line_v); + const std::vector< std::pair > result = marchingSquares(line_i); + REQUIRE ( result.size() == 4 ); + + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 1), size_t2(4, 1)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 1), size_t2(1, 2)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(4, 1), size_t2(4, 2)) ) != result.end() ); + REQUIRE ( std::find(result.begin(), result.end(), std::pair(size_t2(1, 2), size_t2(4, 2)) ) != result.end() ); + } + +} +