///////////////////////////////////////////////////////////////////////////////
// Image.cpp
// 
//     This image class contains the data required to represent a
// graphics image.  There are various routines for accessing the image
// data, but you may not access the data directly.
///////////////////////////////////////////////////////////////////////////////

#include "Image.h"
#include <fstream.h>

// No global variables.


///////////////////////////////////////////////////////////////////////////////
// Image::Image ()                                          Default Constructor
//
//      This constructor initializes this image to be a 1x1 black image.
///////////////////////////////////////////////////////////////////////////////

Image::Image ()
: data (NULL)
{
  allocateImage (1, 1);
}


///////////////////////////////////////////////////////////////////////////////
// Image::Image (int width, int height)                             Constructor
//
//      This constructor initializes this image to be a black image of the
// specified width and height.
///////////////////////////////////////////////////////////////////////////////

Image::Image (int width, int height)
: data (NULL)
{
  allocateImage (width, height);
}


///////////////////////////////////////////////////////////////////////////////
// Image::Image (const Image & in)                             Copy Constructor
//
//      This constructor initializes this image to be a copy of the
// given image.
///////////////////////////////////////////////////////////////////////////////

Image::Image (const Image & in)
: data (NULL)
{
  copyImage (in);
}


///////////////////////////////////////////////////////////////////////////////
// Image::~Image ()                                         Default Denstructor
//
//      Free up all memory associated with this object.
///////////////////////////////////////////////////////////////////////////////

Image::~Image ()
{
  freeImage ();
}

///////////////////////////////////////////////////////////////////////////////
// Image & Image::operator = (const Image & in)             Assignment Operator
//
//      This function makes it so that images can be assigned to other
// image variables.
///////////////////////////////////////////////////////////////////////////////

Image & Image::operator = (const Image & in)
{
  copyImage (in);

  return *this;
}

///////////////////////////////////////////////////////////////////////////////
// Pixel & Image::getPixel (int x, int y)
// void    Image::setPixel (int x, int y, const Pixel &p)
//
//      These routines are used to access pixels.  Attempting to read an
// out-of-bounds pixel will result in a bright purple pixel.  Attempting
// to write an out-of-bounds pixel will do nothing.  All other accesses
// will behave as expected.
//
//      Pixels are numbered from the upper-left hand corner of the image,
// with the uppermost, leftmost pixel being 0, 0.
//
//      Note that pixels are passed by reference.
///////////////////////////////////////////////////////////////////////////////

Pixel & Image::getPixel (int x, int y)
{
  static Pixel errorPixel;

  // Validate parameters and make sure we have an image.  If not valid,
  //   return a bright purple pixel.

  if (data == NULL || x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
  {
    errorPixel.setRGBColor (1, 0, 1);
    return errorPixel;
  }

  // Return the appropriate pixel.

  return data[y][x];
}

void Image::setPixel (int x, int y, const Pixel & p)
{
  // Validate parameters and make sure we have an image.  If no image,
  //   or invalid parameters, return early from the function.

  if (data == NULL || x < 0 || x >= imageWidth || y < 0 || y >= imageHeight)
    return;

  // Set the pixel.

  data[y][x] = p;
}


///////////////////////////////////////////////////////////////////////////////
// int Image::getWidth ()
// int Image::getHeight ()
//
//      These access functions allow you to access the height and width
// of the current image.  If for any reason the current image is invalid,
// these functions will return 0.
///////////////////////////////////////////////////////////////////////////////

int Image::getWidth ()
{
  return imageWidth;
}

int Image::getHeight ()
{
  return imageHeight;
}


///////////////////////////////////////////////////////////////////////////////
// void Image::resize (int width, int height)
//
//      This function destroys the current image, and creates a new image
// of the specified dimensions.  The new image is black.
///////////////////////////////////////////////////////////////////////////////

void Image::resize (int width, int height)
{
  allocateImage (width, height);
}


///////////////////////////////////////////////////////////////////////////////
// bool Image::writeBMP (char * filename, ostream & errorout)
//
//      This function writes the image to a file using Windows .bmp format.
// It is written as a true-color image (24 bit).  You may not call this
// routine from your compressor class!
//
//      Upon successful completion, this function returns true.  If for
// any reason this function fails (cannot open file, no image, etc.), then
// false is returned.
//
//      Error messages are written to errorout.  (errorout may be NULL if
// you do not wish to print error messages.)
///////////////////////////////////////////////////////////////////////////////

bool Image::writeBMP (char * filename, ostream & errorout)
{
  int temp, x, y, c;
  
  // Make sure we have a valid filename and image.

  if (filename == NULL || data == NULL)
  {
    if (errorout != NULL && data == NULL)
      errorout << "Image::writeBMP - No image to write." << endl;
    else
      errorout << "Image::writeBMP - No filename given." << endl;      
    return false;
  }
  
  // Attempt to open the file for writing.

  ofstream out (filename);

  if (!out)
  {
    if (errorout != NULL && data == NULL)
      errorout << "Image::writeBMP - Unable to write to file " << filename
	       << "." << endl;
    return false;
  }
  
  // Rows need to be 32-bit aligned.  Calculate how many bytes of padding
  //   are needed.

  int padding = (4 - ((imageWidth * 3) % 4)) % 4;
  
  // Calculate the size of the file.

  int size = (imageWidth * 3 + padding) * imageHeight + 54; 
  
  // Write the bitmap file header.  Note little-endian values.
  
  out.put ('B');  // Required for proper ID.
  out.put ('M');  // Required for proper ID.

  temp = size;
  out.put (temp % 256); temp /= 256;  // Size of the file.
  out.put (temp % 256); temp /= 256;
  out.put (temp % 256); temp /= 256;
  out.put (temp);

  out.put (0);    // Required -- reserved.
  out.put (0);    // Required -- reserved.
  out.put (0);    // Required -- reserved.
  out.put (0);    // Required -- reserved.

  out.put (54);   // Offset from file start to pixel data (4 bytes).
  out.put (0);
  out.put (0);
  out.put (0);

  // Write the bitmap info header.

  out.put (40);   // Size (in bytes) of this info header (4 bytes).
  out.put (0);
  out.put (0);
  out.put (0);

  temp = imageWidth;
  out.put (temp % 256); temp /= 256;  // Width of the image.
  out.put (temp % 256); temp /= 256;
  out.put (temp % 256); temp /= 256;
  out.put (temp);

  temp = imageHeight;
  out.put (temp % 256); temp /= 256;  // Height of the image.
  out.put (temp % 256); temp /= 256;
  out.put (temp % 256); temp /= 256;
  out.put (temp);

  out.put (1);   // Number of image planes -- must be 1.
  out.put (0);

  out.put (24);   // Number of bits per pixel.  
  out.put (0);

  out.put (0);   // Compression used -- none.
  out.put (0);
  out.put (0);
  out.put (0);

  temp = size - 54;
  out.put (temp % 256); temp /= 256;  // Size (in bytes) of the pixel data.
  out.put (temp % 256); temp /= 256;
  out.put (temp % 256); temp /= 256;
  out.put (temp);

  out.put (0);   // X pixels per meter -- 0 = use default.
  out.put (0);
  out.put (0);
  out.put (0);

  out.put (0);   // Y pixels per meter -- 0 = use default.
  out.put (0);
  out.put (0);
  out.put (0);

  out.put (0);   // Colors used - 0 = all used
  out.put (0);
  out.put (0);
  out.put (0);

  out.put (0);   // Colors important - 0 = all important
  out.put (0);
  out.put (0);
  out.put (0);

  // Next we would write the RGB quad data structure, but since we are
  //   writing a true color image, we can skip this.

  // Finally, we need to write the RGB data.  Write one RGB triplet for
  //   each pixel, beginning in the lower left hand corner.  Write the
  //   data one row at a time, moving from left to right.  Pad each row
  //   to end on a 32 bit boundary.

  for (y = imageHeight; y--;)
  {
    for (x = 0; x < imageWidth; x++)
    {
      out.put ((int) (data[y][x].getBlue () * 255));  // Blue is first.
      out.put ((int) (data[y][x].getGreen () * 255));
      out.put ((int) (data[y][x].getRed () * 255));
    }

    for (c = 0; c < padding; c++)
      out.put (0);
  }

  // Done, close the stream and return true.

  out.close ();

  return true;
}


///////////////////////////////////////////////////////////////////////////////
// bool Image::readBMP (char * filename, ostream & errorout)
//
//      This function re-initializes the current object by reading an image
// from a windows .bmp format file.  You may not call this routine
// from within your compressor class!
//
//      Upon successful completion, this function returns true.  If for
// any reason this function fails (cannot open file, no image, etc.), then
// false is returned and the image is reset to a 1x1 black image.
//
//      Error messages are written to errorout.  (errorout may be NULL if
// you do not wish to print error messages.)
///////////////////////////////////////////////////////////////////////////////

bool Image::readBMP (char * filename, ostream & errorout)
{
  int size, x, y, c;
  unsigned char fileheader[14];  // Buffers to contain the file headers
  unsigned char * infoheader;
  unsigned char * colortable;    // This will point into infoheader
  unsigned char * pixeldata;
  
  // Wipe out any existing image.

  freeImage ();

  // Validate the filename.

  if (filename == NULL)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - No filename specified." << endl;
    return false;
  }
  
  // Attempt to open the file for reading.

  ifstream in (filename);

  if (!in)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - Unable to read from " << filename
	       << "." << endl;
    return false;
  }

  // Read the bitmap file header.

  if (in.read(fileheader, 14).gcount () != 14)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - Unable to read header from " << filename
	       << "." << endl;
    in.close();
    return false;
  }

  // Make sure this header looks legit and extract some info.

  if (fileheader[0] != 'B' || fileheader[1] != 'M')
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - " << filename << "is not a .bmp." << endl;
    in.close ();
    return false;
  }

  int filesize = fileheader[2] + fileheader[3] * 256l
                 + fileheader[4] * 65536l + fileheader[5] * 16777216l;

  int pixeloffset = fileheader[10] + fileheader[11] * 256l
                    + fileheader[12] * 65536l + fileheader[13] * 16777216l;

  // Now that we know where the pixel data starts, we can read the info
  //   header and color table by reading everything up to the pixel data.
  //   Note that we must allocate this memory.

  size = pixeloffset - 14;
  infoheader = new unsigned char [size];

  if (infoheader == NULL)
  {    
    if (errorout != NULL)
      errorout << "Image::readBMP - Out of memory." << endl;
    in.close ();
    return false;
  }
  
  if (in.read (infoheader, size).gcount () != size)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - Unable to read info from " << filename
	       << "." << endl;
    in.close ();
    delete [] infoheader;
    return false;
  }

  // Point to the color table.

  int infosize = infoheader[0] + infoheader[1] * 256l
                 + infoheader[2] * 65536l + infoheader[3] * 16777216l;

  if (infosize < size)
    colortable = &(infoheader[infosize]);
  else
    colortable = NULL;

  // Allocate an array and read all the pixel data.

  size = filesize - size - 14;
  pixeldata = new unsigned char [size];

  if (pixeldata == NULL)
  {    
    if (errorout != NULL)
      errorout << "Image::readBMP - Out of memory." << endl;
    in.close ();
    delete [] infoheader;
    return false;
  }
  
  if (in.read (pixeldata, size).gcount () != size)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - Unable to read pixels from " << filename
	       << "." << endl;
    in.close ();
    delete [] infoheader;
    delete [] pixeldata;
    return false;
  }

  // We are done with the file, close it.

  in.close ();

  // Extract the various fields needed from the info header.

  if (infosize < 16)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - " << filename
	       << " - info block too short." << endl;
    delete [] infoheader;
    delete [] pixeldata;
    return false;
  }
  
  int width = infoheader[4] + infoheader[5] * 256l
              + infoheader[6] * 65536l + infoheader[7] * 16777216l;
  int height = infoheader[8] + infoheader[9] * 256l
              + infoheader[10] * 65536l + infoheader[11] * 16777216l;
  int planes = infoheader[12] + infoheader[13] * 256l;
  int bitcount = infoheader[14] + infoheader[15] * 256l;

  int compression = 0;
  int sizeimage = 0;

  if (infosize >= 20)
  {
    compression = infoheader[16] + infoheader[17] * 256l
                  + infoheader[18] * 65536l + infoheader[19] * 16777216l;
  }
  
  if (infosize >= 24)
  {
    sizeimage = infoheader[20] + infoheader[21] * 256l
                + infoheader[22] * 65536l + infoheader[23] * 16777216l;
  }

  // For now, we can only read images stored uncompressed in 24 bit mode.
  //  Ignore all others.

  int padding = (4 - ((width * 3) % 4)) % 4;

  // Debugging info.
  // cout << "padding "     << padding << endl;
  // cout << "width "       << width << endl;
  // cout << "height "      << height << endl;
  // cout << "compression " << compression << endl;
  // cout << "bitcount "    << bitcount << endl;
  // cout << "size "        << size << endl;
  // cout << "sizeimage "   << sizeimage << endl;
  
  if (width < 1 || height < 1 || compression != 0 || bitcount != 24 ||
      (sizeimage != 0 && sizeimage != size) ||
      size != (width * 3 + padding) * height)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - " << filename
	       << " - this .bmp is not true color (24 bit) and/or is not"
	       << " uncompressed data." << endl;
    delete [] infoheader;
    delete [] pixeldata;
    return false;
  }

  // Build an empty image buffer.

  allocateImage (width, height);

  if (data == NULL)
  {
    if (errorout != NULL)
      errorout << "Image::readBMP - Out of memory." << endl;
    delete [] infoheader;
    delete [] pixeldata;
    return false;
  }

  // Initialize the pixels.  Note that the bitmap is stored beginning at
  //    the bottom left pixel.

  for (c = 0, y = height; y--; c += padding)
    for (x = 0; x < width; x++, c+=3)
      data[y][x].setRGBColor (pixeldata[c+2] / 255.0,
			      pixeldata[c+1] / 255.0,
			      pixeldata[c]   / 255.0);
  
  // Done!  We've successfully built the image, so clean up memory
  //   and return true.

  delete [] infoheader;
  delete [] pixeldata;
  
  return true;
}


///////////////////////////////////////////////////////////////////////////////
// void Image::allocateImage (int width, int height)
//
//      This function allocates the memory needed to hold an image of
// the specified size.  If there is not enough memory, the image size
// is set to 0, 0 and the data pointer is set to NULL.
//
//      By default, the image is black (because Pixel objects default to
// black).
//
//      As a safety precaution, this routine may not be used to allocate
// any image containing more than 50,000,000 pixels.  This will count as
// an error.
///////////////////////////////////////////////////////////////////////////////

void Image::allocateImage (int width, int height)
{
  // Deallocate the current image.

  freeImage ();

  // Validate the parameters.  On error, return early.

  if (width < 1 || height < 1 ||
      width * height > 50000000 || width * height < 0)  // In case of overflow
    return;
  
  // Set up the width and height.

  imageWidth = width;
  imageHeight = height;

  // Allocate an array to hold arrays of pixels.  On error, free the image
  //   again and return early.

  data = new Pixel * [height];

  if (data == NULL)
  {  
    freeImage ();
    return;
  }

  // Allocate the rows of pixels for this image.  Again, on error, free
  //   the image and return early.  (Do it in two steps so that every
  //   array entry has a value.)

  for (int y = 0; y < height; y++)
    data[y] = new Pixel [width];

  for (int y = 0; y < height; y++)
    if (data[y] == NULL)
    {
      freeImage ();
      return;
    }

  // Done.
}


///////////////////////////////////////////////////////////////////////////////
// void Image::freeImage ()
//
//      This function releases any memory allocated by the allocateImage ()
// function.  The image size is set to 0, 0 and the image pointer is
// set to NULL.
///////////////////////////////////////////////////////////////////////////////

void Image::freeImage ()
{
  // Is there a vaild image?  If not, return early.

  if (data == NULL)
  {
    imageWidth = 0;
    imageHeight = 0;
    return;
  }

  // Loop through the rows of the image, delete each row if it exists.

  for (int y = 0; y < imageHeight; y++)
  {
    if (data[y] != NULL)
      delete [] data[y];
  }

  // Delete the array of rows.

  delete [] data;

  // Clear the image variables.

  data = NULL;
  imageWidth = 0;
  imageHeight = 0;
}

///////////////////////////////////////////////////////////////////////////////
// void Image::copyImage (const Image & in)
//
//      This function copies the given image into the current image.
///////////////////////////////////////////////////////////////////////////////

void Image::copyImage (const Image & in)
{
  // Release any current image.

  freeImage ();

  // If the input image is null, bail now because we are done.

  if (in.data == NULL)
    return;

  // Allocate the space for the copy of the image data.

  allocateImage (in.imageWidth, in.imageHeight);

  // If our data field is null, we are out of memory.

  if (data == NULL)
    return;

  // Copy the data.

  for (int y = 0; y < imageHeight; y++)
    for (int x = 0; x < imageWidth; x++)
      data[y][x] = in.data[y][x];
}



