UoM-EECS402 / Project3 / PpmImageClass.cpp
PpmImageClass.cpp
Raw
#include <iostream>
#include <fstream>
#include "PpmImageClass.h"
#include "constants.h"
#include "RowColumnClass.h"
#include "ColorClass.h"
#include "RectangleClass.h"
#include "PaternClass.h"
using namespace std;

// -----Resubmission change
// - Put all global functions that was related to image class into 
//   PpmImageClass methods.
//   ex) readImageFile, saveImageFile, annotateRectangle, insertImage,
//       annotatePattern ...
// - save image file by ColorClass.write() method. 

//Programmer: Youngjun Woo
//Date: November 4, 2021
//Purpose: PpmImageClass is 2d array consist of ColorClass.

bool PpmImageClass::readImageFile(string inFileName)
{
  ifstream inFile;
  bool isSuccess;
  
  inFile.open(inFileName.c_str());
  if (inFile.fail())
  {
    cout << "ERROR: Unable to open input image file! " 
         << "Please check your direction."
         << endl;

    inFile.close();
    return false;
  }

  isSuccess = readMagicNumber(inFile);
  if (isSuccess == false)
  {
    inFile.close();  
    return false;    
  }

  else
  {
    isSuccess = readImageSize(inFile);
    if (isSuccess == false)
    {
      inFile.close();  
      return false;    
    }

    else
    {
      isSuccess = readImageMaxValue(inFile);
      if (isSuccess == false)
      {
        inFile.close();  
        return false;    
      }

      else
      {
        isSuccess = readImageMatrix(inFile);
        if (isSuccess == false)
        {
          inFile.close();  
          return false;    
        }

        else
        {
          inFile.close();
          return true;
        }
      }
    }
  }
}
// This will read Ppm image file and initialize attributes of image.

void PpmImageClass::setMagicNumberTo(string inMagicNumber)
{
  magicNumber = inMagicNumber;    
}
// It will set magic number as input values.

void PpmImageClass::setWidthTo(int inWidth)
{
  width = inWidth;     
}
// It will set image width values as input values.

void PpmImageClass::setHeightTo(int inHeight)
{
  height = inHeight;     
}
// It will set image height values as input values.

void PpmImageClass::setMaxValueTo(int inMaxValue)
{
  maxValue = inMaxValue;     
}
// It will set image max values as input values.

bool PpmImageClass::setColorAtLocation(RowColumnClass &inRowCol, 
                                       ColorClass &inColor)
{
  if (inRowCol.getRow() >= 0 and
      inRowCol.getRow() <= height - 1 and
      inRowCol.getCol() >= 0 and
      inRowCol.getCol() <= width - 1)
  {
    matrixValue[inRowCol.getRow()][inRowCol.getCol()].setTo(inColor);  
    
    return true; 
  }

  else
  {
    return false;
  }
}
// This function attemps to set the pixel at the location specified by the
// inRowCol parameter to the color specified via the inColor parameter. 
// If the location specified is a valid location for the image, the pixel 
// value is changed and the function returns true, otherwise the image is 
// not modified in ANY way, and the function returns false.

string PpmImageClass::getMagicNumber() const
{
  return magicNumber;    
}
// It will get magic number of image.

int PpmImageClass::getWidth() const
{
  return width;    
}
// It will get width of image.

int PpmImageClass::getHeight() const
{
  return height;    
}
// It will get height of image.

int PpmImageClass::getMaxValue() const
{
  return maxValue;    
}
// It will get max value of image.

bool PpmImageClass::getColorAtLocation(RowColumnClass &inRowCol, 
                                       ColorClass &outColor) const
{
  if (inRowCol.getRow() >= 0 and
      inRowCol.getRow() <= height - 1 and
      inRowCol.getCol() >= 0 and
      inRowCol.getCol() <= width - 1)
  {
    outColor = matrixValue[inRowCol.getRow()][inRowCol.getCol()];

    return true;
  }

  else
  {
    return false;
  }
}
// If the row/column provided is a valid row/column for the image, this 
// function returns true and the output parameter "outColor" is assigned 
// to the color of the image pixel at that location. If the row/column is 
// invalid (i.e. outside the image bounds) then the function returns false, 
// and the output parameter "outColor" is not modified in any way.

void PpmImageClass::printHeaderValues() const
{
  cout << "PPM image header information" << endl;
  cout << "  Image magic number: " << getMagicNumber() << endl;
  cout << "  Image width: " << getWidth() << endl;
  cout << "  Image height: " << getHeight() << endl;
  cout << "  Image maxValue: " << getMaxValue() << endl;
}
// It prints image header values: magic number, width, height, max value.

void PpmImageClass::printMatrixValues() const
{
  cout << "PPM image matrix values" << endl;  
  for (int rInd = 0; rInd < height; rInd++)
  {
    matrixValue[rInd][0].printComponentValues();
    
    for (int cInd = 1; cInd < width; cInd++)
    {
      cout << "--" ;
      matrixValue[rInd][cInd].printComponentValues();
    }
    
    cout << endl;  
  }
}
// It will pring image matrix values.

bool PpmImageClass::readMagicNumber(ifstream &inFile)
{
  string headerVal;

  inFile >> headerVal;

  if (headerVal == "")
  {
    cout << "ERROR: EOF before read PPM header values." << endl;

    return false;    
  }

  else if (headerVal != PPM_IMAGE_MAGIC_NUMBER or inFile.fail())
  {
    cout << "ERROR: Invalid ppm image magic number. It must be "
         << PPM_IMAGE_MAGIC_NUMBER << ", but found " << headerVal << endl;

    return false;     
  }
  
  else
  {
    setMagicNumberTo(headerVal);

    return true;
  }
}
// It will read magic number of ppm image file.

bool PpmImageClass::readImageSize(ifstream &inFile)
{
  int headerVal;
  int *imageSizeArray;

  imageSizeArray = new int[PPM_IMAGE_SIZE_VALUE_COUNT];

  for (int i = 0; i < PPM_IMAGE_SIZE_VALUE_COUNT; i++)
  {
    if (inFile.eof())
    {
      cout << "ERROR: EOF before read PPM header values." << endl;
      
      delete [] imageSizeArray; 
      imageSizeArray = 0;
      return false;
    }

    else
    {
      inFile >> headerVal;

      if (inFile.fail() or headerVal <= MIN_PPM_IMAGE_SIZE_VALUE)
      {
        cout << "ERROR: Invalid image size. They must be integer value greater"
             << " than " << MIN_PPM_IMAGE_SIZE_VALUE << endl;         
        
        delete [] imageSizeArray; 
        imageSizeArray = 0;
        return false;
      }  

      else
      {
        imageSizeArray[i] = headerVal;
      }
    }
  }

  setWidthTo(imageSizeArray[0]);
  setHeightTo(imageSizeArray[1]);  
  
  delete [] imageSizeArray; 
  imageSizeArray = 0;
  return true;  
}
// It will read width and height from ppm image file.

bool PpmImageClass::readImageMaxValue(ifstream &inFile)
{
  int headerVal;
  
  if (inFile.eof())
  {
    cout << "ERROR: EOF before read PPM header values." << endl;
    
    return false;
  }

  else
  {
    inFile >> headerVal;
    
    if (inFile.fail())
    {
      cout << "ERROR: Invalid image max value. It must be integer." << endl;       
      
      return false;
    }

    else if (headerVal != MAX_PPM_IMAGE_VALUE)
    {
      cout << "ERROR: Invalid image max value. It must be " 
           << MAX_PPM_IMAGE_VALUE << ", but found " << headerVal << endl;    
      
      return false;
    }

    else
    {
      setMaxValueTo(headerVal);

      return true;    
    }
  }
}
// It will read max value of ppm image file.

bool PpmImageClass::readImageMatrix(ifstream &inFile)
{
  int totalRgbValuesCount;
  int *rgbValuesArray;
  int rgbValue;
  string checkRemainingValues;
  int k;

  totalRgbValuesCount = width * height * 3;
  rgbValuesArray = new int[totalRgbValuesCount];
  
  // Read all matrix values and check errors
  for (int i = 0; i < totalRgbValuesCount; i++)
  {
    if (inFile.eof())
    {
      cout << "ERROR: EOF before read all PPM rgb matrix values." 
           << "Total rgb pixels are supposed to be " 
           << width << " x " << height 
           << ", but there are less rgb pixels in the file." << endl;    
       
      delete [] rgbValuesArray; 
      rgbValuesArray = 0;
      return false;
    }
 
    else 
    {
      inFile >> rgbValue;
 
      if (inFile.fail() or rgbValue < MIN_PPM_IMAGE_VALUE or 
          rgbValue > MAX_PPM_IMAGE_VALUE)
      {
        cout << "ERROR: Invalid rgb values." 
             << "They must be integer value between "
             << MIN_PPM_IMAGE_VALUE << " and " 
             << MAX_PPM_IMAGE_VALUE << "." << endl;  
  
        delete [] rgbValuesArray; 
        rgbValuesArray = 0;
        return false;     
      }
 
      else 
      {
        rgbValuesArray[i] = rgbValue;     
      }
    }
  }    
  
  inFile >> checkRemainingValues;

  if (checkRemainingValues != "")
  {
    cout << "ERROR: There are more rgb values than expected." << endl;  
    
    delete [] rgbValuesArray; 
    rgbValuesArray = 0;
    return false;
  }

  else
  {
    matrixValue = new ColorClass*[height];

    for (int rInd = 0; rInd < height; rInd++)
    {
      matrixValue[rInd] = new ColorClass[width];    
    }    

    k = 0;
    for (int rInd = 0; rInd < height; rInd++)
    {
      for (int cInd = 0; cInd < width; cInd++)
      {
        matrixValue[rInd][cInd] = ColorClass(rgbValuesArray[k], 
                                             rgbValuesArray[k + 1],
                                             rgbValuesArray[k + 2]); 

        k = k + 3;  
      }    
    }

    delete [] rgbValuesArray; 
    rgbValuesArray = 0;
    return true;
  }
}
// It will read matrix values of ppm image file.

void PpmImageClass::saveImageFile() const
{
  ofstream outFile;
  string outFileName;
  bool validInputFound;

  validInputFound = false;
  while (!validInputFound)
  {
    cout << "Enter string for PPM file name to output: ";  
    
    cin >> outFileName;
    if (cin.fail())
    {
      cin.clear();
      cin.ignore(200, '\n');

      cout << "ERROR: Error found in file name. Try again! " << endl;
    }

    else
    {
      outFile.open(outFileName.c_str());  
      if (outFile.fail())
      {
        cout << "ERROR: Unable to open output file" << endl;
        outFile.close();
      }    

      else
      {
        outFile << magicNumber << endl;
        outFile << width << " " << height << endl;
        outFile << maxValue << endl;

        for (int rInd = 0; rInd < height; rInd++)
        {
          for (int cInd = 0; cInd < width; cInd++)
          {
            matrixValue[rInd][cInd].writeRgbValue(outFile);   
          }    

          outFile << endl;
        }

        outFile.close();

        validInputFound = true;
      }
    }
  }
}  
// It will save current image.

bool PpmImageClass::checkValidRowCol(RowColumnClass inRowCol) const
{
  if (inRowCol.getRow() > height - 1 or 
      inRowCol.getCol() > width - 1)
  {
    cout << "ERROR: (row, column) goes out of bounds of the original image." 
         << endl;
    cout << "  original image height: " << MIN_PPM_IMAGE_SIZE_VALUE 
         << " ~ " << getHeight() - 1 << endl;
    cout << "  original image width: " << MIN_PPM_IMAGE_SIZE_VALUE 
         << " ~ " << getWidth() - 1 << endl;     
    
    return false;    
  }

  else 
  {
    return true;    
  }
}
// It will check input inRowCol is under bound of current image.

bool PpmImageClass::annotateRectangle(RectangleClass inRectangle)
{
  RowColumnClass imageRowCol;
  ColorClass rectangleColor = inRectangle.getColor();
  int centerRow = inRectangle.getCenter().getRow();
  int centerCol = inRectangle.getCenter().getCol();
  int numOfHalfRow = inRectangle.getNumOfHalfRow();
  int numOfHalfCol = inRectangle.getNumOfHalfCol();
  

  if ((centerRow - numOfHalfRow) >= 0 and 
      (centerCol - numOfHalfCol) >= 0 and
      (centerRow + numOfHalfRow) <= height - 1 and
      (centerCol + numOfHalfCol) <= width - 1)
  {
    if (inRectangle.getIsFilled())
    {
      for (int rInd = centerRow - numOfHalfRow; 
           rInd <= centerRow + numOfHalfRow; 
           rInd++) 
      {
        for (int cInd = centerCol - numOfHalfCol;
             cInd <= centerCol + numOfHalfCol;
             cInd++)
        {
          imageRowCol.setRowCol(rInd, cInd);
          setColorAtLocation(imageRowCol, rectangleColor);  
        }       
      }
    }

    else
    {
      for (int rInd = centerRow - numOfHalfRow;
           rInd <= centerRow + numOfHalfRow;
           rInd++)
      {
        imageRowCol.setRowCol(rInd, centerCol - numOfHalfCol);
        setColorAtLocation(imageRowCol, rectangleColor);
        imageRowCol.setRowCol(rInd, centerCol + numOfHalfCol);
        setColorAtLocation(imageRowCol, rectangleColor);
      }

      for (int cInd = centerCol - numOfHalfCol;
           cInd <= centerCol + numOfHalfCol;
           cInd ++)
      {
        imageRowCol.setRowCol(centerRow - numOfHalfRow, cInd);
        setColorAtLocation(imageRowCol, rectangleColor);
        imageRowCol.setRowCol(centerRow + numOfHalfRow, cInd);  
        setColorAtLocation(imageRowCol, rectangleColor);
      }              
    }

    return true;
  }

  else 
  {
    cout << "ERROR: Your rectangle goes out of bounds of the original image." 
         << endl;   

    return false;       
  }     
}
// It will annotate input rectangle into current image

void PpmImageClass::insertImage(PpmImageClass inImage, RowColumnClass upperLeft,
                                ColorClass transparencyColor)
{
  RowColumnClass imageRowCol;
  ColorClass outColor;

  for (int rInd = 0; rInd < inImage.getHeight(); rInd++)
  {
    for (int cInd = 0; cInd < inImage.getWidth(); cInd++)
    {
      imageRowCol.setRowCol(rInd, cInd);
      inImage.getColorAtLocation(imageRowCol,outColor);

      if (outColor.getRedAmount() != transparencyColor.getRedAmount() or 
          outColor.getGreenAmount() != transparencyColor.getGreenAmount() or 
          outColor.getBlueAmount() != transparencyColor.getBlueAmount())
      {
        imageRowCol.setRowCol(rInd + upperLeft.getRow(), 
                              cInd + upperLeft.getCol());
        setColorAtLocation(imageRowCol, outColor);    
      }  
    }    
  }      
}
// It will insert input image into current image

void PpmImageClass::annotatePattern(PatternClass inPattern, 
                                    RowColumnClass upperLeft)
{
  RowColumnClass rowCol;
  ColorClass inPatternColor;
  int outPattern;

  inPatternColor = inPattern.getColor();

  for (int rInd = 0; rInd < inPattern.getNumRow(); rInd++)
  {
    for (int cInd = 0; cInd < inPattern.getNumCol(); cInd++)
    {
      rowCol.setRowCol(rInd, cInd);
      inPattern.getPatternAtLocation(rowCol, outPattern);

      if (outPattern == 1)
      {
        rowCol.setRowCol(rInd + upperLeft.getRow(), cInd + upperLeft.getCol());
        setColorAtLocation(rowCol, inPatternColor);    
      }    
    }    
  }
}
// It will annotate pattern into current image.