jenidu
jenidu

Reputation: 9

Returning an 3D array from an image file in C programming

I'm currently trying to convert a bitmap image file into a 3D array. The program gets stuck after 3 tries after start = 1. I don't know what's wrong.

uint8_t*** loadImage(const int size[3], const char url[]){
uint8_t*** output_array;
output_array = malloc((size[0] * size[1]) * 3 * sizeof(uint8_t));
uint8_t byte, RGB = 0, start = 0; int x = 0, y = 0; uint64_t i;
FILE * fp = fopen(url, "rb");
if(fp == NULL){
    perror("Unable to open file!");
    exit(1);
} else {
    for(i = 0; y < size[1]; i++) {
        fread(&byte, sizeof(uint8_t), 1, fp);
        if(start == 1){
            //it ends here in the 3rd rotation after start
            if(RGB < 2){
                output_array[x][y][RGB] = byte;
                RGB++;
            }
            else if (RGB >= 2 && x <= size[0]) {
                x++;
                output_array[x][y][0] = byte;
                RGB = 1;
            }
            else if(RGB >= 2 && x > size[0]){
                y++;
                x = 0;
                RGB = 1;
                output_array[0][y][0] = byte;      
            }
        }
        else if(byte == 255 && start == 0){
        //trying to find the starting position, which is always white(255)
            start = 1;
        }
}}
fclose(fp);
return output_array;
}

Upvotes: 0

Views: 483

Answers (1)

Eric Postpischil
Eric Postpischil

Reputation: 223454

Allocating a Three-Dimensional Array

The statement output_array = malloc((size[0] * size[1]) * 3 * sizeof(uint8_t)); allocates space for size[0] * size[1] * 3 objects of type uint8_t. But output_array does not point to a uint8_t. Its type is uint8_t ***, which means it points to a uint8_t **. Therefore, it would need space for objects of type uint8_t **.

Somebody may have told you that arrays are pointers, and therefore uint8_t *** is a three-dimensional array. That is false. uint8_t *** is a pointer to a pointer to a pointer to a uint8_t (or, if they point to the first of several objects, then a pointer to pointers to pointers to several uint8_t). From your code, it looks like you want a multidimensional array, not a bunch of pointers.

The “easiest” way to do a malloc correctly is to declare a pointer to the thing you want. If you want an array of size[0] arrays of size[1] arrays of 3 arrays of uint8_t, then that would be declared with uint8_t SomeName [size[0]] [size[1]] [3];1, and a pointer to it would be declared with uint8_t (*Pointer) [size[0]] [size[1]] [3];. Then you can allocate space with2:

uint8_t (*Pointer) [size[0]] [size[1]] [3] = malloc(sizeof *Pointer);

Using the thing a pointer points to requires using the * operator, so you would access array elements with (*Pointer)[i][j][k]. However, you can instead make yourself a pointer to the first element of the array with:

uint8_t (*Pointer) [size[0]] [size[1]] [3] = malloc(sizeof *Pointer);
uint8_t (*output_array) [size[1]] [3] = *Pointer;

This first sets Pointer to point to one three-dimensional array. Then *Pointer designates the first array, but it is automatically converted to be a pointer to the first element of that array. (That first element is a two-dimensional array.) This pointer is used to initialize output_array, so output_array points to the first of several two-dimensional arrays. Now you can use output_array much as if it were an array: You can refer to an element as output_array[i][j][k] and return that pointer with return output_array.

However, most people do not use the two lines as I have shown them above. I used that for illustration, to show the single object we want to allocate (a three-dimensional array) and how sizeof calculates its size for you. Most people consolidate these lines into one line that allocates several of the contained arrays, with the same effect:

uint8_t (*output_array) [size[1]] [3] = malloc(size[0] * sizeof *output_array);

Returning the Array

An array defined with uint8_t [size[0]] [size[1]] [3] is a variable length array, because its dimensions are not integer constants as defined by the C standard. Such a type is called a variably modified type, and so is a pointer to it. Functions cannot return variably modified types, so this function cannot return a pointer to the array it created, or even a pointer to its first element, since the subarray is also variably modified.

A workaround is to convert the pointer to void * and return that. First, modify the array declaration to return void *:

void *loadImage(const int size[3], const char url[])

Then, in the caller, put the return value in a pointer of the desired type:

uint8_t (*output_array) [size[1]] [3] = loadImage(size, url);

Another option is to have the caller pass the address of a pointer which they would like to be set to point to the new array (by its first element). The function would change to:

void loadImage(const int size[3], const char url[],
    uint8_t (**returned_array) [size[1]] [3])
{
    …
    *returned_array = output_array;
}

and the caller would change to:

    uint8_t (*output_array) [size[1]] [3];
    loadImage(size, url, &output_array);

Footnotes

1 The spaces between the brackets are just for readability, since there are so many brackets here. You can use that spacing in source code or not, as desired.

2 This requires that your C implementation support variable-length arrays. Those are currently optional in the C standard but are supported by common C compilers, although they might not be when compiling for C++.

Upvotes: 1

Related Questions