Code Review Asked by JimmyHu on January 21, 2021
I am trying to implement a monochromic image container with std::unique_ptr
.
The example usages
The example usages is as below.
int main()
{
auto test_data = std::make_unique<unsigned char[]>(100 * 100);
for (size_t i = 0; i < 100 * 100; i++)
{
test_data[i] = 3;
}
auto test = DigitalImageProcessing::MonoImage(DigitalImageProcessing::MyImageSize(100, 100), test_data);
auto test2 = test++;
std::cout << test.Sum() << std::endl;
std::cout << test2.Sum() << std::endl;
return 0;
}
The experimental implementation
The contents in MyImageSize.h file:
#ifndef IMAGESIZE
#define IMAGESIZE
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstdbool>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <execution>
#include <exception>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
namespace DigitalImageProcessing
{
template<class T = unsigned int>
class MyImageSize
{
private:
T xsize;
T ysize;
public:
MyImageSize(T new_xsize, T new_ysize)
{
this->xsize = new_xsize;
this->ysize = new_ysize;
}
~MyImageSize()
{
}
T GetSizeX()
{
return this->xsize;
}
T GetSizeY()
{
return this->ysize;
}
void SetSizeX(T sizex)
{
this->xsize = sizex;
return;
}
void SetSizeY(T sizey)
{
this->ysize = sizey;
return;
}
};
}
#endif
The contents in MyImageSize.cpp file:
#include "IMAGESIZE.h"
The contents in MonoPixelOperation.h file:
#ifndef MONOPIXELOPERATION
#define MONOPIXELOPERATION
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstdbool>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <execution>
#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <omp.h>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <variant>
#include <vector>
namespace DigitalImageProcessing
{
class MonoPixelOperation
{
public:
MonoPixelOperation()
{
}
~MonoPixelOperation()
{
}
static void SetMonoPixelValue(unsigned char *input_pixel, unsigned char value);
static void SetMonoPixelValue(unsigned char *input_pixel, unsigned int value);
static void SetMonoPixelValue(unsigned char *input_pixel, signed int value);
static void SetMonoPixelValue(unsigned char *input_pixel, long double value);
static void SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, unsigned char value);
static void SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, long double value);
static void SetMonoPixelValue(std::shared_ptr<unsigned char> input_pixel, unsigned char value);
static unsigned char GetMonoPixelValue(unsigned char *input_pixel);
static void SetMonoPixelValueToZero(unsigned char *input_pixel);
private:
const static unsigned char PixelMAXValue = 255;
const static unsigned char PixelMINValue = 0;
template<class T>
static unsigned char TruncatePixelValue(T value)
{
if (value > PixelMAXValue)
{
return PixelMAXValue;
}
else if (value < PixelMINValue)
{
return PixelMINValue;
}
else
{
return static_cast<unsigned char>(value);
}
}
};
}
#endif // !MONOPIXELOPERATION
The contents in MonoPixelOperation.cpp file:
#include "MonoPixelOperation.h"
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(unsigned char * input_pixel, unsigned char value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(unsigned char * input_pixel, unsigned int value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(unsigned char * input_pixel, signed int value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(unsigned char * input_pixel, long double value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, unsigned char value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, long double value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(std::shared_ptr<unsigned char> input_pixel, unsigned char value)
{
*input_pixel = TruncatePixelValue(value);
return;
}
unsigned char DigitalImageProcessing::MonoPixelOperation::GetMonoPixelValue(unsigned char * input_pixel)
{
return *input_pixel;
}
void DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValueToZero(unsigned char * input_pixel)
{
*input_pixel = 0;
return;
}
The contents in MonoImage.h file:
#ifndef MONOIMAGE
#define MONOIMAGE
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <complex>
#include <concepts>
#include <cstdbool>
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <execution>
#include <exception>
#include <fstream>
#include <functional>
#include <future>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <omp.h>
#include <optional>
#include <ranges>
#include <stdexcept>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
#include "ImageSize.h"
#include "MonoPixelOperation.h"
namespace DigitalImageProcessing
{
template<class SizeT = unsigned int>
class MonoImage
{
public:
MonoImage()
{
}
constexpr auto GetimageSizeX()
{
return image_size.xsize;
}
constexpr auto GetimageSizeY()
{
return this->image_size.ysize;
}
constexpr auto GetimageSize()
{
return this->image_size;
}
std::unique_ptr<unsigned char[]> GetImageData()
{
auto output = std::make_unique<unsigned char[]>(image_size.xsize * image_size.ysize);
if (image_data == NULL)
{
std::printf("Memory allocation error!");
throw std::logic_error("Memory allocation error!");
}
for (SizeT y = 0; y < image_size.ysize; y++)
{
for (SizeT x = 0; x < image_size.xsize; x++)
{
DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValue(&output[y * image_size.xsize + x],
DigitalImageProcessing::MonoPixelOperation::GetMonoPixelValue(&image_data[y * image_size.xsize + x]));
}
}
return output;
}
MonoImage& operator=(MonoImage const& input) // Copy Assign
{
this->image_size.xsize = input.image_size.xsize;
this->image_size.ysize = input.image_size.ysize;
this->image_data = std::make_unique<unsigned char[]>(image_size.xsize * image_size.ysize);
for (SizeT y = 0; y < image_size.ysize; y++)
{
for (SizeT x = 0; x < image_size.xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&this->image_data[y * image_size.xsize + x],
MonoPixelOperation::GetMonoPixelValue(&input.image_data[y * image_size.xsize + x]));
}
}
return *this;
}
MonoImage& operator=(MonoImage&& other) // Move Assign
{
image_size = std::move(other.image_size);
image_data = std::move(other.image_data);
return *this;
}
MonoImage& operator+=(const MonoImage& rhs)
{
if (rhs.image_size.xsize == this->image_size.xsize &&
rhs.image_size.ysize == this->image_size.ysize)
{
auto output = MonoImage(rhs.image_size.xsize, rhs.image_size.ysize);
for (SizeT y = 0; y < image_size.ysize; y++)
{
for (SizeT x = 0; x < image_size.xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y* image_size.xsize + x],
MonoPixelOperation::GetMonoPixelValue(&image_data[(y)*(image_size.xsize) + (x)]) +
MonoPixelOperation::GetMonoPixelValue(&rhs.image_data[(y) * (image_size.xsize) + (x)])
);
}
}
return output;
}
}
MonoImage& operator++()
{
return *this;
}
/* This operator is used as
MonoImage MonoImage1(100, 100);
MonoImage MonoImage2(100, 100);
MonoImage2 = MonoImage1++;
*/
MonoImage operator++(int)
{
auto output = MonoImage(this->image_size.GetSizeX(), this->image_size.GetSizeY());
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y* image_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y)*(image_size.GetSizeX()) + (x)])
);
}
}
for (SizeT y = 0; y< image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x< image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&this->image_data[y * image_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y) * (image_size.GetSizeX()) + (x)]) + 1
);
}
}
return output;
}
MonoImage& operator--()
{
return *this;
}
MonoImage operator--(int)
{
auto output = MonoImage(this->image_size.xsize, this->image_size.ysize);
for (SizeT y = 0; y< image_size.ysize; y++)
{
for (SizeT x = 0; x< image_size.xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y* image_size.xsize + x],
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y)*(image_size.xsize) + (x)]) - 1
);
}
}
return output;
}
friend bool operator==(const MonoImage& input1, const MonoImage& input2)
{
if (input1.image_size != input2.image_size)
{
return false;
}
for (SizeT y = 0; y < input1.image_size.ysize; y++)
{
for (SizeT x = 0; x < input1.image_size.xsize; x++)
{
if (MonoPixelOperation::GetMonoPixelValue(&input1.image_data[y * input1.image_size.xsize + x]) -
MonoPixelOperation::GetMonoPixelValue(&input2.image_data[y * input2.image_size.xsize + x]) != 0)
{
return false;
};
}
}
return true;
}
friend bool operator!=(const MonoImage& input1, const MonoImage& input2)
{
return !(input1 == input2);
}
MonoImage Difference(const MonoImage& input)
{
auto output = MonoImage(this->image_size.xsize, this->image_size.ysize);
for (SizeT y = 0; y < image_size.ysize; y++)
{
for (SizeT x = 0; x < image_size.xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y * image_size.xsize + x],
std::abs(
MonoPixelOperation::GetMonoPixelValue(&input.image_data[(y) * (image_size.xsize) + (x)]) -
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y) * (image_size.xsize) + (x)])
)
);
}
}
return output;
}
constexpr auto Subtract(const MonoImage& input)
{
auto output = MonoImage<SizeT>(this->image_size.GetSizeX(), this->image_size.GetSizeY());
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y * image_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&input.image_data[(y) * (image_size.GetSizeX()) + (x)]) -
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y) * (image_size.GetSizeX()) + (x)])
);
}
}
return output;
}
constexpr auto Sum()
{
unsigned long long int ReturnValue = 0;
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
ReturnValue += MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y) * (image_size.GetSizeX()) + (x)]);
}
}
return ReturnValue;
}
MonoImage(const MonoImage<SizeT> &input)
: image_size(input.image_size)
{
if (input.image_size.xsize == 0 || input.image_size.ysize == 0)
{
return;
}
image_data = std::make_unique<unsigned char[]>(image_size.xsize * image_size.ysize);
for (SizeT y = 0; y < image_size.ysize; y++)
{
for (SizeT x = 0; x < image_size.xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&image_data[y * image_size.xsize + x],
MonoPixelOperation::GetMonoPixelValue(&input.image_data[y * image_size.xsize + x]));
}
}
}
/* Move Constructor
*/
MonoImage(MonoImage &&input) : image_size(input.image_size), image_data(std::move(input.image_data))
{
}
MonoImage(DigitalImageProcessing::MyImageSize<SizeT> input_size)
: image_size(input_size)
{
this->image_data = std::make_unique<unsigned char[]>(this->image_size.GetSizeX() * this->image_size.GetSizeY());
if (this->image_data == NULL)
{
printf("Memory allocation error!");
throw std::logic_error("Memory allocation error!");
}
SetImageDataToBlack();
}
MonoImage(DigitalImageProcessing::MyImageSize<SizeT> input_size, const std::unique_ptr<unsigned char[]>& input_image_data)
: image_size(input_size)
{
this->image_data = std::make_unique<unsigned char[]>(this->image_size.GetSizeX() * this->image_size.GetSizeY());
if (this->image_data == NULL)
{
printf("Memory allocation error!");
throw std::logic_error("Memory allocation error!");
}
for (SizeT y = 0; y < input_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < input_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&this->image_data[y * input_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&input_image_data[y * input_size.GetSizeX() + x]));
}
}
}
template<class T1, class T2>
MonoImage(const T1& xsize, const T2& ysize)
: image_size(MyImageSize<SizeT>(static_cast<SizeT>(xsize), static_cast<SizeT>(ysize)))
{
this->image_data = std::make_unique<unsigned char[]>(xsize * ysize);
if (this->image_data == NULL)
{
std::printf("Memory allocation error!");
throw std::logic_error("Memory allocation error!");
}
SetImageDataToBlack();
}
template<class T1, class T2>
MonoImage(const SizeT& xsize, const SizeT& ysize, const std::unique_ptr<unsigned char[]>& input_image_data)
: image_size(MyImageSize<SizeT>(static_cast<SizeT>(xsize), static_cast<SizeT>(ysize)))
{
this->image_data = std::make_unique<unsigned char[]>(xsize * ysize);
if (this->image_data == NULL)
{
printf("Memory allocation error!");
throw std::logic_error("Memory allocation error!");
}
for (SizeT y = 0; y < ysize; y++)
{
for (SizeT x = 0; x < xsize; x++)
{
MonoPixelOperation::SetMonoPixelValue(&this->image_data[y * xsize + x],
MonoPixelOperation::GetMonoPixelValue(&input_image_data[y * xsize + x]));
}
}
}
~MonoImage()
{}
private:
MyImageSize<SizeT> image_size;
std::unique_ptr<unsigned char[]> image_data;
void SetImageDataToBlack()
{
for (SizeT y = 0; y < this->image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < this->image_size.GetSizeX(); x++)
{
DigitalImageProcessing::MonoPixelOperation::SetMonoPixelValueToZero(&image_data[y * image_size.GetSizeX() + x]);
}
}
}
void SetImageDataToWhite()
{
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&image_data[y * image_size.GetSizeX() + x], 255);
}
}
}
void SetImageDataToSpecificValue(const unsigned char input_value)
{
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&image_data[y * image_size.GetSizeX() + x], input_value);
}
}
}
};
}
#endif // !MONOIMAGE
The contents in MonoImage.cpp file:
#include "MonoImage.h"
If there is any possible improvement about:
Performance: including data accessing/updating speed things
The naming and readability
Potential drawbacks of the implemented member functions
, please let me know.
#include
only the headers you need where you need them. MyImageSize.h
needs no includes. MonoPixelOperation.h
needs only <memory>
.
template<class T = unsigned int>
class MyImageSize
{
private:
T xsize;
T ysize;
public:
MyImageSize(T new_xsize, T new_ysize)
{
this->xsize = new_xsize;
this->ysize = new_ysize;
}
~MyImageSize()
{
}
T GetSizeX()
{
return this->xsize;
}
T GetSizeY()
{
return this->ysize;
}
void SetSizeX(T sizex)
{
this->xsize = sizex;
return;
}
void SetSizeY(T sizey)
{
this->ysize = sizey;
return;
}
};
We could replace this whole thing with:
template<class T = unsigned int>
struct MyImageSize
{
T xsize;
T ysize;
};
Only the constructor provides extra functionality (ensuring the variables are initialized), but that's arguably unnecessary too.
static void SetMonoPixelValue(unsigned char *input_pixel, unsigned char value);
static void SetMonoPixelValue(unsigned char *input_pixel, unsigned int value);
static void SetMonoPixelValue(unsigned char *input_pixel, signed int value);
static void SetMonoPixelValue(unsigned char *input_pixel, long double value);
static void SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, unsigned char value);
static void SetMonoPixelValue(std::unique_ptr<unsigned char> input_pixel, long double value);
static void SetMonoPixelValue(std::shared_ptr<unsigned char> input_pixel, unsigned char value);
static unsigned char GetMonoPixelValue(unsigned char *input_pixel);
static void SetMonoPixelValueToZero(unsigned char *input_pixel);
This is kinda silly...
input_pixel
is an output for most of these.nullptr
wouldn't work).Set
function that takes the correct type (the same as the pixel type)?std::clamp
for clamping.Compare:
pixel = std::clamp(value, min, max);
vs.
MonoPixelOperation::SetMonoPixelValue(&pixel, value);
I don't think there's much point in templating the SizeT
of the image, rather than just using std::size_t
for the index type.
Consider templating the pixel type instead, so we don't need a separate MonoImage
and RGBImage
; we could use an Image<unsigned char>
or Image<Vec3>
.
MonoImage& operator+=(const MonoImage& rhs)
{
if (rhs.image_size.xsize == this->image_size.xsize &&
rhs.image_size.ysize == this->image_size.ysize)
{
...
return output;
}
}
So if the sizes aren't the same, we... err... don't return anything? I'm surprised that compiles. If you aren't getting a compiler warning about that, you need to turn up the compiler warning level (and set warnings to errors).
MonoImage& operator++()
{
return *this;
}
// TODO: implement me? ;)
MonoImage operator++(int)
{
auto output = MonoImage(this->image_size.GetSizeX(), this->image_size.GetSizeY());
for (SizeT y = 0; y < image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x < image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&output.image_data[y* image_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y)*(image_size.GetSizeX()) + (x)])
);
}
}
for (SizeT y = 0; y< image_size.GetSizeY(); y++)
{
for (SizeT x = 0; x< image_size.GetSizeX(); x++)
{
MonoPixelOperation::SetMonoPixelValue(&this->image_data[y * image_size.GetSizeX() + x],
MonoPixelOperation::GetMonoPixelValue(&this->image_data[(y) * (image_size.GetSizeX()) + (x)]) + 1
);
}
}
return output;
}
Or... we could just:
auto output = *this;
for (auto i = std::size_t{ 0 }; i != image_size.GetImageSize(); ++i)
++output.image_data[i];
std::vector<>
provides a much cleaner and more useful interface than std::unique_ptr<[]>
for holding and manipulating the data.
Consider:
struct ImageSize
{
std::size_t x, y;
};
template<class PixelT>
class Image
{
public:
Image():
m_size(0, 0),
m_data() { }
Image(std::size_t width, std::size_t height, PixelT value = PixelT()):
m_size(width, height),
m_data(GetSize(), value) { }
// ... other std::vector-style constructors
Image(Image const&) = default; // woo!
Image& operator=(Image const&) = default; // yay!
Image(Image&&) = default; // yaaaaaaaaas queen!
Image& operator=(Image&&) = default; // it's so easy!
std::vector<PixelT> CloneData() const { return m_data; } // copy the whole thing!
Image& operator++()
{
for (auto& v : m_data) // easy peasy...
++v;
return *this;
}
Image& operator+=(Image const& rhs)
{
if (m_size != rhs.m_size)
throw std::runtime_error("unequal image sizes in operator+=");
for (auto i = std::size_t{ 0 }; i != m_data.size(); ++i) // this is as hard as it gets
m_data[i] += rhs.m_data[i];
return *this;
}
// ...
private:
ImageSize m_size;
std::vector<PixelT> m_data;
};
For the interface I'd suggest to:
Look at the std::vector
interface: constructors, indexed pixel access, iterators, range-based for loops etc. would all be useful. We can simply write a wrapper around the std::vector
implementations for this part of the interface (e.g. use std::vector<T>::iterator
).
Look at the basic mathematical operators: +, -, /, *, %, and the assignment versions and unary operators would all be useful. We can mainly apply the built-in operations to every pixel in the image (with a bit of bounds checking).
Other operations (e.g. Abs()
, Difference()
, Clamp()
etc.) can be implemented as free-functions instead of member functions. They can use the math / vector interfaces and should be a couple of lines of code each. e.g.:
template<class ImageT>
void Clamp(ImageT& image, typename ImageT::PixelT min, typename ImageT::PixelT max)
{
for (auto& p : image)
p = std::clamp(p, min, max);
}
Correct answer by user673679 on January 21, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP