3

i've got a Class Image with a member called buffer, a vector of unsigned char that store the pixels of image. I'd like to handle this buffer as a matrix.i'd like to access to the buffer as img[ i ][ j ] but i've not idea how? anyone can suggest me a solution?

here's my tentative:

unsigned char * &Image::operator[](int i) {

       return buffer[ rows+i  ]
}

i read this post then i think that is possible. my class is like that:

class Image {


private:

unsigned char * buffer;
int buf_size;
int rows;
int cols;
Phisical_img_manager phisical_img_manager;

const char * in_img_filename;


public:

unsigned char * & operator[](int i);

Image(const char * in_img_filename);

unsigned char * get_buffer();
int get_cols();
int get_rows();

};
Community
  • 1
  • 1
userfi
  • 175
  • 1
  • 1
  • 15
  • 4
    Have `operator[]` return a row or column object that itself implements `operator[]`. – Drew Dormann Jun 03 '16 at 19:28
  • with a nested class? – userfi Jun 03 '16 at 19:30
  • 3
    @userfi Before overloading `[]`, [see this](https://isocpp.org/wiki/faq/operator-overloading#matrix-subscript-op) – PaulMcKenzie Jun 03 '16 at 19:32
  • @PaulMcKenzie beautiful!! thus you suggest me to use `(i,j) ` operator right? – userfi Jun 03 '16 at 19:37
  • 2
    @userfi - I leave it to you to read the linked FAQ and decide. Looks like your app is better suited for overloading `()`, but that's just my opinion. – PaulMcKenzie Jun 03 '16 at 19:41
  • Your opeartor[] returns type uchar*, so you should be able to use Image[i][j] as is...did you try? – Assimilater Jun 03 '16 at 19:51
  • On second thought looks like your 2D is already typed as 1D statically (my brain thought of dynamic 2D array for some reason), in which case all you're missing is the reference (see 101010's answer)...but if this compiles maybe not...clarify if this question needs a different answer – Assimilater Jun 03 '16 at 19:55

1 Answers1

7

Solution

You could do it in row-major order as below:

class Image {
  int rows, cols;
  unsigned char *buffer;
public:
  // ...  
  unsigned char*       operator[](int const i)       { return &(buffer[i * cols]); }
  unsigned char const* operator[](int const i) const { return &(buffer[i * cols]); }
};

Live Demo

Discussion

Your buffer is a contiguous block of memory. Of size N = rows * cols.

enter image description here

However the notion of an image is a set of pixels arranged in a 2D construct/matrix:

enter image description here

What we want is to arrange this 2D construct in the 1D buffer in computer's memory. We can do this in two ways:

  1. Row major order.
  2. Column major order.

In row major order we store each row of the image one after the other. That is, for the following size 2x2 image

enter image description here

the respective buffer storage would look like:

enter image description here

In column major order we store each column of the image one after the other. That is, the buffer storage for the same size 2x2 image would look like: enter image description here

Programming languages that support multi-dimensional arrays either implement row-major or column-major storage order for them. In C and C++ row-major order is used.

In the code showed above we implement row-major order. We define an overloaded subscript operator that takes as input the row index (e.g., i) we want to access. Multiplying this i with the number of columns we get the starting index of the ith row in the image. We return the address of the element that lies in this specific address. This address marks the beginning of a sub-array of the original buffer as well as the beginning of the ith row. To clarify further see the following code example:

Image I(2, 3);
I[1][2] = 42;

calling I[1][2] = 42 is like calling:

(I.operator[](1))[2];

sub-call I.operator[](1) returns a pointer to the address where the second row of the image starts. Then we use this returned pointer as an ordinary dynamic allocated array. In this specific example we add 2 to this pointer (i.e., that is what [2] does) and thus we get the element of the row that lies 2 positions after the first element of the row (i.e., the third element of the second row of the image).

Alternative Solution For C++11

If your compiler supports C++11 and smart pointers you could do the following scheme in order to avoid memory management. Furthermore I would overload operator()(std::size_t const, std::size_t const) because overloading as above you expose the buffer and thus you're hurting encapsulation:

class Image {
  int rows, cols;
  std::unique_ptr<unsigned char[]> buffer;
public:
  Image(int const rows_ = 0, int const cols_ = 0) 
  : rows(rows_), cols(cols_), buffer(new unsigned char[rows * cols]) {}
  Image(Image const &other)
  : rows(other.rows), cols(other.cols), buffer(new unsigned char[rows * cols]) {
    std::copy(other.buffer.get(), other.buffer.get() + (rows * cols), buffer.get());
  }
  Image(Image &&other) 
  : rows(other.rows), cols(other.cols), buffer(std::move(other.buffer)) {
    other.rows = 0;
    other.cols = 0;
  }
  unsigned char&       operator()(std::size_t const i, std::size_t const j) {
    return buffer[cols * i + j];
  }
  unsigned char const& operator()(std::size_t const i, std::size_t const j) const {
    return buffer[cols * i + j];
  }
  Image& operator=(Image const &other) {
    rows = other.rows;
    cols = other.cols;
    buffer.reset(new unsigned char[rows * cols]);
    std::copy(other.buffer.get(), other.buffer.get() + (rows * cols), buffer.get());
    return *this;
  }
  Image& operator=(Image &&other) {
    rows = other.rows;
    cols = other.cols;
    buffer.swap(other.buffer);
    other.rows = 0;
    other.cols = 0;
    return *this;
  }
  // ...
};

Live Demo

101010
  • 41,839
  • 11
  • 94
  • 168