0

I am trying to get my code to read a PPM image (P3) and it isn't working as it should. The idea is to get a 3 unsigned chars and store them in RGB. But at the moment it is only resulting in taking the first character and ignoring the rest.

Image Image::readImg(std::string const &filename) {
    std::ifstream ifs;
    ifs.open(filename.c_str(), std::ios::binary);
    Image _in;
    try {
        if (ifs.fail()) {
            throw("Can't open input file");
        }
        std::string header;
        int w, h, max;
        ifs >> header;
        if (strcmp(header.c_str(), "P3") != 0) throw("Can't read input file");
        ifs >> w >> h >> max;
        _in.init(w, h);
        ifs.ignore(256, '\n');
        unsigned char pix[3];
        for (int i = 0; i < h; ++i){
            for (int j = 0; j < w; ++j){
                ifs.read(reinterpret_cast<char *>(pix), 3);
                _in.pixels[i][j].R = pix[0];
                _in.pixels[i][j].G = pix[1];
                _in.pixels[i][j].B = pix[2];
            }
        }
        std::cout << "|" << _in.pixels[0][0].R << " " << _in.pixels[0][0].G << " " << _in.pixels[0][0].B << "|";
        ifs.close();
    }
    catch (const char *err) {
        fprintf(stderr, "%s\n", err);
        ifs.close();
    }
    return _in;
}

Note the std::cout is supposed to output in my scenario |186 0 255|, but instead I get |1 8 6|.


EDIT: The file (original.ppm), when opened in Notepad++, looks like this (UNIX / UTF-8):

P3
1024 768
255
186 0 255 186 0 255 186 0 255 186 0 255 186 0 255 186 0 255 186 1 255 
186 1 254 186 1 254 185 2 254 185 2 254 185 1 254 185 2 253 185 3 253 
185 2 252 185 3 252 185 3 252 185 3 252 185 3 251 184 4 251 184 4 251 
184 4 251 184 4 251 184 5 250 184 5 250 183 5 250 183 6 249 183 6 249 
183 6 249 183 6 248 183 7 249 183 7 249 183 7 248 183 7 247 183 8 247 
182 7 247 182 8 246 183 9 247 183 9 246 183 9 246 182 9 246 181 9 246 
182 9 246 181 10 245 182 10 245 181 10 244 181 10 245 181 11 244 181 11 244
...

The result should be: _in.pixels[0][0].R = 186 _in.pixels[0][0].G = 0 _in.pixels[0][0].B = 255 and to continue collecting the RGB of all the pixels in the file.

  • `operator>>` is for reading whitespace-delimited text. Unless `P3` is followed by a space, in the file, this is not going to work. – Sam Varshavchik Oct 30 '16 at 23:51
  • This isn't the problem, but don't use exceptions for control flow. You don't need to close the file; the destructor will do that. So when you detect an error, print the message and return. – Pete Becker Oct 31 '16 at 00:00
  • "Note the std::cout is supposed to output in my scenario |186 0 255|, but instead I get |1 8 6|." From which I deduce your input is text formatted - meaning the components aren't encoded as binary bytes but as textual representation of those bytes written in decimal. And yet you try to read them as 3 bytes in your `ifs.read(..., 3)` and the next 3 assgns. – Adrian Colomitchi Oct 31 '16 at 00:11
  • P3 PPM is a text format. You got 3 digits of the same number because there were 3 text characters `186` in those positions. – Retired Ninja Oct 31 '16 at 00:19
  • How do you go about reading exactly 3 values per pixel loop and organizing it in a way to get RGB? Mind you these are supposed to represent bytes. – Alexander Shuev Oct 31 '16 at 01:15

2 Answers2

0

Using >> operations in streams skip whitespace by default.

If you want to preserve all characters then do (before you read from it):

ifs >> std::noskipws;

you might also need to open the file in binary mode. But I don't think it is necessary.

if you want to read exactly two bytes into a string you can use this instead of getline:

std::string st;
st.resize(2);
ifs.read(&st[0], st.size());
dmg
  • 4,231
  • 1
  • 18
  • 24
  • Did the first bit, it's not the problem considering how the file is layed out (see new edit in OP). Already in binary mode. and std::string st(2); doesn't compile properly. – Alexander Shuev Oct 31 '16 at 01:10
  • use instead std::string st {}; st.resize(2); – dmg Oct 31 '16 at 01:31
  • String resize, by the looks of it only resizes the # of characters in the string so by doing the above, it will read st[0] = 1 and st[1] = 8. This will not work. – Alexander Shuev Oct 31 '16 at 01:45
  • This is an example of how to read two bytes (as in the header). if you want to read three bytes, well, just change it to three. – dmg Oct 31 '16 at 01:48
  • I did that already, just didn't mention it. st[0] = 1, st[1] = 8 and st[2] = 6 – Alexander Shuev Oct 31 '16 at 01:49
0

[Updated] This should work:

int pix[3]; // not an unsigned char
...
ifs >> pix[0] >> pix[1] >> pix[2];

instead of:

ifs.read(reinterpret_cast<char *>(pix), 3);

Like you do for width and height?

ifs >> w >> h >> max;
Mikhail Churbanov
  • 4,436
  • 1
  • 28
  • 36
  • Still the same problem. It determines that 1 8 and 6 are all separate char when they should be one cohesive unsigned char. – Alexander Shuev Oct 31 '16 at 01:21
  • Your `_in.pixels[i][j].R` are also chars? If so use temp int / uint values to read in. I've updated the answer with it. – Mikhail Churbanov Oct 31 '16 at 01:26
  • Output just became scrambled mess of symbols. – Alexander Shuev Oct 31 '16 at 01:35
  • Let me guess... �� ? Have you checked in debugger what the values are? Cause of course if the char value is, e.g. 65 you will get the beatufill 'A' letter in output, while for 186 and 255 you will get some "scrambled mess"... – Mikhail Churbanov Oct 31 '16 at 01:52
  • Aha, it does work. In UTF-8, notepad++ identifies that values are being taken and stored as an 8 bit char. So 186 appears as 'xBA' or 186 when converted. Thanks :) Now I just have to figure out how to manipulate it and output the converted value. – Alexander Shuev Oct 31 '16 at 02:17