flipping data of .ppm image using three unsigned char ** arrays creating strange sheared image with supposedly correct algorithm

45 views Asked by At

(EDIT: included all source files)

So, for this console application I've been attempting to read .ppm files as input in either ASCII or binary format and when given an output type either ASCII or binary would create a copy in said format. This functionality works just fine, but there is also an optional command line argument for image operations like flipX, flipY, rotateCW, etc. For reference here is my header file with my image struct and pixel typedef along with the read and write functions from my main file, additionally my memory allocation and freeing functions in case there is something up with it too.

.ppm images

The input .ppm images are input as such with either a P3 or P6 value in the first line corresponding to if the file is in ASCII or binary format:

P3
# CREATOR: GIMP PNM Filter Version 1.1
# This has multiple comments to
# test that you handle them.
735 486
255
91
158
247
88
155
244
87
154
241

For this project the output file if this were to remain ASCII would look like so for easier readability of which pixel values are red, green, and blue.

P3
# CREATOR: GIMP PNM Filter Version 1.1
# This has multiple comments to
# test that you handle them.
735 486
255
91 158 247
88 155 244
87 154 241

The image I have been using for this function is this (yes it's a bit questionable but an image is an image)

original image

When calling my function using the command prompt "thpe11.exe --flipX --ascii skeletonburn skeletonburn.ppm" it results in the following which is wildly different then expected.

post "flip" image

The rows seemingly appear to still flip across the x axis, but the rest of the image is wildly sheared and ripped.

Below I have the code that pertains to the issue:

netPBM.h

This is for a school assignment hence why the comments state that I cannot change the image struct.

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <fstream>
#include <string>

using namespace std;
#ifndef __NETPBM__H__
#define __NETPBM__H__
typedef unsigned char pixel;

struct image
{                        // you may not add other fields to this
                         // structure.
    string magicNumber;
    string comment;
    int rows;
    int cols;
    pixel **redGray;     // handles red channel or grayscale
    pixel **green;
    pixel **blue;
};
// place your function prototypes here

bool isValidOperation( const char *operation );
bool readImage( ifstream &fin, image &img );
void writeImage( ofstream &fout, image &img );
bool openImageInput( ifstream &fin, char *fileName );
void closeImageInput( ifstream &fin );
bool openImageOutput( ofstream &fout, char *fileName );
void closeImageOutput( ofstream &fout );
bool allocate2DArray( pixel **&pixels, int rows, int cols );
void free2DArray( pixel **&pixels, int rows );
void flipX( image &img );
void flipY( image &img );
void rotateCW( image &img );

#endif

thpe11.cpp

main file

#include "netPBM.h"

const char *operationList[] = { "--flipX", "--flipY", "--rotateCW", "--rotateCCW", "--grayscale", "--sepia" };

int main( int argc, char *argv[] )
{
    ifstream fin;
    ofstream fout;
    image img;
    char *foutName;
    int offset;

    if ( argc == 5 )
    {
        offset = 0;
    }
    else if ( argc == 4 )
    {
        offset = 1;
    }
    else
    {
        // incorrect number of arguments error
        cout << "Usage: thpe11.exe [operation] --[ascii | binary] basename image" << endl;
        cout << endl;
        cout << "    basename     filename for the new image without extension" << endl;
        cout << "    image        a color ppm to be opened and manipulated" << endl;
        cout << endl;
        cout << "Output Type (required)" << endl;
        cout << "    --ascii      image data will be written out in using ascii numbers." << endl;
        cout << "    --binary     image data will be written out in raw format." << endl;
        cout << endl;
        cout << "Operations" << endl;
        cout << "    --flipX      swap the contents along the X axis" << endl;
        cout << "    --flipY      swap the contents along the Y axis" << endl;
        cout << "    --rotateCW   rotate the image clockwise" << endl;
        cout << "    --rotateCCW  rotate the image counter clockwise" << endl;
        cout << "    --grayscale  convert a color image to grayscale" << endl;
        cout << "    --sepia      antique a color image" << endl;

        exit( 0 );
    }

    // invalid output type error
    if ( !( strcmp( argv [ 2 - offset ], "--ascii" ) == 0 || strcmp( argv [ 2 - offset ], "--binary" ) == 0 ) )
    {
        cout << "Invalid Output Type Specified" << endl;
        cout << "Usage: thpe11.exe [operation] --[ascii | binary] basename image" << endl;
        cout << endl;
        cout << "    basename     filename for the new image without extension" << endl;
        cout << "    image        a color ppm to be opened and manipulated" << endl;
        cout << endl;
        cout << "Output Type (required)" << endl;
        cout << "    --ascii      image data will be written out in using ascii numbers." << endl;
        cout << "    --binary     image data will be written out in raw format." << endl;
        cout << endl;
        cout << "Operations" << endl;
        cout << "    --flipX      swap the contents along the X axis" << endl;
        cout << "    --flipY      swap the contents along the Y axis" << endl;
        cout << "    --rotateCW   rotate the image clockwise" << endl;
        cout << "    --rotateCCW  rotate the image counter clockwise" << endl;
        cout << "    --grayscale  convert a color image to grayscale" << endl;
        cout << "    --sepia      antique a color image" << endl;

        exit( 0 );
    }

    // invalid operation error
    if ( offset == 0 && !isValidOperation( argv [ 1 ] ) )
    {
        cout << "Invalid Operation Given" << endl;
        cout << "Usage: thpe11.exe [operation] --[ascii | binary] basename image" << endl;
        cout << endl;
        cout << "    basename     filename for the new image without extension" << endl;
        cout << "    image        a color ppm to be opened and manipulated" << endl;
        cout << endl;
        cout << "Output Type (required)" << endl;
        cout << "    --ascii      image data will be written out in using ascii numbers." << endl;
        cout << "    --binary     image data will be written out in raw format." << endl;
        cout << endl;
        cout << "Operations" << endl;
        cout << "    --flipX      swap the contents along the X axis" << endl;
        cout << "    --flipY      swap the contents along the Y axis" << endl;
        cout << "    --rotateCW   rotate the image clockwise" << endl;
        cout << "    --rotateCCW  rotate the image counter clockwise" << endl;
        cout << "    --grayscale  convert a color image to grayscale" << endl;
        cout << "    --sepia      antique a color image" << endl;

        exit( 0 );
    }

    // open input file
    if ( !openImageInput( fin, argv [ 4 - offset ] ) )
    {
        cout << "Unable to open image file: " << argv [ 4 - offset ] << endl;

        exit( 0 );
    }

    // read image data header and pixels
    if ( !readImage( fin, img ) )
    {
        cout << "Unable to allocate memory for storage" << endl;

        exit( 0 );
    }

    // change magicNumber and set output file extension
    foutName = argv [ 3 - offset ];
    if ( offset == 0 && strcmp( argv [ 1 ], "--grayscale" ) == 0 ) // grayscale case to change to P2 or P5
    {
        strcat( foutName, ".pgm" );
        if ( strcmp( argv [ 2 ], "--ascii" ) == 0 )
        {
            img.magicNumber = "P2";
        }
        else if ( strcmp( argv [ 2 ], "--binary" ) == 0 )
        {
            img.magicNumber = "P5";
        }
    }
    else // normal case to change to P3 to P6 if necessary
    {
        strcat( foutName, ".ppm" );
        if ( img.magicNumber == "P3" && strcmp( argv [ 2 - offset ], "--binary" ) == 0 )
        {
            img.magicNumber = "P6";
        }
        else if ( img.magicNumber == "P6" && strcmp( argv [ 2 - offset ], "--ascii" ) == 0 )
        {
            img.magicNumber = "P3";
        }
    }

    // open output file of corresponding file type with magicNumber
    if ( !openImageOutput( fout, foutName ) )
    {
        cout << "Unable to open image file: " << foutName << endl;

        exit( 0 );
    }

    // call edit operation "--flipX", "--flipY", "--rotateCW", "--rotateCCW", "--grayscale", "--sepia" 
    if ( offset == 0 )
    {
        if ( strcmp( argv [ 1 ], "--flipX" ) == 0 )
        {
            flipX( img );
        }
        else if ( strcmp( argv [ 1 ], "--flipY" ) == 0 )
        {
            flipY( img );
        }
        else if ( strcmp( argv [ 1 ], "--rotateCW" ) == 0 )
        {
            // call rotateCW
        }
        else if ( strcmp( argv [ 1 ], "--rotateCCW" ) == 0 )
        {
            // call rotateCCW
        }
        else if ( strcmp( argv [ 1 ], "--grayscale" ) == 0 )
        {
            // call grayscale
        }
        else if ( strcmp( argv [ 1 ], "--sepia" ) == 0 )
        {
            // call sepia
        }
    }

    // write data to output image
    writeImage( fout, img );

    // call deallocate memory
    free2DArray( img.redGray, img.rows );
    free2DArray( img.green, img.rows );
    free2DArray( img.blue, img.rows );

    // call close file
    closeImageInput( fin );
    closeImageOutput( fout );

    return 0;
}


bool isValidOperation( const char *operation )
{
    for ( size_t i = 0; i < size( operationList ); i++ )
    {
        if ( strcmp( operation, operationList [ i ] ) == 0 )
        {
            // operation valid
            return true;
        }
    }
    // operation not valid
    return false;
}


bool readImage( ifstream &fin, image &img )
{
    string tempStr;
    pixel value;
    int i, j, k;

    // read header
    // read magic number
    getline( fin, img.magicNumber );

    // read the rest of the header
    while ( true )
    {
        fin >> tempStr;
        if ( tempStr == "#" )
        {
            getline( fin, tempStr );
            if ( img.comment.empty() )
            {
                img.comment = "#" + tempStr;
            }
            else
            {
                img.comment += "\n";
                img.comment += "#" + tempStr;
            }
        }
        else
        {
            img.rows = stoi( tempStr );
            fin >> tempStr;
            img.cols = stoi( tempStr );
            break;
        }
    }

    // read maximum pixel size
    fin.ignore( 1 );
    getline( fin, tempStr );
    if ( stoi( tempStr ) > 255 )
    {
        cout << "Image does not consist of 8-bit pixels" << endl;

        exit( 0 );
    }

    // allocate all 3 pixel arrays
    if ( !allocate2DArray( img.redGray, img.rows, img.cols ) )
    {
        return false;
    }

    if ( !allocate2DArray( img.green, img.rows, img.cols ) )
    {
        return false;
    }

    if ( !allocate2DArray( img.blue, img.rows, img.cols ) )
    {
        return false;
    }

    // verify magicNumber and then read pixels
    if ( img.magicNumber == "P3" ) // ascii
    {
        // read in pixel values and store in pixel arrays
        for ( i = 0; i < img.rows; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                for ( k = 0; k < 3; k++ )
                {
                    fin >> tempStr;
                    fin.ignore(); // ignore space/newline character
                    value = static_cast< char >( stoi( tempStr ) );

                    // assign pixel to appropriate color band
                    switch ( k % 3 )
                    {
                        case 0:
                            img.redGray [ i ][ j ] = value;
                            break;
                        case 1:
                            img.green [ i ][ j ] = value;
                            break;
                        case 2:
                            img.blue [ i ][ j ] = value;
                            break;
                    }
                }
            }
        }
    }
    else if ( img.magicNumber == "P6" ) // binary
    {
        // read in pixel values and store in pixel arrays
        for ( i = 0; i < img.rows; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                for ( k = 0; k < 3; k++ )
                {
                    fin.read( reinterpret_cast< char * >( &value ), sizeof( pixel ) );

                    // assign pixel to appropriate color band
                    switch ( k % 3 )
                    {
                        case 0:
                            img.redGray [ i ][ j ] = value;
                            break;
                        case 1:
                            img.green [ i ][ j ] = value;
                            break;
                        case 2:
                            img.blue [ i ][ j ] = value;
                            break;
                    }
                }
            }
        }
    }
    else // error case if image is not of type P3 or P6
    {
        cout << "Not a valid netpbm image" << endl;

        exit( 0 );
    }

    return true;
}


void writeImage( ofstream &fout, image &img )
{
    pixel value;
    int i, j, k;

    // populate header
    fout << img.magicNumber << '\n';
    fout << img.comment << '\n';
    fout << img.rows << ' ' << img.cols << '\n';
    fout << 255 << '\n';

    // verify magicNumber and then populate pixels
    if ( img.magicNumber == "P3" ) // ascii
    {
        for ( i = 0; i < img.rows; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                // assign pixels to appropriate color bands
                fout << static_cast< int >( img.redGray [ i ][ j ] ) << " ";
                fout << static_cast< int >( img.green [ i ][ j ] ) << " ";
                fout << static_cast< int >( img.blue [ i ][ j ] ) << "\n";
            }
        }
    }
    else if ( img.magicNumber == "P2" ) // greyscale ascii
    {
        for ( i = 0; i < img.rows * img.cols; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                // assign pixel to color band
                fout << static_cast< int >( img.redGray [ i ][ j ] ) << "\n";
            }
        }
    }
    else if ( img.magicNumber == "P6" ) // binary
    {
        for ( i = 0; i < img.rows; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                for ( k = 0; k < 3; k++ )
                {
                    // assign pixel to appropriate color band
                    switch ( k % 3 )
                    {
                        case 0:
                            value = img.redGray [ i ][ j ];
                            break;
                        case 1:
                            value = img.green [ i ][ j ];
                            break;
                        case 2:
                            value = img.blue [ i ][ j ];
                            break;
                    }

                    fout.write( reinterpret_cast< char * >( &value ), sizeof( pixel ) );
                }
            }
        }
    }
    else if ( img.magicNumber == "P5" ) // greyscale binary
    {
        for ( i = 0; i < img.rows; i++ )
        {
            for ( j = 0; j < img.cols; j++ )
            {
                // assign pixel to color band
                value = img.redGray [ i ][ j ];

                fout.write( reinterpret_cast< char * >( &value ), sizeof( pixel ) );
            }
        }
    }
}

memory.cpp

#include "netPBM.h"

// allocate 2d array for color-band data
bool allocate2DArray( pixel **&pixels, int rows, int cols )
{
    int i;

    pixels = new ( nothrow ) pixel * [ rows ];
    if ( pixels == nullptr )
    {
        return false;
    }
    for ( i = 0; i < rows; i++ )
    {
        pixels [ i ] = new ( nothrow ) pixel [ cols ];
        if ( pixels [ i ] == nullptr )
        {
            free2DArray( pixels, i );
            return false;
        }
    }

    return true;
}

// free 2d array of color-band data
void free2DArray( pixel **&pixels, int rows )
{
    int i;

    if ( pixels == nullptr )
    {
        return;
    }

    for ( i = 0; i < rows; i++ )
    {
        delete[] pixels [ i ];
    }
    delete[] pixels;
    pixels = nullptr;
}

imageOperations.cpp

#include "netPBM.h"

// flip on x-axis
void flipX( image &img )
{
    pixel temp;
    int i, j;

    for ( i = 0; i < img.rows / 2; i++ )
    {
        for ( j = 0; j < img.cols; j++ )
        {
            temp = img.redGray [ i ][ j ];
            img.redGray [ i ][ j ] = img.redGray [ img.rows - 1 - i ][ j ];
            img.redGray [ img.rows - 1 - i ][ j ] = temp;

            temp = img.green [ i ][ j ];
            img.green [ i ][ j ] = img.green [ img.rows - 1 - i ][ j ];
            img.green [ img.rows - 1 - i ][ j ] = temp;

            temp = img.blue [ i ][ j ];
            img.blue [ i ][ j ] = img.blue [ img.rows - 1 - i ][ j ];
            img.blue [ img.rows - 1 - i ][ j ] = temp;
        }
    }
}

// flip on y-axis
void flipY( image &img )
{
    int start = 0;
    int end = img.rows - 1;

    while ( start < end )
    {
        swap( img.redGray [ start ], img.redGray [ end ] );
        swap( img.green [ start ], img.green [ end ] );
        swap( img.blue [ start ], img.blue [ end ] );

        start++;
        end--;
    }
}

// rotate clockwise


// rotate counter clockwise


// grayscale


// sepia

imageFileIO.cpp

#include "netPBM.h"

// open image input file
bool openImageInput( ifstream &fin, char *fileName )
{
    fin.open( fileName, ios::in | ios::binary );
    if ( !fin.is_open() )
    {
        return false;
    }

    return true;
}

// close image input file
void closeImageInput( ifstream &fin )
{
    fin.close();
}

// open image output file
bool openImageOutput( ofstream &fout, char *fileName )
{
    fout.open( fileName, ios::out | ios::trunc | ios::binary );
    if ( !fout.is_open() )
    {
        return false;
    }

    return true;
}

// close image output file
void closeImageOutput( ofstream &fout )
{
    fout.close();
}

I have spent countless hours attempting to rewrite my function for flipX by changing up whether rows and columns were i or j and using different algorithms I have seen searching the web. There was at one point that I had this working but that was before I had realized I was storing my pixel data wildly different and more complicated than I should've. Earlier I was storing each individual number to the 2nd index of the pixel arrays when I should've been storing them as ascii characters from the start. I know that simply reversing the columns/rows of a 2d array should be straightforward since the simplicity of the code should reflect that, so I must be missing something with how my data is stored and reinterpreting it incorrectly as opposed to how writeImage reads it to copy it. I figured I've spent a good amount of hours to warrant creating a post here so I can stop wasting more without coming to a proper conclusion.

0

There are 0 answers