This is a follow-up question for Dictionary based non-local mean implementation in C++. There are some issues about operators (operator+
and operator-
) mentioned by Edward's answer and JDługosz's comment. I am trying to propose the fixed version of Image
class as below.
operator+
andoperator-
operator overloading:friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 += input2; } friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 -= input2; }
The full
Image
class implementation:template <typename ElementT> class Image { public: Image() = default; Image(const std::size_t width, const std::size_t height): width(width), height(height), image_data(width * height) { } Image(const std::size_t width, const std::size_t height, const ElementT initVal): width(width), height(height), image_data(width * height, initVal) {} Image(const std::vector<ElementT> input, std::size_t newWidth, std::size_t newHeight): width(newWidth), height(newHeight) { if (input.size() != newWidth * newHeight) { throw std::runtime_error("Image data input and the given size are mismatched!"); } image_data = std::move(input); } constexpr ElementT& at(const unsigned int x, const unsigned int y) { checkBoundary(x, y); return image_data[y * width + x]; } constexpr ElementT const& at(const unsigned int x, const unsigned int y) const { checkBoundary(x, y); return image_data[y * width + x]; } constexpr std::size_t getWidth() const { return width; } constexpr std::size_t getHeight() const { return height; } constexpr auto getSize() { return std::make_tuple(width, height); } std::vector<ElementT> const& getImageData() const { return image_data; } // expose the internal data void print(std::string separator = "\t", std::ostream& os = std::cout) const { for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y) << separator; } os << "\n"; } os << "\n"; return; } // Enable this function if ElementT = RGB void print(std::string separator = "\t", std::ostream& os = std::cout) const requires(std::same_as<ElementT, RGB>) { for (std::size_t y = 0; y < height; ++y) { for (std::size_t x = 0; x < width; ++x) { os << "( "; for (std::size_t channel_index = 0; channel_index < 3; ++channel_index) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +at(x, y).channels[channel_index] << separator; } os << ")" << separator; } os << "\n"; } os << "\n"; return; } friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs) { const std::string separator = "\t"; for (std::size_t y = 0; y < rhs.height; ++y) { for (std::size_t x = 0; x < rhs.width; ++x) { // Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number os << +rhs.at(x, y) << separator; } os << "\n"; } os << "\n"; return os; } Image<ElementT>& operator+=(const Image<ElementT>& rhs) { assert(rhs.width == this->width); assert(rhs.height == this->height); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::plus<>{}); return *this; } Image<ElementT>& operator-=(const Image<ElementT>& rhs) { assert(rhs.width == this->width); assert(rhs.height == this->height); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::minus<>{}); return *this; } Image<ElementT>& operator*=(const Image<ElementT>& rhs) { assert(rhs.width == this->width); assert(rhs.height == this->height); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::multiplies<>{}); return *this; } Image<ElementT>& operator/=(const Image<ElementT>& rhs) { assert(rhs.width == this->width); assert(rhs.height == this->height); std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data), std::ranges::begin(image_data), std::divides<>{}); return *this; } friend bool operator==(Image<ElementT> const&, Image<ElementT> const&) = default; friend bool operator!=(Image<ElementT> const&, Image<ElementT> const&) = default; friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 += input2; } friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2) { return input1 -= input2; } Image<ElementT>& operator=(Image<ElementT> const& input) = default; // Copy Assign Image<ElementT>& operator=(Image<ElementT>&& other) = default; // Move Assign Image(const Image<ElementT> &input) = default; // Copy Constructor Image(Image<ElementT> &&input) = default; // Move Constructor private: std::size_t width; std::size_t height; std::vector<ElementT> image_data; void checkBoundary(const size_t x, const size_t y) const { assert(x < width); assert(y < height); } };
Unit tests for operator+
and operator-
:
void operatorAddInImageClassTest(const std::size_t sizex, const std::size_t sizey)
{
// Assign
auto test_image1 = TinyDIP::Image(sizex, sizey, 1);
auto test_image2 = TinyDIP::Image(sizex, sizey, 2);
// Action
auto actual_result = test_image1 + test_image2;
// Assert
auto expected_result = TinyDIP::Image(sizex, sizey, 3);
assert(actual_result == expected_result);
return;
}
void operatorMinusInImageClassTest(const std::size_t sizex, const std::size_t sizey)
{
// Assign
auto test_image1 = TinyDIP::Image(sizex, sizey, 1);
auto test_image2 = TinyDIP::Image(sizex, sizey, 2);
// Action
auto actual_result = test_image1 - test_image2;
// Assert
auto expected_result = TinyDIP::Image(sizex, sizey, -1);
assert(actual_result == expected_result);
return;
}
Full Testing Code
The full tests for operator+
and operator-
:
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <concepts>
#include <exception>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <ranges>
#include <type_traits>
#include <utility>
#include <vector>
using BYTE = unsigned char;
struct RGB
{
BYTE channels[3];
};
using GrayScale = BYTE;
namespace TinyDIP
{
#define is_size_same(x, y) {assert(x.getWidth() == y.getWidth()); assert(x.getHeight() == y.getHeight());}
template <typename ElementT>
class Image
{
public:
Image() = default;
Image(const std::size_t width, const std::size_t height):
width(width),
height(height),
image_data(width * height) { }
Image(const std::size_t width, const std::size_t height, const ElementT initVal):
width(width),
height(height),
image_data(width * height, initVal) {}
Image(const std::vector<ElementT> input, std::size_t newWidth, std::size_t newHeight):
width(newWidth),
height(newHeight)
{
if (input.size() != newWidth * newHeight)
{
throw std::runtime_error("Image data input and the given size are mismatched!");
}
image_data = std::move(input);
}
constexpr ElementT& at(const unsigned int x, const unsigned int y)
{
checkBoundary(x, y);
return image_data[y * width + x];
}
constexpr ElementT const& at(const unsigned int x, const unsigned int y) const
{
checkBoundary(x, y);
return image_data[y * width + x];
}
constexpr std::size_t getWidth() const
{
return width;
}
constexpr std::size_t getHeight() const
{
return height;
}
constexpr auto getSize()
{
return std::make_tuple(width, height);
}
std::vector<ElementT> const& getImageData() const { return image_data; } // expose the internal data
void print(std::string separator = "\t", std::ostream& os = std::cout) const
{
for (std::size_t y = 0; y < height; ++y)
{
for (std::size_t x = 0; x < width; ++x)
{
// Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
os << +at(x, y) << separator;
}
os << "\n";
}
os << "\n";
return;
}
// Enable this function if ElementT = RGB
void print(std::string separator = "\t", std::ostream& os = std::cout) const
requires(std::same_as<ElementT, RGB>)
{
for (std::size_t y = 0; y < height; ++y)
{
for (std::size_t x = 0; x < width; ++x)
{
os << "( ";
for (std::size_t channel_index = 0; channel_index < 3; ++channel_index)
{
// Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
os << +at(x, y).channels[channel_index] << separator;
}
os << ")" << separator;
}
os << "\n";
}
os << "\n";
return;
}
friend std::ostream& operator<<(std::ostream& os, const Image<ElementT>& rhs)
{
const std::string separator = "\t";
for (std::size_t y = 0; y < rhs.height; ++y)
{
for (std::size_t x = 0; x < rhs.width; ++x)
{
// Ref: https://isocpp.org/wiki/faq/input-output#print-char-or-ptr-as-number
os << +rhs.at(x, y) << separator;
}
os << "\n";
}
os << "\n";
return os;
}
Image<ElementT>& operator+=(const Image<ElementT>& rhs)
{
assert(rhs.width == this->width);
assert(rhs.height == this->height);
std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
std::ranges::begin(image_data), std::plus<>{});
return *this;
}
Image<ElementT>& operator-=(const Image<ElementT>& rhs)
{
assert(rhs.width == this->width);
assert(rhs.height == this->height);
std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
std::ranges::begin(image_data), std::minus<>{});
return *this;
}
Image<ElementT>& operator*=(const Image<ElementT>& rhs)
{
assert(rhs.width == this->width);
assert(rhs.height == this->height);
std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
std::ranges::begin(image_data), std::multiplies<>{});
return *this;
}
Image<ElementT>& operator/=(const Image<ElementT>& rhs)
{
assert(rhs.width == this->width);
assert(rhs.height == this->height);
std::transform(std::ranges::cbegin(image_data), std::ranges::cend(image_data), std::ranges::cbegin(rhs.image_data),
std::ranges::begin(image_data), std::divides<>{});
return *this;
}
friend bool operator==(Image<ElementT> const&, Image<ElementT> const&) = default;
friend bool operator!=(Image<ElementT> const&, Image<ElementT> const&) = default;
friend Image<ElementT> operator+(Image<ElementT> input1, const Image<ElementT>& input2)
{
return input1 += input2;
}
friend Image<ElementT> operator-(Image<ElementT> input1, const Image<ElementT>& input2)
{
return input1 -= input2;
}
Image<ElementT>& operator=(Image<ElementT> const& input) = default; // Copy Assign
Image<ElementT>& operator=(Image<ElementT>&& other) = default; // Move Assign
Image(const Image<ElementT> &input) = default; // Copy Constructor
Image(Image<ElementT> &&input) = default; // Move Constructor
private:
std::size_t width;
std::size_t height;
std::vector<ElementT> image_data;
void checkBoundary(const size_t x, const size_t y) const
{
assert(x < width);
assert(y < height);
}
};
}
void operatorAddInImageClassTest(const std::size_t sizex, const std::size_t sizey)
{
// Assign
auto test_image1 = TinyDIP::Image(sizex, sizey, 1);
auto test_image2 = TinyDIP::Image(sizex, sizey, 2);
// Action
auto actual_result = test_image1 + test_image2;
// Assert
auto expected_result = TinyDIP::Image(sizex, sizey, 3);
assert(actual_result == expected_result);
return;
}
void operatorMinusInImageClassTest(const std::size_t sizex, const std::size_t sizey)
{
// Assign
auto test_image1 = TinyDIP::Image(sizex, sizey, 1);
auto test_image2 = TinyDIP::Image(sizex, sizey, 2);
// Action
auto actual_result = test_image1 - test_image2;
// Assert
auto expected_result = TinyDIP::Image(sizex, sizey, -1);
assert(actual_result == expected_result);
return;
}
int main()
{
auto start = std::chrono::system_clock::now();
operatorAddInImageClassTest(10, 10);
operatorMinusInImageClassTest(10, 10);
auto max_size = std::numeric_limits<std::size_t>::max();
operatorAddInImageClassTest(max_size, max_size);
operatorMinusInImageClassTest(max_size, max_size);
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
std::cout << "Computation finished at " << std::ctime(&end_time) << "elapsed time: " << elapsed_seconds.count() << '\n';
return 0;
}
The output of the testing code above:
Computation finished at Sat Dec 25 07:08:32 2021
elapsed time: 1.2452e-05
All suggestions are welcome.
The summary information:
Which question it is a follow-up to?
What changes has been made in the code since last question?
Update and fix the
operator+
andoperator-
implementation inImage
class.Why a new review is being asked for?
If there is any possible improvement, please let me know.