Listing 14.4 shows an outline of the file image-io.c. I will give the details of the parts dealing with PGM images. You will write the parts dealing with PPM images.
14.8.1 The functionread_image()
The functionread_image()that appears on line 7 of Listing 14.4 receives the name of an existing image file. It reads the file’s contents, allocates memory for a “struct image”
structure and its members, populates the structure with the image’s data, and returns a pointer to that structure. If anything fails in the process, it prints a diagnostic message to thestderrand callsexit()to kill the program. Listing 14.5 shows my implementa- tion ofread_pgm_image(). It handles PGM and PPM images, both the plain and raw types. Extending it to handle PBM images is straightforward, and you are welcome to try it yourself, but I will refrain from doing so in the interest of brevity.
14.8. The implementation of theimage-io module 125
Let us go through the details of the functionread_image().
Line 3. We allocate memory for a “struct image” and setimgto point to that mem- ory location. The purpose of the current function is to read image data from the input file, deposit that information into that structure, and return the structure’s address to the caller.
Line 4. The pointer&img→pamis needed in quite a few places in the rest of the code. It wouldn’t be wrong to write&img→pameverywhere, but it would clutter things.
Thepamvariable defined here is a shorthand meant to reduce clutter; doing so isn’t essential in any way.
Line 5. Here we calllibnetpbm’spm_openr()function, described in Section 14.5, to open the image file for reading. We could have called C’s standardfopen()in- stead, as in
FILE *fp = fopen(filename, "r");
but, as noted in Section 14.5,pm_openr()offers a couple of extra helpful features.
For one thing, it treats the file name"-"as a request forstdin, as seen in the usage examples in Remark 14.5 on page 123. For another thing, it checks for errors. If the requested file cannot be opened for reading, it prints a message tostderrand exits the program! Consequently, there is no point in checking for the success of thepm_openr()call.
Line 6. Here we call libnetpbm’s pnm_readpaminit() function, described in Sec- tion 14.5, to read the image’s header data. The first argument is the streamfp, which was set up on the previous line. The second argument,pam, points to the
“struct pam”, which is embedded in “struct image”. The function will populate pam’s members (see Listing 14.1) with the information that it extracts from the image’s header. The third argument, as noted in Section 14.5, is always the expression I have shown. Also as was noted there, you may have to change it to sizeof(struct pam)if you have an older version oflibnetpbm.
Line 7. Here the program branches depending on the image type. Theformatmember of “struct pam” encodes the image type that has just been read from the input stream (see Section 14.5 for the details of the “struct pam”). If the image is of type PGM, we callread_pgm_pixel_data()to read the pixel data. If the image is of type PPM, we callread_ppm_pixel_data()to read the pixel data. (These functions will be described shortly.) If the image is something else—presumably a PBM or a PAM45—then we print a message saying that we are not prepared to handle that type and exit the program.
Lines 16 and 17. We are done with reading the image; therefore we close the input stream and returnimgto the caller. The functionpm_close(), described in Section 14.5, is a wrapper around C’s standardfclose()—it checks forfclose()’s return status and callsexit()if an error occurs.
45I have neither mentioned the PAM format up to this point, nor will I deal with it from here on. PAM is an “adaptable” format in the sense that any PBM, PGM, or PPM image may be presented as a PAM image. You will have to readNetpbm’s documentation if you want to learn more about that format.
126 Chapter 14. Image I/O
Listing 14.6: The implementation of the functionread_pgm_pixel_data().
1 static void read_pgm_pixel_data(struct image *img)
2 {
3 struct pam *pam = &img→pam;
4 tuple *row = pnm_allocpamrow(pam);
5 make_matrix(img→g, pam→height, pam→width);
6 img→r = img→b = NULL;
7 for (int i = 0; i < pam→height; i++) {
8 pnm_readpamrow(pam, row);
9 for (int j = 0; j < pam→width; j++)
10 img→g[i][j] = row[j][0];
11 }
12 pnm_freepamrow(row);
13 }
14.8.2 The functionread_pgm_pixel_data()
The functionread_pgm_pixel_data()that appears on line 5 of Listing 14.4 reads the pixel data of a PGM image. Its implementation is shown in Listing 14.6. Let us go through the details.
Line 3. The variablepamis introduced here for the same reason as that on line 4 of List- ing 14.5. It’s not essential in any way; it’s a shorthand meant to reduce clutter.
Line 4. Here we call libnetpbm’s pnm_allocpamrow() function, described in Sec- tion 14.5, to allocate memory for atuplerowarray. We are going to read the image one row at a time into that array and then copy the result into a matrix. Fig- ure 14.4 shows a schematic representation of atuplerowarray. There is no need to check for success—if memory allocation fails,pnm_allocpamrow()will print a message tostderrand exit the program.
Lines 5 and 6. We call themake_matrix() macro from the header file array.h(see Chapter 8) to allocate a matrix ofdouble, assigned toimg→g, to hold the pixel data of a(pam→height)×(pam→width)image. The membersimg→rand img→bare meant for use in PPM images, so they are not needed here. We set them toNULLso that they can be passed tofree_matrix()with no ill effects;
see subsection 14.8.5.
Lines 7–11. Thefor-loop goes through the image one row at a time. At each step it calls libnetpbm’spnm_readpamrow()function, described in Section 14.5, to read an entire row into therowarray. Thus,row[j]points to the tuple associated with the pixel at rowiand columnj. Since this is a PGM image, the tuple vector has length 1 (see Figure 14.4); therefore its first (and only) element is given byrow[j][0]. We store that value inimg→g[i][j].
Line 12. We are done with reading. We calllibnetpbm’spnm_freepamrow()function, described in Section 14.5, to free the memory allocated on line 4.
14.8.3 The functionwrite_image()
The functionwrite_image()that appears on line 10 of Listing 14.4 receives the name of an output file and a pointer to a fully populated “struct image”. It writes the
14.8. The implementation of theimage-io module 127
Listing 14.7:The implementation of the functionwrite_image().
1 void write_image(char *filename, struct image *img)
2 {
3 struct pam *pam = &img→pam;
4 pam→file = pm_openw(filename);
5 pam→plainformat
6 = (pam→format == PGM_FORMAT || pam→format == PPM_FORMAT) ? 1 : 0;
7 pnm_writepaminit(pam);
8 if (pam→format == PGM_FORMAT || pam→format == RPGM_FORMAT)
9 write_pgm_pixel_data(img);
10 else if (pam→format == PPM_FORMAT || pam→format == RPPM_FORMAT)
11 write_ppm_pixel_data(img);
12 else {
13 fprintf(stderr, "error: file %s, line %d: shouldn’t be here!\n",
14 __FILE__, __LINE__);
15 exit(EXIT_FAILURE);
16 }
17 pm_close(pam→file);
18 }
image’s data into the named file as a PGM or PPM image. If a file of the given name does not exist, it’s created. If it exists, it’s overwritten. If anything fails in the process, it prints a diagnostic message to thestderrand exits the program. Listing 14.7 shows the implementation ofwrite_image().
Line 3. We introduce the variablepamas a shorthand for&img→pam, just as we did in read_image(). This is not essential; it serves only to reduce clutter.
Line 4. Here we calllibnetpbm’spm_openw()function, described in Section 14.5, to open the image file for writing. We could have called C’s standardfopen()in- stead, as in
FILE *fp = fopen(filename, "w");
but, as noted in Section 14.5,pm_openw()offers a couple of extra helpful features.
For one thing, it treats a file named"-" as a request forstdout; see the usage examples in Remark 14.5 on page 123. For another thing, it checks for errors. If the requested file cannot be opened for writing, it prints a message tostderrand exits the program! Consequently, there is no point in checking for the success of thepm_openw()call.
Note that we setpam→fileto the stream opened bypm_openw(). Other func- tions which write to the stream look up the stream’s address in thepamstructure.
Line 5. As noted in Section 14.5,libnetpbmignores the format encoded in thepamstruc- ture’sformatmember and writes images in the raw format by default. Here we ex- amine theformatmember. If it indicates a plain format, we set theplainformat member totrueto override the library’s default behavior.
Line 7. Libnetpbm’spnm_writepaminit()function, described in Section 14.5, writes an image’s header data (e.g., the “P5 w h M” part described in subsection 14.3.2) by extracting the needed information from thepamstructure. The output goes to the streampam→file.
128 Chapter 14. Image I/O
Listing 14.8: The implementation of the functionwrite_pgm_pixel_data().
1 static void write_pgm_pixel_data(struct image *img)
2 {
3 struct pam *pam = &img→pam;
4 tuple *row = pnm_allocpamrow(pam);
5 for (int i = 0; i < pam→height; i++) {
6 for (int j = 0; j < pam→width; j++)
7 row[j][0] = img→g[i][j];
8 pnm_writepamrow(pam, row);
9 }
10 pnm_freepamrow(row);
11 }
Line 8. Here the program branches depending on the image type. If the image is of type PGM, we callwrite_pgm_pixel_data()to write the pixel data. If the im- age is of type PPM, we callwrite_ppm_pixel_data()to write the pixel data.
(These functions will be described shortly.) Unless we have carelessly meddled with thepamstructure’sformatmember, these two should be the only possibilities.
Anything else is abnormal and should never occur. Nevertheless, we supply code to catch the hypothetical implausible case, write a diagnostic, and exit the program.
Line 17. We are done with writing; therefore we callpm_close(), described in Sec- tion 14.5, to close the output stream. The functionpm_close() is a wrapper around C’s standardfclose()—it checks forfclose()’s return status and calls exit()if an error occurs.
14.8.4 The functionwrite_pgm_pixel_data()
The functionwrite_pgm_pixel_data()that appears on line 8 of Listing 14.4 has many aspects in common withread_pgm_pixel_data(); therefore my comments will be somewhat terser here. It receives a pointer to a fully populated “struct image”
corresponding to a PGM image and writes the image’s pixel data into an output stream.
Its implementation is shown in Listing 14.8. Let us go through the details.
Lines 3 and 4. These are just like what we had inread_pgm_pixel_data(). Read the explanations there.
Lines 5–9. The outer for-loop goes through the img→g matrix one row at a time.
The innerfor-loop goes through the row and copies the value of img[i][j]
to the first (and only) element of the tuple associated with the pixelrow[j]. (This is the opposite of what we did when reading the image.)
Remark 14.6. Note thatimg→g[i][j]is of typedouble, whilerow[j][0]
is of type “sample”, which is an alias forunsigned long. C’s automatic con- version rules are applied during the assignment. If the right-hand side is an integer stored as adouble, then the left-hand side receives the exact value of that integer. If the right-hand side is a noninteger, then its fractional part is dropped! For instance, both 3.1 and 3.9 are converted to 3; no rounding takes place. It is the responsibility of the caller to do all necessary rounding/processing on the image’s sample values before arriving here. In particular, it is the caller’s responsibility to ensure that the