1

Given image data how can I overlay the RGB values onto a vector of vectors. From the code below; I have an array of type uint8_t representing the image, I then reinterpret_cast 3-bytes to the RGB struct and place these into the 2D array over a for-loop.

This works (below screen shot) but feels 'clunky', is there any other way of achieving the same thing? (perhaps by an iterator to a vector of vectors, copy_n or similar)

enter image description here

#include<vector>


struct RGB {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
};


int main() {
    std::vector<std::vector<RGB>> data;
    uint8_t height, width, idx = 0, jdx = 0;

    // data array representing image data - this would really be a bin fle
    //                    header         r,    g,    b        r,    g,    b      r,    g,    b        r,    g,    b       r,    g,    b       r,    g,    b      footer
    uint8_t temp[22] = { 0x02, 0x03,    0x01, 0x02, 0x03,   0x04, 0x05, 0x06,   0x07, 0xef, 0x05,   0x0a, 0x01, 0x02,   0x0d, 0xfe, 0x00,   0x10, 0xff, 0xff,   0xef, 0xef };

    // resize data vector based on (h,w) which we get from header info
    height = temp[0];
    width = temp[1];
    data.resize(height, std::vector<RGB>(width));

    // populate the 2D data of RGB values
    for (uint8_t i = 0; i<6; i++) {
        jdx = i % width;
        data[idx][jdx] = *(reinterpret_cast<RGB*>(&temp[(2 + (i*3))]));

        if (jdx == height)
            idx++;
    }

    return 0;
}
Harry Lime
  • 2,167
  • 8
  • 31
  • 53

1 Answers1

1
  1. IMHO, a vector<vector< >> is suitable if rows may have varying lengths but such thing isn't actually called a matrix anymore. (The raw data of the OP's sample forms a matrix stored row-wise.)

  2. Why is it necessary to copy the temp array to a vector<>? This is duplicating the data. If the life-time can be granted the temp might be accessed directly. (Otherwise, it could be copied to a vector<uint8_t>.)

  3. AFAIK, the packing of components in struct RGB is not granted. Hence, the reinterpret_cast<RGB*>() is not the cleanest way to access the bytes.

So, I would like to suggest a solution that prevents all these issues – class Image which works as a wrapper/accessor to the raw data in temp:

#include <cstddef>
#include <cstdint>

struct RGB {
  uint8_t red, green, blue;

  RGB() { } // leaving contents uninitialized
  RGB(uint8_t red, uint8_t green, uint8_t blue):
    red(red), green(green), blue(blue)
  { }
};

class Image {
  private:
    const uint8_t *_pData;
  public:
    class Row {
      private:
        const uint8_t *_pData;
      public:
        Row(const uint8_t *pData): _pData(pData) { }

        RGB operator[](size_t i) const
        {
          const uint8_t *pixel = _pData + 3 * i;
          return RGB(pixel[0], pixel[1], pixel[2]);
        }
    };

    Image(const uint8_t *pData): _pData(pData) { }

    size_t height() const { return _pData[0]; }
    size_t width() const { return _pData[1]; }

    RGB get(size_t i, size_t j) const
    {
      const uint8_t *pixel = _pData + 2 + (i * _pData[1] + j) * 3;
      return RGB(pixel[0], pixel[1], pixel[2]);
    }

    Row operator[](size_t i) const
    {
      return Row(_pData + 2 + i * _pData[1] * 3);
    }
};

Access to an RGB-tripel can be performed with Image::get() as well as with the more intuitive Image::operator[]().

Sample code:

#include <iomanip>
#include <iostream>

int main()
{
  // data array representing image data - this would really be a bin fle
  //                    header         r,    g,    b        r,    g,    b      r,    g,    b      footer
  uint8_t temp[22] = { 0x02, 0x03,    0x01, 0x02, 0x03,   0x04, 0x05, 0x06,   0x07, 0xef, 0x05,
                                      0x0a, 0x01, 0x02,   0x0d, 0xfe, 0x00,   0x10, 0xff, 0xff,   0xef, 0xef };
  // access via
  Image img(temp);
  std::cout << std::hex << std::setfill('0');
  // via Image::get()
  std::cout << "access with img.get(i, j):\n";
  for (size_t i = 0, n = img.height(); i < n; ++i) {
    for (size_t j = 0, m = img.width(); j < m; ++j) {
      RGB rgb = img.get(i, j);
      std::cout << "  "
        << std::setw(2) << (unsigned)rgb.red << std::setw(0) << ' '
        << std::setw(2) << (unsigned)rgb.green << std::setw(0) << ' '
        << std::setw(2) << (unsigned)rgb.blue << std::setw(0);
    }
    std::cout << '\n';
  }
  // via Image::operator[]
  std::cout << "access with img[i][j]:\n";
  for (size_t i = 0, n = img.height(); i < n; ++i) {
    for (size_t j = 0, m = img.width(); j < m; ++j) {
      RGB rgb = img[i][j];
      std::cout << "  "
        << std::setw(2) << (unsigned)rgb.red << std::setw(0) << ' '
        << std::setw(2) << (unsigned)rgb.green << std::setw(0) << ' '
        << std::setw(2) << (unsigned)rgb.blue << std::setw(0);
    }
    std::cout << '\n';
  }
  return 0;
}

Output:

access with img.get(i, j):
  01 02 03  04 05 06  07 ef 05
  0a 01 02  0d fe 00  10 ff ff
access with img[i][j]:
  01 02 03  04 05 06  07 ef 05
  0a 01 02  0d fe 00  10 ff ff

Live Demo on coliru

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • regarding 3., it seems to me that [this answer](https://stackoverflow.com/questions/30617519/reinterpret-cast-from-object-to-first-member?noredirect=1&lq=1) and [this one](https://stackoverflow.com/a/22404659/4070489) suggest that for POD types, or better trivial types, the reinterpret-cast version is safe. Not that it does not still seem better to use less bare-bones c++ if possible... – GeckoGeorge May 24 '18 at 13:09
  • many thanks, Scheff in response (1) image data will form a matrix, (2) the temp array represents data that's really from a binary file (as said in the comments, I should've been more clear), and (3) yes packing may be an issue, should use #pragma pack() for alignment – Harry Lime May 24 '18 at 13:23
  • @Scheff it's a subtly different question being asked, so the sentence you are referring to is about non-trivial types, which are non-trivial via inheritance. – GeckoGeorge May 24 '18 at 14:07
  • 1
    @Scheff This makes it dangerous, IMO, to rely on your type being a POD in larger projects. If you inherit from a POD, and certain prerequisites are fulfilled, suddenly what may look like a POD and inherit from a POD may not be a POD anymore. `struct RGB` here fulfills, however, `all data members must be in the same class, not in more than one.` and shows no inheritance, so no padding may be used by the compiler. – GeckoGeorge May 24 '18 at 14:07
  • @GeckoGeorge Yes, the question is a bit different - I agree. Though, the comment refers to `r` and `g` which both are components of that `struct RGB` - just like in this question/sample. – Scheff's Cat May 24 '18 at 14:10
  • @Scheff Indeed, but the question is asked about using `struct RGBA : RGB`, and in this struct, which does have all variables of RGB as members, there is no guarantee anymore. The similarities of names, both between the question here and the linked one, and between RGB and RGBA, obfuscates the points a bit. – GeckoGeorge May 24 '18 at 14:14
  • Some further duckduckgo-ing suggest that the term "standard layout" could lead to helpful references in that regard. I'm really not enough a language lawyer to find any clear references to that, as it seems... – GeckoGeorge May 24 '18 at 14:23