Sign in to follow this  
coordz

dynamic C++ clases

Recommended Posts

I'm not sure dynamic is exactly the right word but this is what I want to do: I'm trying to load an image file (TIFF) into an Image class of my own making. This image class provides methods for manipulating the image, saving and loading. At the moment the class essentially only supports data in byte format (i.e. each sample being an unsigned byte, 3 samples for an RGB image). Now what I'd like to do is be able to load a TIFF file (or any other file for that matter) which may have floating point data, unsigned sample data, etc. and still use the same class. As an example of what I want to do: Image myImage; myImage.loadFromFile("someimage.tif"); myImage.resize(640,480); // scale image to 640x480 myImage.saveToFile("newimage.tif"); and be able to always use this code regardless of whether "someimage.tif" was floating point, byte or whatever. What I was thinking was to have a virtual class Image and then a TypedImage class which would actually provide the different methods. If I did the TypedImage class as a template and then used that to provide the specific classes I'd save a lot on typing. That would work (I think) if all the class types were known at compile time, e.g. floatImage image1; ...... Image image2 = image1; // as floatImage inherits from Image The flaw in my plan comes when loading a file at run time because the compiler doesn't know the different types to assign. Can I dynamically change the type of a class from within one of the class's member functions? i.e. in the loadFromFile method be able to morph the class into whatever is needed? What would be a good way of going about tackling this problem. Many TIA

Share this post


Link to post
Share on other sites
I would use a template class:


template <class T>
class Image
{
T data;
//other stuff
};

//To create an Image class of float data:
Image<float> ifl;




EDIT: tried to declare if as a variable [smile]

[Edited by - Captain_Thunder on July 12, 2007 5:37:56 AM]

Share this post


Link to post
Share on other sites
In C++ you cannot dynamically create new types, that is, create a new type at run time. Templates only create types at compile time. But you can dynamically load a new type using DLLs on Win32 platforms, often known as plugins.

I would consider refactoring your design into at least three separate parts: the image, a codec and a manipulator.

The image type is just a wrapper to the memory the image is stored in together with format information: bits per pixel/channel, data type, get/set pixel data, dimensions, etc.

The codec transforms a stream of bytes to an image and converts an image back to a stream of bytes. Note that I say 'stream', it need not be a file.

Finally, the manipulator performs transformations on images such as scaling.

Some of these separate types can be put into their own DLLs - a TIFF codec, a PNG codec, etc. When decoding/encoding a stream, the DLL containing the appropriate codec can be dynamically loaded. This allows the system to be highly extensible.

Skizz

Share this post


Link to post
Share on other sites
Quote:
The flaw in my plan comes when loading a file at run time because the compiler doesn't know the different types to assign. Can I dynamically change the type of a class from within one of the class's member functions? i.e. in the loadFromFile method be able to morph the class into whatever is needed?

No.

What you can do is let Image be an interface that uses the different implementations of image types as members. Something like this:

class ImgFile{
virtual Data* ReadFile(string filename) = 0;
};

class ByteImgFile : public ImgFile{
virtual Data* ReadFile(string filename);
};

class FloatImgFile : public ImgFile{
virtual Data* ReadFile(string filename);
};

...

class Image{
public:
void Load(string filename);
private:
ImgFile* filereader;
Data* imgdata;
};

void Image::Load(string filename){
int type = ReadFileHeaderAndCheckType(filename);
switch(type){
case 0:
filereader = new ByteImgFile;
break;
case 1:
...
}

imgdata = filereader.ReadFile(filename);
delete filereader;
}



Basically, you let your Image class be a general, non-polymorphic data type that deals with the general concept of "images". You let it figure out what data type a file is when it reads it and then uses a special read/write class to convert the file to a general data format that you like. You can add support for different loaded data formats, as many file types as you like and whatnot with this concept, and it's very maintainable and easy to understand.

ETA: Ninja'd! Anyhoo, Skizz says essentially the same thing as I do, but his point about streams is a very good thing to keep in mind. Don't limit yourself to files, use streams instead.

Share this post


Link to post
Share on other sites
If you just want to use Image as an interface and load whatever you want in there, use the Pimple idiom; simple example here.

Basicly you have one member variable of type ImageImp. All different image types are derived from ImageImp. When you load an image you 'decide'* which Imp you want to use for this file, eg. TiffImageImp, set your member variable as such, and route every call to that member variable.

From the outside it looks like you're doing everything with Image, while it's actually being done by a specialisation of ImageImp under the hood.

hth,
CipherCaft

* Use a factory class of some kind, which uses extension as key for example.

Share this post


Link to post
Share on other sites
I want to implement a texture/image class too. What i have thought is storing format information in an int and having a void pointer to the data. Then i use arrays of function pointers to call the appropriate functions for each format. For example i plan to have a blit function like this:



void BLIT(const Image &src, Rect srcRect, const Image &dst, Rect dstRect, Int32 options);
// options stores info about blending, flipping etc..






then the blit function uses the "format" integral members of src and dest as indices to a 2d
array of function pointers to call the appropriate function:



void BLIT(const Image &src, Rect srcRect, const Image &dst, Rect dstRect, Int32 options)
{
BLITfuncPtr[src.format][dst.format](/*parameters*/);
}

// You could add a 3rd dimension for blending mode if you want, three dereferences are
// free compared to the time it takes to execute the blit.







The same could be done with a "Convert" or "Load" function etc. Of course some operations may not make sense (e.g blitting a sprite on a greyscale heightmap :P) , in which case the function pointer calls a function to take care of this. You can also cast the data pointer to whatever pixel struct you want to retrieve data. I would like to hear what problems my approach could have, as i haven't implemented this yet.. :) But i really couldn't think of something better :P The formats need not be known at compile-time and it's easy to add new formats (just define a symbolic constant and write the new format-specific functions).

EDIT: a couple of additions/corrections

[Edited by - D_Tr on July 11, 2007 8:37:07 AM]

Share this post


Link to post
Share on other sites
Quote:
TypedImage class as a template and then used that to provide the specific classes I'd save a lot on typing.


When someone starts saving on typing, bad things happen.

OO isn't type-writer's helper. It's design tool.

IIRC, TIFF stores image data in sequential array of desired values. This implies something like this:

template < typename PixelFormat >
class RasterData
{
public:
typedef PixelFormat Format;

size_t width;
size_t height;
PixelFormat *pixels;
};








Next thing you need is interface to access that. Here you get the problem: if image type is unknown at compile time, you cannot gain from typesafety or additional optimizations at global level.

So the question you need to ask yourself is this: How will I use this image?

One way is this:

template < class F1, class F2, class F3, class F4 >
class generic_image
{
private:
union RasterDataType {
RasterData<F1> r1,
RasterData<F2> r2,
RasterData<F3> r3,
RasterData<F4> r4
} ;

int type; // 0..3, which member of union is valid
RasterDataType raster;
}








Now we need a way to manipulate this. The general form of manipulator will be something like this:


class ImageOp
{
template < class PixelFormat >
void operator( RasterData<PixelFormat> &raster );
}

...

template < class F1, class F2, class F3, class F4 >
class generic_image
{
...
void resize( size_t w, size_t h )
{
ResizeImageOp op( w, h );
transform( op );
}

template < class Operation >
void transform( const Operation &op )
{
switch (type) {
case 0 : op(raster.r1); break;
case 1 : op(raster.r2); break;
case 2 : op(raster.r3); break;
case 3 : op(raster.r4); break;
}
}
}





Then, we just need to define the available rasters:

typedef RasterData<float> FloatRaster;
typedef RasterData<int> IntRaster;

struct RGBA {
byte r;
byte g;
byte b;
byte a;
};

typedef RasterData<RGBA> RGBARaster;
typedef RasterData<byte> ByteRaster;






The number of template parameters that your TIFFImage defines will be identical to number of different rasters it supports.

And we get:

typedef generic_image<FloatRaster, IntRaster, RGBARaster, ByteRaster> TIFImage;






And you get a nice image handling tool with support for multiple formats, all completely typesafe, almost certainly fully expanded.

The ImageOp class will be your repository of tools. You can specialize them:


class WeirdOp
{
template < class PixelFormat >
void operator()( RasterData<PixelFormat> &raster );

void operator()( RasterData<RGBA> &raster )
{
// do something extra
}
};




or equip the PixelFormat types with the operations you'll be using.


Disclaimer: This is *A* solution, not *THE* solution. Most image packages define raster simply as an array of bytes, and their interpretation is left to the class performing the operation. This goes one step further, and allows you to specialize the operations at compile time.

The downside (from maintainance perspective) is that you need to fix your data types at compile time. Adding a new type isn't problematic (add template parameter + extend the union).

Generally, you should only need a limited number of different formats.

[Edited by - Antheus on July 11, 2007 8:47:09 AM]

Share this post


Link to post
Share on other sites
Another solution would be to take a look at how GIL does it: http://opensource.adobe.com/gil. It solves the problems you mention very elegantly with meta-programming techniques and I find it a very useful library to do my own processing with.

I used it to write a tool that converts GeoTIFF files into elevation files for our engine and generate normal maps from them as well.

Its only downside is the sparse documentation and the fact its hard to understand if you want to expand on it. But the mailing list provides ample support if you need it.

Share this post


Link to post
Share on other sites
Quote:
Original post by El Greco
Another solution would be to take a look at how GIL does it: http://opensource.adobe.com/gil. It solves the problems you mention very elegantly with meta-programming techniques and I find it a very useful library to do my own processing with.


Which is almost identical to the solution I proposed, but uses boost::variant type to handle multiple formats (I proposed formats to be part of image template).

I'd definitely go with GIL if license permits it.

Share this post


Link to post
Share on other sites
First up, a big thank you to everyone that made a suggestion. In order:

Captain_Thunder: Sure, using templates was my first thought

Skizz: Sounds like a good way of organizing things but probably an overkill for my purposes. When you say
Quote:
you can dynamically load a new type using DLLs on Win32 platforms

do you mean it is possible to load an actual new data type? Or just meaning a new DLL with some predefined interface that gives all the required functionality for a particular file type? The latter is what I've always believed plugins to be.

Hnefi: When I load the image file I want to preserve the data type rather than convert to a generic internal format. The final destination of the images are OpenGL textures with an internal format matched to the image type.

CipherCraft: the Pimple idea looks useful. does that mean I can do something like

class Img{
public:
void loadImg(...);
void doSomething(...);
private:
class Impl;
Impl *pImpl;
}

class floatImpl : public Impl{
// some stuff to manipulate float image
virtual doSomething();
}

class byteImpl : public Impl{
// some stuff to manipulate byte image
virtual doSomething();
}

Img::loadImg(..){
// do some stuff
if(byteImage)
pImpl = new byteImpl();
else if(floatImage)
pImpl = new floatImpl();
}

Img::doSomething(..){
pImpl-&gt;doSomething();
}


and when I do pImpl->doSomething() the correct doSomething() will be called depending on what was assigned in loadImg()? Sounds perfect if it works like that.

D_Tr: If I was programming in C I'd probably do something like you suggest. I was hoping that C++ would give me some way of doing things a bit cleaner and look after the housekeeping.

Antheus: I like what you're saying and it matches what I'm already doing quite well.
Quote:
Most image packages define raster simply as an array of bytes, and their interpretation is left to the class performing the operation.

My image data is kept as an array of bytes so I think I'll go down this route. Perhaps lets me put in some of what Skizz suggests at a later data.

Also, I code for fun so my code grows rather organically rather than being designed. When I notice myself typing almost the same thing over and over that tells me it's time to refactor into classes, functions whatever, i.e. to save typing ;)

El Greco: I'll pass on using GIL but will look at the code. The exercise is to learn about C++ and how to implement something vaguely useful rather than just taking a library to slide into a larger project.

I think I know now what I'm going to do and I'll see how successful I am. Thanks again for all the help :-D

Share this post


Link to post
Share on other sites
Quote:

Skizz: Sounds like a good way of organizing things but probably an overkill for my purposes. When you say
Quote:

Quote:
you can dynamically load a new type using DLLs on Win32 platforms

do you mean it is possible to load an actual new data type? Or just meaning a new DLL with some predefined interface that gives all the required functionality for a particular file type? The latter is what I've always believed plugins to be.

OK, perhaps I should have been a bit clearer. The new types are implementations of known interfaces. For example, your app defines the IImageCodec interface (with Serialise and Deserialise methods) and defines the BMPCodec, TIFFCodec as implementations of IImageCodec. You can then create a third codec, PNGCodec for example, that resides in the DLL, which is effectively a new type that your application can use without rebuilding it. It is exactly what you believed a plugin to be.

Skizz

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this