/* 
 * This file Copyright (C) 2010 Christopher Stamm
 *                              Fachhochschule Nordwestschweiz 
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#include "pnm.h"
#include <cstdio>
#include <fstream>

#ifdef WIN32
	#include <io.h>
	#include <fcntl.h>
#endif

using namespace std;

/////////////////////////////////////////////////////////////////////////////
// constants

/////////////////////////////////////////////////////////////////////////////
__inline UINT16 ByteSwap(UINT16 wX) {
#ifdef __BIG_ENDIAN__ 
	return wX;
#else
	return ((wX & 0xFF00) >> 8) | ((wX & 0x00FF) << 8);
#endif
}

/////////////////////////////////////////////////////////////////////////////
CPNM::CPNM() : m_w(0), m_h(0), m_maxValue(0)
{
}

/////////////////////////////////////////////////////////////////////////////
CPNM::~CPNM() {
	delete[] m_buffer;
}

/////////////////////////////////////////////////////////////////////////////
bool CPNM::ReadPNM(CPGFImage& pgf, int quality, int levels, bool roi, bool alpha) {
	int channels = 1, type = 0;

	istream& in = cin;

	// read magic
	if (!ReadMagic(in, type)) return false;

#ifdef WIN32
	if (type >= 4) {
		// set binary mode for cin (= 0)
		_setmode(0, _O_BINARY);
	}
#endif

	// read width and height
	if (!ReadSize(in, m_w, m_h)) return false;

	// read further data depending on image type
	switch(type) {
	case 1: // Black and White
	case 4:
		if (in.good()) {
			m_mode = ImageModeBitmap; 
			m_bpp = 1;
			m_pitch = (((m_w + 7)/8 + 3)/4)*4;
			channels = 1;

			// set colortable
			RGBQUAD colors[2];
			FillInColorTable(colors, 2);
			pgf.SetColorTable(0, 2, colors);

		} else {
			cerr << "Error: Wrong PNM header" << endl;
			return false;
		}
		break;
	case 2: // Gray
	case 5:
		// read max value
		if (!ReadMaxValue(in, m_maxValue)) return false;
		{
			// set colortable
			RGBQUAD colors[256];
			FillInColorTable(colors, 256);
			pgf.SetColorTable(0, 256, colors);
		}
		channels = 1;

		if (m_maxValue == 255) {
			m_mode = ImageModeGrayScale; 
			m_bpp = 8;
			m_pitch = ((m_w + 3)/4)*4;
		} else {
			m_mode = ImageModeGray16; 
			m_bpp = 16;
			m_pitch = ((2*m_w + 3)/4)*4;
			pgf.SetMaxValue(m_maxValue);
		}
		break;
	case 3: // RGB
	case 6:
		// read max value
		if (!ReadMaxValue(in, m_maxValue)) return false;
		if (m_maxValue == 255) {
			if (alpha) {
				m_mode = ImageModeRGBA;
				m_bpp = 32;
				channels = 4;
				m_pitch = 4*m_w;
			} else {
				m_mode = ImageModeRGBColor;
				m_bpp = 24;
				channels = 3;
				m_pitch = ((3*m_w + 3)/4)*4;
			}
		} else {
			m_mode = ImageModeRGB48;
			m_bpp = 48;
			m_pitch = ((6*m_w + 3)/4)*4;
			pgf.SetMaxValue(m_maxValue);
		}		
		break;
	default:
		ASSERT(false);
		return false;
	}

	SkipComments(in);
	if (in.fail()) {
		cerr << "Error: Wrong PNM header" << endl;
		return false;
	}

	// create buffer
	m_buffer = new UINT8[m_pitch*m_h];
	if (!m_buffer) cerr << "Error: out of memory" << endl;

	// create pgf image
	PGFHeader header;
	header.bpp = m_bpp;
	header.channels = channels;
	header.height = m_h;
	header.width = m_w;
	header.mode = m_mode;
	header.nLevels = levels;
	header.quality = quality;
	//	header.background.rgbtBlue = header.background.rgbtGreen = header.background.rgbtRed = 255;
	pgf.SetHeader(header, (roi) ? PGFROI : 0);

	// read image data depending on image type
	switch(type) {
		case 1: // ASCII Black and White
			if (!ReadP1(in)) return false;
			break;
		case 2: // ASCII Gray
			if (!ReadP2(in)) return false;
			break;
		case 3: // ASCII RGB
			if (!ReadP3(in)) return false;
			break;
		case 4: // binary Black and White
			if (!ReadP4(in)) return false;
			break;
		case 5: // binary Gray
			if (!ReadP5(in)) return false;
			break;
		case 6: // binary RGB
			if (!ReadP6(in)) return false;
			break;
		default:
			ASSERT(false);
			return false;
	}

	if (m_mode == ImageModeRGBA) {
		// read alpha channel (PGM)
		int type2 = 0, width = 0, height = 0;

		if (!ReadMagic(in, type2)) return false;
		if (type2 != 2 && type2 != 5) {
			cerr << "Error: PPM followed by PGM expected" << endl;
			return false;
		}
		if (!ReadSize(in, width, height)) return false;
		if (width != m_w || height != m_h) {
			cerr << "Error: dimensions for PPM and PGM do not agree" << endl;
			return false;
		}
		if (!ReadMaxValue(in, m_maxValue)) return false;

		SkipComments(in);

		if (type2 == 2) {
			if (!ReadP2(in)) return false;
		} else {
			if (!ReadP5(in)) return false;
		}
	}

	// import bitmap data into pgf
	try {
		pgf.ImportBitmap(m_pitch, m_buffer, m_bpp);
	} catch(IOException&) {
		cerr << "Error: PGF import failed" << endl;
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
bool CPNM::WritePNM(CPGFImage& pgf, bool roi, PGFRect& rect, bool binary /*= true*/) {
	if (!ImportIsSupported(pgf.Mode())) {
		cerr << "Error: PGF image mode is not supported" << endl;
		return false;
	} else {
		m_mode = pgf.Mode();
	}

	// store image size
	if (roi) {
		m_w = rect.Width();
		m_h = rect.Height();
	} else {
		m_w = pgf.Width();
		m_h = pgf.Height();
	}

	// set pnm file type and pitch
	int type = 0;
	switch(m_mode) {
		case ImageModeBitmap:
			type = 1; 
			m_bpp = 1;
			m_pitch = (((m_w + 7)/8 + 3)/4)*4;
			break;
		case ImageModeGrayScale:
			type = 2; 
			m_bpp = 8;
			m_pitch = ((m_w + 3)/4)*4;
			break;
		case ImageModeGray16:
			type = 2; 
			m_bpp = 16;
			m_pitch = ((2*m_w + 3)/4)*4;
			m_maxValue = pgf.GetMaxValue();
			break;
		case ImageModeRGBColor:
			type = 3; 
			m_bpp = 24;
			m_pitch = ((3*m_w + 3)/4)*4;
			break;
		case ImageModeRGB48:
			type = 3; 
			m_bpp = 48;
			m_pitch = ((6*m_w + 3)/4)*4;
			m_maxValue = pgf.GetMaxValue();
			break;
		case ImageModeRGBA:
			type = 3; 
			m_bpp = 32;
			m_pitch = 4*m_w;
			break;
		default:
			ASSERT(false);
			return false;
	}
	if (binary) type += 3;

	// create buffer
	m_buffer = new UINT8[m_pitch*m_h];
	if (!m_buffer) cerr << "Error: out of memory" << endl;

	// copy pgf image buffer
	try {
		pgf.GetBitmap(m_pitch, m_buffer, m_bpp);
	} catch(IOException&) {
		cerr << "Error: PGF decompression failed" << endl;
		return false;
	}

	// write pnm
	ostream& out = cout;
	
#ifdef WIN32
	if (binary) {
		// set binary mode for cout (= 1)
		_setmode(1, _O_BINARY);
	}
#endif

	// write magic
	ASSERT(type);
	out << "P" /*<< noshowpos*/ << type << endl;

	// write width and height
	out << m_w << ' ' << m_h << endl;

	// write further data depending on image type
	switch(type) {
		case 1:
			if (!WriteP1(out)) return false;
			break;
		case 2:
			if (!WriteP2(out)) return false;
			break;
		case 3:
			if (!WriteP3(out)) return false;
			break;
		case 4:
			if (!WriteP4(out)) return false;
			break;
		case 5:
			if (!WriteP5(out)) return false;
			break;
		case 6:
			if (!WriteP6(out)) return false;
			break;
		default:
			ASSERT(false);
			return false;
	}

	if (m_mode == ImageModeRGBA) {
		// write alpha channel as PGM image
		if (binary) {
			out << endl << "P5" << endl;
		} else {
			out << "P2" << endl;
		}

		// write width and height
		out << m_w << ' ' << m_h << endl;

		if (binary) {
			if (!WriteP5(out)) return false;
		} else {
			if (!WriteP2(out)) return false;
		}
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read ASCII Black and White image file
// Pixel order of a row in file corresponds with pixel order on screen. But in memory it is different:
// |01234567|01234567|01234567| ... pixel number on sreen and in file
// | Byte 0 | Byte 1 | Byte 2 | ... bytes on screen and in file and memory
// |msb  lsb|msb  lsb|msb  lsb| ... bits in memory
//
bool CPNM::ReadP1(std::istream& in) {
	// read data
	UINT8 *buff = m_buffer;
	int pos, val;
	int v;
	int numOfBytes = (m_w + 7)/8;

	for(int i = 0; i < m_h; i++) {
		pos = 0;
		for(int j = 0; j < numOfBytes; j++) {
			val = 0;
			for(int k = 0; k < 8; k++) {
				val *= 2;
				if (pos < m_w) {
					in >> v; ASSERT(v == 0 || v == 1); // read msb first
 					val += v;
				}
				pos++;
			}
			buff[j] = (UINT8)val;
		}
		buff += m_pitch;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read ASCII Gray image file
bool CPNM::ReadP2(std::istream& in) {
	// read data
	int v;

	if (m_mode == ImageModeGray16) {
		int pitch16 = m_pitch/2;
		UINT16 *buff16 = (UINT16 *)m_buffer;

		for(int i = 0; i < m_h; i++) {
			for(int j = 0; j < m_w; j++) {
				in >> v;	
				buff16[j] = (UINT16)v;
			}
			buff16 += pitch16;
		}
	} else if (m_mode == ImageModeRGBA) {
		// read alpha channel
		ASSERT(m_bpp == 32);
		UINT8 *buff = m_buffer;
		int pos;

		for(int i = 0; i < m_h; i++) {
			pos = 3;
			for(int j = 0; j < m_w; j++) {
				in >> v; 
				buff[pos] = UINT8(v*255/m_maxValue);
				pos += 4;
			}
			buff += m_pitch;
		}
	} else {
		UINT8 *buff = m_buffer;

		for(int i = 0; i < m_h; i++) {
			for(int j = 0; j < m_w; j++) {
				in >> v; 
				buff[j] = UINT8(v*255/m_maxValue);
			}
			buff += m_pitch;
		}
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read ASCII RGB image file
bool CPNM::ReadP3(std::istream& in) {
	// read data
	int pos, v;

	if (m_mode == ImageModeRGB48) {
		int pitch16 = m_pitch/2;
		UINT16 *buff16 = (UINT16 *)m_buffer;

		for(int i = 0; i < m_h; i++) {
			pos = 0;
			for(int j = 0; j < m_w; j++) {
				in >> v; buff16[pos + 2] = (UINT16)v; // R
				in >> v; buff16[pos + 1] = (UINT16)v; // G
				in >> v; buff16[pos]     = (UINT16)v; // B
				pos += 3;
			}
			buff16 += pitch16;
		}
	} else {
		UINT8 *buff = m_buffer;
		int bypp = 3;
		if (m_mode == ImageModeRGBA) {
			// read RGB of RGBA
			ASSERT(m_bpp == 32);
			bypp = 4;
		}

		for(int i = 0; i < m_h; i++) {
			pos = 0;
			for(int j = 0; j < m_w; j++) {
				in >> v; buff[pos + 2] = UINT8(v*255/m_maxValue); // R
				in >> v; buff[pos + 1] = UINT8(v*255/m_maxValue); // G
				in >> v; buff[pos]     = UINT8(v*255/m_maxValue); // B
				pos += bypp;
			}
			buff += m_pitch;
		}
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read binary Black and White image file
bool CPNM::ReadP4(std::istream& in) {
	// read data
	int numOfBytes = (m_w + 7)/8;
	char *buff = (char *)m_buffer;

	for(int i = 0; i < m_h; i++) {
		in.read(buff, numOfBytes);
		buff += m_pitch;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read binary Gray image file
bool CPNM::ReadP5(std::istream& in) {
	// read data 
	if (m_mode == ImageModeGray16) {
		// data is in big-endian format
		int pitch16 = m_pitch/2;
		UINT16 *buff16 = (UINT16 *)m_buffer;

		for(int i = 0; i < m_h; i++) {
			in.read((char *)buff16, m_w*2);
			for(int j = 0; j < m_w; j++) {
				buff16[j] = ByteSwap(buff16[j]);
			}
			buff16 += pitch16;
		}
	} else if (m_mode == ImageModeRGBA) {
		// read alpha channel
		ASSERT(m_bpp == 32);
		UINT8 *buff = m_buffer;
		char *row = new char[m_w];
		int pos;

		for(int i = 0; i < m_h; i++) {
			in.read(row, m_w);
			pos = 3;
			if (m_maxValue < 255) {
				for(int j = 0; j < m_w; j++) {
					buff[pos] = UINT8(row[j]*255/m_maxValue);
					pos += 4;
				}
			} else {
				for(int j = 0; j < m_w; j++) {
					buff[pos] = row[j];
					pos += 4;
				}
			}
			buff += m_pitch;
		}
		delete[] row;
	} else {
		char *buff = (char *)m_buffer;

		for(int i = 0; i < m_h; i++) {
			in.read(buff, m_w);
			if (m_maxValue < 255) {
				for(int j = 0; j < m_w; j++) {
					buff[j] = UINT8(buff[j]*255/m_maxValue);
				}
			}
			buff += m_pitch;
		}
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Read binary RGB image file
bool CPNM::ReadP6(std::istream& in) {
	// read data
	UINT8 tmp;
	int pos;

	if (m_mode == ImageModeRGB48) {
		// data is in big-endian format
		int pitch16 = m_pitch/2;
		UINT16 *buff16 = (UINT16 *)m_buffer;
		UINT16 t;

		for(int i = 0; i < m_h; i++) {
			in.read((char *)buff16, m_w*6);
			pos = 0;
			for(int j = 0; j < m_w; j++) {
				// swap R and B
				t = buff16[pos];
				buff16[pos] = ByteSwap(buff16[pos + 2]);
				buff16[pos + 1] = ByteSwap(buff16[pos + 1]);
				buff16[pos + 2] = ByteSwap(t);
			}
			buff16 += pitch16;
		}
	} else if (m_mode == ImageModeRGBA) {
		// read alpha channel
		ASSERT(m_bpp == 32);
		UINT8 *buff = m_buffer;
		char *row = new char[3*m_w];
		int rpos;

		for(int i = 0; i < m_h; i++) {
			in.read(row, 3*m_w);
			pos = 0;
			rpos = 0;
			if (m_maxValue < 255) {
				for(int j = 0; j < m_w; j++) {
					buff[pos]		= UINT8(row[rpos + 2]*255/m_maxValue); // B
					buff[pos + 1]	= UINT8(row[rpos + 1]*255/m_maxValue); // G
					buff[pos + 2]	= UINT8(row[rpos    ]*255/m_maxValue); // R
					pos += 4;
					rpos += 3;
				}
			} else {
				for(int j = 0; j < m_w; j++) {
					buff[pos]		= row[rpos + 2]; // B
					buff[pos + 1]	= row[rpos + 1]; // G
					buff[pos + 2]	= row[rpos];	 // R
					pos += 4;
					rpos += 3;
				}
			}
			buff += m_pitch;
		}
		delete[] row;
	} else {
		char *buff = (char *)m_buffer;

		for(int i = 0; i < m_h; i++) {
			in.read((char *)buff, 3*m_w);
			pos = 0;
			if (m_maxValue < 255) {
				for(int j = 0; j < m_w; j++) {
					// swap R and B
					tmp = buff[pos];
					buff[pos] = buff[pos + 2];
					buff[pos + 2] = tmp;
					pos += 3;
				}
			} else {
				for(int j = 0; j < m_w; j++) {
					buff[pos + 1]	= UINT8(buff[pos + 1]*255/m_maxValue); // G
					// swap R and B
					tmp = UINT8(buff[pos]*255/m_maxValue);
					buff[pos] = UINT8(buff[pos + 2]*255/m_maxValue);
					buff[pos + 2] = tmp;
					pos += 3;
				}
			}
			buff += m_pitch;
		}
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write ASCII black and white file
// Pixel order of a row in file corresponds with pixel order on screen. But in memory it is different:
// |01234567|01234567|01234567| ... pixel number on sreen and in file
// | Byte 0 | Byte 1 | Byte 2 | ... bytes on screen and in file and memory
// |msb  lsb|msb  lsb|msb  lsb| ... bits in memory
//
bool CPNM::WriteP1(std::ostream& out) {
	ASSERT(m_buffer);
	const int numOfBytes = (m_w + 7)/8;
	UINT8 *buff8 = m_buffer;
	int pos;
	char z;

	// write data
	for (int i = 0; i < m_h; i++) {
		pos = 0;
		for (int j = 0; j < numOfBytes; j++) {
			z = buff8[j];
			for(int k = 0; k < 8 && pos < m_w; k++) {
				out << ((z < 0) ? 1 : 0) << ' ';  // write msb first
 				z <<= 1;
				pos++;
			}
		}
		buff8 += m_pitch;
		out << std::endl;
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write ASCII gray image file
bool CPNM::WriteP2(std::ostream& out) {
	ASSERT(m_buffer);

	if (m_mode == ImageModeGrayScale) {
		UINT8 *buff = m_buffer;

		// write maxvalue
		out << 255 << std::endl;

		// write data
		for (int i = 0; i < m_h; i++) {
			for (int j = 0; j < m_w; j++) {
				out << (int)buff[j] << ' ';
			}
			buff += m_pitch;
			out << std::endl;
		}
	} else if (m_mode == ImageModeRGBA) {
		// write alpha channel
		ASSERT(m_bpp == 32);
		UINT8 *buff = m_buffer;

		// write maxvalue
		out << 255 << std::endl;

		// write data
		for (int i = 0; i < m_h; i++) {
			for (int j = 0; j < m_w; j++) {
				out << (int)buff[j*4 + 3] << ' ';
			}
			buff += m_pitch;
			out << std::endl;
		}
	} else {
		ASSERT(m_mode == ImageModeGray16);
		int pitch = m_pitch/2;
		UINT16 *buff = (UINT16*)m_buffer;

		// write maxvalue
		out << m_maxValue << std::endl;

		// write data
		for (int i = 0; i < m_h; i++) {
			for (int j = 0; j < m_w; j++) {
				out << (int)buff[j] << ' ';
			}
			buff += pitch;
			out << std::endl;
		}
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write ASCII RGB color file
bool CPNM::WriteP3(std::ostream& out) { 
	ASSERT(m_buffer);

	if (m_mode == ImageModeRGB48) {
		int pos, pitch = m_pitch/2;
		UINT16 *buff = (UINT16*)m_buffer;

		// write maxvalue
		out << m_maxValue << std::endl;

		// write data
		for (int i = 0; i < m_h; i++) {
			pos = 0;
			for (int j = 0; j < m_w; j++) {
				out << (int)buff[pos + 2] << ' '; // R
				out << (int)buff[pos + 1] << ' '; // G
				out << (int)buff[pos] << ' ';	  // B
				pos += 3;
			}
			buff += pitch;
			out << std::endl;
		}
	} else {
		int pos;
		UINT8 *buff = m_buffer;
		int bypp = 3;
		if (m_mode == ImageModeRGBA) {
			// write RGB of RGBA
			ASSERT(m_bpp == 32);
			bypp = 4;
		}

		// write maxvalue
		out << 255 << std::endl;

		// write data
		for (int i = 0; i < m_h; i++) {
			pos = 0;
			for (int j = 0; j < m_w; j++) {
				out << (int)buff[pos + 2] << ' '; // R
				out << (int)buff[pos + 1] << ' '; // G
				out << (int)buff[pos] << ' ';	  // B
				pos += bypp;
			}
			buff += m_pitch;
			out << std::endl;
		}
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write binary black and white file
bool CPNM::WriteP4(std::ostream& out) {
	ASSERT(m_buffer);
	const int numOfBytes = (m_w + 7)/8;
	ASSERT(m_pitch >= numOfBytes);
	char *buff8 = (char *)m_buffer;

	// write buffer
	for(int i = 0; i < m_h; i++) {
		out.write(buff8, numOfBytes);
		buff8 += m_pitch;
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write binary gray image file
bool CPNM::WriteP5(std::ostream& out) {
	ASSERT(m_buffer);

	// write maxvalue
	if (m_mode == ImageModeGrayScale) {
		char *buff = (char *)m_buffer;

		// write max value
		out << 255 << std::endl;

		// write data
		for(int i = 0; i < m_h; i++) {
			out.write(buff, m_w);
			buff += m_pitch;
		}
	} else if (m_mode == ImageModeRGBA) {
		// write alpha channel
		ASSERT(m_bpp == 32);

		// write max value
		out << 255 << std::endl;

		char *row = new char[m_w];
		char *buff = (char *)m_buffer;

		// write data
		for (int i = 0; i < m_h; i++) {
			for(int j = 0; j < m_w; j++) {
				row[j] = buff[j*4 + 3];
			}
			out.write(row, m_w);
			buff += m_pitch;
		}
		delete[] row;
	} else {
		ASSERT(m_mode == ImageModeGray16);
		int pitch = m_pitch/2;
		UINT16 *buff = (UINT16 *)m_buffer;
		UINT16 val;

		// write max value
		out << m_maxValue << std::endl;

		// write data in big-endian format
		for(int i = 0; i < m_h; i++) {
			for(int j = 0; j < m_w; j++) {
				val = ByteSwap(buff[j]);
				out.write((char *)&val, 2);
			}
			buff += pitch;
		}
	}
	return true;
}

/////////////////////////////////////////////////////////////////////////////
// Write binary RGB color file
bool CPNM::WriteP6(std::ostream& out) {
	ASSERT(m_buffer);
	int pos = 0;

	if (m_mode == ImageModeRGB48) {
		int pitch = m_pitch/2;
		UINT16 *buff = (UINT16 *)m_buffer;
		UINT16 val;

		// write maxvalue
		out << m_maxValue << std::endl;

		// write data in big-endian format
		for(int i = 0; i < m_h; i++) {
			pos = 0;
			for(int j = 0; j < m_w; j++) {
				val = ByteSwap(buff[pos + 2]); out.write((char *)&val, 2); // R
				val = ByteSwap(buff[pos + 1]); out.write((char *)&val, 2); // G
				val = ByteSwap(buff[pos]);     out.write((char *)&val, 2); // B
				pos += 3;
			}
			buff += pitch;
		}

	} else {
		char *buff = (char *)m_buffer;
		int bypp = 3;
		if (m_mode == ImageModeRGBA) {
			// write RGB of RGBA
			ASSERT(m_bpp == 32);
			bypp = 4;
		}

		// write maxvalue
		out << 255 << std::endl;

		// write data
		for(int i = 0; i < m_h; i++) {
			pos = 0;
			for(int j = 0; j < m_w; j++) {
				out.write(&buff[pos + 2], 1); // R
				out.write(&buff[pos + 1], 1); // G
				out.write(&buff[pos], 1);     // B
				pos += bypp;
			}
			buff += m_pitch;
		}
	}
	return true;
}

//////////////////////////////////////////////////////////////////
// Return true if the given image mode is supported for import
bool CPNM::ImportIsSupported(BYTE mode) {
	switch(mode) {
		case ImageModeBitmap:
		case ImageModeGrayScale:
		case ImageModeRGBColor:
		case ImageModeGray16:
		case ImageModeRGB48:
		case ImageModeRGBA:
			return true;
	}
	return false;
}

/////////////////////////////////////////////////////////////////////////////
void CPNM::FillInColorTable(RGBQUAD* table, int size) {
	if (size == 2) {
		// bitmap color table
		table[0].rgbBlue = 255;
		table[0].rgbGreen = 255;
		table[0].rgbRed = 255;
		table[1].rgbBlue = 0;
		table[1].rgbGreen = 0;
		table[1].rgbRed = 0;
	} else if (size == 256) {
		// gray scale color table
		for (int i=0; i < 256; i++) {
			table[i].rgbBlue = (BYTE)i;
			table[i].rgbGreen = (BYTE)i;
			table[i].rgbRed = (BYTE)i;
		}
	} else {
		ASSERT(false);
	}
}

/////////////////////////////////////////////////////////////////////////////
bool CPNM::ReadMagic(istream& in, int& type) {
	char m = 0;

	SkipComments(in);
	if (in.good()) {
		in >> m >> type;
	}
	if (m != 'P' || type <= 0 || type > 6) {
		// unknown type
		cerr << "Error: Unknown PNM type specifier" << endl;
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
bool CPNM::ReadSize(istream& in, int& width, int& height) {
	SkipComments(in);
	if (in.good()) {
		in >> width >> height;
		if (width <= 0 || height <= 0) {
			cerr << "Error: Wrong PNM width or height" << endl;
			return false;
		}
	} else {
		cerr << "Error: Wrong PNM width or height" << endl;
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
bool CPNM::ReadMaxValue(istream& in, int& maxValue) {
	SkipComments(in);
	if (in.good()) {
		in >> maxValue;
		if (maxValue <= 0 || maxValue >= 65536) {
			cerr << "Error: Wrong PNM maximum value" << endl;
			return false;
		}
	} else {
		cerr << "Error: Wrong PNM maximum value" << endl;
		return false;
	}

	return true;
}

/////////////////////////////////////////////////////////////////////////////
void CPNM::SkipComments(istream& in) {
	int c = in.peek();
	while(in.good() && (c == '#' || c == '\n')) {
		in.ignore(1000, '\n');
		c = in.peek();
	}
}

