/*---------------------------------------------------------------------------*
 *                                   IT++			             *
 *---------------------------------------------------------------------------*
 * Copyright (c) 1995-2004 by Tony Ottosson, Thomas Eriksson, Pål Frenger,   *
 * Tobias Ringström, and Jonas Samuelsson.                                   *
 *                                                                           *
 * Permission to use, copy, modify, and distribute this software and its     *
 * documentation under the terms of the GNU General Public License is hereby *
 * granted. No representations are made about the suitability of this        *
 * software for any purpose. It is provided "as is" without expressed or     *
 * implied warranty. See the GNU General Public License for more details.    *
 *---------------------------------------------------------------------------*/

/*! 
  \file 
  \brief The PNM graphics format I/O function implementations.

  1.4

  2004/09/28 07:23:38
*/

#include "base/itassert.h"
#include "srccode/pnm.h"
#include <fstream>

using std::istream;
using std::ostream;
using std::endl;
using std::string;
using std::ifstream;
using std::ofstream;
using std::istringstream;
using std::ios;
using std::ios_base;
using std::streampos;
using std::exception;


namespace itpp {


// Suppress the additional white characters and return the comments
static void pnm_read_comments( istream & i, string & comments );

// Write comment in the image file
static void pnm_write_comments( ostream & o, const string & comments );

// Read/Write the header for the pnm file format
static bool pnm_read_header(ifstream & file, char & pnm_type, 
			    int & width, int & height, int & max_val,
			    string & comments, char pnm_type_required = '0' );

static bool pnm_write_header(ofstream & file, char type,
			     int width, int height, int max_val,
			     const string & comments );


//--------------------------------------------------------------
// General PNM functions
//--------------------------------------------------------------
char pnm_type( const string & filename )
{
  ifstream file;
  char pnm_type;

  try
    {
      file.open( filename.c_str() );

      string comments;
      int width, height, max_val;
      pnm_read_header( file, pnm_type, width, height, max_val, comments );
    }
  catch( exception & e )
    {
      string err_msg( "Unable to determine image type " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );
      return '0';
    }
  
  return pnm_type;
}


//--------------------------------------------------------------
bool pnm_info( const string & filename, char & pnm_type, 
	       int & width, int & height, int & max_val,
	       string & comments )
{
  ifstream file;

  try
    {
      file.open( filename.c_str() );

      pnm_read_header( file, pnm_type, width, height, max_val, comments );
    }
  catch( exception & e )
    {
      string err_msg( "Unable to determine image type " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );
      return false;
    }
  
  return true;
}


//--------------------------------------------------------------
// PGM related functions (gray images)
//--------------------------------------------------------------

bool pgm_read(const string & filename, 
	      imat & m, string & comments )
{
  ifstream file;
  int width, height, max_val, i, j;
  comments = "";

  try
    {
      file.open( filename.c_str() );

      // The format code is 'P5' for pgm files
      char pnm_type;
      if ( !pnm_read_header(file, pnm_type, width, height, max_val, comments, '5' ) )
	return false;

      // Format the returned matrix    
      m.set_size( height, width, false );

      // Retrieve the integer value from the file
      for( i = 0 ; i<height; i++)
	for( j = 0; j<width; j++)
	  m(i,j) = file.get();
    }
  // Exceptions may be thrown by pnm_read_header
  catch( exception & e )
    {
      string err_msg( "Unable to read an image component from file " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );
      m = imat();
      return false;
    }
	
  return true;
}


//--------------------------------------------------------------
// Simplified version of read_pgm
imat pgm_read( const string & filename )
{
  imat I;
  string comments;
  if( !pgm_read( filename, I, comments) )
    it_warning( "pgm_read (PGM file->imat) failed " );

  return I;
}


//--------------------------------------------------------------
bool pgm_read(const string & filename, imat &m, 
	      int r1, int r2, int c1, int c2)
{
  ifstream file;
  int width, height, max_val, i, j;

  // This is a dummy variable. 
  // Its purpose is the call of function pnm_read_header.
  string comments;

  try
    {
      file.open( filename.c_str() );

      char pnm_type;
      if (!pnm_read_header(file, pnm_type, width, height, max_val, comments, '5' ) )
	return false;

      // Inversion of the column/row numbers may be required
      if( r1 > r2 )
	{
	  int rtmp = r2;
	  r2 = r1;
	  r1 = rtmp;
	}

      if( c1 > c2 )
	{
	  int ctmp = c2;
	  c2 = c1;
	  c1 = ctmp;
	}

      if( r1 < 0 )
	throw ios_base::failure( "Bad parameter value : row number must be >=0" );
   
      if( c1 < 0 )
	throw ios_base::failure( "Bad parameter value : column number must be >=0" );
   
      if (r2 >= height )
 	throw ios_base::failure( "Bad parameter value : row number exceeds the image heigth" );

      if( c1 >= width )
	throw ios_base::failure( "Bad parameter value : column number exceeds the image width" );
    
      m.set_size( r2-r1+1, c2-c1+1, false );
      file.seekg( r1 * width + c1, ios::cur );

      for( i = 0 ; i < m.rows() ; i++ ) 
	{
	  for( j = 0 ; j < m.cols() ; j++ )
	    m( i, j ) = file.get();
	  file.seekg( width - ( c2-c1+1 ), ios::cur );
	}
    }

  catch( exception & e )
    {
      string err_msg( "A problem occured while reading image file " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );

      // Return the void matrix
      m = imat();
      return false;
    }
  
  return true;
}


//--------------------------------------------------------------
bool pgm_write( const string & filename, 
		const imat &m, const string & comments )
{

  ofstream file;
  int i, j;

  file.open( filename.c_str(), ofstream::out | ofstream::binary );

  if (!pnm_write_header(file, '5', m.cols(), m.rows(), 255, comments ))
    return false;
	
  for (i=0; i<m.rows(); i++)
    for (j=0; j<m.cols(); j++)
      file.put( m(i,j) );
	
  if (!file)
    return false;
	
  return true;
}


//--------------------------------------------------------------
// PPM related functions (color images)
//--------------------------------------------------------------

bool ppm_read( const string & filename, 
	       imat &r, imat &g, imat &b,
	       string & comments )
{
  ifstream file;
  int width, height, max_val, i, j;

  try 
    {
      file.open( filename.c_str() );
      
      char pnm_type;
      if(!pnm_read_header(file, pnm_type, width, height, max_val, comments, '6' ) )
	return false;
      
      r.set_size(height, width, false);
      g.set_size(height, width, false);
      b.set_size(height, width, false);
      for (i=0; i<height; i++)
	for (j=0; j<width; j++) {
	  r(i,j) = file.get();
	  g(i,j) = file.get();
	  b(i,j) = file.get();
	}
    }
  catch( exception & e )
    {
      string err_msg( "A problem occured while reading image file " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );

      // Each component is set to the void matrix in case of error
      r = imat();
      g = imat();
      b = imat();
      return false;
    }
	
  return true;
}


//--------------------------------------------------------------
// Same function but suppress the comments
bool ppm_read( const string & filename, 
	       imat &r, imat &g, imat &b )
{
  string comments; // This is a dummy variable

  return ppm_read( filename, r, g, b, comments );
}

//--------------------------------------------------------------
bool ppm_read( const string & filename, 
	       imat &r, imat &g, imat &b,
	       int r1, int r2, int c1, int c2)
{
  ifstream file;
  int width, height, max_val, i, j;

  // This is a dummy variable. Its purpose is the call of function pnm_read_header.
  string comments;

  try 
    {
      file.open( filename.c_str() );
      
      char pnm_type;
      if (!pnm_read_header(file, pnm_type, width, height, max_val, comments, '6' ) )
	return false;

      // Inversion of the column/row numbers may be required
      if( r1 > r2 )
	{
	  // Funny way to do it... (without using any temporary variable)
  	  r1 += r2;
  	  r2 = r1 - r2;
  	  r1 -= r2;
	}

      if( c1 > c2 )
	{
	  // Conventionnal way to do it
	  int ctmp = c2;
	  c2 = c1;
	  c1 = ctmp;
	}

      if( r1 < 0 )
	throw ios_base::failure( "Bad parameter value : row number must be >=0" );
   
      if( c1 < 0 )
	throw ios_base::failure( "Bad parameter value : column number must be >=0" );
   
      if (r2 >= height )
 	throw ios_base::failure( "Bad parameter value : row number exceeds the image heigth" );

      if( c1 >= width)
	throw ios_base::failure( "Bad parameter value : column number exceeds the image width" );
    
      r.set_size( r2-r1+1, c2-c1+1, false);
      g.set_size( r2-r1+1, c2-c1+1, false);
      b.set_size( r2-r1+1, c2-c1+1, false);
      file.seekg( 3 *( r1 * width + c1 ), ios::cur);

      for (i=0; i<r.rows(); i++) {
	for (j=0; j<r.cols(); j++) {
	  r(i,j) = file.get();
	  g(i,j) = file.get();
	  b(i,j) = file.get();
	}
	file.seekg( 3 * ( width - (c2-c1+1) ), ios::cur);
      }
    }
  catch( exception & e )
    {
      string err_msg( "A problem occured while reading image file " );
      err_msg += filename + " : " + e.what();
      it_warning( err_msg );

      // Each component is set to the void matrix in case of error
      r = imat();
      g = imat();
      b = imat();
      return false;
    }

  return true;
}


//--------------------------------------------------------------
bool ppm_write( const string & filename, 
		const imat &r, const imat &g, const imat &b,
		const string & comments,
		int max_val )
{
  ofstream file;
  int i, j;

  it_assert1(r.cols() == g.cols() && g.cols() == b.cols() &&
	     r.rows() == g.rows() && g.rows() == b.rows(),
	     "Matrices r, g and b must have the same size in ppm_write()");

  file.open( filename.c_str(), ofstream::out | ofstream::binary );

  if( max_val < 0 || max_val > 65535 )
    {
      it_warning( "Proposed maximal value is incorrect" );
      return false;
    }
  
  if (!pnm_write_header(file, '6', r.cols(), r.rows(), max_val, comments ))
    return false;
	
  for (i=0; i<r.rows(); i++)
    for (j=0; j<r.cols(); j++) {
      file.put( r(i,j) );
      file.put( g(i,j) );
      file.put( b(i,j) );
    }
	
  if (!file)
    return false;
	
  return true;
}


//--------------------------------------------------------------
imat img_double2int( const mat & m, 
		     int max_val,
		     double double_min,
		     double double_max )
{
  int i, j;
  imat M( m.rows(), m.cols() );

  for( i = 0 ; i < m.rows() ; i++ )
    for( j = 0 ; j < m.cols() ; j++ )
      if( m( i, j ) <= double_min )
	M( i, j ) = 0;
  
      else if( m( i, j ) >= double_max )
	M( i, j ) = max_val;
  
      else 
	M( i, j ) = (int) ( max_val * ( m( i, j ) - double_min )
			    / ( double_max - double_min ) + 0.5 );

  return M;
}

//--------------------------------------------------------------
mat img_int2double( const imat & m, 
		    int max_val,
		    double double_min,
		    double double_max )
{
  int i, j;
  mat M( m.rows(), m.cols() );

  for( i = 0 ; i < m.rows() ; i++ )
    for( j = 0 ; j < m.cols() ; j++ )
      if( m( i, j ) <= 0 )
	M( i, j ) = double_min;
  
      else if( m( i, j ) >= max_val )
	M( i, j ) = double_max;
  
      else 
	// This rounding works well when m(i,j) is positive
	M( i, j ) = double_min + ( double_max - double_min ) 
	  * m( i, j ) / (double) max_val;

  return M;
}


//--------------------------------------------------------------
// Static functions: Used in this file only
//--------------------------------------------------------------

//--------------------------------------------------------------
static void pnm_read_comments( istream & i, string & comments )
{
  while (isspace(i.peek())) 
    {
      while (isspace(i.peek()))
	i.get();
      
      if (i.peek() == '#')
	while (i.peek()!='\r' && i.peek()!='\n')
	  comments += i.get();
    }
}


//--------------------------------------------------------------
static void pnm_write_comments( ostream & o, const string & comments )
{
  istringstream comments_stream( comments );
  char comment_line[ 256 ];


  // Put header and comment
  while( !comments_stream.eof() )
    {
      o << "#";
      comments_stream.get( comment_line, 256 );
      o << comment_line << endl;
    }
}


//--------------------------------------------------------------
// Read the header of a pnm file
static bool pnm_read_header( ifstream & file, char & pnm_type, 
			     int & width, int & height, int & max_val,
			     string & comments, char pnm_type_required )
{
  try
    {
      bool return_code = true;

      if (file.get() != 'P')
	return_code = false;

      if( !return_code )
	{
	  throw ios_base::failure( (string) "Invalid format file: code of file format has not been found" );
	  return false;
	}

      // Read the type of the pnm file
      pnm_type = file.get();

      if( pnm_type < '1' || pnm_type > '6' )
	throw ios_base::failure( "Bad file code P" + pnm_type );

      // If a type has been specified
      if( pnm_type_required != '0' )
	if( pnm_type_required != pnm_type )
	  {
	    string err_msg( "Found file code P" );
	    err_msg += pnm_type + " instead of P" + pnm_type_required;
	    throw ios_base::failure( err_msg );
	    return false;
	  }

      // Retrieve the image format and the comments
      pnm_read_comments(file, comments );
      file >> width;
      pnm_read_comments(file, comments );
      file >> height;
      pnm_read_comments(file, comments );

      if( height < 0 || width < 0 )
	throw ios_base::failure( "Bad image size" );

      // Maximal values is not present in PBM files
      if( pnm_type == '2' || pnm_type == '3' || pnm_type == '5' || pnm_type == '6' )
	file >> max_val;

      file.get(); // Eat the last whitespace

      // According to the pnm specification, the maximal value should not
      // be greater than 65536 and lower than 0
      if( max_val >= 65536 || max_val < 0 )
	{
	  throw ios_base::failure( "Invalid maximum number in pnm header" );
	  return false;
	}

      // For type P5 and P6, the value have to be lower than 255
      if( ( pnm_type == '5' || pnm_type == '6' ) && max_val > 255 )
	{
	  throw ios_base::failure( "Invalid maximum number in pnm header" );
	  return false;
	}
    }
  catch(...)
    {
      throw;
      return false;
    }

  return file.good();
}


//--------------------------------------------------------------
static bool pnm_write_header( ofstream &file, char pnm_type,
			      int width, int height, int max_val,
			      const string & comments )
{
  try
    {
      file << 'P' << pnm_type << endl;
      pnm_write_comments( file, comments );
      file << width << ' ' << height << endl;

      // Maximal values is not present in PBM files
      if( pnm_type == '2' || pnm_type == '3' || pnm_type == '5' || pnm_type == '6' )
	   file << max_val << endl;

      
    }
  catch(...)
    {
      throw;
      return false;
    }

  return file.good();
}


} // End of namespace itpp


syntax highlighted by Code2HTML, v. 0.9.1