
#include "ofImage.h"
#include "freeImage.h"


static bool bFreeImageInited = false;	

//-----------------------------------------
// 
class freeImageBitmap{
	
	public:
	
		FIBITMAP * bmp;	
		
		// when OF users ask for pixels
		// we will get them contiguous memory
		// ie, non-aligned
		// see the pixel access chapter of the freeImage pdf
		// these pixel will also be RGB
		// on both platforms
		// bmp will be RGB / BGR depending
		// on the endianess of the platform.
		
		unsigned char * pixels;	
	

		//-----------------------
		freeImageBitmap(){
			bmp = NULL;
		}
		
		//-----------------------
		//swap my image out in a (hopefully) non-memory leakish way
		void swap(FIBITMAP *dst) {
			if(dst == NULL) 	return;
			if(bmp != NULL)		FreeImage_Unload(bmp);
			bmp = dst;
		}		
		
		//-----------------------
		void setSize(FREE_IMAGE_TYPE image_type, WORD width, WORD height, WORD bpp) {
			if(bmp == NULL) {
				FreeImage_Unload(bmp);
			}
			bmp = FreeImage_AllocateT(image_type, width, height, bpp);
		}
		
		//-----------------------
		bool isValid(){
			return (bmp != NULL) ? true : false;
		}

};


//----------------------------------------------------------
ofImage::ofImage(){
	
	//----------------------- init free image if necessary
	if (!bFreeImageInited){
		FreeImage_Initialise();
		bFreeImageInited = true;
	}
	//-----------------------
	IMG 			= new freeImageBitmap();
	width = height = bpp = 0;
	type 			= OF_IMAGE_UNDEFINED;
	bUseTexture 	= true;		// the default is, yes, use a texture	
	bAllocatedPixels	= false;
}

//----------------------------------------------------------
void ofImage::grabScreen(int _x, int _y, int _w, int _h){
	if (IMG->isValid()){
		
		// flip y if origin is top:
		if (!ofbOriginBottomLeft()){
			int height = ofGetHeight();
			_y = height - _y;
			_y -= _h; // top, bottom issues
		}
		
		if (!(width == _w && height == _h)){
			resize(_w, _h);
		}
		unsigned char * pixels = (unsigned char *)FreeImage_GetBits(IMG->bmp);
		switch (bpp){
			case 8:
				glReadPixels(_x, _y, _w, _h, GL_LUMINANCE,GL_UNSIGNED_BYTE, pixels);
				break;
			case 24:
				
				#ifdef TARGET_LITTLE_ENDIAN
					glReadPixels(_x, _y, _w, _h, GL_BGR_EXT,GL_UNSIGNED_BYTE, pixels);
				#else
					glReadPixels(_x, _y, _w, _h, GL_RGB,GL_UNSIGNED_BYTE, pixels);
				#endif
				
				break;
			case 32:
				glReadPixels(_x, _y, _w, _h, GL_RGBA,GL_UNSIGNED_BYTE, pixels);
				break;
		}
		// we will need to flip images
		// if the 0,0 position is in the top left:
		if (!ofbOriginBottomLeft()){
			FreeImage_FlipVertical(IMG->bmp);
		}
		
		update();
	} else {
		// assume that this is color....
		IMG->setSize(FIT_BITMAP, _w, _h, 24);
		unsigned char * pixels = (unsigned char *)FreeImage_GetBits(IMG->bmp);
		
		// flip y if origin is top:
		if (!ofbOriginBottomLeft()){
			int height = ofGetHeight();
			_y = height - _y;
			_y -= _h; // top, bottom issues
		}

		#ifdef TARGET_LITTLE_ENDIAN
			glReadPixels(_x, _y, _w, _h, GL_BGR_EXT,GL_UNSIGNED_BYTE, pixels);
		#else
			glReadPixels(_x, _y, _w, _h, GL_RGB,GL_UNSIGNED_BYTE, pixels);
		#endif
		
		// we will need to flip images
		// if the 0,0 position is in the top left:
		if (!ofbOriginBottomLeft()){
			FreeImage_FlipVertical(IMG->bmp);
		}
		
		update();
	}
}

//----------------------------------------------------------
void ofImage::allocate(int _w, int _h, int _type){
	int newbpp;
	switch (_type){
		case OF_IMAGE_GRAYSCALE:
			newbpp = 8;
			break;
		case OF_IMAGE_COLOR:
			newbpp = 24;
			break;
		case OF_IMAGE_COLOR_ALPHA:
			newbpp = 32;		
			break;
		default:
			newbpp = 24;
			break; 		
	}
	IMG->setSize(FIT_BITMAP, _w, _h, newbpp);
}		

//----------------------------------------------------------
void ofImage::update(){
	
	width 		= FreeImage_GetWidth(IMG->bmp);
	height 		= FreeImage_GetHeight(IMG->bmp);
	bpp 		= FreeImage_GetBPP(IMG->bmp);
	
	
	// this is check to see if it's a gif
	bool bNoPallette 		= (FreeImage_GetColorType(IMG->bmp) != FIC_PALETTE);
	
	switch (bpp){
		case 8:
			if (bNoPallette) { //!bUsesPallette){
				type = OF_IMAGE_GRAYSCALE;
			} else {
				// 	convert to RGB - 
				// 	this is a gif (or other) with 256 colors, not b&w.
				setImageType(OF_IMAGE_COLOR);
			}
			break;
		case 24:
			type = OF_IMAGE_COLOR;
			break;
		case 32:
			type = OF_IMAGE_COLOR_ALPHA;		
			break;
		default:
			// if we've got here, something is wrong, the image
			// is not 8bit, 24bit or 32bit, so let's at least
			// make it color:
			setImageType(OF_IMAGE_COLOR);
			break; 		
	}
	
	//----------------------------------
	// now, since the "freeImage" memory is padded
	// (see freeImage392.pdf - "Pixel access functions")
	// lets get the data into contiguous memory - to make it easier 
	// for folks to work with...

	if (IMG->pixels != NULL && bAllocatedPixels == true)
		delete IMG->pixels;				// delete anything that might have existed
	int byteCount = bpp / 8;		// bytes ber pixel (not bits per pixel)
	IMG->pixels = new unsigned char[width*height*byteCount];	//allocate that memory for the image (contiguously)
	bAllocatedPixels = true;
	FreeImage_ConvertToRawBits(IMG->pixels, IMG->bmp, width*byteCount, bpp, FI_RGBA_RED_MASK,FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK, TRUE);  // get bits
	
	#ifdef TARGET_LITTLE_ENDIAN
		if (type == OF_IMAGE_COLOR) 		swapChannels(false);
		if (type == OF_IMAGE_COLOR_ALPHA)   swapChannels(true);
	#endif
	//----------------------------------
	
	// if we are using a texture and the ofImage is upadate, 
	// allocate (or reallocate) the texture and upload the data
	// I will upload from the contguous memory, and use pixelStorei to 
	// make sure opengl knows I am contiguous. 
	
	if (bUseTexture){		
		switch (type){
			case OF_IMAGE_GRAYSCALE:
				tex.allocate(width,height,GL_LUMINANCE);
				tex.loadData(IMG->pixels, width,height, GL_LUMINANCE);
				break;
			case OF_IMAGE_COLOR:
				tex.allocate(width,height,GL_RGB);
				tex.loadData(IMG->pixels, width,height, GL_RGB);
				break;
			case OF_IMAGE_COLOR_ALPHA:
				tex.allocate(width,height,GL_RGBA);
				tex.loadData(IMG->pixels, width,height, GL_RGBA);
				break;
		}
	}
	
	
	
	
}

//----------------------------------------------------------
void ofImage::loadImage(char * fileName){
	
	bool bLoaded = false;
	
	//----------------------------- find the format
	FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
	fif = FreeImage_GetFileType(fileName, 0);
	if(fif == FIF_UNKNOWN) {
		// or guess via filename
		fif = FreeImage_GetFIFFromFilename(fileName);
	}
	if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
		if(IMG->isValid()) {
			FreeImage_Unload(IMG->bmp);			
		}
		IMG->bmp = FreeImage_Load(fif, fileName, 0);
		bLoaded = true;
		if(!IMG->isValid()){
			bLoaded = false;
		}
	}
	//-----------------------------
	
	if (bLoaded){
	
		// we will need to flip images
		// if the 0,0 position is in the top left:
		if (!ofbOriginBottomLeft()){
			FreeImage_FlipVertical(IMG->bmp);
		}
		
		update();
	} else {
		width = height = bpp = 0;
	}
}

//----------------------------------------------------------
void ofImage::saveImage(char * fileName){
	if (IMG->isValid()){
		FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
		fif = FreeImage_GetFileType(fileName, 0);
		if(fif == FIF_UNKNOWN) {
			// or guess via filename
			fif = FreeImage_GetFIFFromFilename(fileName);
		}
		if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
			// ok, if we are 0,0 top left, flip once, save and flip back 
			// this is because the native format for freeImage is 0,0 lower left
			// there  be a smarter way to do this (ie, just draw upside down)
			// as opposed to continually 
			if (!ofbOriginBottomLeft()){
				FreeImage_FlipVertical(IMG->bmp);
			}
			FreeImage_Save(fif, IMG->bmp, fileName, 0);
			if (!ofbOriginBottomLeft()){
				FreeImage_FlipVertical(IMG->bmp);
			}
		}
	}
}


//----------------------------------------------------------
unsigned char * ofImage::getPixels(){	
	// be careful!	might not be allocated.  you need to check for null...
	if (IMG->isValid()){
		return (unsigned char *)IMG->pixels;
	} else {
		return NULL;
	}
}


//----------------------------------------------------------
void ofImage::resize(int newWidth, int newHeight){
	if (IMG->isValid()){
		FIBITMAP * scaledBmp = FreeImage_Rescale(IMG->bmp, newWidth, newHeight, FILTER_BICUBIC);
		IMG->swap(scaledBmp);
		update();
	}
}

//----------------------------------------------------------
void ofImage::setImageType(int newType){
	
	if (IMG->isValid()){
		switch (newType){
			//------------------------------------
			case OF_IMAGE_GRAYSCALE:
				if (type != OF_IMAGE_GRAYSCALE){
					FIBITMAP * convertedBmp = FreeImage_ConvertToGreyscale(IMG->bmp);
					IMG->swap(convertedBmp);
					type = OF_IMAGE_GRAYSCALE;
				}
				break;
			//------------------------------------
			case OF_IMAGE_COLOR:
				if (type != OF_IMAGE_COLOR){
					FIBITMAP * convertedBmp = FreeImage_ConvertTo24Bits(IMG->bmp);
					IMG->swap(convertedBmp);
					type = OF_IMAGE_COLOR;
				}
				break;
			//------------------------------------
			case OF_IMAGE_COLOR_ALPHA:
				if (type != OF_IMAGE_COLOR_ALPHA){
					FIBITMAP * convertedBmp = FreeImage_ConvertTo32Bits(IMG->bmp);
					IMG->swap(convertedBmp);
					type = OF_IMAGE_COLOR_ALPHA;
				}		
				break;
		}
		update();
	}
	
}

//------------------------------------
void ofImage::swapChannels(bool bAlpha){
	
	
	// this swaps the red and blue channel of an image
	// turning an bgr image to rgb and vice versa...
	// needed because of windows byte order...
	
	
	if (IMG->isValid()){
	
		unsigned char * pixels = IMG->pixels;
		unsigned char temp;
		int totalPixels = width*height;
		// unoptimized for now...
		
		if (bAlpha == false){
			for (int i = 0; i < totalPixels; i++){
				temp = pixels[i*3 + 2];
				pixels[i*3 + 2] = pixels[i*3];
				pixels[i*3] = temp;
				
			}
			
		} else {
			for (int i = 0; i < totalPixels; i++){
				temp = pixels[i*4];
				pixels[i*4] = pixels[i*4 + 2];
				pixels[i*4 + 2] = temp;
				
			}			
		}
	}
}
		
//------------------------------------
void ofImage::setUseTexture(bool bUse){
	bUseTexture = bUse;
}
		
		
//------------------------------------
void ofImage::draw(float _x, float _y, float _w, float _h){
	if (bUseTexture){
		tex.draw(_x, _y, _w, _h);
	}
}

//------------------------------------
void ofImage::draw(float x, float y){
	draw(x,y,width,height);
}
