This is where the Netbpm image formats come handy, and this is why I'm using them in this series. They don't require us to install any library, thus we don't need to learn any new API, so we can concentrate on the task of learning C or C++, and on play with image processing or whatever we like to do with an image.
We already saw, in the previous post , how to create a small PPM image, one of the image formats implemented in the Netpbm package. But I bet that, if you are reading this post, you wish to do something more than just creating a randomly coloured image.
A PPM image is an RGB image, so it has three channels for each one of its pixels. We can easily represent this in code by using a structure:
typedef struct {
unsigned char r,g,b;
}Pixel;
an image is made of pixels (and a lot of them), which form a quadrilateral shape. So, the next natural step in our coding, it's to write another structure to represent this shape:
typedef struct {
unsigned int width, height;
Pixel * data;
}Image;
the data field, it's a pointer used to read and to write the values in the array of pixels forming our quadrilateral shape, or more precisely, our image, the size of which will be width x height. Remember that each pixel has three channels, which we have represented in the Pixel structure with the fields, r, g and b. In a PPM image, each channel contains 1 byte of data(8 bits). This makes the size of the image equals to width x height x 3 bytes large, plus few bytes for the header.
In the previous post we used a nested loop to write the data of each pixel. Now that we have structured our data, we can write a function which will help us to write more efficient code. But first we are going to write two helper functions, so our code will be more readable:
int getImgWidth(Image * i){ return i->width; }
int getImgHeight(Image * i){ return i->height; }
the next function will make it very easy for us to set the value of any of the pixels in the image, without traversing the image with a loop until the desired pixel's position, first:
void setPixel(int x, int y, Image ** data, Pixel value)
{
Pixel * pixel = (*data)->data;
pixel[x + y * getImgWidth(*data)].r = value.r;
pixel[x + y * getImgWidth(*data)].g = value.g;
pixel[x + y * getImgWidth(*data)].b = value.b;
}
The first two parameter represent the coordinate of the pixel. The third parameter is the image which the pixel belongs to. We need a pointer to pointer because when in C we want to modify the argument of a function, we need to pass it by reference, and that is, we need to pass the address of the "thing" we want to modify, hence a pointer to it. Now, because we already handle our image with a pointer (we will see this later), to modify the image through the function, we need a pointer to a pointer parameter.
The fourth parameter is the new value for the pixel to.
The following is a very stripped version of the main function for the test program: I removed error checking and other stuff, to make more clear to the beginner what is happening in the code:
int main() {
char filename[80] = "myimage.ppm";
//create and open image file
FILE * fp = fopen(filename, "wb");
//allocate immage memory
Image * i = malloc(sizeof(Image));
//initialize image structure
i->width = 256;
i->height = 256;
//allocate pixels memory
i->data = malloc(sizeof(Pixel) * getImgWidth(i) * getImgHeight(i));
memset(i->data, 255, sizeof(Pixel) * getImgWidth(i) * getImgHeight(i));
//initialize a pixel to red
Pixel red = {255,0,0};
//and set with it the central pixel of the image
setPixel(getImgWidth(i)/2, getImgHeight(i)/2, &i, red);
//write the image header to the file
fprintf(fp, "P6 %d\n %d\n 255\n", getImgWidth(i), getImgHeight(i));
//write the image data to file
fwrite(i->data, sizeof(Pixel), getImgWidth(i) * getImgHeight(i), fp);
fclose(fp);
return 0;
}
If we go through the code, it's quite intuitive and easy to read. We create a new file for the image and open it, then we allocate memory and initialize the variables storing its size (width and height). After we allocate memory for the pixel array. With memset, we fill all the element of the pixel array with the value 255. Then, we initialize a pixel to red which we will use in our setPixel function to paint the image's central pixel.
Below, the image generated by the code:
One pixel image |
In the browser it's hard to see, but if you look well at the image generated by the code on your computer, you will notice that the central pixel is painted in red. From this link you can download the complete source for this post.
No comments:
Post a Comment