Added tests for write, and added tests/implementation of operator==. Also added implementation of FillChannel()

This commit is contained in:
Leonetienne 2022-03-06 12:45:09 +01:00
parent eb4fc0e964
commit 04a1104065
8 changed files with 223 additions and 12 deletions

View File

@ -37,7 +37,11 @@ namespace Leonetienne::BmpPP {
// Re-initialize the pixelbuffer // Re-initialize the pixelbuffer
pixelBuffer.clear(); pixelBuffer.clear();
pixelBuffer.resize(size.x * size.y * GetNumColorChannels()); pixelBuffer.resize(size.x * size.y * GetNumChannels());
// If we're initializing with an alpha channel, set it to 255
if (colormode == Colormode::RGBA)
FillChannel(3, 255);
return; return;
} }
@ -51,7 +55,11 @@ namespace Leonetienne::BmpPP {
// Re-initialize the pixelbuffer // Re-initialize the pixelbuffer
pixelBuffer.clear(); pixelBuffer.clear();
pixelBuffer.resize(size.x * size.y * GetNumColorChannels()); pixelBuffer.resize(size.x * size.y * GetNumChannels());
// If we're initializing with an alpha channel, set it to 255
if (colormode == Colormode::RGBA)
FillChannel(3, 255);
return; return;
} }
@ -70,7 +78,7 @@ namespace Leonetienne::BmpPP {
CHECK_IF_INITIALIZED CHECK_IF_INITIALIZED
const std::size_t pixelIndex = const std::size_t pixelIndex =
(position.y * size.x + position.x) * GetNumColorChannels(); (position.y * size.x + position.x) * GetNumChannels();
if (pixelIndex >= pixelBuffer.size()) if (pixelIndex >= pixelBuffer.size())
throw std::runtime_error("Pixel index out of range!"); throw std::runtime_error("Pixel index out of range!");
@ -82,7 +90,7 @@ namespace Leonetienne::BmpPP {
CHECK_IF_INITIALIZED CHECK_IF_INITIALIZED
const std::size_t pixelIndex = const std::size_t pixelIndex =
(position.y * size.x + position.x) * GetNumColorChannels(); (position.y * size.x + position.x) * GetNumChannels();
if (pixelIndex >= pixelBuffer.size()) if (pixelIndex >= pixelBuffer.size())
throw std::runtime_error("Pixel index out of range!"); throw std::runtime_error("Pixel index out of range!");
@ -142,7 +150,7 @@ namespace Leonetienne::BmpPP {
return colormode; return colormode;
} }
std::size_t BMP::GetNumColorChannels() const { std::size_t BMP::GetNumChannels() const {
CHECK_IF_INITIALIZED CHECK_IF_INITIALIZED
switch (colormode) { switch (colormode) {
@ -167,6 +175,34 @@ namespace Leonetienne::BmpPP {
return isInitialized; return isInitialized;
} }
bool BMP::operator==(const BMP &other) const {
// Check metadata
if (colormode != other.colormode)
return false;
if (size != other.size)
return false;
// Check pixel values
if (pixelBuffer != other.pixelBuffer)
return false;
return true;
}
bool BMP::operator!=(const BMP &other) const {
return !operator==(other);
}
void BMP::FillChannel(const size_t &channel, const std::uint8_t value) {
const std::size_t numChannels = GetNumChannels();
for (std::size_t i = 0; i < pixelBuffer.size(); i += numChannels)
pixelBuffer[i + channel] = value;
return;
}
} }
#undef CHECK_IF_INITIALIZED #undef CHECK_IF_INITIALIZED

View File

@ -50,8 +50,8 @@ namespace Leonetienne::BmpPP {
//! Will return the color mode of the image //! Will return the color mode of the image
[[nodiscard]] const Colormode& GetColormode() const; [[nodiscard]] const Colormode& GetColormode() const;
//! Will return the amount of color channels used //! Will return the amount of channels used
[[nodiscard]] std::size_t GetNumColorChannels() const; [[nodiscard]] std::size_t GetNumChannels() const;
//! Will return the size of the raw pixel buffer, in bytes //! Will return the size of the raw pixel buffer, in bytes
[[nodiscard]] std::size_t GetPixelbufferSize() const; [[nodiscard]] std::size_t GetPixelbufferSize() const;
@ -67,6 +67,12 @@ namespace Leonetienne::BmpPP {
//! Returns false, if unable to open, or parse, file //! Returns false, if unable to open, or parse, file
bool Read(const std::string& filename); bool Read(const std::string& filename);
//! Will compare two images for being exactly identical regarding resolution, bit depth, and pixel values.
bool operator==(const BMP& other) const;
//! Will compare two images for not being exactly identical regarding resolution, bit depth, and pixel values.
bool operator!=(const BMP& other) const;
//! Will mirror the image horizontally //! Will mirror the image horizontally
void MirrorHorizontally(); void MirrorHorizontally();
@ -91,6 +97,9 @@ namespace Leonetienne::BmpPP {
//! Will copy the specified rectangle-area, and return it as a new image //! Will copy the specified rectangle-area, and return it as a new image
BMP Crop(const Eule::Rect& area); BMP Crop(const Eule::Rect& area);
//! Will fill a specific channel with a value
void FillChannel(const std::size_t& channel, const std::uint8_t value);
private: private:
Eule::Vector2i size; Eule::Vector2i size;
Colormode colormode; Colormode colormode;

View File

@ -21,7 +21,7 @@ namespace Leonetienne::BmpPP {
// Populate dib header // Populate dib header
bmpHeader.dibHeader.imageWidth = image.size.x; bmpHeader.dibHeader.imageWidth = image.size.x;
bmpHeader.dibHeader.imageHeight = image.size.y; bmpHeader.dibHeader.imageHeight = image.size.y;
bmpHeader.dibHeader.numBitsPerPixel = image.GetNumColorChannels() * 8; bmpHeader.dibHeader.numBitsPerPixel = image.GetNumChannels() * 8;
// The size of the pixel array is not known yet (because rows require to be padded) // The size of the pixel array is not known yet (because rows require to be padded)
@ -30,7 +30,7 @@ namespace Leonetienne::BmpPP {
packedPixels.reserve(image.pixelBuffer.size()); packedPixels.reserve(image.pixelBuffer.size());
// How many channels do we have? // How many channels do we have?
const std::size_t numChannels = image.GetNumColorChannels(); const std::size_t numChannels = image.GetNumChannels();
// Calculate how many padding bytes to add per row // Calculate how many padding bytes to add per row
const std::size_t paddingBytesPerRow = (4 - ((image.size.x * numChannels) % 4)) % 4; const std::size_t paddingBytesPerRow = (4 - ((image.size.x * numChannels) % 4)) % 4;

View File

@ -22,6 +22,8 @@ add_executable(Test
ReInitialize.cpp ReInitialize.cpp
Uninitialized.cpp Uninitialized.cpp
Read.cpp Read.cpp
Write.cpp
OperatorEquals.cpp
) )
# Move test images to build dir # Move test images to build dir

55
Test/OperatorEquals.cpp Normal file
View File

@ -0,0 +1,55 @@
#include <Bmp.h>
#include <stdexcept>
#include "Catch2.h"
using namespace Leonetienne::BmpPP;
using namespace Eule;
// Don't have to test operator not equal, because it just returns the opposite of this
// Tests that two RGB images containing almost every possible color are equal, after being copied
TEST_CASE(__FILE__"/CopiedImagesAreEqual", "[OperatorEqual]")
{
// Read a gradient image
BMP bmp_a("base_gradient.bmp");
// Copy it
BMP bmp_b = bmp_a;
// Assert that they are equal
REQUIRE(bmp_a == bmp_b);
return;
}
// Tests that changing a single pixel channel results in them not being equal anymore
TEST_CASE(__FILE__"/OneDifferingValueMakesUnequal", "[OperatorEqual]")
{
// Read a gradient image
BMP bmp_a("base_gradient.bmp");
// Copy it
BMP bmp_b = bmp_a;
// Bop it
*(bmp_a.GetPixel(bmp_a.GetDimensions() / 2) + 1) = 69;
// Assert that they are equal
REQUIRE_FALSE(bmp_a == bmp_b);
return;
}
// Tests that two images with the exact same pixelbuffer, but differing metadata are not equal
TEST_CASE(__FILE__"/SamePixelbufferButDifferentMetadataUnequal", "[OperatorEqual]")
{
// Create image a
BMP bmp_a(Vector2i(800, 600), Colormode::RGB); // 1440000 values of 0
// Create image b
BMP bmp_b(Vector2i(600, 600), Colormode::RGBA); // Also 1440000 values of 0
bmp_b.FillChannel(3, 0); // Make sure the alpha channel actually is zeroed
// They only differ by their metadata. Not by pixel data. Make sure they are not euqal.
REQUIRE_FALSE(bmp_a == bmp_b);
return;
}

View File

@ -12,7 +12,7 @@ TEST_CASE(__FILE__"/ReInitialize", "[ReInitialize]")
SECTION("Check that the initial values are OK") { SECTION("Check that the initial values are OK") {
REQUIRE(bmp.GetDimensions().x == 800); REQUIRE(bmp.GetDimensions().x == 800);
REQUIRE(bmp.GetDimensions().y == 600); REQUIRE(bmp.GetDimensions().y == 600);
REQUIRE(bmp.GetNumColorChannels() == 4); REQUIRE(bmp.GetNumChannels() == 4);
REQUIRE(bmp.GetColormode() == Colormode::RGBA); REQUIRE(bmp.GetColormode() == Colormode::RGBA);
REQUIRE(bmp.GetPixelbufferSize() == 800*600*4); REQUIRE(bmp.GetPixelbufferSize() == 800*600*4);
} }
@ -23,7 +23,7 @@ TEST_CASE(__FILE__"/ReInitialize", "[ReInitialize]")
SECTION("Check that getters now return the updated values") { SECTION("Check that getters now return the updated values") {
REQUIRE(bmp.GetDimensions().x == 1920); REQUIRE(bmp.GetDimensions().x == 1920);
REQUIRE(bmp.GetDimensions().y == 1080); REQUIRE(bmp.GetDimensions().y == 1080);
REQUIRE(bmp.GetNumColorChannels() == 3); REQUIRE(bmp.GetNumChannels() == 3);
REQUIRE(bmp.GetColormode() == Colormode::RGB); REQUIRE(bmp.GetColormode() == Colormode::RGB);
REQUIRE(bmp.GetPixelbufferSize() == 1920*1080*3); REQUIRE(bmp.GetPixelbufferSize() == 1920*1080*3);
} }

View File

@ -37,7 +37,7 @@ TEST_CASE(__FILE__"/RuntimeErrorOnUninitialized", "[Uninitialized]")
); );
REQUIRE_THROWS_AS( REQUIRE_THROWS_AS(
bmp.GetNumColorChannels() bmp.GetNumChannels()
, std::runtime_error , std::runtime_error
); );

109
Test/Write.cpp Normal file
View File

@ -0,0 +1,109 @@
#include <Bmp.h>
#include "Catch2.h"
#include <tuple>
#include <Eule/Math.h>
using namespace Leonetienne::BmpPP;
using namespace Eule;
#define IMSIZE Vector2i(800, 600)
namespace {
inline std::tuple<std::uint8_t, std::uint8_t, std::uint8_t, std::uint8_t>
ColorGradient(const Vector2i& pos)
{
std::uint8_t r, g, b, a;
// This assumes IMSIZE.x >= IMSIZE.y
r = ((float)pos.x / (float)IMSIZE.x) * 255.0f;
g = (1.0f - (float)pos.x / (float)IMSIZE.x) * 255.0f;
b = (1.0f - (float)pos.y / (float)IMSIZE.x) * 255.0f;
a = Math::Clamp(((float)pos.y / (float)IMSIZE.x) * 2 * 255.0f, 0.0, 255.0);
return std::make_tuple(r, g, b, a);
}
}
// Tests that writing an image works at all (without crashing the program)
TEST_CASE(__FILE__"/WritingDoesntCrash", "[Write]")
{
SECTION("RGB image") {
// Create a new RGB image
BMP bmp(IMSIZE, Colormode::RGB);
// Write it to a file
bmp.Write("test_artifact.bmp");
}
SECTION("RGBA image") {
// Create a new RGB image
BMP bmp(IMSIZE, Colormode::RGBA);
// Write it to a file
bmp.Write("test_artifact.bmp");
}
return;
}
// Tests that writing a file will write the correct image data
TEST_CASE(__FILE__"/WillWriteTheCorrectData", "[Write]")
{
SECTION("RGB image") {
// Create a new RGB image
BMP bmp(IMSIZE, Colormode::RGB);
// Populate it with colors
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const auto px = ColorGradient(Vector2i(x, y));
bmp.SetPixel(
Vector2i(x, y),
std::get<0>(px),
std::get<1>(px),
std::get<2>(px)
);
}
// Write it to a file
bmp.Write("test_artifact_rgb_gradient.bmp");
// Read it back in (reading function is tested independently)
BMP readBmp("test_artifact_rgb_gradient.bmp");
// Compare them
REQUIRE(bmp == readBmp);
}
SECTION("RGBA image") {
// Create a new RGB image
BMP bmp(IMSIZE, Colormode::RGBA);
// Populate it with colors
for (std::size_t x = 0; x < bmp.GetDimensions().x; x++)
for (std::size_t y = 0; y < bmp.GetDimensions().y; y++) {
const auto px = ColorGradient(Vector2i(x, y));
bmp.SetPixel(
Vector2i(x, y),
std::get<0>(px),
std::get<1>(px),
std::get<2>(px),
std::get<3>(px)
);
}
// Write it to a file
bmp.Write("test_artifact_rgba_gradient.bmp");
// Read it back in (reading function is tested independently)
BMP readBmp("test_artifact_rgba_gradient.bmp");
// Compare them
REQUIRE(bmp == readBmp);
}
return;
}