Added tests for write, and added tests/implementation of operator==. Also added implementation of FillChannel()
This commit is contained in:
parent
eb4fc0e964
commit
04a1104065
46
Src/BMP.cpp
46
Src/BMP.cpp
@ -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
|
||||||
|
13
Src/BMP.h
13
Src/BMP.h
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
55
Test/OperatorEquals.cpp
Normal 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;
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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
109
Test/Write.cpp
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user