/*! \file RgbImage.cpp
 * This file is related to the graphic part with OpenGL. See the documentation.
 *
 * RayTrace Software Package, release 1.0.4,  February 2004.
 *
 * Author: Samuel R. Buss
 *
 * Software accompanying the book
 *		3D Computer Graphics: A Mathematical Introduction with OpenGL,
 *		by S. Buss, Cambridge University Press, 2003.
 *
 * Software is "as-is" and carries no warranty.  It may be used without
 *   restriction, but if you modify it, please change the filenames to
 *   prevent confusion between different versions.  Please acknowledge
 *   all use of the software in any publications or products based on it.
 *
 * Bug reports: Sam Buss, sbuss@ucsd.edu.
 * Web page: http://math.ucsd.edu/~sbuss/MathCG
 *
 */

#include "RgbImage.h"

#ifndef RGBIMAGE_DONT_USE_OPENGL
//#include <windows.h>
#include "GL/gl.h"
#include "configuration.h"
#endif

RgbImage::RgbImage( int numRows, int numCols )
{
  NumRows = numRows;
  NumCols = numCols;
  ImagePtr = new unsigned char[NumRows*GetNumBytesPerRow()];
  if ( !ImagePtr ) {
    _ERROR("Unable to allocate memory for %ld x %ld bitmap.", 
	   NumRows, NumCols);
    Reset();
    ErrorCode = MemoryError;
  }
  // Zero out the image
  unsigned char* c = ImagePtr;
  int rowLen = GetNumBytesPerRow();
  for ( int i=0; i<NumRows; i++ ) {
    for ( int j=0; j<rowLen; j++ ) {
      *(c++) = 0;
    }
  }
}

/* ********************************************************************
 *  LoadBmpFile
 *  Read into memory an RGB image from an uncompressed BMP file.
 *  Return true for success, false for failure.  Error code is available
 *     with a separate call.
 *  Author: Sam Buss December 2001.
 **********************************************************************/

bool RgbImage::LoadBmpFile( const char* filename ) 
{  
  Reset();
  FILE* infile = fopen( filename, "rb" );		// Open for reading binary data
  if ( !infile ) {
    fprintf(stderr, "Unable to open file: %s\n", filename);
    ErrorCode = OpenError;
    return false;
  }

  bool fileFormatOK = false;
  int bChar = fgetc( infile );
  int mChar = fgetc( infile );
  if ( bChar=='B' && mChar=='M' ) {			// If starts with "BM" for "BitMap"
    skipChars( infile, 4+2+2+4+4 );			// Skip 4 fields we don't care about
    NumCols = readLong( infile );
    NumRows = readLong( infile );
    skipChars( infile, 2 );					// Skip one field
    int bitsPerPixel = readShort( infile );
    skipChars( infile, 4+4+4+4+4+4 );		// Skip 6 more fields

    if ( NumCols>0 && NumCols<=100000 && NumRows>0 && NumRows<=100000  
	 && bitsPerPixel==24 && !feof(infile) ) {
      fileFormatOK = true;
    }
  }
  if ( !fileFormatOK ) {
    Reset();
    ErrorCode = FileFormatError;
    fprintf(stderr, "Not a valid 24-bit bitmap file: %s.\n", filename);
    fclose ( infile );
    return false;
  }

  // Allocate memory
  ImagePtr = new unsigned char[NumRows*GetNumBytesPerRow()];
  if ( !ImagePtr ) {
    fprintf(stderr, "Unable to allocate memory for %ld x %ld bitmap: %s.\n", 
	    NumRows, NumCols, filename);
    Reset();
    ErrorCode = MemoryError;
    fclose ( infile );
    return false;
  }

  unsigned char* cPtr = ImagePtr;
  for ( int i=0; i<NumRows; i++ ) {
    int j;
    for ( j=0; j<NumCols; j++ ) {
      *(cPtr+2) = fgetc( infile );	// Blue color value
      *(cPtr+1) = fgetc( infile );	// Green color value
      *cPtr = fgetc( infile );		// Red color value
      cPtr += 3;
    }
    int k=3*NumCols;					// Num bytes already read
    for ( ; k<GetNumBytesPerRow(); k++ ) {
      fgetc( infile );				// Read and ignore padding;
      *(cPtr++) = 0;
    }
  }
  if ( feof( infile ) ) {
    fprintf( stderr, "Premature end of file: %s.\n", filename );
    Reset();
    ErrorCode = ReadError;
    fclose ( infile );
    return false;
  }
  fclose( infile );	// Close the file
  return true;
}

short RgbImage::readShort( FILE* infile )
{
  // read a 16 bit integer
  unsigned char lowByte, hiByte;
  lowByte = fgetc(infile);			// Read the low order byte (little endian form)
  hiByte = fgetc(infile);			// Read the high order byte

  // Pack together
  short ret = hiByte;
  ret <<= 8;
  ret |= lowByte;
  return ret;
}

long RgbImage::readLong( FILE* infile )
{  
  // Read in 32 bit integer
  unsigned char byte0, byte1, byte2, byte3;
  byte0 = fgetc(infile);			// Read bytes, low order to high order
  byte1 = fgetc(infile);
  byte2 = fgetc(infile);
  byte3 = fgetc(infile);

  // Pack together
  long ret = byte3;
  ret <<= 8;
  ret |= byte2;
  ret <<= 8;
  ret |= byte1;
  ret <<= 8;
  ret |= byte0;
  return ret;
}

void RgbImage::skipChars( FILE* infile, int numChars )
{
  for ( int i=0; i<numChars; i++ ) {
    fgetc( infile );
  }
}

/* ********************************************************************
 *  WriteBmpFile
 *  Write an RGB image to an uncompressed BMP file.
 *  Return true for success, false for failure.  Error code is available
 *     with a separate call.
 *  Author: Sam Buss, January 2003.
 **********************************************************************/

bool RgbImage::WriteBmpFile( const char* filename )
{
  FILE* outfile = fopen( filename, "wb" );		// Open for reading binary data
  if ( !outfile ) {
    fprintf(stderr, "Unable to open file: %s\n", filename);
    ErrorCode = OpenError;
    return false;
  }

  fputc('B',outfile);
  fputc('M',outfile);
  int rowLen = GetNumBytesPerRow();
  writeLong( 40+14+NumRows*rowLen, outfile );	// Length of file
  writeShort( 0, outfile );					// Reserved for future use
  writeShort( 0, outfile );
  writeLong( 40+14, outfile );				// Offset to pixel data
  writeLong( 40, outfile );					// header length
  writeLong( NumCols, outfile );				// width in pixels
  writeLong( NumRows, outfile );				// height in pixels (pos for bottom up)
  writeShort( 1, outfile );		// number of planes
  writeShort( 24, outfile );		// bits per pixel
  writeLong( 0, outfile );		// no compression
  writeLong( 0, outfile );		// not used if no compression
  writeLong( 0, outfile );		// Pixels per meter
  writeLong( 0, outfile );		// Pixels per meter
  writeLong( 0, outfile );		// unused for 24 bits/pixel
  writeLong( 0, outfile );		// unused for 24 bits/pixel

  // Now write out the pixel data:
  unsigned char* cPtr = ImagePtr;
  for ( int i=0; i<NumRows; i++ ) {
    // Write out i-th row's data
    int j;
    for ( j=0; j<NumCols; j++ ) {
      fputc( *(cPtr+2), outfile);		// Blue color value
      fputc( *(cPtr+1), outfile);		// Blue color value
      fputc( *(cPtr+0), outfile);		// Blue color value
      cPtr+=3;
    }
    // Pad row to word boundary
    int k=3*NumCols;					// Num bytes already read
    for ( ; k<GetNumBytesPerRow(); k++ ) {
      fputc( 0, outfile );				// Read and ignore padding;
      cPtr++;
    }
  }

  fclose( outfile );	// Close the file
  return true;
}

void RgbImage::writeLong( long data, FILE* outfile )
{  
  // Read in 32 bit integer
  unsigned char byte0, byte1, byte2, byte3;
  byte0 = (unsigned char)(data&0x000000ff);		// Write bytes, low order to high order
  byte1 = (unsigned char)((data>>8)&0x000000ff);
  byte2 = (unsigned char)((data>>16)&0x000000ff);
  byte3 = (unsigned char)((data>>24)&0x000000ff);

  fputc( byte0, outfile );
  fputc( byte1, outfile );
  fputc( byte2, outfile );
  fputc( byte3, outfile );
}

void RgbImage::writeShort( short data, FILE* outfile )
{  
  // Read in 32 bit integer
  unsigned char byte0, byte1;
  byte0 = data&0x000000ff;		// Write bytes, low order to high order
  byte1 = (data>>8)&0x000000ff;

  fputc( byte0, outfile );
  fputc( byte1, outfile );
}


/*********************************************************************
 * SetRgbPixel routines allow changing the contents of the RgbImage. *
 *********************************************************************/

void RgbImage::SetRgbPixelf( long row, long col, double red, double green, double blue )
{
  SetRgbPixelc( row, col, doubleToUnsignedChar(red), 
		doubleToUnsignedChar(green),
		doubleToUnsignedChar(blue) );
}

void RgbImage::SetRgbPixelc( long row, long col,
			     unsigned char red, unsigned char green, unsigned char blue )
{
  assert ( row<NumRows && col<NumCols );
  unsigned char* thePixel = GetRgbPixel( row, col );
  *(thePixel++) = red;
  *(thePixel++) = green;
  *(thePixel) = blue;
}


unsigned char RgbImage::doubleToUnsignedChar( double x )
{
  if ( x>=1.0 ) {
    return (unsigned char)255;
  }
  else if ( x<=0.0 ) {
    return (unsigned char)0;
  }
  else {
    return (unsigned char)(x*255.0);		// Rounds down
  }
}
// Bitmap file format  (24 bit/pixel form)		BITMAPFILEHEADER
// Header (14 bytes)
//	 2 bytes: "BM"
//   4 bytes: long int, file size
//   4 bytes: reserved (actually 2 bytes twice)
//   4 bytes: long int, offset to raster data
// Info header (40 bytes)						BITMAPINFOHEADER
//   4 bytes: long int, size of info header (=40)
//	 4 bytes: long int, bitmap width in pixels
//   4 bytes: long int, bitmap height in pixels
//   2 bytes: short int, number of planes (=1)
//   2 bytes: short int, bits per pixel
//   4 bytes: long int, type of compression (not applicable to 24 bits/pixel)
//   4 bytes: long int, image size (not used unless compression is used)
//   4 bytes: long int, x pixels per meter
//   4 bytes: long int, y pixels per meter
//   4 bytes: colors used (not applicable to 24 bit color)
//   4 bytes: colors important (not applicable to 24 bit color)
// "long int" really means "unsigned long int"
// Pixel data: 3 bytes per pixel: RGB values (in reverse order).
//	Rows padded to multiples of four.


#ifndef RGBIMAGE_DONT_USE_OPENGL

bool RgbImage::LoadFromOpenglBuffer()					// Load the bitmap from the current OpenGL buffer
{
  int viewportData[4];
  glGetIntegerv( GL_VIEWPORT, viewportData );
  int& vWidth = viewportData[2];
  int& vHeight = viewportData[3];
	
  if ( ImagePtr==0 ) { // If no memory allocated
    NumRows = vHeight;
    NumCols = vWidth;
    ImagePtr = new unsigned char[NumRows*GetNumBytesPerRow()];
    if ( !ImagePtr ) {
      fprintf(stderr, "Unable to allocate memory for %ld x %ld buffer.\n", 
	      NumRows, NumCols);
      Reset();
      ErrorCode = MemoryError;
      return false;
    }
  }
  //	assert ( vWidth>=NumCols && vHeight>=NumRows );
  int oldGlRowLen;
  if ( vWidth>=NumCols ) {
    glGetIntegerv( GL_UNPACK_ROW_LENGTH, &oldGlRowLen );
    glPixelStorei( GL_UNPACK_ROW_LENGTH, NumCols );
  }
  glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

  // Get the frame buffer data.
  glReadPixels( 0, 0, NumCols, NumRows, GL_RGB, GL_UNSIGNED_BYTE, ImagePtr);

  // Restore the row length in glPixelStorei  (really ought to restore alignment too).
  if ( vWidth>=NumCols ) {
    glPixelStorei( GL_UNPACK_ROW_LENGTH, oldGlRowLen );
  }	
  return true;
}

#endif   // RGBIMAGE_DONT_USE_OPENGL