Search code examples
c++ppm

How to read and write a ppm file?


I try to read a ppm file aand create a new one identical. But when I open them with GIMP2 the images are not the same.

Where is the problem with my code ?

int main()
{
    FILE *in, *out;
    in = fopen("parrots.ppm","r");
    if( in == NULL )
    {
        std::cout<<"Error.\n";
        return 0;
    }

    unsigned char *buffer = NULL;

    long size = 0;
    fseek(in, 0, 2);
    size = ftell(in);
    fseek(in, 0, 0);

    buffer = new unsigned char[size];
    if( buffer == NULL )
    {
        std::cout<<"Error\n";
        return 0;
    }

    if( fread(buffer, size, 1, in) < 0 )
    {
          std::cout<<"Error.\n";
          return 0 ; 
    }

    out = fopen("out.ppm","w");
    if( in == NULL )
    {
         std::cout<<"Error.\n";
         return 0;
    }

    if( fwrite(buffer, size, 1, out) < 0 )
    {
         std::cout<<"Error.\n";
         return 0;
    }

    delete[] buffer;

    fcloseall();

    return 0;
}

Before that I read the ppm file in a structure and when I wrote it I get the same image but the green was more intense than in the original picture. Then I tried this simple reading and writing but I get the same result.


Solution

  • int main()
    

    Missing includes.

    FILE *in, *out;
    

    C style I/O in a C++ program, why? Also, declare at point of initialization, close to first use.

    in = fopen("parrots.ppm","r");
    

    This is opening the file in text mode, which is most certainly not what you want. Use "rb" for mode.

    unsigned char *buffer = NULL;
    

    Declare at point of initialization, close to first use.

    fseek(in, 0, 2);
    

    You are supposed to use SEEK_END, which is not guaranteed to be defined as 2.

    fseek(in, 0, 0);
    

    See above, for SEEK_SET not guaranteed to be defined as 0.

    buffer = new unsigned char[size];
    if( buffer == NULL )
    

    By default, new will not return a NULL pointer, but throw a std::bad_alloc exception. (With overallocation being the norm on most current operating systems, checking for NULL would not protect you from out-of-memory even with malloc(), but good to see you got into the habit of checking anyway.)

    C++11 brought us smart pointers. Use them. They are an excellent tool to avoid memory leaks (one of the very few weaknesses of C++).

    if( fread(buffer, size, 1, in) < 0 )
    

    Successful use of fread should return the number of objects written, which should be checked to be equal the third parameter (!= 1), not < 0.

    out = fopen("out.ppm","w");
    

    Text mode again, you want "wb" here.

    if( fwrite(buffer, size, 1, out) < 0 )
    

    See the note about the fread return value above. Same applies here.

    fcloseall();
    

    Not a standard function. Use fclose( in ); and fclose( out );.


    A C++11-ified solution (omitting the error checking for brevity) would look somewhat like this:

    #include <iostream>
    #include <fstream>
    #include <memory>
    
    int main()
    {
        std::ifstream in( "parrots.ppm", std::ios::binary );
        std::ofstream out( "out.ppm", std::ios::binary );
    
        in.seekg( 0, std::ios::end );
        auto size = in.tellg();
        in.seekg( 0 );
    
        std::unique_ptr< char[] > buffer( new char[ size ] );
    
        in.read( buffer.get(), size );
        out.write( buffer.get(), size );
    
        in.close();
        out.close();
    
        return 0;
    }
    

    Of course, a smart solution would do an actual filesystem copy, either through Boost.Filesystem or the standard functionality (experimental at the point of this writing).