// SPDX-License-Identifier: LGPL-3.0-or-later #include #include #include #include #include #include "bresenham_line_tool.h" #include "canvas_buffer.h" #include "dda_line_tool.h" #include "non_recursive_fill_tool.h" #include "rectangle_tool.h" #include "recursive_fill_tool.h" #include "util.h" TEST_CASE("Transform Mirror") { // elementary operations REQUIRE(transform(TRANSFORM_MIRROR_X, 10, 20) == std::make_pair(10, -20)); REQUIRE(transform(TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(-10, 20)); // composite operations REQUIRE(transform(TRANSFORM_MIRROR_X | TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(-10, -20)); } TEST_CASE("Transform Rotate") { REQUIRE(transform(TRANSFORM_ROTATE_CW, 10, 20) == std::make_pair(-20, 10)); // 4th quadrant REQUIRE(transform(TRANSFORM_ROTATE_CW, -20, 10) == std::make_pair(-10, -20)); // 3rd quadrant REQUIRE(transform(TRANSFORM_ROTATE_CW, -10, -20) == std::make_pair(20, -10)); // 2nd quadrant REQUIRE(transform(TRANSFORM_ROTATE_CW, 20, -10) == std::make_pair(10, 20)); // 1st quadrant REQUIRE(transform(TRANSFORM_ROTATE_CCW, 20, -10) == std::make_pair(-10, -20)); // 1nd quadrant REQUIRE(transform(TRANSFORM_ROTATE_CCW, -10, -20) == std::make_pair(-20, 10)); // 2rd quadrant REQUIRE(transform(TRANSFORM_ROTATE_CCW, -20, 10) == std::make_pair(10, 20)); // 3th quadrant REQUIRE(transform(TRANSFORM_ROTATE_CCW, 10, 20) == std::make_pair(20, -10)); // 4st quadrant } TEST_CASE("Transform Rotate + Mirror") { REQUIRE(transform(TRANSFORM_ROTATE_CW | TRANSFORM_MIRROR_X, 10, 20) == std::make_pair(-20, -10)); REQUIRE(transform(TRANSFORM_ROTATE_CW | TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(20, 10)); REQUIRE( transform(TRANSFORM_ROTATE_CW | TRANSFORM_MIRROR_X | TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(20, -10)); REQUIRE(transform(TRANSFORM_ROTATE_CCW | TRANSFORM_MIRROR_X, 10, 20) == std::make_pair(20, 10)); REQUIRE(transform(TRANSFORM_ROTATE_CCW | TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(-20, -10)); REQUIRE( transform(TRANSFORM_ROTATE_CCW | TRANSFORM_MIRROR_X | TRANSFORM_MIRROR_Y, 10, 20) == std::make_pair(-20, 10)); } TEST_CASE("Transform = Inverse Transform ○ Transform") { const int x = GENERATE(take(10, random(-100, 100))); const int y = GENERATE(take(10, random(-100, 100))); // this iterates over all possible transformations, // even bogus ones (like rotating cw and ccw) for (Transformation transformation = 0; transformation < 0b10000; transformation++) { int xt, yt; std::tie(xt, yt) = transform(transformation, x, y); int xti, yti; std::tie(xti, yti) = transform_inv(transformation, xt, yt); REQUIRE(x == xti); REQUIRE(y == yti); } } TEST_CASE("Transformation to standard case") { REQUIRE(transformation_to_standard_case(5, 20, 20, 10) == 0); REQUIRE(transformation_to_standard_case(5, 5, 20, 15) == TRANSFORM_MIRROR_X); REQUIRE(transformation_to_standard_case(20, 15, 5, 5) == TRANSFORM_MIRROR_Y); REQUIRE(transformation_to_standard_case(20, 10, 5, 20) == (TRANSFORM_MIRROR_X | TRANSFORM_MIRROR_Y)); REQUIRE(transformation_to_standard_case(5, 20, 15, 5) == (TRANSFORM_ROTATE_CW | TRANSFORM_MIRROR_X)); REQUIRE(transformation_to_standard_case(5, 5, 15, 20) == TRANSFORM_ROTATE_CCW); REQUIRE(transformation_to_standard_case(15, 5, 5, 20) == (TRANSFORM_ROTATE_CCW | TRANSFORM_MIRROR_X)); REQUIRE(transformation_to_standard_case(15, 20, 5, 5) == TRANSFORM_ROTATE_CW); } TEST_CASE("Transformation to standard case (prop)") { int x0 = GENERATE(take(10, random(-100, 100))); int y0 = GENERATE(take(10, random(-100, 100))); int x1 = GENERATE(take(10, random(-100, 100))); int y1 = GENERATE(take(10, random(-100, 100))); const Transformation transformation = transformation_to_standard_case(x0, y0, x1, y1); transform_mut(transformation, x0, y0); transform_mut(transformation, x1, y1); REQUIRE(x0 <= x1); REQUIRE(y0 >= y1); } TEST_CASE("Bresenham/DDA line tool (prop: for every row/column, only one pixel " "is set)") { const int size = 100; canvas_buffer *canvas = new canvas_buffer(size, size); bresenham_line_tool *tool_bresenham = new bresenham_line_tool(*canvas); dda_line_tool *tool_dda = new dda_line_tool(*canvas); tool_base *tool; const int tool_idx = GENERATE(0, 1); switch (tool_idx) { case 0: tool = tool_bresenham; break; case 1: tool = tool_dda; break; } const int x0 = GENERATE(take(10, random(0, size - 1))); const int y0 = GENERATE(take(10, random(0, size - 1))); const int x1 = GENERATE(take(10, random(0, size - 1))); const int y1 = GENERATE(take(10, random(0, size - 1))); tool->draw(x0, y0, x1, y1); const int x_min = std::min(x0, x1); const int x_max = std::max(x0, x1); const int y_min = std::min(y0, y1); const int y_max = std::max(y0, y1); // Depending on what the direction of the line (rounded to the next 90°) is, // either every row or column has only one pixel set. bool vertical = false; int draw_direction_min; int draw_direction_max; int unique_direction_min; int unique_direction_max; if (abs(y1 - y0) > abs(x1 - x0)) { vertical = true; draw_direction_min = y_min; draw_direction_max = y_max; unique_direction_min = x_min; unique_direction_max = x_max; } else { draw_direction_min = x_min; draw_direction_max = x_max; unique_direction_min = y_min; unique_direction_max = y_max; } bool all_sums_are_one = true; int sum; for (int dd = draw_direction_min; dd <= draw_direction_max; dd++) { sum = 0; for (int ud = unique_direction_min; ud <= unique_direction_max; ud++) { int x, y; if (vertical) { x = ud; y = dd; } else { x = dd; y = ud; } if (canvas->get_pixel(x, y)) sum++; } if (sum != 1) all_sums_are_one = false; } REQUIRE(all_sums_are_one); } TEST_CASE("Fill (recursive and non recursive) test shape") { canvas_buffer *canvas = new canvas_buffer(100, 100); bresenham_line_tool *tool_line = new bresenham_line_tool(*canvas); recursive_fill_tool *tool_fill_recursive = new recursive_fill_tool(*canvas); non_recursive_fill_tool *tool_fill_non_recursive = new non_recursive_fill_tool(*canvas); tool_base *tool_fill; const int tool_fill_idx = GENERATE(0, 1); switch (tool_fill_idx) { case 0: tool_fill = tool_fill_recursive; break; case 1: tool_fill = tool_fill_non_recursive; break; } canvas->draw_test_shape(); REQUIRE_FALSE(canvas->get_pixel(50, 49)); REQUIRE_FALSE(canvas->get_pixel(50, 25)); REQUIRE_FALSE(canvas->get_pixel(50, 75)); tool_fill->draw(50, 25); REQUIRE(canvas->get_pixel(50, 49)); REQUIRE(canvas->get_pixel(50, 25)); REQUIRE(canvas->get_pixel(50, 75)); REQUIRE_FALSE(canvas->get_pixel(75, 40)); REQUIRE_FALSE(canvas->get_pixel(75, 60)); tool_fill->draw(75, 50); REQUIRE(canvas->get_pixel(75, 40)); REQUIRE(canvas->get_pixel(75, 60)); REQUIRE_FALSE(canvas->get_pixel(0, 0)); REQUIRE_FALSE(canvas->get_pixel(99, 99)); tool_fill->draw(25, 50); REQUIRE(canvas->get_pixel(0, 0)); REQUIRE(canvas->get_pixel(99, 99)); } TEST_CASE("Fill recursive == Fill non recursive (prop, 5 random lines)") { const int size = 100; canvas_buffer *canvas_recursive = new canvas_buffer(size, size); canvas_buffer *canvas_non_recursive = new canvas_buffer(size, size); bresenham_line_tool *tool_line_recursive = new bresenham_line_tool(*canvas_recursive); bresenham_line_tool *tool_line_non_recursive = new bresenham_line_tool(*canvas_non_recursive); recursive_fill_tool *tool_fill_recursive = new recursive_fill_tool(*canvas_recursive); non_recursive_fill_tool *tool_fill_non_recursive = new non_recursive_fill_tool(*canvas_non_recursive); for (int i = 0; i < 5; i++) { const int x0 = GENERATE(take(1, random(0, size - 1))); const int y0 = GENERATE(take(1, random(0, size - 1))); const int x1 = GENERATE(take(1, random(0, size - 1))); const int y1 = GENERATE(take(1, random(0, size - 1))); tool_line_recursive->draw(x0, y0, x1, y1); tool_line_non_recursive->draw(x0, y0, x1, y1); } const int x = GENERATE(take(3, random(0, size - 1))); const int y = GENERATE(take(3, random(0, size - 1))); tool_fill_recursive->draw(x, y); tool_fill_non_recursive->draw(x, y); bool equal = true; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { if (canvas_recursive->get_pixel(x, y) != canvas_non_recursive->get_pixel(x, y)) equal = false; } } REQUIRE(equal); } TEST_CASE("Rectangle (prop)") { const int size = 100; const int x0 = GENERATE(take(5, random(0, size - 1))); const int y0 = GENERATE(take(5, random(0, size - 1))); const int x1 = GENERATE(take(5, random(0, size - 1))); const int y1 = GENERATE(take(5, random(0, size - 1))); const int x_min = std::min(x0, x1); const int x_max = std::max(x0, x1); const int y_min = std::min(y0, y1); const int y_max = std::max(y0, y1); canvas_buffer *canvas = new canvas_buffer(size, size); rectangle_tool *tool = new rectangle_tool(*canvas); tool->draw(x0, y0, x1, y1); bool pass = true; for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { if (((x == x0 || x == x1) && (y >= y_min && y <= y_max)) || ((y == y0 || y == y1) && (x >= x_min && x <= x_max))) { if (!canvas->get_pixel(x, y)) pass = false; } else { if (canvas->get_pixel(x, y)) pass = false; } } } REQUIRE(pass); }