Search code examples
cgenericsunions

Generic datatype in c


I'm working on a small image processing library in C.

I want to represent an image as an array of pixels which can have different types : uint8_t (for pixels whose the max value does not exceed 255), uint16_t (same but with max value at 65535), uint32_t.

I'm going with this for the moment :

typedef struct img8_t {
    unsigned int width, height, depth;
    size_t dsize;
    uint8_t *data;
}img8_t;

typedef struct img16_t {
    unsigned int width, height, depth;
    size_t dsize;
   uint16_t *data;
}img16_t;

typedef struct img32_t {
    unsigned int width, height, depth;
    size_t dsize;
    uint32_t *data;
}img32_t;

dsize contains the size of the datatype for the pixels

And I have the same number of functions to allocate/deallocate and process these images.

Is there a way to define a generic unsigned int type that can handle values on 8-bits, 16-bits etc.. without creating a struct/function for each case ?

Should I use a union?


Solution

  • Is there a way to define a generic "unsigned int" type that can handle values on 8-bits, 16-bits etc.. without creating a struct/function for each case ?

    No, not that would suit your purpose. Every complete C data type has a specific representation, and you can read or write objects' values only if you have a complete type for them. You certainly could define a union that can hold different-size integers:

    union uint_any {
        uint8_t  u8;
        uint16_t u16;
        uint32_t u32;
    };
    

    ... but objects of that type all have the same size, which is sufficient to accommodate the largest member. Thus, a raster of, say, 16-bit pixels without any padding between does not match up with an array of union uint_any, at least not if you want to read all the pixels via the u16 member.


    You can use a void * to point to your array, so as to avoid separate structures for each pixel size, and your functions can sort it out internally so that you need only one function for each purpose. Additionally, if you have image manipulation functions complex enough to warrant it then you can probably benefit from reducing or eliminating code duplication by using macros. Here's an example for a simpler case than probably warrants such treatment:

    struct img {
        unsigned int width, height, depth;
        size_t dsize;
        void *data;
    };
    
    uint32_t get_pixel(struct img *image, unsigned x, unsigned y) {
        // Warning: evaluates its first argument twice:
        #define GET_PIXEL(i,t,x,y) (((t*)((i)->data))[(x)*((i)->width)+(y)])
        switch (img.dsize) {
            case 8:
                return GET_PIXEL(image, uint8_t, x, y);
            case 16:
                return GET_PIXEL(image, uint16_t, x, y);
            case 32:
                return GET_PIXEL(image, uint32_t, x, y);
            default:
                // handle error ...
        }
        #undef GET_PIXEL
    }