Click here to Skip to main content
15,886,199 members
Articles / Programming Languages / C++14
Tip/Trick

Managing a Raster Data Using Modern C++11 Features

Rate me:
Please Sign up or sign in to vote.
3.75/5 (5 votes)
4 Nov 2015CPOL2 min read 13.1K   10   4
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)


Written By
Engineer
France France
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSlooooooow??? Pin
R.D.H.5-Nov-15 12:01
R.D.H.5-Nov-15 12:01 
Seems like this is such an over-kill. I sure wouldn't want to scan in a large negative at 8 microns or so and then try to do any real-time filtering on such a heavy construct!
AnswerTwo points Pin
jaybus565-Nov-15 3:14
jaybus565-Nov-15 3:14 
GeneralRe: Two points Pin
Bernd Schroeder5-Nov-15 4:59
Bernd Schroeder5-Nov-15 4:59 
AnswerRe: Two points Pin
jaybus565-Nov-15 5:33
jaybus565-Nov-15 5:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.