This is just proof of concept (POC) source code of an Image template class written in C++. The code serves as an example basis and can extend to support more sophisticated tools for image analysis and processing.
To template or not to?
Generally the decision to present this class as a template is that it can handle different STL containers and data types. This is quite useful in cases where we want to use STL valarray instead of a vector.
Two dimensional image array?
Yes and no. In fact, the image data is indeed two-dimensional, but we can still use a continuous buffer for it. For example, we can use the following mathematics to convert from two-dimensional space to one-dimensional:
int GetPixel(const int row, const int col) {
return m_data[row*m_stride*m_width + col];
}
I’m not saying this is the only approach, but even if we replace the internal representation of the image buffer, the overall interface can remain intact.
Source code
template <typename T = unsigned char,
typename Container = std::vector<T>>
class TImage
{
// Remove assignemnt and copy operators
// Prevent accidental data copy (performance issues) during assignments
TImage & operator=(const TImage&) = delete;
TImage(const TImage&) = delete;
public:
typedef typename T value_type;
virtual ~TImage() {}
TImage() {}
/*
* The image class constructor
* @param width - the image width
* @param height - the image height
* @param stride - the offset between elements
for example 1 - Grayscale, 3 - RGB, 4-RGBA when Type is uint8_t
*/
explicit TImage(unsigned width, unsigned height, unsigned stride)
{
Resize(width, height, stride);
}
TImage& Resize(unsigned width, unsigned height, unsigned stride)
{
m_stride = stride;
m_width = width;
m_height = height;
m_data.resize(m_width*m_height*m_stride);
return *this;
}
TImage& From(const TImage& source)
{
m_stride = source.m_stride;
m_data = source.m_data;
return *this;
}
// Buffer related getters
const std::size_t GetWidth() const { return m_width; }
const std::size_t GetHeight() const { return m_height; }
const std::size_t GetSize() const { return m_data.size(); }
const std::size_t GetStride() const { return m_stride; }
T * GetBuffer() { return m_data.data(); }
const T * GetBuffer() const { return m_data.data(); }
Container& GetImageData() { return m_data; }
const Container& GetImageData() const { return m_data; }
T * GetRow(const std::size_t row) {
return &m_data[row*m_stride*m_width];
}
const T * GetRow(const std::size_t row) const {
return &m_data[row*m_stride*m_width];
}
// Pixel related operators
T& operator()(std::size_t row, std::size_t col) {
return m_data[row*m_stride*m_width + col];
}
const T& operator()(std::size_t row, std::size_t col) const {
return m_data[row*m_stride*m_width + col];
}
T& operator[](std::size_t idx) {
return m_data[idx];
}
const T& operator[](std::size_t idx) const {
return m_data[idx];
}
protected:
unsigned m_stride{ 1 };
unsigned m_width{ 0 };
unsigned m_height{ 0 };
Container m_data;
};
Of course, we can define some types of images that we think we will often use as follows:
// Common image types we will use
typedef TImage<> Image; // Use the default types
typedef TImage<short> ImageS16;
Conclusion
In this post, we reveal a simple way to present a 2D image array as a template C ++ class, where we can use different types of data and containers. In case you find this article useful you may take a look at our image analysis algorithms and our software developers portal.