1

So I have to read this image in a PPM format, and then write it to another file. It works, however if the image has comments it stops working. If the image is like this:

P3
3 2
255
255   0   0
  0 255   0
  0   0 255
255 255   0
255 255 255
  0   0   0

It works, but if the image is like this:

P3
3 2
255
# "3 2" is the width and height of the image in pixels
# "255" is the maximum value for each color
# The part below is image data: RGB triplets
255   0   0  # red
  0 255   0  # green
  0   0 255  # blue
255 255   0  # yellow
255 255 255  # white
  0   0   0  # black

It stops working. I really don't know why since I have programmed to ignore comments. I will leave the code below, maybe someone can help.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

typedef struct pixel {
  unsigned int r,g,b;
} pixel;

typedef struct imagem{
  int cols,rows;
  char mnumber[2];
  int maxcolor;
  pixel *matrix;
}imagem;

static imagem read(const char *filename)
{
  imagem img;
  FILE *f;

  f = fopen(filename,"rb");

  if(f == NULL) {
    scanf("%c %c",&img.mnumber[0],&img.mnumber[1]);
    scanf("%d %d",&img.cols,&img.rows);
    scanf("%d",&img.maxcolor);

    img.matrix = (pixel*) malloc(img.cols*img.rows*sizeof(pixel));

    for(int i = 0; i < img.cols*img.rows;i++)
    {
      scanf("%u %u %u",&img.matrix[i].r,&img.matrix[i].g,&img.matrix[i].b);
    }
  } else {

      int i = 0;

      while(i != 2)
      {
        img.mnumber[i] = getc(f);
        //printf("%c\n",(char) img.mnumber[i]);
        i++;
      }
      
        int c = getc(f);
        while (c == '#') {
          while (getc(f) != '\n') {
            getc(f);
          }
          c = getc(f);
        }
        ungetc(c, f);

      
      fscanf(f,"%d %d",&img.cols,&img.rows);
     
      fscanf(f,"%d",&img.maxcolor);

      img.matrix = (pixel*)malloc(img.cols * img.rows* sizeof(pixel));


      for(int i = 0; i < img.cols*img.rows;i++)
      {
        fscanf(f,"%u %u %u",&img.matrix[i].r,&img.matrix[i].g,&img.matrix[i].b);
      }

      fclose(f);
      return img;
    }
    return img;
}
  • Follow basic dev and debugging principles: 1. Do proper error checking. Specifically, check the return value of `fscanf` and `malloc` calls. 2 Use a debugger to step through the code, watching its flow and variable values to find where things start to go wrong. – kaylum May 14 '21 at 23:07
  • Your code only handles comments once and only in a specific part of the file. It will only have any chance of working if the comment starts on the second line of the file. So obviously it will not work for the example given as that is not where the comment starts. Running the code in a debugger (or even adding basic debug print statements in the loop) should clearly show you that the comment loop never runs. – kaylum May 14 '21 at 23:10
  • Actually I think it will not even work if the comment starts on the second line. Because the code does not correctly handle the newline character at the end of the first line. That newline will always be the character compared to `'#'` so will never match. Again, debug the code more systematically and you should easily catch these issues. – kaylum May 14 '21 at 23:19
  • To handle comments, I would read each line using `fgets`, then find the location (if any) of the `#` using `strchr`, and replace the `#` with a nul character `\0`. Then you need to check whether the line is blank, and if so, skip it. – user3386109 May 14 '21 at 23:29
  • note: each 'row' of the actual image needs to be a multiple of 4 pixel bytes so a single pixel (3 bytes) will not display correctly so each of the rows in your image needs a single 'dummy' byte – user3629249 May 15 '21 at 16:12

1 Answers1

1

I would write it as something like:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

typedef struct pixel {
    unsigned int r,g,b;
} pixel;

typedef struct imagem{
    int cols, rows;
    char mnumber[2];
    int maxcolor;
    pixel *matrix;
} imagem;

static imagem read(const char *filename)
{
    imagem img;
    FILE *f;
    int i;
    char buf[BUFSIZ];

    if (filename == NULL) {
        f = stdin;
    } else {
        f = fopen(filename, "r");
        if (f == NULL) {
            fprintf(stderr, "Can't open %s\n", filename);
            exit(1);
        }
    }

    if (fgets(buf, sizeof buf, f) == NULL) {
        fprintf(stderr, "Can't read data\n");
        exit(1);
    }

    for (i = 0; i < 2; i++) {
        img.mnumber[i] = buf[i];
    }

    if (fgets(buf, sizeof buf, f) == NULL) {
        fprintf(stderr, "Can't read data\n");
        exit(1);
    }
    sscanf(buf, "%d %d", &img.cols, &img.rows);
    if (fgets(buf, sizeof buf, f) == NULL) {
        fprintf(stderr, "Can't read data\n");
        exit(1);
    }
    sscanf(buf, "%d", &img.maxcolor);

    img.matrix = malloc(img.cols * img.rows * sizeof(pixel));
    if (img.matrix == NULL) {
        fprintf(stderr, "malloc failed\n");
        exit(1);
    }

    i = 0;
    while (fgets(buf, sizeof buf, f) != NULL) {
        if (buf[0] == '#') continue;
        else {
            sscanf(buf, "%u %u %u", &img.matrix[i].r, &img.matrix[i].g, &img.matrix[i].b);
            i++;
        }
    }    fclose(f);
    return img;
}
  • As suggested, it is better to use the combination of fgets() and sscanf() rather than fscanf() especially when we need to handle irregular lines.
  • You do not have to write a specific code to read from stdin when the filename is omitted. Just assign the file pointer f to stdin.
  • It is recommended to change the function name read() to something else as it conflicts with the existing system call function read(2).
tshiono
  • 21,248
  • 2
  • 14
  • 22