I am working on some code (not written by be, and too big to fundamentally change the structure of) where a pure virtual Image class is implemented. The user is expected to implement a function of their derived Image class with signature:

virtual Rgb* GetRgb();

Where the Rgb type is defined as:

struct Rgb
{
  static const int NUM_CHANNELS = 3;
  unsigned char channel[NUM_CHANNELS];
};

This Rgb type is never instantiated directly, it is simply used to determine the stride length of the pointer through the image data.

If I read a file and determine the pixels have more than 3 components, is there anyway I can change this Rgb type to now have the desired stride length, for example, unsigned char * 4 ? Note: templates are not an option (design decision not made by me).

Any helpful hints?

Thanks,

David

Recommended Answers

All 9 Replies

>>is there anyway I can change this Rgb type to now have the desired stride length?

No. All types in C/C++ have a fixed size, that cannot be changed at run-time. No way.

If you could change the interface, it would be better to output an iterator object which then stores a stride which could vary at run-time (but, of course, it would make the increment/decrement operations much slower).

Another option is to have another function like "getRgba" that returns a 4-byte stride pointer instead. If the user attempts to call getRgb with an image that has 4 bpp, then you throw an exception or return a NULL pointer. Of course, this isn't very nice on the user-side, but that's life.

>>is there anyway I can change this Rgb type to now have the desired stride length?

No. All types in C/C++ have a fixed size, that cannot be changed at run-time. No way.

If you could change the interface, it would be better to output an iterator object which then stores a stride which could vary at run-time (but, of course, it would make the increment/decrement operations much slower).

Another option is to have another function like "getRgba" that returns a 4-byte stride pointer instead. If the user attempts to call getRgb with an image that has 4 bpp, then you throw an exception or return a NULL pointer. Of course, this isn't very nice on the user-side, but that's life.

Hm, the iterator is interesting, but that would require massive work to change the existing code.

The getRgba() function sounds easier, but the problem with this is that the code currently does things like:

double Energy(Rgb* A, Rgb* B)
{
double sum=0;
for(unsigned int i = 0; i < 3; i++)
  sum+=A[i] - B[i];
}

Would I have to overload every function that accepts a Rgb*? And what about functions that use them internally?

double OtherFunction(Image* image)
{
Rgb* A = image->GetRgb();
}

?

(Sorry, as usual I am being annoyingly vague because the problem is very large and I'm trying to simplify it down to something post-able here. I really like just hearing a bit of discussion about the problem to try to nudge me in a new direction, rather than looking for an actual "do this and it will work" type of answer.)

David

Ok, I thought that your function getRgb would only affect the user-side code. If you have a complete system that is based on the fact that all images have 3 bpp and are all rgb-formatted, then I would say you have to fit everything you have into that assumption. This is what OpenCV does, all their images are 3 bpp bgr images (but I think that 2.1 version is a bit more flexible, at least I remember seeing some basic use of generic programming).

I think that if you are loading an image that has 4 bpp (whether it is rgba or high color-depth), you should convert it to the format that your software platform requires. You will certainly lose information in the process, but that information is beyond what your platform can handle.

I was a bit shocked to see the two example functions you have posted. Are some functions of that software platform actually programmed like that? That's worse than I thought. It's incredible what damage the idea of "strongly typed" can make when it's wrongly interpreted. Someone has decided, very naively, that all pixels are RGB, made a type for that kind of pixel, and then made the entire platform dependent on that type, and bloated with magic numbers and hidden assumptions. You need a jack-hammer to break through such a rigid software platform, and a lot of man-power.

If you do decide to create another type "Rgba" and overload all the functions to deal with that type, then I would recommend that at the same time, you define a few type_traits for the pixel types (to replace magic numbers and hidden assumptions) and turn those functions into function templates instead of hand-written overloads. It will save you a lot of time and will help improve the software platform a little bit. And turning functions into function templates is OK because it creates no ripple effect since the call-side code remains the same, and the API can remain the same as well.

Is there a way you can just create the RGBa type and make it inherit from the RGB type? Then you could just add the Alpha channel instead of completely recreating the type...

Something like:

struct RGB {
  //...
  //...
};

struct RGBa : public RGB {
  //add alpha channel
};

Because of the polymorph, I don't know how the size situation will be affected (I've never really gotten into that aspect of polymorph), but the inheritance should give you the ability to use a pointer to an RGBa wherever you have a pointer to an RGB.

@FBody: That won't change the stride of the pointer. The problem is that he has a contiguous array of RGBA pixels (with stride of 4 bytes) and he needs to output a pointer to an Rgb (with stride of 3 bytes). That just cannot be done, polymorphism won't achieve anything to this end. Imagine this situation:

struct Rgb { char r,g,b; };
struct Rgba : Rgb { char a; };

int main() {
  //if you have an array of Rgba:
  Rgba my_image[256*256];

  //you can cast the pointer to a pointer to Rgb:
  Rgb* pix_ptr = my_image;
  //then accessing the first element is valid:
  if(pix_ptr[0].r == my_image[0].r)
    std::cout << "red equals red!" << std::endl;
  //but accessing any element beyond that will be aliased:
  if(pix_ptr[1].r != my_image[1].r)
    std::cout << "red is not equal to red!" << std::endl;
};

This is because, when incrementing the Rgb pointer, the compiler assumes that it is pointing to an Rgb object, and it increments by the sizeof the type (i.e. pix_ptr + 1; is the same as ((char*)pix_ptr) + 1*sizeof(Rgb); ).

@mike: I pretty much expected that, but I wasn't positive.

Since, in C++, a struct is essentially just another name for a class with different default behavior what about re-working the struct to use dynamic allocation? One of the beautiful things about OOP is that, if done properly, you can transparently change the "nuts and bolts" of a class/struct without changing the interface and the client code will be none the wiser.

Perhaps dynamically allocate the RGB sub-array using a 1-argument constructor with a default argument?

struct RGB {
  int my_size
  char *my_array;
  //...

  RGB(int size = 3) : my_array(new char[size]), my_size(size) {}
  ~RGB() { delete[] my_array; }
};

struct RGBa : public RGB {
  RGBa() : RGB(4) {}
};

Unfortunately, this begs the question. "Are you compiling this as C or as C++?" If you're compiling as C it won't work. I'm assuming your compiling as C++ since this is the C++ forum, not the C forum.

Depending on how your code works, you may still have a typing issue here, but hopefully it will get you a step closer.

Mike, that was the first thing I thought of, but I didn't know how to do that so that it would happen for not a specific object. That is, how do you do something like that so that the general type of RGB changes size, not just a specific instance of the object (i.e. before it was a static property, so all RGB's were affected, not just the one you called the constructor on)?

Here is an attempt at a demo. You can see the error on the << operator - how would I output the value in 'numbers' at the location pointed to by the Rgb pointer?

#include <iostream>

class Rgb
{
public:
  static int* Channel;
  static void SetChannelSize(unsigned int channelSize)
  {
    Channel = new int(channelSize);
  }
};

int main()
{

  int numbers[1000];
  for(unsigned int i = 0; i < 1000; i++)
    {
    numbers[i] = i;
    }

  Rgb::SetChannelSize(3);

  Rgb* rgbNnumbers = reinterpret_cast<Rgb*>(numbers);
  
  for(unsigned int i = 0; i < 5; i++)
    {
    std::cout << rgbNnumbers[i] << std::endl; //error: no match for ‘operator<<’ in ‘std::cout << *(rgbNnumbers + ((unsigned int)i))’

    }
  
  return 0;
}

The stride of a pointer is determined by the sizeof the type to which it points. And the sizeof a type is fixed at compile-time. If you can't change the type it points to, you can't change the stride. End of story.

The attempt you posted achieves nothing to this end, because it is impossible.

You would need to change the pointer type into an iterator for which you can change the stride at run-time:

class pixel_iterator {
  public:
    enum pixel_format {
      rgb = 3,
      rgba = 4;
    };
  private:
    char* ptr;
    const pixel_format stride; //const will make this class a bit faster.
  public:
    pixel_iterator(char* aPtr, pixel_format aStride = rgb) : ptr(aPtr), stride(aStride) { };

    char& r() { return ptr[0]; };
    const char& r() const { return ptr[0]; };
    char& g() { return ptr[1]; };
    const char& g() const { return ptr[1]; };
    char& b() { return ptr[2]; };
    const char& b() const { return ptr[2]; };
    char& a() { return ptr[3]; };
    const char& a() const { return ptr[3]; };

    pixel_iterator& operator++(int) { ptr += stride; return *this; };
    //.. all other operators that are relevant.
};

You have to replace the C++ mechanism that increments pointers by the sizeof the type they point to, and the only way to do that is to replace the pointer with a custom object.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.