How to Read PPM File in C++

Abdul Mateen Feb 02, 2024
  1. PPM File
  2. Read PPM File in C++
How to Read PPM File in C++

In this tutorial, we will learn about PPM files and reading them using C++.

We will first discuss and understand the PPM file format. Later, we will learn the step-by-step procedure to read a PPM file in C++.

PPM File

Every image file has a header/metadata attached to the image data. The header contains different information about the image and file.

At the start of every image file, there exists an identification of file format. For example, BM is written at the beginning of every BMP file.

The rest of the header contains information like width, height, number of colors, type of image (binary, grayscale, 16 colors, 256 colors, 24-bit colors), compression, offset, etc.

The Portable Pixel Map (PPM) format is a colored image with a relatively simple format. There are two variants of PPM: one is P3, and the other is P6.

P3 is ASCII format, whereas P6 is a binary format; the ASCII format typically takes more space than the binary format. Most images have a lot of data; therefore, the space is crucial.

For example, a small image of 100x100 has 10000 pixels. In the case of a 24-bit colored image, each pixel takes 3 bytes; hence, 30000 bytes will be required to store the image.

In this tutorial, we will discuss the P6 format. It is relatively easier to read and understand P3; however, it will become easier for you after learning the P6 format.

P6 is a 24-bit colored image. The header of P6 is relatively more straightforward; you can compare it with the BMP format.

The PPM file format has three header lines, which take 15 bytes of space. The first line has an identifier/signature of the PPM (either P3 or P6).

The second line has the image’s width & space (separated by space) information, and the third line has maximum color value information (e.g., 15 or 255).

For example, look at the following image:

House Image PPM File

Let’s look at the meta information of the house image (you may not believe that data belongs to this image).

P6
111 132
255
U`6Xe8Xk8Ul9Tg:Ve<Wd7Wd5Td5N_0MY/NZ3P^5Ub5Wc4T`2R`4T`4[d6Yd7NY1CM,@J-FQ/O\2Vg8Ra5FW.?O+@M,BQ/:F-0:**5)*1(6@-CQ5=H1;H-

As discussed earlier, the first three lines have header information. P6 in the first line identifies the type of image.

The programmers may check the identifier and read the image according to the format.

The second line describes the width as 111 and the height as 132. The third line states 255 as the value of maximum color.

From the fourth line, we have the image data stored in binary format (presented on screen in the form of characters, some of them recognizable but not all).

Read PPM File in C++

To read a PPM file, first of all, we must read its header to get information about width & height. This is important as we must declare dynamic memory accordingly and read the data of the image.

P6 is a binary format; therefore, we have to open the image in binary format and do reading accordingly:

FILE *read;
read = fopen("west_1.ppm", "rb");

In the first line, the FILE pointer is declared to store a handler of the file in the second line. Note the second parameter in the fopen function is rb, where r is for reading, and b is for binary (i.e., binary read mode for the file).

If you are not comfortable with binary storage, read this article on binary files. Once you have opened the file, you must read the file’s header.

Here is the code for that:

unsigned char header[15];
fread(header, 15, 1, read);

More about fread can be found here.

Once the header is read successfully, the next thing is to extract the width & height information to proceed further. As this information is stored in character form, we may read character information one by one and convert it into an integer value by using the following code (you may use the atoi function alternatively):

int x = 0;
for (pos = 3; header[pos] != '\n' && header[pos] != ' '; pos++)
  x = x * 10 + (header[pos] - '0');

As discussed earlier, the first two bytes of the header contain a P6 value, and the third is the separator for the next line. Therefore, we will start from the 4th element, which begins from index 3.

We read character by character to complete an integer value (separated by a space character or line feed character). For each character, we have to subtract the ASCII value of 0 because the ASCII value of digits starts from 48 or 32 hex (ASCII value of 0).

For example, the ASCII value of 6 is 54 in decimal; therefore, if we subtract 48 from 54, we will get 6. Using this fact, we can get an integer value from a character array.

See a small example to convert a character array 135 to an integer equivalent.

integer x = 0                             // initial integer value
    x = x* 10 + 1 = 1 x = x* 10 + 3 = 13  // find out 10s by
    x = x* 10 + 5 = 135  // find out 100s and x will have final integer value

After getting the width and height information, we will declare a dynamic array of size width x height x 3 as there are width x height pixels in the image, and each image has 3 bytes (red, green & blue, known as RGB).

unsigned char *image;
image = new unsigned char[width * height * 3];

We are using unsigned char instead of char because, in simple char type, the first bit is used for sign (0 for a positive number and 1 for a negative number).

In the case of char, the maximum range of positive values is 0 to 127, whereas we have to read information from 0 to 255. Therefore, we need an unsigned char.

The final step is to read the image, which is pretty simple. Just call the fread function as:

fread(image, size, 1, file);  // where size is width x height x 3

Again, the first parameter is a char array to store image data, and the size is already well explained.

The variable image has all pixels - red, green, and blue values. We may declare three separate arrays of size width x height, or we may handle operations by remembering that the first value is red, the second is green, and the third is blue.

For example, if we want to remove the red component from the image, we must assign 0 in the first index of all the pixels.

We can do that as follows:

for (i = 0; i < size; i = i + 3) {
  image[i] = 0;

Please note the increment step in the for loop, where we have increased i with 3 instead of 1. It is because the second and third values are for green and blue, and we do not want to change the green and blue values.

See the example image below to see the difference after removing a red component from the image. We have the original image on the left, and the modified image without the red component is on the right.

Original VS Without Red Component

We hope you got the whole idea about reading PPM files. In the end, let us look at a complete C++ program to read, modify, and write a PPM file:

#include <cstdio>
#include <iostream>

using namespace std;

void readPPMHeader(FILE *file, unsigned char *header) {
  fread(header, 15, 1, file);
}
void readPPMImage(FILE *file, unsigned char *image, int size) {
  fread(image, size, 1, file);
}
void writePPM(FILE *file, unsigned char *header, unsigned char *image,
              int size) {
  fwrite(header, 15, 1, file);   // writing header information
  fwrite(image, size, 1, file);  // writing image information
}
void removeRed(unsigned char *image, unsigned char *withoutredimage, int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutredimage[i] = 0;  // red component is set to 0
    withoutredimage[i + 1] = image[i + 1];
    withoutredimage[i + 2] = image[i + 2];
  }
}
void removeGreen(unsigned char *image, unsigned char *withoutgreenimage,
                 int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutgreenimage[i] = image[i];
    withoutgreenimage[i + 1] = 0;  // green component is set to 0
    withoutgreenimage[i + 2] = image[i + 2];
  }
}
void removeBlue(unsigned char *image, unsigned char *withoutblueimage,
                int size) {
  int i;
  for (i = 0; i < size; i = i + 3) {
    withoutblueimage[i] = image[i];
    withoutblueimage[i + 1] = image[i + 1];
    withoutblueimage[i + 2] = 0;  // blue component is set to 0
  }
}
// To extract width & height from header
int getDimension(unsigned char *header, int &pos) {
  int dim = 0;
  for (; header[pos] != '\n' && header[pos] != ' '; pos++)
    dim = dim * 10 + (header[pos] - '0');
  return dim;
}
int main() {
  FILE *read, *write1, *write2, *write3;
  read = fopen("west_1.ppm", "rb");
  unsigned char header[15], *image;
  readPPMHeader(read, header);
  if (header[0] != 'P' || header[1] != '6') {
    cout << "Wrong file format\n";
    return 0;
  }
  write1 = fopen("west_1_without_red.ppm", "wb");
  write2 = fopen("west_1_without_green.ppm", "wb");
  write3 = fopen("west_1_without_blue.ppm", "wb");
  int width, height, clrs, pos = 3;
  width = getDimension(header, pos);
  pos++;
  height = getDimension(header, pos);
  cout << "Width:" << width << "\tHeight:" << height << '\n';
  image = new unsigned char[width * height * 3];
  unsigned char *withoutredimage, *withoutgreenimage, *withoutblueimage;
  withoutredimage = new unsigned char[width * height * 3];
  withoutgreenimage = new unsigned char[width * height * 3];
  withoutblueimage = new unsigned char[width * height * 3];
  readPPMImage(read, image, width * height * 3);
  removeRed(image, withoutredimage, width * height * 3);
  writePPM(write1, header, withoutredimage, width * height * 3);
  removeGreen(image, withoutgreenimage, width * height * 3);
  writePPM(write2, header, withoutgreenimage, width * height * 3);
  removeBlue(image, withoutblueimage, width * height * 3);
  writePPM(write3, header, withoutblueimage, width * height * 3);
  fclose(read);
  fclose(write1);
  fclose(write2);
  fclose(write3);
  return 0;
}

In this program, we read one PPM file and wrote three PPM files after modification.

Let’s look at the output images:

Without Red VS Without Green VS Without Blue

The red color is removed in the leftmost picture. The middle one is without green, and the third one has no blue component.