Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++14

Managing a Raster Data Using Modern C++11 Features

3.75/5 (5 votes)
4 Nov 2015CPOL2 min read 13.5K  
The purpose of this tip is to discuss a solution for managing a raster data format using modern C++ features.

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:

  • type alias "using"

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++
//
// using color = unsigned char; // C++11 typedef equivalent
//
  • delegating constructor

C++11 allow us to define a unique target constructor to concentrate common initialization. The class for pixel will have following definition:

C++
class CPixel
{
    color m_red, m_green, m_blue;

    CPixel():CPixel(0,0,0) {} // C++11 delegating constructor
    CPixel(color r, color g, color b): m_red(r), m_green(g), m_blue(b) {}
};
  • unique_ptr

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:

C++
class CRaster
{
    unique_ptr<CPixel> m_arrayPixels; // C++11, the object is empty, owns nothing.
    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();  // C++11 unique_ptr, 
                     // return a reference, don't have the ownership. No copy of data is done
        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.

  • move semantic

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.

C++
class CRaster;

class CPixel
{
    color m_red, m_green, m_blue;

    CPixel():CPixel(0,0,0){} // C++11 constructor, delegating constructor
    CPixel(color r, color g, color b): m_red(r), m_green(g), m_blue(b){}

    friend class CRaster;
public:

    // Setter
    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.

C++
#include <string>
#include <sstream>
#include <iostream>
#include <memory>

using namespace std;
using color = unsigned char; // C++11 typedef equivalent, 
     // color is an integer in the range of 256 values. So we decide to store it in an unsigned char

class CRaster;

class CPixel
{
    color m_red, m_green, m_blue;

    // Ctor, avoid any instantiation outside of friend classes
    // No new CPixel class will be instantiated until the end of program
    CPixel():CPixel(0,0,0){ CPixel::nctor++; } // C++11 constructor, delegating constructor
    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;

    // Assignment Ctor
    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;
    }

    // Setter
       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; // C++11, the object is empty, owns nothing. 
				// It will contain an 1 dimension array of height by width pixels
    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]);
        // C++11, to assign a value to our pointer, we need a temporary value. 
        // Exactly we will assign it an rvalue like new CPixel[h*w];
        // unique_ptr class provided exactly a move assignment unique_ptr& 
        // operator= (unique_ptr&& x) noexcept; which guarantee us the ownership of our pointer.
        // We have just to update color value of our array
    }

    CPixel& operator() (unsigned int row, unsigned int col)
    {
        CPixel* pref = m_arrayPixels.get();  // C++11 unique_ptr, return a reference, 
                                             // don't have the ownership. No copy of data is done
        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

C++
int main()
{
    int m_width = 5;
    CRaster rst(m_width,m_width);

    // change colors
        for(int j=0; j<m_width; j++)
        {
            CPixel& px = rst(0, j);
            px.SetColor(255, j*2, 0);
        }
    // Print
        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;
        }
    // Statistics
        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++!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)