Introduction
A raster data is a 2D matrix of height by width pixels. Generally, we managed to use an array of pixels for that, for example px[x][y]
. Instead, the solution I propose here is to manage a 1D array of height by width using specific C++11 functions.
Background
Last month, I wrote a tip on the use of lvalue and rvalue for a C-like 2D class. That brings me to a new approach of managing only 1D array instead, with a linear complexity O(n).
Raster Class Definition
Good practices for C++11 are used to define a raster data class:
Let's define a type color
for each color component red, green blue. Since color has a range of values 0 to 255, an unsigned char
is suitable for our type.
C++11 allow us to define a unique target constructor to concentrate common initialization. The class for pixel will have following definition:
class CPixel
{
color m_red, m_green, m_blue;
CPixel():CPixel(0,0,0) {} CPixel(color r, color g, color b): m_red(r), m_green(g), m_blue(b) {}
};
Naturally the raster
class will be composed by pointers of CPixel
. I need it to be unique and owned by an instance of CRaster
, any copy of CPixel
pointer is not allowed. So our class has this definition:
class CRaster
{
unique_ptr<CPixel> m_arrayPixels; unsigned int m_w, m_h;
public:
CRaster(unsigned int w, unsigned int h): m_w(w), m_h(h)
{
m_arrayPixels = unique_ptr<CPixel>(new CPixel[h*w]);
}
CPixel& operator() (unsigned int row, unsigned int cool)
{
CPixel* pref = m_arrayPixels.get(); return pref[(m_w * row) + col];
}
m_arrayPixels
will contain a 1 dimension array of height by width pixels, not an array of array of pixels. I just need to add an accessor to allow updating CPixel
object values into my program.
Remember that here we use a temporary value because we assign to our unique_ptr
an rvalue
new CPixel[h*w]
. Fortunately, unique_ptr
class provides a move assignment unique_ptr& operator= (unique_ptr&& x) noexcept
which guarantees us the ownership of our pointer.
private
constructor & friendship
The last point is to prohibit any CPixel
instanciation outside of CRaster
. Why is that? only to assume no copy of CPixel
is allowed. Only a setter is needed for color modification, CRaster
manager will compose himself his raster data.
class CRaster;
class CPixel
{
color m_red, m_green, m_blue;
CPixel():CPixel(0,0,0){} CPixel(color r, color g, color b): m_red(r), m_green(g), m_blue(b){}
friend class CRaster;
public:
void SetColor(color r, color g, color b){ m_red = r; m_green = g; m_blue = b;}
};
Our Definitive Class
Let's propose the definitive class and do some tests to illustrate this tip.
#include <string>
#include <sstream>
#include <iostream>
#include <memory>
using namespace std;
using color = unsigned char;
class CRaster;
class CPixel
{
color m_red, m_green, m_blue;
CPixel():CPixel(0,0,0){ CPixel::nctor++; } CPixel(color r, color g, color b): m_red(r), m_green(g), m_blue(b){ CPixel::nctor_param++;}
CPixel(const CPixel& px): m_red(px.m_red), m_green(px.m_green),
m_blue(px.m_blue){ CPixel::nctor_copy++;}
CPixel(CPixel&& px): m_red(move(px.m_red)), m_green(move(px.m_green)),
m_blue(move(px.m_blue)){ CPixel::nctor_move++; }
friend class CRaster;
public:
static int nctor;
static int nctor_param;
static int nctor_copy;
static int nctor_move;
static int nctor_assign;
CPixel& operator=(const CPixel& px)
{
m_red = px.m_red;
m_green = px.m_green;
m_blue = px.m_blue;
CPixel::nctor_assign++;
return *this;
}
void SetColor(color r, color g, color b){ m_red = r; m_green = g; m_blue = b;}
string ToString()
{
ostringstream oss;
oss << (int)m_red << " - " <<
(int)m_green << " - " << (int)m_blue;
return move(oss.str());
}
static void Stats()
{
cout << endl << nctor << " CPixel constructed with CPixel()";
cout << endl << nctor_param << " CPixel constructed with CPixel(r,g,b)";
cout << endl << nctor_copy << " CPixel constructed by copy constructor";
cout << endl << nctor_move << " CPixel constructed by move constructor";
cout << endl << nctor_assign << " CPixel constructed by assignment";
}
};
class CRaster
{
unique_ptr<CPixel> m_arrayPixels; unsigned int m_w, m_h;
public:
CRaster(unsigned int w, unsigned int h): m_w(w), m_h(h)
{
m_arrayPixels = unique_ptr<CPixel>(new CPixel[h*w]);
}
CPixel& operator() (unsigned int row, unsigned int col)
{
CPixel* pref = m_arrayPixels.get(); return pref[(m_w * row) + col];
}
};
int CPixel::nctor = 0;;
int CPixel::nctor_param = 0;
int CPixel::nctor_copy = 0;
int CPixel::nctor_move = 0;
int CPixel::nctor_assign = 0;
Test
int main()
{
int m_width = 5;
CRaster rst(m_width,m_width);
for(int j=0; j<m_width; j++)
{
CPixel& px = rst(0, j);
px.SetColor(255, j*2, 0);
}
for(int i=0; i<m_width; i++)
{
for(int j=0; j<m_width; j++)
{
CPixel& px = rst(i, j);
cout << px.ToString() << "\t";
}
cout << endl;
}
CPixel::Stats();
}
As a result, we agree that we only construct 5x5 = 25 CPixels
in the life of our program. Here is the output of this sample:
255 - 0 - 0 255 - 2 - 0 255 - 4 - 0 255 - 6 - 0 255 - 8 - 0
0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0
0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0
0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0
0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0 0 - 0 - 0
25 CPixel constructed with CPixel()
25 CPixel constructed with CPixel(r,g,b)
0 CPixel constructed by copy constructor
0 CPixel constructed by move constructor
0 CPixel constructed by assignment
Hope this article will help you, enjoy using modern C++!