Search code examples
c++fileifstream

ifstream.read only reads half the file


I'm trying to make a simple image converter (ppm format to a custom one) and i'm having a problem with the ifstream.read method. Despite having this:

    int rows,cols, maxV;
    char header [100], *ptr;


    std::ifstream im;
    //open image in binary format
    im.open(name.c_str(), std::ios::in | std::ios::binary);

    if (!im)
    {
        std::cout << "Can't read image!" << std::endl;
        exit(1);
    }

    //read the header of the image
    im.getline(header, 3,'\n');
    //test if header is P6
    if ((header[0] != 80) || (header[1] != 54))
    {
        std::cout << "Image" << name << "is not .ppm format" << std::endl;
    }

    //get next line for height and width
    im.getline(header,100,'\n');
    //dont read the comments
    while (header[0] == '#')
        im.getline(header,100,'\n');

    //number of columns, rows
    cols = strtol(header, &ptr, 0);
    rows = strtol(header, &ptr, 0);
    maxV = strtol(header, &ptr, 0);

    const int rows1=rows;
    const int cols1=cols;


    Component * tbuffer;
    const_cast<Component*> (tbuffer);
    tbuffer = new Component[rows1*cols1 * 3];

    im.read((char *)tbuffer, cols*rows * 3);
    std::cout << tbuffer[3000000] << std::endl;
    im.close();

It only reads 2.700.007 elements out of 4.320.000 of the image i'm trying to read. so tbuffer[3.000.000] will "cout" NULL. Am i missing anything?

Edit: About component:

typedef unsigned char Component;

Edit2: The image is 1200*1200 (cols*rows). 2.700.007 is the last index of the tbuffer with a value in it. the rest of the tbuffer remains empty


Solution

  • The PPM format that you read does not guarantee that the magic number P6 is followed ended by a newline, nor that the rest of the header is followed by a newline, nor that lentgh, heigth and maxV are on the same line.

    But the main problem that you have is

    cols = strtol(header, &ptr, 0);  // you start at the begin of the header
    rows = strtol(header, &ptr, 0);  // you start again at the begin of the header
    maxV = strtol(header, &ptr, 0);  // and another time !! 
    

    So your rows and maxV might not be the values in the file. You should --regardless of the other changes mentionned above-- rather use:

    cols = strtol(header, &ptr, 0);  // you start at the begin of the header
    rows = strtol(ptr, &ptr, 0);  // continue after the first number
    maxV = strtol(ptr, &ptr, 0);  // ... 
    

    But keep also in mind that you should not assume that the three are on the same line. And that there might be additional comments.

    I propose you the following utility function to skip whitespace and comments according to the PPM format:

    ifstream& skipwcmt(ifstream& im) {
        char c; 
        do {
            while ((c = im.get()) != EOF && isspace(c)) ; 
            if (isdigit(c))
                im.unget(); 
            else if (c == '#')
                while ((c = im.get()) != EOF && c != '\n' && c != '\r');
        } while (isspace(im.peek()));
        return im;
    }
    

    You can use this function for reading the header as here:

    // ...
    // check magic number 
    im.read(header, 2); 
    if ((header[0] != 'P') || (header[1] != '6'))
    {
        std::cout << "Image" << name << "is not .ppm format" << std::endl;
        exit(1); 
    }
    skipwcmt(im) >> cols;
    skipwcmt(im) >> rows;
    skipwcmt(im) >> maxV; 
    if (!isspace(im.get())) { // folowed by exactly one whitespace !
        std::cout << "Image" << name << "has a corrupted header" << std::endl;
        exit(1);
    }
    
    // display the header to check the data
    cout << "cols=" << cols << ", rows=" << rows << ", maxcol=" << maxV << endl;
    

    Remark: I don't know if the files you have to read are guaranteed to have maxV<=255. In theory you could have values up to 65535 in which case you'd need to read 2 bytes for a color cmponent instead of one.