// ==============================================================
//	This file is part of Glest Shared Library (www.glest.org)
//
//	Copyright (C) 2001-2008 Martiño Figueroa
//
//	You can redistribute this code and/or modify it under
//	the terms of the GNU General Public License as published
//	by the Free Software Foundation; either version 2 of the
//	License, or (at your option) any later version
// ==============================================================

#include "math_wrapper.h"
#include "pixmap.h"

#include <stdexcept>
#include <cstdio>
#include <cassert>

#include "util.h"
#include "math_util.h"
#include "randomgen.h"
#include "FileReader.h"
#include "ImageReaders.h"
#include <png.h>
#include <jpeglib.h>
#include <setjmp.h>
//#include <memory>
#include "opengl.h"
#include "leak_dumper.h"

using namespace Shared::Util;
using namespace std;
using namespace Shared::Graphics::Gl;

namespace Shared{ namespace Graphics{

using namespace Util;

// =====================================================
//	file structs
// =====================================================

#pragma pack(push, 1)

struct BitmapFileHeader{
	uint8 type1;
	uint8 type2;
	uint32 size;
	uint16 reserved1;
	uint16 reserved2;
	uint32 offsetBits;
};

struct BitmapInfoHeader{
	uint32 size;
	int32 width;
	int32 height;
	uint16 planes;
	uint16 bitCount;
	uint32 compression;
	uint32 sizeImage;
	int32 xPelsPerMeter;
	int32 yPelsPerMeter;
	uint32 clrUsed;
	uint32 clrImportant;
};

struct TargaFileHeader{
	int8 idLength;
	int8 colourMapType;
	int8 dataTypeCode;
	int16 colourMapOrigin;
	int16 colourMapLength;
	int8 colourMapDepth;
	int16 xOrigin;
	int16 yOrigin;
	int16 width;
	int16 height;
	int8 bitsPerPixel;
	int8 imageDescriptor;
};

#pragma pack(pop)

const int tgaUncompressedRgb= 2;
const int tgaUncompressedBw= 3;

void CalculatePixelsCRC(uint8 *pixels,std::size_t pixelByteCount, Checksum &crc) {
//	crc = Checksum();
//	for(std::size_t i = 0; i < pixelByteCount; ++i) {
//		crc.addByte(pixels[i]);
//	}
}

// =====================================================
//	class PixmapIoTga
// =====================================================

PixmapIoTga::PixmapIoTga() {
	file= NULL;
}

PixmapIoTga::~PixmapIoTga() {
	if(file != NULL) {
		fclose(file);
		file=NULL;
	}
}

void PixmapIoTga::openRead(const string &path) {

	try {
	#ifdef WIN32
		file= _wfopen(utf8_decode(path).c_str(), L"rb");
	#else
		file= fopen(path.c_str(),"rb");
	#endif
		if (file == NULL) {
			throw megaglest_runtime_error("Can't open TGA file: "+ path,true);
		}

		//read header
		TargaFileHeader fileHeader;
		size_t readBytes = fread(&fileHeader, sizeof(TargaFileHeader), 1, file);
		if(readBytes != 1) {
			char szBuf[8096]="";
			snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
			throw megaglest_runtime_error(szBuf);
		}
		static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
		if(bigEndianSystem == true) {
			fileHeader.bitsPerPixel = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.bitsPerPixel);
			fileHeader.colourMapDepth = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.colourMapDepth);
			fileHeader.colourMapLength = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.colourMapDepth);
			fileHeader.colourMapOrigin = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.colourMapOrigin);
			fileHeader.colourMapType = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.colourMapType);
			fileHeader.dataTypeCode = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.dataTypeCode);
			fileHeader.height = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.height);
			fileHeader.idLength = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.idLength);
			fileHeader.imageDescriptor = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.imageDescriptor);
			fileHeader.width = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.width);
		}
		//check that we can load this tga file
		if(fileHeader.idLength != 0) {
			throw megaglest_runtime_error(path + ": id field is not 0",true);
		}

		if(fileHeader.dataTypeCode != tgaUncompressedRgb && fileHeader.dataTypeCode != tgaUncompressedBw) {
			throw megaglest_runtime_error(path + ": only uncompressed BW and RGB targa images are supported",true);
		}

		//check bits per pixel
		if(fileHeader.bitsPerPixel != 8 && fileHeader.bitsPerPixel != 24 && fileHeader.bitsPerPixel !=32) {
			throw megaglest_runtime_error(path + ": only 8, 24 and 32 bit targa images are supported",true);
		}

		h= fileHeader.height;
		w= fileHeader.width;
		components= fileHeader.bitsPerPixel / 8;
	}
	catch(megaglest_runtime_error& ex) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"Error in [%s] on line: %d msg: %s\n",extractFileFromDirectoryPath(__FILE__).c_str(),__LINE__,ex.what());
		throw megaglest_runtime_error(szBuf,!ex.wantStackTrace());
	}
	catch(exception& ex) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"Error in [%s] on line: %d msg: %s\n",extractFileFromDirectoryPath(__FILE__).c_str(),__LINE__,ex.what());
		throw megaglest_runtime_error(szBuf);
	}
}

void PixmapIoTga::read(uint8 *pixels) {
	read(pixels, components);
}

void PixmapIoTga::read(uint8 *pixels, int components) {
	try {
		static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();

		for(int i=0; i<h*w*components; i+=components) {
			uint8 r=0, g=0, b=0, a=0, l=0;

			if(this->components == 1) {
				size_t readBytes = fread(&l, 1, 1, file);
				if(readBytes != 1) {
					char szBuf[8096]="";
					snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
					throw megaglest_runtime_error(szBuf);
				}
				if(bigEndianSystem == true) {
					l = Shared::PlatformByteOrder::fromCommonEndian(l);
				}
				r= l;
				g= l;
				b= l;
				a= 255;
			}
			else {
				size_t readBytes = fread(&b, 1, 1, file);
				if(readBytes != 1) {
					char szBuf[8096]="";
					snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
					throw megaglest_runtime_error(szBuf);
				}
				if(bigEndianSystem == true) {
					b = Shared::PlatformByteOrder::fromCommonEndian(b);
				}

				readBytes = fread(&g, 1, 1, file);
				if(readBytes != 1) {
					char szBuf[8096]="";
					snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
					throw megaglest_runtime_error(szBuf);
				}
				if(bigEndianSystem == true) {
					g = Shared::PlatformByteOrder::fromCommonEndian(g);
				}

				readBytes = fread(&r, 1, 1, file);
				if(readBytes != 1) {
					char szBuf[8096]="";
					snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
					throw megaglest_runtime_error(szBuf);
				}
				if(bigEndianSystem == true) {
					r = Shared::PlatformByteOrder::fromCommonEndian(r);
				}

				if(this->components == 4) {
					readBytes = fread(&a, 1, 1, file);
					if(readBytes != 1) {
						char szBuf[8096]="";
						snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
						throw megaglest_runtime_error(szBuf);
					}
					if(bigEndianSystem == true) {
						a = Shared::PlatformByteOrder::fromCommonEndian(a);
					}

				}
				else {
					a= 255;
				}
				l= (r+g+b)/3;
			}

			switch(components) {
			case 1:
				pixels[i]= l;
				break;
			case 3:
				pixels[i]= r;
				pixels[i+1]= g;
				pixels[i+2]= b;
				break;
			case 4:
				pixels[i]= r;
				pixels[i+1]= g;
				pixels[i+2]= b;
				pixels[i+3]= a;
				break;
			}
		}
	}
	catch(megaglest_runtime_error& ex) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"Error in [%s] on line: %d msg: %s\n",extractFileFromDirectoryPath(__FILE__).c_str(),__LINE__,ex.what());
		throw megaglest_runtime_error(szBuf,!ex.wantStackTrace());
	}
	catch(exception& ex) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"Error in [%s] on line: %d msg: %s\n",extractFileFromDirectoryPath(__FILE__).c_str(),__LINE__,ex.what());
		throw megaglest_runtime_error(szBuf);
	}
}

void PixmapIoTga::openWrite(const string &path, int w, int h, int components) {
    this->w= w;
	this->h= h;
	this->components= components;

#ifdef WIN32
	file= _wfopen(utf8_decode(path).c_str(), L"wb");
#else
    file= fopen(path.c_str(),"wb");
#endif
	if (file == NULL) {
		throw megaglest_runtime_error("Can't open TGA file: "+ path,true);
	}

	TargaFileHeader fileHeader;
	memset(&fileHeader, 0, sizeof(TargaFileHeader));
	fileHeader.dataTypeCode= components==1? tgaUncompressedBw: tgaUncompressedRgb;
	fileHeader.bitsPerPixel= components*8;
	fileHeader.width= w;
	fileHeader.height= h;
	fileHeader.imageDescriptor= components==4? 8: 0;

	static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
	if(bigEndianSystem == true) {
		fileHeader.bitsPerPixel = Shared::PlatformByteOrder::toCommonEndian(fileHeader.bitsPerPixel);
		fileHeader.colourMapDepth = Shared::PlatformByteOrder::toCommonEndian(fileHeader.colourMapDepth);
		fileHeader.colourMapLength = Shared::PlatformByteOrder::toCommonEndian(fileHeader.colourMapDepth);
		fileHeader.colourMapOrigin = Shared::PlatformByteOrder::toCommonEndian(fileHeader.colourMapOrigin);
		fileHeader.colourMapType = Shared::PlatformByteOrder::toCommonEndian(fileHeader.colourMapType);
		fileHeader.dataTypeCode = Shared::PlatformByteOrder::toCommonEndian(fileHeader.dataTypeCode);
		fileHeader.height = Shared::PlatformByteOrder::toCommonEndian(fileHeader.height);
		fileHeader.idLength = Shared::PlatformByteOrder::toCommonEndian(fileHeader.idLength);
		fileHeader.imageDescriptor = Shared::PlatformByteOrder::toCommonEndian(fileHeader.imageDescriptor);
		fileHeader.width = Shared::PlatformByteOrder::toCommonEndian(fileHeader.width);
	}

	fwrite(&fileHeader, sizeof(TargaFileHeader), 1, file);
}

void PixmapIoTga::write(uint8 *pixels) {
	if(components == 1) {

		static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
		if(bigEndianSystem == true) {
			Shared::PlatformByteOrder::toEndianTypeArray<uint8>(pixels,h*w);
		}

		fwrite(pixels, h*w, 1, file);
	}
	else {
		static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
		if(bigEndianSystem == true) {
			Shared::PlatformByteOrder::toEndianTypeArray<uint8>(pixels,h*w*components);
		}

		for(int i=0; i<h*w*components; i+=components) {
			fwrite(&pixels[i+2], 1, 1, file);
			fwrite(&pixels[i+1], 1, 1, file);
			fwrite(&pixels[i], 1, 1, file);
			if(components==4){
				fwrite(&pixels[i+3], 1, 1, file);
			}
		}
	}
}

// =====================================================
//	class PixmapIoBmp
// =====================================================

PixmapIoBmp::PixmapIoBmp() {
	file= NULL;
}

PixmapIoBmp::~PixmapIoBmp() {
	if(file!=NULL){
		fclose(file);
		file=NULL;
	}
}

void PixmapIoBmp::openRead(const string &path){
#ifdef WIN32
	file= _wfopen(utf8_decode(path).c_str(), L"rb");
#else
    file= fopen(path.c_str(),"rb");
#endif
	if (file==NULL){
		throw megaglest_runtime_error("Can't open BMP file: "+ path,true);
	}

	//read file header
    BitmapFileHeader fileHeader;
    size_t readBytes = fread(&fileHeader, sizeof(BitmapFileHeader), 1, file);
	if(readBytes != 1) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
		throw megaglest_runtime_error(szBuf);
	}
	static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
	if(bigEndianSystem == true) {
		fileHeader.offsetBits = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.offsetBits);
		fileHeader.reserved1 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.reserved1);
		fileHeader.reserved2 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.reserved2);
		fileHeader.size = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.size);
		fileHeader.type1 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.type1);
		fileHeader.type2 = Shared::PlatformByteOrder::fromCommonEndian(fileHeader.type2);
	}

	if(fileHeader.type1!='B' || fileHeader.type2!='M'){
		throw megaglest_runtime_error(path +" is not a bitmap",true);
	}

	//read info header
	BitmapInfoHeader infoHeader;
	readBytes = fread(&infoHeader, sizeof(BitmapInfoHeader), 1, file);
	if(readBytes != 1) {
		char szBuf[8096]="";
		snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
		throw megaglest_runtime_error(szBuf);
	}
	if(bigEndianSystem == true) {
		infoHeader.bitCount = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.bitCount);
		infoHeader.clrImportant = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.clrImportant);
		infoHeader.clrUsed = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.clrUsed);
		infoHeader.compression = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.compression);
		infoHeader.height = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.height);
		infoHeader.planes = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.planes);
		infoHeader.size = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.size);
		infoHeader.sizeImage = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.sizeImage);
		infoHeader.width = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.width);
		infoHeader.xPelsPerMeter = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.xPelsPerMeter);
		infoHeader.yPelsPerMeter = Shared::PlatformByteOrder::fromCommonEndian(infoHeader.yPelsPerMeter);
	}
	if(infoHeader.bitCount!=24){
        throw megaglest_runtime_error(path+" is not a 24 bit bitmap",true);
	}

    h= infoHeader.height;
    w= infoHeader.width;
	components= 3;
}

void PixmapIoBmp::read(uint8 *pixels) {
	read(pixels, 3);
}

void PixmapIoBmp::read(uint8 *pixels, int components) {
	static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();

	for(int i=0; i<h*w*components; i+=components) {
		uint8 r, g, b;
		size_t readBytes = fread(&b, 1, 1, file);
		if(readBytes != 1) {
			char szBuf[8096]="";
			snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
			throw megaglest_runtime_error(szBuf);
		}
		if(bigEndianSystem == true) {
			b = Shared::PlatformByteOrder::fromCommonEndian(b);
		}
		readBytes = fread(&g, 1, 1, file);
		if(readBytes != 1) {
			char szBuf[8096]="";
			snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
			throw megaglest_runtime_error(szBuf);
		}
		if(bigEndianSystem == true) {
			g = Shared::PlatformByteOrder::fromCommonEndian(g);
		}

		readBytes = fread(&r, 1, 1, file);
		if(readBytes != 1) {
			char szBuf[8096]="";
			snprintf(szBuf,8096,"fread returned wrong size = " MG_SIZE_T_SPECIFIER " on line: %d.",readBytes,__LINE__);
			throw megaglest_runtime_error(szBuf);
		}
		if(bigEndianSystem == true) {
			r = Shared::PlatformByteOrder::fromCommonEndian(r);
		}


		switch(components){
		case 1:
			pixels[i]= (r+g+b)/3;
			break;
		case 3:
			pixels[i]= r;
			pixels[i+1]= g;
			pixels[i+2]= b;
			break;
		case 4:
			pixels[i]= r;
			pixels[i+1]= g;
			pixels[i+2]= b;
			pixels[i+3]= 255;
			break;
		}
    }
}

void PixmapIoBmp::openWrite(const string &path, int w, int h, int components) {
    this->w= w;
	this->h= h;
	this->components= components;

#ifdef WIN32
	file= _wfopen(utf8_decode(path).c_str(), L"wb");
#else
	file= fopen(path.c_str(),"wb");
#endif
	if (file == NULL) {
		throw megaglest_runtime_error("Can't open BMP file for writing: "+ path);
	}

	BitmapFileHeader fileHeader;
    fileHeader.type1='B';
	fileHeader.type2='M';
	fileHeader.offsetBits=sizeof(BitmapFileHeader)+sizeof(BitmapInfoHeader);
	fileHeader.size=sizeof(BitmapFileHeader)+sizeof(BitmapInfoHeader)+3*h*w;
	fileHeader.reserved1 = 0;
	fileHeader.reserved2 = 0;

	static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
	if(bigEndianSystem == true) {
		fileHeader.offsetBits = Shared::PlatformByteOrder::toCommonEndian(fileHeader.offsetBits);
		fileHeader.reserved1 = Shared::PlatformByteOrder::toCommonEndian(fileHeader.reserved1);
		fileHeader.reserved2 = Shared::PlatformByteOrder::toCommonEndian(fileHeader.reserved2);
		fileHeader.size = Shared::PlatformByteOrder::toCommonEndian(fileHeader.size);
		fileHeader.type1 = Shared::PlatformByteOrder::toCommonEndian(fileHeader.type1);
		fileHeader.type2 = Shared::PlatformByteOrder::toCommonEndian(fileHeader.type2);
	}

    fwrite(&fileHeader, sizeof(BitmapFileHeader), 1, file);

	//info header
	BitmapInfoHeader infoHeader;
	infoHeader.bitCount=24;
	infoHeader.clrImportant=0;
	infoHeader.clrUsed=0;
	infoHeader.compression=0;
	infoHeader.height= h;
	infoHeader.planes=1;
	infoHeader.size=sizeof(BitmapInfoHeader);
	infoHeader.sizeImage=0;
	infoHeader.width= w;
	infoHeader.xPelsPerMeter= 0;
	infoHeader.yPelsPerMeter= 0;

	if(bigEndianSystem == true) {
		infoHeader.bitCount = Shared::PlatformByteOrder::toCommonEndian(infoHeader.bitCount);
		infoHeader.clrImportant = Shared::PlatformByteOrder::toCommonEndian(infoHeader.clrImportant);
		infoHeader.clrUsed = Shared::PlatformByteOrder::toCommonEndian(infoHeader.clrUsed);
		infoHeader.compression = Shared::PlatformByteOrder::toCommonEndian(infoHeader.compression);
		infoHeader.height = Shared::PlatformByteOrder::toCommonEndian(infoHeader.height);
		infoHeader.planes = Shared::PlatformByteOrder::toCommonEndian(infoHeader.planes);
		infoHeader.size = Shared::PlatformByteOrder::toCommonEndian(infoHeader.size);
		infoHeader.sizeImage = Shared::PlatformByteOrder::toCommonEndian(infoHeader.sizeImage);
		infoHeader.width = Shared::PlatformByteOrder::toCommonEndian(infoHeader.width);
		infoHeader.xPelsPerMeter = Shared::PlatformByteOrder::toCommonEndian(infoHeader.xPelsPerMeter);
		infoHeader.yPelsPerMeter = Shared::PlatformByteOrder::toCommonEndian(infoHeader.yPelsPerMeter);
	}

	fwrite(&infoHeader, sizeof(BitmapInfoHeader), 1, file);
}

void PixmapIoBmp::write(uint8 *pixels) {
	static bool bigEndianSystem = Shared::PlatformByteOrder::isBigEndian();
	if(bigEndianSystem == true) {
		Shared::PlatformByteOrder::toEndianTypeArray<uint8>(pixels,h*w*components);
	}

    for (int i=0; i<h*w*components; i+=components){
        fwrite(&pixels[i+2], 1, 1, file);
		fwrite(&pixels[i+1], 1, 1, file);
		fwrite(&pixels[i], 1, 1, file);
    }
}

// =====================================================
//	class PixmapIoPng
// =====================================================

PixmapIoPng::PixmapIoPng() {
	file= NULL;
}

PixmapIoPng::~PixmapIoPng() {
	if(file!=NULL){
		fclose(file);
		file=NULL;
	}
}

void PixmapIoPng::openRead(const string &path) {
	throw megaglest_runtime_error("PixmapIoPng::openRead not implemented!");
}

void PixmapIoPng::read(uint8 *pixels) {
	throw megaglest_runtime_error("PixmapIoPng::read not implemented!");
}

void PixmapIoPng::read(uint8 *pixels, int components) {

	throw megaglest_runtime_error("PixmapIoPng::read #2 not implemented!");
}

void PixmapIoPng::openWrite(const string &path, int w, int h, int components) {
    this->path = path;
	this->w= w;
	this->h= h;
	this->components= components;

#ifdef WIN32
	file= _wfopen(utf8_decode(path).c_str(), L"wb");
#else
	file= fopen(path.c_str(),"wb");
#endif
	if (file == NULL) {
		throw megaglest_runtime_error("Can't open PNG file for writing: "+ path);
	}
}

void PixmapIoPng::write(uint8 *pixels) {
    // initialize stuff
    png_bytep *imrow = new png_bytep[h];
	//png_bytep *imrow = (png_bytep*) malloc(sizeof(png_bytep) * height);
    for(int i = 0; i < h; ++i) {
        imrow[i] = pixels + (h-1-i) * w * components;
    }

    png_structp imgp = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
    png_infop infop = png_create_info_struct(imgp);
    png_init_io(imgp, file);

    int color_type = PNG_COLOR_TYPE_RGB;
    if(components == 4) {
    	color_type = PNG_COLOR_TYPE_RGBA;
    }
    png_set_IHDR(imgp, infop, w, h,
		 8, color_type, PNG_INTERLACE_NONE,
		 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    // write file
    png_write_info(imgp, infop);
    png_write_image(imgp, imrow);
    png_write_end(imgp, NULL);

    delete [] imrow;
}

// =====================================================
//	class PixmapIoJpg
// =====================================================

PixmapIoJpg::PixmapIoJpg() {
	file= NULL;
}

PixmapIoJpg::~PixmapIoJpg() {
	if(file!=NULL){
		fclose(file);
		file=NULL;
	}
}

void PixmapIoJpg::openRead(const string &path) {
	throw megaglest_runtime_error("PixmapIoJpg::openRead not implemented!");
}

void PixmapIoJpg::read(uint8 *pixels) {
	throw megaglest_runtime_error("PixmapIoJpg::read not implemented!");
}

void PixmapIoJpg::read(uint8 *pixels, int components) {

	throw megaglest_runtime_error("PixmapIoJpg::read #2 not implemented!");
}

void PixmapIoJpg::openWrite(const string &path, int w, int h, int components) {
    this->path = path;
	this->w= w;
	this->h= h;
	this->components= components;

#ifdef WIN32
	file= _wfopen(utf8_decode(path).c_str(), L"wb");
#else
	file= fopen(path.c_str(),"wb");
#endif
	if (file == NULL) {
		throw megaglest_runtime_error("Can't open JPG file for writing: "+ path);
	}
}

void PixmapIoJpg::write(uint8 *pixels) {
	/*
	* alpha channel is not supported for jpeg. strip it.
	*/
	unsigned char * tmpbytes = NULL;
	if(components == 4) {
		int n = w * h;
		unsigned char *dst = tmpbytes = (unsigned char *) malloc(n*3);
		if(dst != NULL) {
			const unsigned char *src = pixels;
			for (int i = 0; i < n; i++) {
				*dst++ = *src++;
				*dst++ = *src++;
				*dst++ = *src++;
				src++;
			}
		}
		components = 3;
	}

	struct jpeg_compress_struct cinfo;
	struct jpeg_error_mgr jerr;

	/* this is a pointer to one row of image data */
	JSAMPROW row_pointer[1];

	cinfo.err = jpeg_std_error( &jerr );
	jpeg_create_compress(&cinfo);
	jpeg_stdio_dest(&cinfo, file);

	/* Setting the parameters of the output file here */
	cinfo.image_width = w;
	cinfo.image_height = h;
	cinfo.input_components = 3; // no alpha channel for jpeg
	cinfo.in_color_space = JCS_RGB;
	/* default compression parameters, we shouldn't be worried about these */
	jpeg_set_defaults( &cinfo );
	/* Now do the compression .. */
	jpeg_start_compress( &cinfo, TRUE );

	/* use stripped alpha channel bytes */
	if(tmpbytes) {
		pixels = tmpbytes;
	}

	// OpenGL writes from bottom to top.
    // libjpeg goes from top to bottom.
    // flip lines.
	uint8 *flip = (uint8 *)malloc(sizeof(uint8) * w * h * 3);

	if(pixels != NULL && flip != NULL) {
		for (int y = 0;y < h; ++y) {
			for (int x = 0;x < w; ++x) {
				flip[(y * w + x) * 3] = pixels[((h - 1 - y) * w + x) * 3];
				flip[(y * w + x) * 3 + 1] = pixels[((h - 1 - y) * w + x) * 3 + 1];
				flip[(y * w + x) * 3 + 2] = pixels[((h - 1 - y) * w + x) * 3 + 2];
			}
		}
	
		/* like reading a file, this time write one row at a time */
		while( cinfo.next_scanline < cinfo.image_height ) {
			row_pointer[0] = &flip[ cinfo.next_scanline * cinfo.image_width *  cinfo.input_components];
			jpeg_write_scanlines( &cinfo, row_pointer, 1 );
		}
	}
	if (tmpbytes) {
		free(tmpbytes);
		tmpbytes=NULL;
	}
	if(flip) {
		free(flip);
		flip=NULL;
	}

	/* similar to read file, clean up after we're done compressing */
	jpeg_finish_compress( &cinfo );
	jpeg_destroy_compress( &cinfo );
	//fclose( outfile );
	/* success code is 1! */
}

// =====================================================
//	class Pixmap1D
// =====================================================


// ===================== PUBLIC ========================

Pixmap1D::Pixmap1D() {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}


    w= -1;
	components= -1;
    pixels= NULL;
}

Pixmap1D::Pixmap1D(int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}


	init(components);
}

Pixmap1D::Pixmap1D(int w, int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}


	init(w, components);
}

void Pixmap1D::init(int components){
	this->w= -1;
	this->components= components;
	pixels= NULL;
	CalculatePixelsCRC(pixels,0, crc);
}

void Pixmap1D::init(int w, int components){
	this->w= w;
	this->components= components;
	pixels= new uint8[getPixelByteCount()];
	CalculatePixelsCRC(pixels,0, crc);
}

std::size_t Pixmap1D::getPixelByteCount() const {
	return (w * components);
}

void Pixmap1D::deletePixels() {
	delete [] pixels;
	pixels = NULL;
}

Pixmap1D::~Pixmap1D(){
	deletePixels();
}

void Pixmap1D::load(const string &path) {
	string extension = (path.empty() == false ? path.substr(path.find_last_of('.')+1) : "");
	if(toLower(extension) == "bmp") {
		loadBmp(path);
	}
	else if(toLower(extension) == "tga") {
		loadTga(path);
	}
	else {
		throw megaglest_runtime_error("Unknown pixmap extension [" + extension + "] file [" + path + "]");
	}
	this->path = path;
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap1D::loadBmp(const string &path) {
    this->path = path;

	PixmapIoBmp plb;
	plb.openRead(path);

	//init
	if(plb.getH()==1) {
		w= plb.getW();
	}
	else if(plb.getW()==1) {
		w= plb.getH();
	}
	else {
		throw megaglest_runtime_error("One of the texture dimensions must be 1");
	}

	if(components == -1) {
		components= 3;
	}
	if(pixels == NULL) {
		pixels= new uint8[getPixelByteCount()];
	}

	//data
	plb.read(pixels, components);
}

void Pixmap1D::loadTga(const string &path) {
    this->path = path;

	PixmapIoTga plt;
	plt.openRead(path);

	//init
	if(plt.getH()==1) {
		w= plt.getW();
	}
	else if(plt.getW()==1) {
		w= plt.getH();
	}
	else {
		throw megaglest_runtime_error("One of the texture dimensions must be 1");
	}

	int fileComponents= plt.getComponents();

	if(components == -1) {
		components= fileComponents;
	}
	if(pixels == NULL) {
		pixels= new uint8[getPixelByteCount()];
	}

	//read data
	plt.read(pixels, components);
}

// =====================================================
//	class Pixmap2D
// =====================================================

// ===================== PUBLIC ========================

Pixmap2D::Pixmap2D() {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

    h= -1;
    w= -1;
	components= -1;
    pixels= NULL;
}

Pixmap2D::Pixmap2D(int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

    h= -1;
    w= -1;
	this->components= -1;
    pixels= NULL;

	init(components);
}

Pixmap2D::Pixmap2D(int w, int h, int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

    this->h= 0;
    this->w= -1;
    this->components= -1;
    pixels= NULL;

	init(w, h, components);
}

void Pixmap2D::init(int components) {
	this->w= -1;
	this->h= -1;
	this->components= components;
	deletePixels();
	pixels= NULL;
	CalculatePixelsCRC(pixels,0, crc);
}

void Pixmap2D::init(int w, int h, int components) {
	this->w= w;
	this->h= h;
	this->components= components;
	deletePixels();

	if(getPixelByteCount() <= 0 || (h <= 0 || w <= 0 || components <= 0)) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap dimensions for [%s], h = %d, w = %d, components = %d\n",path.c_str(),h,w,components);
		throw megaglest_runtime_error(szBuf);
	}
	pixels= new uint8[getPixelByteCount()];
	CalculatePixelsCRC(pixels,0, crc);
}

std::size_t Pixmap2D::getPixelByteCount() const {
	return (h * w * components);
}

void Pixmap2D::deletePixels() {
	if(pixels) {
		delete [] pixels;
		pixels = NULL;
	}
}

Pixmap2D::~Pixmap2D() {
	deletePixels();
}

void Pixmap2D::Scale(int format, int newW, int newH) {
	int useComponents = this->getComponents();
	int originalW = w;
	int originalH = h;
	uint8 *newpixels= new uint8[newW * newH * useComponents];

	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	GLenum glErr = gluScaleImage(	format,
				w, h, GL_UNSIGNED_BYTE, pixels,
				newW, newH, GL_UNSIGNED_BYTE, newpixels);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	if(glErr == GL_NO_ERROR) {
		init(newW,newH,this->components);
		pixels = newpixels;

		if(SystemFlags::VERBOSE_MODE_ENABLED) printf("Scaled image from [%d x %d] to [%d x %d]\n",originalW,originalH,w,h);
	}
	else {
		const char *errorString= reinterpret_cast<const char*>(gluErrorString(glErr));
		printf("ERROR Scaling image from [%d x %d] to [%d x %d] error: %u [%s]\n",originalW,originalH,w,h,glErr,errorString);

		assertGlWithErrorNumber(glErr);
	}

	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

Pixmap2D* Pixmap2D::loadPath(const string& path) {
	//printf("Loading Pixmap2D [%s]\n",path.c_str());

	Pixmap2D *pixmap = FileReader<Pixmap2D>::readPath(path);
	if(pixmap != NULL) {
		pixmap->path = path;
		CalculatePixelsCRC(pixmap->pixels,pixmap->getPixelByteCount(), pixmap->crc);
	}
	return pixmap;
}

void Pixmap2D::load(const string &path) {
	//printf("Loading Pixmap2D [%s]\n",path.c_str());

	FileReader<Pixmap2D>::readPath(path,this);
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
	this->path = path;
}

void Pixmap2D::save(const string &path) {
	string extension = (path.empty() == false ? path.substr(path.find_last_of('.')+1) : "");
	if(toLower(extension) == "bmp") {
		saveBmp(path);
	}
	else if(toLower(extension) == "tga") {
		saveTga(path);
	}
	else if(toLower(extension) == "jpg") {
		saveJpg(path);
	}
	else if(toLower(extension) == "png") {
		savePng(path);
	}
	else {
		throw megaglest_runtime_error("Unknown pixmap extension [" + extension + "] file [" + path + "]");
	}
}

void Pixmap2D::saveBmp(const string &path) {
	PixmapIoBmp psb;
	psb.openWrite(path, w, h, components);
	psb.write(pixels);
}

void Pixmap2D::saveTga(const string &path) {
	PixmapIoTga pst;
	pst.openWrite(path, w, h, components);
	pst.write(pixels);
}

void Pixmap2D::saveJpg(const string &path) {
	PixmapIoJpg pst;
	pst.openWrite(path, w, h, components);
	pst.write(pixels);
}

void Pixmap2D::savePng(const string &path) {
	PixmapIoPng pst;
	pst.openWrite(path, w, h, components);
	pst.write(pixels);
}

void Pixmap2D::getPixel(int x, int y, uint8 *value) const {
	for(int i=0; i<components; ++i){
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}

		value[i]= pixels[index];
	}
}

void Pixmap2D::getPixel(int x, int y, float32 *value) const {
	for(int i = 0; i < components; ++i) {
		std::size_t index = (w * y + x) * components + i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}

		value[i] = pixels[index] / 255.f;
	}
}

void Pixmap2D::getComponent(int x, int y, int component, uint8 &value) const {
	std::size_t index = (w*y+x)*components+component;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	value = pixels[index];
}

void Pixmap2D::getComponent(int x, int y, int component, float32 &value) const {
	std::size_t index = (w*y+x)*components+component;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	value= pixels[index] / 255.f;
}

//vector get
Vec4f Pixmap2D::getPixel4f(int x, int y) const {
	Vec4f v(0.f);
	for(int i=0; i<components && i<4; ++i){
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}

		v.ptr()[i]= pixels[index] / 255.f;
	}
	return v;
}

Vec3f Pixmap2D::getPixel3f(int x, int y) const {
	Vec3f v(0.f);
	for(int i=0; i<components && i<3; ++i){
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}

		v.ptr()[i]= pixels[index] / 255.f;
	}
	return v;
}

float Pixmap2D::getPixelf(int x, int y) const {
	std::size_t index = (w * y + x) * components;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}
	float result = pixels[index] / 255.f;
	return truncateDecimal<float>(result,6);
}

float Pixmap2D::getComponentf(int x, int y, int component) const {
	float c=0;
	getComponent(x, y, component, c);
	return c;
}

void Pixmap2D::setPixel(int x, int y, const uint8 *value, int arraySize) {
	if(arraySize > components) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap arraySize: %d for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",arraySize,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}
	for(int i = 0; i < components; ++i) {
		std::size_t index = (w * y + x) * components + i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}

		pixels[index]= value[i];
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setPixel(int x, int y, const float32 *value, int arraySize) {
	if(arraySize > components) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap arraySize: %d for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",arraySize,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	for(int i = 0; i < components; ++i) {
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}
		pixels[index] = static_cast<uint8>(value[i] * 255.f);
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setComponent(int x, int y, int component, uint8 value) {
	std::size_t index = (w*y+x)*components+component;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	pixels[index] = value;
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setComponent(int x, int y, int component, float32 value) {
	std::size_t index = (w*y+x)*components+component;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	pixels[index] = static_cast<uint8>(value * 255.f);
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

//vector set
void Pixmap2D::setPixel(int x, int y, const Vec3f &p) {
	for(int i = 0; i < components  && i < 3; ++i) {
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}
		pixels[index] = static_cast<uint8>(p.ptr()[i] * 255.f);
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setPixel(int x, int y, const Vec4f &p) {
	for(int i = 0; i < components && i < 4; ++i) {
		std::size_t index = (w*y+x)*components+i;
		if(index >= getPixelByteCount()) {
			char szBuf[8096];
			snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
			throw megaglest_runtime_error(szBuf);
		}
		pixels[index] = static_cast<uint8>(p.ptr()[i] * 255.f);
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setPixel(int x, int y, float p) {
	std::size_t index = (w * y + x) * components;
	if(index >= getPixelByteCount()) {
		char szBuf[8096];
		snprintf(szBuf,8096,"Invalid pixmap index: " MG_SIZE_T_SPECIFIER " for [%s], h = %d, w = %d, components = %d x = %d y = %d\n",index,path.c_str(),h,w,components,x,y);
		throw megaglest_runtime_error(szBuf);
	}

	pixels[index] = static_cast<uint8>(p * 255.f);
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setPixels(const uint8 *value, int arraySize) {
	for(int i = 0; i < w; ++i) {
		for(int j = 0; j < h; ++j) {
			setPixel(i, j, value, arraySize);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setPixels(const float32 *value, int arraySize) {
	for(int i = 0; i < w; ++i) {
		for(int j = 0; j < h; ++j) {
			setPixel(i, j, value, arraySize);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setComponents(int component, uint8 value){
	assert(component<components);
	for(int i=0; i<w; ++i){
		for(int j=0; j<h; ++j){
			setComponent(i, j, component, value);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::setComponents(int component, float32 value){
	assert(component<components);
	for(int i=0; i<w; ++i){
		for(int j=0; j<h; ++j){
			setComponent(i, j, component, value);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

float splatDist(Vec2i a, Vec2i b){
	return (max(abs(a.x-b.x),abs(a.y- b.y)) + 3.f*a.dist(b))/4.f;
}

void Pixmap2D::splat(const Pixmap2D *leftUp, const Pixmap2D *rightUp, const Pixmap2D *leftDown, const Pixmap2D *rightDown){

	RandomGen random;

	assert(components==3 || components==4);

	if(
		!doDimensionsAgree(leftUp) ||
		!doDimensionsAgree(rightUp) ||
		!doDimensionsAgree(leftDown) ||
		!doDimensionsAgree(rightDown))
	{
		throw megaglest_runtime_error("Pixmap2D::splat: pixmap dimensions don't agree");
	}

	for(int i=0; i<w; ++i){
		for(int j=0; j<h; ++j){

			float avg= (w+h)/2.f;

			float distLu= splatDist(Vec2i(i, j), Vec2i(0, 0));
			float distRu= splatDist(Vec2i(i, j), Vec2i(w, 0));
			float distLd= splatDist(Vec2i(i, j), Vec2i(0, h));
			float distRd= splatDist(Vec2i(i, j), Vec2i(w, h));

			const float powFactor= 2.0f;

			distLu	= std::pow(distLu, powFactor);
			distRu	= std::pow(distRu, powFactor);
			distLd	= std::pow(distLd, powFactor);
			distRd	= std::pow(distRd, powFactor);
			avg		= std::pow(avg, powFactor);

			float lu= distLu>avg? 0: ((avg-distLu))*random.randRange(0.5f, 1.0f);
			float ru= distRu>avg? 0: ((avg-distRu))*random.randRange(0.5f, 1.0f);
			float ld= distLd>avg? 0: ((avg-distLd))*random.randRange(0.5f, 1.0f);
			float rd= distRd>avg? 0: ((avg-distRd))*random.randRange(0.5f, 1.0f);

			float total= lu+ru+ld+rd;

			Vec4f pix= (leftUp->getPixel4f(i, j)*lu+
				rightUp->getPixel4f(i, j)*ru+
				leftDown->getPixel4f(i, j)*ld+
				rightDown->getPixel4f(i, j)*rd)*(1.0f/total);

			setPixel(i, j, pix);
		}
	}
}

void Pixmap2D::lerp(float t, const Pixmap2D *pixmap1, const Pixmap2D *pixmap2){
	if(
		!doDimensionsAgree(pixmap1) ||
		!doDimensionsAgree(pixmap2))
	{
		throw megaglest_runtime_error("Pixmap2D::lerp: pixmap dimensions don't agree");
	}

	for(int i=0; i<w; ++i){
		for(int j=0; j<h; ++j){
			setPixel(i, j, pixmap1->getPixel4f(i, j).lerp(t, pixmap2->getPixel4f(i, j)));
		}
	}
}

void Pixmap2D::copy(const Pixmap2D *sourcePixmap){

	assert(components==sourcePixmap->getComponents());

	if(w!=sourcePixmap->getW() || h!=sourcePixmap->getH()){
		throw megaglest_runtime_error("Pixmap2D::copy() dimensions must agree");
	}
	memcpy(pixels, sourcePixmap->getPixels(), w*h*sourcePixmap->getComponents());
	this->path = sourcePixmap->path;
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap2D::subCopy(int x, int y, const Pixmap2D *sourcePixmap){
	assert(components==sourcePixmap->getComponents());

	if(w<sourcePixmap->getW() && h<sourcePixmap->getH()){
		throw megaglest_runtime_error("Pixmap2D::subCopy(), bad dimensions");
	}

	uint8 *pixel= new uint8[components];

	for(int i = 0; i < sourcePixmap->getW(); ++i) {
		for(int j = 0; j < sourcePixmap->getH(); ++j) {
			sourcePixmap->getPixel(i, j, pixel);
			setPixel(i+x, j+y, pixel, components);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);

	delete [] pixel;
}

// uses a a part of a bigger source image to fill this image.
void Pixmap2D::copyImagePart(int x, int y, const Pixmap2D *sourcePixmap){
	assert(components==sourcePixmap->getComponents());

	if(x+w>sourcePixmap->getW() && y+h>sourcePixmap->getH()){
		throw megaglest_runtime_error("Pixmap2D::copyImagePart(), bad dimensions");
	}

	uint8 *pixel= new uint8[components];

	for(int i = x; i < x + w; ++i) {
		for(int j = y; j < y + h; ++j) {
			sourcePixmap->getPixel(i, j, pixel);
			setPixel(i-x, j-y, pixel, components);
		}
	}
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);

	delete [] pixel;
}

bool Pixmap2D::doDimensionsAgree(const Pixmap2D *pixmap){
	return pixmap->getW() == w && pixmap->getH() == h;
}

// =====================================================
//	class Pixmap3D
// =====================================================

Pixmap3D::Pixmap3D() {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}


	w= -1;
	h= -1;
	d= -1;
	components= -1;
	pixels = NULL;
	slice=0;
}

Pixmap3D::Pixmap3D(int w, int h, int d, int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

	pixels = NULL;
	slice=0;
	init(w, h, d, components);
}

Pixmap3D::Pixmap3D(int d, int components) {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

	pixels = NULL;
	slice=0;
	init(d, components);
}

void Pixmap3D::init(int w, int h, int d, int components){
	this->w= w;
	this->h= h;
	this->d= d;
	this->components= components;
	pixels= new uint8[getPixelByteCount()];
	CalculatePixelsCRC(pixels,0, crc);
}

std::size_t Pixmap3D::getPixelByteCount() const {
	return (h * w * d * components);
}

void Pixmap3D::init(int d, int components){
	this->w= -1;
	this->h= -1;
	this->d= d;
	this->components= components;
	pixels= NULL;
	CalculatePixelsCRC(pixels,0, crc);
}

void Pixmap3D::init(int components) {
	this->w= -1;
	this->h= -1;
	this->d= -1;
	this->components= components;
	pixels= NULL;
	CalculatePixelsCRC(pixels,0, crc);
}

void Pixmap3D::deletePixels() {
	delete [] pixels;
	pixels = NULL;
}

Pixmap3D::~Pixmap3D() {
	deletePixels();
}

void Pixmap3D::loadSlice(const string &path, int slice) {
	this->slice = slice;
	string extension = (path.empty() == false ? path.substr(path.find_last_of('.') + 1) : "");
	if(toLower(extension) == "png") {
		loadSlicePng(path, slice);
	}
	else if(toLower(extension) == "bmp") {
		loadSliceBmp(path, slice);
	}
	else if(toLower(extension) == "tga") {
		loadSliceTga(path, slice);
	}
	else {
		throw megaglest_runtime_error("Unknown pixmap extension [" + extension + "] file [" + path + "]");
	}
	this->path = path;
	CalculatePixelsCRC(pixels,getPixelByteCount(), crc);
}

void Pixmap3D::loadSlicePng(const string &path, int slice) {
	this->path = path;

	//deletePixels();
	//Pixmap3D *pixmap = FileReader<Pixmap3D>::readPath(path,this);
	FileReader<Pixmap3D>::readPath(path,this);
	//printf("Loading 3D pixmap PNG [%s] pixmap [%p] this [%p]\n",path.c_str(),pixmap, this);
}

void Pixmap3D::loadSliceBmp(const string &path, int slice){
	this->path = path;

	PixmapIoBmp plb;
	plb.openRead(path);

	//init
	w= plb.getW();
	h= plb.getH();
	if(components==-1){
		components= 3;
	}
	if(pixels==NULL){
		pixels= new uint8[getPixelByteCount()];
	}

	//data
	plb.read(&pixels[slice*w*h*components], components);
}

void Pixmap3D::loadSliceTga(const string &path, int slice){
	this->path = path;

	//deletePixels();
	FileReader<Pixmap3D>::readPath(path,this);
	//printf("Loading 3D pixmap TGA [%s] this [%p]\n",path.c_str(),this);
}

// =====================================================
//	class PixmapCube
// =====================================================
PixmapCube::PixmapCube() {
	if(GlobalStaticFlags::getIsNonGraphicalModeEnabled() == true) {
		throw megaglest_runtime_error("Loading graphics in headless server mode not allowed!");
	}

}

PixmapCube::~PixmapCube() {

}

void PixmapCube::init(int w, int h, int components) {
	for(int i=0; i<6; ++i) {
		faces[i].init(w, h, components);
	}
}

void PixmapCube::init(int components) {
	for(int i=0; i<6; ++i) {
		faces[i].init(components);
	}
}

std::size_t PixmapCube::getPixelByteCount() const {
	std::size_t result = 0;
	for(int i=0; i<6; ++i) {
		result += faces[i].getPixelByteCount();
	}

	return result;
}

//load & save
void PixmapCube::loadFace(const string &path, int face) {
	this->path[face] = path;

	faces[face].load(path);
}

void PixmapCube::deletePixels() {
	for(int i=0; i<6; ++i){
		faces[i].deletePixels();
	}
}


}}//end namespace
