CS140 Lecture notes -- More Information Hiding with (void *)'s: PGM Files

  • Jim Plank
  • Directory: /home/plank/cs140/Notes/Pgm-Files
  • Lecture notes: http://www.cs.utk.edu/~plank/plank/classes/cs140/Notes/Pgm-Files
  • Wed Sep 12 13:13:58 EDT 2007

    Jpgm.h and jpgm.c

    We're going to build a data structure that lets us mess with PGM files. The main data structure will be a void * that holds a PGM file. It will export the following interface (in jpgm.h):

    void *read_pgm_file(char *filename);            /* Read a PGM file into the data structure
                                                       and return it.  NULL reads from stdin  */
    
    void *new_black_pgm_file(int rows, int cols);   /* Create a new black PGM file */
    
    void write_pgm_file(void *pgm, char *filename); /* Write the PGM file to a file (NULL for stdout) */
    
    int pgm_get_pixel(void *pgm, int r, int c);     /* Get the value of a pixel */
    
    void pgm_set_pixel(void *pgm, int r, int c, int value);  /* Set the value of a pixel */
    
    void jettison_pgm(void *pgm);                   /* Call free() on relevant pointers */
    

    This is going to be implemented in jpgm.c. Let's just look at the internal struct definition and new_black_pgm_file().

    #define talloc(type, num) (type *) malloc(sizeof(type)*(num))
    
    typedef struct {
      int rows;
      int cols;
      int **pixels;
    } Pgm;
    
    void *new_black_pgm_file(int rows, int cols)
    {
      Pgm *p;
      int i, j;
    
      p = talloc(Pgm, 1);
      if (p == NULL) { return NULL; }
    
      p->rows = rows;
      p->cols = cols;
     
      p->pixels = talloc(int *, p->rows);
      if (p->pixels == NULL) { return NULL; }
      for (i = 0; i < p->rows; i++) {
        p->pixels[i] = talloc(int, p->cols);
        if (p->pixels[i] == NULL) { return NULL; }
        for (j = 0; j < p->cols; j++) p->pixels[i][j] = 0;
      }
      return (void *) p;
    }
    

    The Pgm struct is straightforward, holding rows, columns, and a two-dimensional array of pixels. If you haven't seen it before, talloc() is a nice little macro that simplifies malloc() calls. The pixels are all set to zero (black), And a pointer to the struct is returned, but cast to a (void *) so that information is hidden from whoever uses it.

    Let's also look at write_pgm_file():

    void write_pgm_file(void *pgm, char *filename)
    {
      FILE *f;
      Pgm *p;
      int i, j;
    
      if (filename == NULL) {
        f = stdout;
      } else {
        f = fopen(filename, "w");
        if (f == NULL) { perror(filename); exit(1); }
      }
    
      p = (Pgm *) pgm;
    
      fprintf(f, "P2\n%d %d\n255\n", p->cols, p->rows);
    
      for (i = 0; i < p->rows; i++) {
        for (j = 0; j < p->cols; j++) {
          fprintf(f, "%d\n", p->pixels[i][j]);
        } 
      }
    
      if (filename != NULL) fclose(f);
    }
    

    This too is straightforward. First, we cast the pgm argument to a (Pgm *). Then we write out the PGM header information and the pixels.

    Now, the program make_black_pgm.c uses these two procedures to create a black pgm file. It is excpetionally straightforward, and notice how it simply uses the (void *) representation of the PGM file:

    main(int argc, char **argv)
    {
      int r, c;
      void *pgm;
    
      if (argc != 3
          || sscanf(argv[1], "%d", &r) != 1 || r <= 0 
          || sscanf(argv[2], "%d", &c) != 1 || c <= 0) {
        fprintf(stderr, "usage: make_black_pgm rows cols\n");
        exit(1);
      }
      pgm = new_black_pgm_file(r, c);
      write_pgm_file(pgm, NULL);
      exit(0);
    }
    

    Try it out:

    UNIX> make_black_pgm 25 100 > black.pgm
    

    This makes the following PGM file:


    The program make_shaded_pgm.c is a little more ambitious -- it uses pgm_set_pixel() to set each pixel to a lighter shade as its row numbers increase:

    main(int argc, char **argv)
    {
      int r, c, i, j;
      void *pgm;
    
      if (argc != 3
          || sscanf(argv[1], "%d", &r) != 1 || r <= 0 
          || sscanf(argv[2], "%d", &c) != 1 || c <= 0) {
        fprintf(stderr, "usage: make_shaded_pgm rows cols\n");
        exit(1);
      }
      pgm = new_black_pgm_file(r, c);
    
      for (i = 0; i < r; i++) {
        for (j = 0; j < c; j++) {
          pgm_set_pixel(pgm, i, j, 255*i/r);
        }
      }
      write_pgm_file(pgm, NULL);
      exit(0);
    }
    

    Try it out:

    UNIX> make_shaded_pgm 40 40 > shaded.pgm
    

    This makes the following PGM file:


    Before going further, you should check out the implementation of pgm_set_pixel() and pgm_get_pixel() in jpgm.c. Note how each performs error checking on its arguments. That's not really necessary, but it's a nice feature.

    int pgm_get_pixel(void *pgm, int r, int c)
    {
      Pgm *p;
    
      p = (Pgm *) pgm;
    
      if (r < 0 || r >= p->rows ||  c < 0 || c >= p->cols) return -1;
      return (p->pixels[r][c]);
    }
    
    void pgm_set_pixel(void *pgm, int r, int c, int value)
    {
      Pgm *p;
    
      p = (Pgm *) pgm;
    
      if (r < 0 || r >= p->rows ||  c < 0 || c >= p->cols) return;
      if (value < 0 || value > 255) return;
      p->pixels[r][c] = value;
    }
    


    Reading a PGM file

    jpgm.c implements read_pgm_file(). Look in the C file for the implementation -- it is straightforward, and has the added perk that it skips comment lines, which many graphic conversion programs place into PGM files. rw_pgm.c is an extremely simple program that uses read_pgm_file() and write_pgm_file() to read a pgm file on standard input and write it to standard output:

    main(int argc, char **argv)
    {
      void *pgm;
    
      pgm = read_pgm_file(NULL);
      if (pgm == NULL) exit(1);
      write_pgm_file(pgm, NULL);
    }
    

    Try it out:

    UNIX> rw_pgm < meepit.pgm > output.pgm
    

    This makes a second copy of meepit.pgm:

    meepit.pgm
    output.pgm

    Additionally, we can easily write neg_pgm.c to negate a PGM file, and hflip_pgm.c to perform a horizontal flipping. Note that the latter program reads in a PGM file, then creates a new one into which to write the flipped rows.

    Here's neg_pgm.c:

    main(int argc, char **argv)
    {
      int i, j, pix;
      void *pgm;
    
      pgm = read_pgm_file(NULL);
      for (i = 0; i < pgm_rows(pgm); i++) {
        for (j = 0; j < pgm_cols(pgm); j++) {
          pix = pgm_get_pixel(pgm, i, j);
          pgm_set_pixel(pgm, i, j, 255-pix);
        }
      }
      write_pgm_file(pgm, NULL);
    }
    

    And here's hflip_pgm.c:

    main(int argc, char **argv)
    {
      int i, j, pix;
      void *pgm, *newpgm;
    
      pgm = read_pgm_file(NULL);
      newpgm = new_black_pgm_file(pgm_rows(pgm), pgm_cols(pgm));
      for (i = 0; i < pgm_rows(pgm); i++) {
        for (j = 0; j < pgm_cols(pgm); j++) {
          pix = pgm_get_pixel(pgm, i, j);
          pgm_set_pixel(newpgm, i, pgm_cols(pgm)-j-1, pix);
        }
      }
      write_pgm_file(newpgm, NULL);
    }
    

    Try them out:

    UNIX> neg_pgm < meepit.pgm > meepit-neg.pgm
    UNIX> hflip_pgm < meepit.pgm > meepit-hflip.pgm
    

    Here are the outputs:

    meepit.pgm
    meepit-neg.pgm
    meepit-hflip.pgm