1

For my Project I need to read an PPM(P3) image into memory. Because I want to rotate the input picture and therefore I would like to go through an x and y axis/array.

First I read the values of the image into an "unsigned char" since the color values used are only between 0 and 255 and to save memory I convert them into unsigned chars.

Every pixel in the PPM image has a red, green, blue values.

For this, I created this typedef struct.

typedef struct{
    unsigned char red;
    unsigned char greed;
    unsigned char blue;
} color;

I tried to make a simple 2-dimensional array like this:

color inputColor[pictureHeight][pictureWidth];

But this fails very fast when the pictures get bigger. I am trying to make it work, so I can allocate that 2d array with malloc. One attempt was:

color *inputColor[pictureHeight][pictureWidth];

//Allocating memory
for (int y = 0; y < pictureHeight; y++){
    for (int x = 0; x < pictureWidth; x++){
        inputColor[y][x] = malloc(sizeof(color));
    }
}

// Here i am copying values from an inputStream to the structure
int pixel = 0;
for (int y = 0; y < pictureHeight; y++){
    for (int x = 0; x < pictureWidth; x++){
        inputColor[y][x]->red = inputMalloc[pixel];
        pixel++;
        inputColor[y][x]->green = inputMalloc[pixel];
        pixel++;
        inputColor[y][x]->blue = inputMalloc[pixel];
        pixel++;
    }
}

But it fails again in the first line...

How can a 2-dimensional struct array be allocated with malloc, so the picture sizes don't matter that much any more?

Right now it fails around a picture size of 700x700 pixels.

anastaciu
  • 23,467
  • 7
  • 28
  • 53
TheTrashinger
  • 47
  • 1
  • 1
  • 7

3 Answers3

3

Double pointer solutions have a very weak point when considering images. They cannot be transferred directly to the screen memory or accessed fast way.

Much better is to use a pointer to array.

typedef struct{
    unsigned char red;
    unsigned char greed;
    unsigned char blue;
} color;

int main(void)
{

    size_t pictureHeight = 600, pictureWidth = 800;

    color (*inputColor)[pictureHeight][pictureWidth];

    inputColor = malloc(sizeof(*inputColor));
}
0___________
  • 60,014
  • 4
  • 34
  • 74
1

The pointer to array approach posted by mch, I believe would be the best approach:

#include <stdlib.h>

color (*inputColor)[pictureWidth] = malloc(pictureHeight * sizeof *inputColor);

The access is the same as you are using, inputColor[y] for line and inputColor[y][x] for column, inputColor[y][x].red to access struct member.

Since you stated you can't make it work, you can try using the pointer to pointer method, though slower, it may be easier to understand and apply:

color **inputColor;

inputColor = malloc(pictureHeight * sizeof *inputColor);

for (int y = 0; y < pictureHeight; y++)
{
    inputColor[y] = malloc(pictureWidth * sizeof **inputColor);
}

The first malloc allocates memory for pictureHeight number of rows, the for loop allocates memory for each row with the size of pictureWidth.

The access is the same as the pointer to array method.

In these simplified pieces of code no error checks are performed, it's something you should do, namely checking malloc return values for errors.

Don't forget to free the memory when you're done with it:

For the first example:

free(inputColor);

For the second:

for (int y = 0; y < pictureHeight; y++)
{
    free(inputColor[y]);
}
free(inputColor);
anastaciu
  • 23,467
  • 7
  • 28
  • 53
  • This approach is much worse unless you want the ability to be able to "swap rows" in O(1) time. It uses more memory, fragments memory more (takes more cache lines) and is less efficient to access (double pointer dereference rather than single). You really should use the approach from mch's comment, which should be an answer. – R.. GitHub STOP HELPING ICE Feb 09 '21 at 15:20
  • 1
    @R..GitHubSTOPHELPINGICE though I agree that the pointer to array method is better, the fact remains the OP doesn't seem to be able to make it work, hence my alternative answer. As is always the case, fuctional code upon which improvements can be made is better than code that doesn't work. – anastaciu Feb 09 '21 at 16:10
  • @anastaciu it is a very bad way to allocate the buffer for the image or screen buffer. The most inefficient way. – 0___________ Feb 09 '21 at 16:29
  • 2
    @0___________ It is arguably a bad way; it is not "very bad". And while it is probably less efficient, it is not "the most inefficient". The pointer-to-pointer technique has been used for many years, by people who know what they are doing, with perfectly adequate results. – Steve Summit Feb 09 '21 at 16:42
  • @0___________ it's not the best way, I agree, I state as much in my answer, it's still a valid way. – anastaciu Feb 09 '21 at 17:02
1

Just allocate a double pointer and allocate memory to it:

color **inputColor;

inputcolor= malloc(sizeof(color*)*pictureHeight);
for (int y = 0; y < pictureHeight; y++){
        inputColor[y] = malloc(sizeof(color)*pictureWidth);
   
}
Alex S
  • 36
  • 3
  • Thank you! This just solved my problem perfectly :D – TheTrashinger Feb 09 '21 at 12:15
  • 2
    Correct , i meant to write color* – Alex S Feb 09 '21 at 12:24
  • 1
    Whenever you see looped `malloc()` calls, you're not allocated a two-dimensional array. You're allocating a one-dimensional array of pointers to multiple and completely separate one-dimensional arrays. And when the dimensions get large, that's ***slow***. – Andrew Henle Feb 09 '21 at 12:55
  • @AndrewHenle The performance of code is much more a function of how the allocated data is used rather than how it is allocated. For a good performance critique, we need to the larger application. – chux - Reinstate Monica Feb 09 '21 at 18:14
  • @chux-ReinstateMonica Time the allocation of an `int ****` using `malloc()` in nested loops where the final dimensions are something like `[512][512][512][512]`. That should be 256GB, and systems with enough memory to do that are getting common. Now time the allocation of a true four-dimensional array with the one `malloc()` call necessary. You can even include `memset()` in the latter... When you get into larger pointer-to-pointer-to-pointer "fake arrays", those allocations get significant. Of course, they're almost trivial to optimize away. – Andrew Henle Feb 09 '21 at 18:39