diff --git a/Src/BMP.cpp b/Src/BMP.cpp index d7162a6..874903b 100644 --- a/Src/BMP.cpp +++ b/Src/BMP.cpp @@ -466,6 +466,47 @@ namespace Leonetienne::BmpPP { return; } + + BMP BMP::Crop(const Eule::Vector2i &topleft, const Eule::Vector2i &cropSize) const { + CHECK_IF_INITIALIZED + + // Check that the cropping rect is within our pixel coordinates + if ( + (topleft.x < 0) || (topleft.y < 0) || + (topleft.x + cropSize.x > size.x) || (topleft.y + cropSize.y > size.y) + ) + throw std::runtime_error("Rect coordinates are not contained inside image!"); + + // Check that the area of the cropping rect is > 0 + if (cropSize.x * cropSize.y == 0) + throw std::runtime_error("Cropping area is 0!"); + + /////////////////// + // Enough checks... + + // Create a new image of the rects size, and our color mode + BMP bmp(cropSize, colormode); + + // Now copy over our pixel data + const std::size_t numChannels = GetNumChannels(); + const std::size_t sourceRowLength = size.x * numChannels; + const std::size_t targetRowLength = bmp.GetDimensions().x * numChannels; + + for (std::size_t y = topleft.y; y < topleft.y + cropSize.y; y++) { + const std::size_t sourceRowIndex = y * sourceRowLength; + const std::size_t targetRowIndex = (y - topleft.y) * targetRowLength; + + // Now just copy over this entire row(segment) + std::copy( + pixelBuffer.cbegin() + sourceRowIndex + topleft.x * numChannels, + pixelBuffer.cbegin() + sourceRowIndex + (topleft.x * numChannels) + targetRowLength, + bmp.pixelBuffer.begin() + targetRowIndex + ); + } + + // Done. + return bmp; + } } #undef CHECK_IF_INITIALIZED diff --git a/Src/BMP.h b/Src/BMP.h index 062f34d..0977a09 100644 --- a/Src/BMP.h +++ b/Src/BMP.h @@ -107,7 +107,7 @@ namespace Leonetienne::BmpPP { void SwapChannels(const std::size_t& channel1, const std::size_t& channel2); //! Will copy the specified rectangle-area, and return it as a new image - BMP Crop(const Eule::Rect& area) const; + BMP Crop(const Eule::Vector2i& topleft, const Eule::Vector2i& size) const; //! Will fill a specific channel with a value void FillChannel(const std::size_t& channel, const std::uint8_t value); diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index c3b2ed7..285fd40 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(Test SwapChannels.cpp Rotate.cpp ConvertColormode.cpp + Crop.cpp ) # Move test images to build dir diff --git a/Test/Crop.cpp b/Test/Crop.cpp new file mode 100644 index 0000000..f7d5783 --- /dev/null +++ b/Test/Crop.cpp @@ -0,0 +1,100 @@ +#include +#include +#include "Catch2.h" + +using namespace Leonetienne::BmpPP; +using namespace Eule; + +// Tests that cropping produces the correct result +TEST_CASE(__FILE__"/HappyPath", "[Cropping]") +{ + SECTION("RGB") { + // Read an image + BMP bmp("base_hachi.bmp"); + + // Crop it + bmp = bmp.Crop( + Vector2i(95, 83), + Vector2i(149, 239) + ); + + // Read reference image + const BMP reference("base_hachi_cropped.bmp"); + + // Assert that they are equal + REQUIRE(bmp == reference); + } + + SECTION("RGBA") { + // Read an image + BMP bmp("basea_hachi.bmp"); + + // Crop it + bmp = bmp.Crop( + Vector2i(95, 83), + Vector2i(149, 239) + ); + + // Read reference image + const BMP reference("basea_hachi_cropped.bmp"); + + // Assert that they are equal + REQUIRE(bmp == reference); + } + + return; +} + +// Tests that cropping with extreme points works (like, 0,0, widht, height) +TEST_CASE(__FILE__"/ExtremePoints", "[Cropping]") +{ + SECTION("Crop includes {0,0}") { + // Read an image + BMP bmp("base_hachi.bmp"); + + // Crop it + bmp = bmp.Crop( + Vector2i(0, 0), + Vector2i(252, 337) + ); + + // Read reference image + const BMP reference("base_hachi_cropped_extreme_topleft.bmp"); + + // Assert that they are equal + REQUIRE(bmp == reference); + } + + SECTION("Crop includes {width, height}") { + // Read an image + BMP bmp("base_hachi.bmp"); + + // Crop it + bmp = bmp.Crop( + Vector2i(96, 54), + bmp.GetDimensions() - Vector2i(96, 54) // As far-right as we can get + ); + + // Read reference image + const BMP reference("base_hachi_cropped_extreme_bottomright.bmp"); + + // Assert that they are equal + REQUIRE(bmp == reference); + } + + SECTION("Crop includes {0, 0, width, height}") { + // Read an image + BMP bmp("base_hachi.bmp"); + + // Crop it (without taking away anything, lol) + BMP cropped = bmp.Crop( + Vector2i(0, 0), + bmp.GetDimensions() // As far-right as we can get + ); + + // Assert that the cropped image remains the same (as we cropped from 0,0 to width,height) + REQUIRE(bmp == cropped); + } + + return; +} diff --git a/Test/TestAssets/base_hachi.bmp b/Test/TestAssets/base_hachi.bmp new file mode 100644 index 0000000..66f0d5f Binary files /dev/null and b/Test/TestAssets/base_hachi.bmp differ diff --git a/Test/TestAssets/base_hachi_cropped.bmp b/Test/TestAssets/base_hachi_cropped.bmp new file mode 100644 index 0000000..5c770b8 Binary files /dev/null and b/Test/TestAssets/base_hachi_cropped.bmp differ diff --git a/Test/TestAssets/base_hachi_cropped_extreme_bottomright.bmp b/Test/TestAssets/base_hachi_cropped_extreme_bottomright.bmp new file mode 100644 index 0000000..86722eb Binary files /dev/null and b/Test/TestAssets/base_hachi_cropped_extreme_bottomright.bmp differ diff --git a/Test/TestAssets/base_hachi_cropped_extreme_topleft.bmp b/Test/TestAssets/base_hachi_cropped_extreme_topleft.bmp new file mode 100644 index 0000000..858d275 Binary files /dev/null and b/Test/TestAssets/base_hachi_cropped_extreme_topleft.bmp differ diff --git a/Test/TestAssets/basea_hachi.bmp b/Test/TestAssets/basea_hachi.bmp new file mode 100644 index 0000000..4209e8f Binary files /dev/null and b/Test/TestAssets/basea_hachi.bmp differ diff --git a/Test/TestAssets/basea_hachi_cropped.bmp b/Test/TestAssets/basea_hachi_cropped.bmp new file mode 100644 index 0000000..4e3d600 Binary files /dev/null and b/Test/TestAssets/basea_hachi_cropped.bmp differ