/****************************************************************************************
 *
 * Author:		Barry Theobald, Iain Matthews
 * Data:			30/12/2002
 * Function:	Use windows API calls to set up an offscreen bitmap for image warping.
 *
 * Revision:	$Id: gltriwarp.c,v 1.0 2003-12-08 11:32:56+00 barry-john_theobald Exp barry-john_theobald $
 * Revision:	$Id: gltriwarp.c,v 1.0 2005-09-26 15:34:00+00 andrew ian hanna Exp andrew ian hanna $
 * Revision:	$Id: gltriwarp.c,v 1.0 2006-02-07 16:08:00+00 andrew (god) courtenay Exp andrew (god) courtenay $

****************************************************************************************/


#include <windows.h>
#include <stdio.h>
#include <math.h>
#include "GL/gl.h"

#include "mex.h"



// *************************************************************************************
// Store windows and offscreen bitmap information
typedef struct _WGLINFO {
	HDC hDC;
	HGLRC hGLRC;
	HPALETTE hPalette;
	HBITMAP hBitmap;
	HBITMAP	hOldBitmap;

	HINSTANCE hCInst;
	WNDCLASS wndClass;
	HWND hWnd;

} WGLINFO; 


GLuint texName;


// Define globals **********************************************************************
#define BMP_WIDTH	1024
#define BMP_HEIGHT	1024

static WGLINFO wglInfo;
static int initialised = 0;

char *className = "gltriwarp";
char *windowName = "gltriwarp";




// Function declarations ****************************************************************
LRESULT APIENTRY WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void setupDIB(void);
void Initalways();
void setupPixelFormat(void);
void Initialise(int nrhs, const mxArray *prhs[]);
void KillGLWindow(void);
void InitGL(void);
void InitTexture(void);
GLvoid getMappedImage(int opwidth, int opheight);
GLubyte *GetInputImage(const mxArray *img, int *width, int *height);
void ParseVert(float *vert, int nvert, int *opwidth, int *opheight);
float *ScaleTexVert(float *vert, int Nvert, int width, int height);

// **************************************************************************************
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
	int k;
	int width, height, opwidth, opheight, Nvert;
	int dims[3];
	float *ivert, *overt, *tvert;
	GLubyte *pix;
	mxArray *oimg;


	if(nrhs < 3)
		mexErrMsgTxt("Must give the input image and the vertices!");

	// Setup the bitmap on first call *****************************************************
    if(!initialised)
		Initialise(nrhs, prhs);

    Initalways();
        

	// Get the texture image **************************************************************
	pix = GetInputImage(prhs[0], &width, &height);

	// Check the image size < bitmap
	if(width > BMP_WIDTH || height > BMP_HEIGHT)
		mexErrMsgTxt("\nImage is larger than the rendering window\n");

  // Copy image into texture memory
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pix);



	// Get input vertices *****************************************************************
	if(!mxIsNumeric(prhs[1]) || mxIsEmpty(prhs[1]))
		mexErrMsgTxt("Input vertices must not be empty\n");
	else
		ivert = (float*)mxGetData(prhs[1]);

	// The number of vertices
	Nvert = mxGetNumberOfElements(prhs[1]) / 2;



	// Get output vertices *****************************************************************
	if(!mxIsNumeric(prhs[2]) || mxIsEmpty(prhs[2]))
		mexErrMsgTxt("Output vertices must not be empty\n");
	else
		overt = (float*)mxGetData(prhs[2]);

	// The number of vertices
	if((mxGetNumberOfElements(prhs[2]) / 2) != Nvert)
		mexErrMsgTxt("Number of input and output vertices must match");



	// Get the range of output verticies and set the output image size ********************
	ParseVert(overt, Nvert, &opwidth, &opheight);

	// Scale texture coordinates into the range 0->1
 	tvert = ScaleTexVert(ivert, Nvert, BMP_WIDTH, BMP_HEIGHT);


	// Now draw the texture ***************************************************************
	glEnable(GL_TEXTURE_2D);
	glClear(GL_COLOR_BUFFER_BIT);
	glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    
    glBindTexture(GL_TEXTURE_2D, texName);
	glBegin(GL_TRIANGLES);
	for(k=0; k < Nvert * 2; k+=2) {
		glTexCoord2fv(tvert + k);
		glVertex2fv(overt + k);
	}
	glEnd();
	glFinish();

	glDisable(GL_TEXTURE_2D);


	// Texture coordinates are no longer needed
	mxFree(tvert);

	// Setup the return image and return **************************************************
	dims[0] = opwidth;
	dims[1] = opheight;
	dims[2] = 3;
	if ((oimg = mxCreateNumericArray(3, dims, mxUINT8_CLASS, mxREAL)) == NULL)
		mexErrMsgTxt("Unable to allocate output matrix\n");

	// Read pixels from framebuffer to output matrix
	glReadPixels(0, 0, opwidth, opheight, GL_RGB, GL_UNSIGNED_BYTE, (GLubyte*)mxGetData(oimg));
	glFinish();

	// Output matrix
	plhs[0] = oimg;
    wglDeleteContext(wglInfo.hGLRC);
   // DestroyWindow(wglInfo.hWnd);
    }




//****************************************************************************************
//****************************************************************************************
//********************************* Windows Stuff   **************************************
//****************************************************************************************
//****************************************************************************************

void Initalways()
{
	// Create the OpenGL context and make it current
	if (!(wglInfo.hGLRC = wglCreateContext(wglInfo.hDC)))				// Are We Able To Get A Rendering Context?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}

	if(!wglMakeCurrent(wglInfo.hDC, wglInfo.hGLRC))					// Try To Activate The Rendering Context
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
	//	return FALSE;								// Return FALSE
	}	


	// Initialise OpenGL state variables and OpenGL texture mapping
	InitGL();
	InitTexture();
}
// Initialise a window (but do not display), and use properties to setup an DIB
void Initialise(int nrhs, const mxArray *prhs[])
{
	// Define and register a window class
	wglInfo.wndClass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wglInfo.wndClass.lpfnWndProc = WndProc;
	wglInfo.wndClass.cbClsExtra = 0;
	wglInfo.wndClass.cbWndExtra = 0;
	wglInfo.wndClass.hInstance = wglInfo.hCInst;
	wglInfo.wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wglInfo.wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wglInfo.wndClass.hbrBackground = GetStockObject(WHITE_BRUSH);
	wglInfo.wndClass.lpszMenuName = NULL;
	wglInfo.wndClass.lpszClassName = className;
	
	
	if (!RegisterClass(&wglInfo.wndClass))									// Attempt To Register The Window Class
	{
		MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;											// Return FALSE
	}


	//
	//	NOTE: --
	//	Create a window of the previously defined class, but do not display!
	//	The window properties are used to setup the bitmap

	//
	if (!(	wglInfo.hWnd = CreateWindow(
		className,							// Window class's name 
		windowName,							// Title bar text 
		WS_OVERLAPPEDWINDOW |		// The window's style 
		WS_CLIPCHILDREN |
		WS_CLIPSIBLINGS,
		0, 0,										// Position 
		BMP_WIDTH, BMP_HEIGHT,	// Size 
		NULL,										// Parent window's handle 
		NULL,										// Menu handle 
		wglInfo.hCInst,					// Instance handle 
		NULL)))									// No additional data 
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}

if (!(wglInfo.hDC = GetDC(wglInfo.hWnd)))							// Did We Get A Device Context?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}
if (!(wglInfo.hDC = CreateCompatibleDC(wglInfo.hDC)))							// Did We Get A Device Context?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Create Compatible DC.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}

	


	// Set up the bitmap
	setupDIB();

	// Define the desired pixel format
	setupPixelFormat();





	// Lock the mex file for return calls
	mexMakeMemoryPersistent(&wglInfo);
	initialised = 1;
	mexLock();

}



// This never actually gets used as MEX will clear everything when Matlab quits
LRESULT APIENTRY WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message) {
		case WM_DESTROY:
			PostQuitMessage(0);
			return 0;
		default:
			break;
	}

	// Deal with any unprocessed messages
	return DefWindowProc(hWnd, message, wParam, lParam);
}





//****************************************************************************************

// Set up a device independent bitmap in memory for offscreen rendering
void setupDIB(void)
{
	BITMAPINFO *bmInfo;
	BITMAPINFOHEADER *bmHeader;
	UINT usage;
	VOID *base;
	int bmiSize;
	int bitsPerPixel;

	bmiSize = sizeof(*bmInfo);
	bitsPerPixel = GetDeviceCaps(wglInfo.hDC, BITSPIXEL);

	bmInfo = (BITMAPINFO *) calloc(1, bmiSize);
	bmHeader = &bmInfo->bmiHeader;

	bmHeader->biSize = sizeof(*bmHeader);
	bmHeader->biWidth = BMP_WIDTH;
	bmHeader->biHeight = BMP_HEIGHT;
	bmHeader->biPlanes = 1;
	bmHeader->biBitCount = bitsPerPixel;
	bmHeader->biXPelsPerMeter = 0;
	bmHeader->biYPelsPerMeter = 0;
	bmHeader->biClrUsed = 0;
	bmHeader->biClrImportant = 0;

	bmHeader->biCompression = BI_RGB;
	bmHeader->biSizeImage = 0;
	usage = DIB_RGB_COLORS;

	wglInfo.hBitmap = CreateDIBSection(wglInfo.hDC, bmInfo, usage, &base, NULL, 0);
	if(wglInfo.hBitmap == NULL)
		mexErrMsgTxt("Failed to create DIBSection.");

	wglInfo.hOldBitmap = SelectObject(wglInfo.hDC, wglInfo.hBitmap);

	free(bmInfo);	
}





//****************************************************************************************

// Set the desire pixel format and ensure we can select it
void setupPixelFormat(void)
{
	PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),	// size of this pfd
		1,															// version num 
		PFD_SUPPORT_OPENGL,							// support OpenGL 
		0,															// pixel type 
		0,															// 8-bit color depth 
		0, 0, 0, 0, 0, 0,								// color bits (ignored) 
		0,															// no alpha buffer 
		0,															// alpha bits (ignored) 
		0,															// no accumulation buffer 
		0, 0, 0, 0,											// accum bits (ignored) 
		16,															// depth buffer 
		0,															// no stencil buffer 
		0,															// no auxiliary buffers 
		PFD_MAIN_PLANE,									// main layer 
		0,															// reserved 
		0, 0, 0,												// no layer, visible, damage masks 
	};
    
	int SelectedPixelFormat;
	BOOL retVal;

	pfd.cColorBits = GetDeviceCaps(wglInfo.hDC, BITSPIXEL);

	pfd.iPixelType = PFD_TYPE_RGBA;

	pfd.dwFlags |= PFD_DRAW_TO_BITMAP;

	
		if (!(SelectedPixelFormat = ChoosePixelFormat(wglInfo.hDC, &pfd)))	// Did Windows Find A Matching Pixel Format?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}

		retVal = SetPixelFormat(wglInfo.hDC, SelectedPixelFormat, &pfd);
	if(!retVal)		// Are We Able To Set The Pixel Format?
	{
		KillGLWindow();								// Reset The Display
		MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
		//return FALSE;								// Return FALSE
	}
	

}



void KillGLWindow(void)
{
}




//****************************************************************************************
//****************************************************************************************
//********************************   OpenGL Stuff   **************************************
//****************************************************************************************
//****************************************************************************************

void InitGL(void)
{
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glShadeModel(GL_FLAT);

	glViewport(0, 0, BMP_WIDTH, BMP_HEIGHT);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0.0, BMP_WIDTH, 0.0, BMP_HEIGHT, -1.0, 1.0);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

	glPixelStorei(GL_PACK_ALIGNMENT, 1);

}


void InitTexture(void)
{

	glGenTextures(1, &texName);
	glBindTexture(GL_TEXTURE_2D, texName);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, 3, BMP_WIDTH, BMP_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}


//****************************************************************************************
//****************************************************************************************
//********************************  Iain's Stuff   ***************************************
//****************************************************************************************
//****************************************************************************************


GLubyte *GetInputImage(const mxArray *img, int *width, int *height)
{
	int	ndim, nx, ny;
	const int *dim;

 if(!mxIsNumeric(img) || mxIsEmpty(img) || !mxIsUint8(img))
		mexErrMsgTxt("Input image must be uint8 2D or 3D numeric matrix");
		
	ndim = mxGetNumberOfDimensions(img);
	if(ndim != 3)
		mexErrMsgTxt("Input image must be (nx * ny * 3) 24-bit RGB");

	dim = mxGetDimensions(img);
	*width = nx = dim[0];
	*height = ny = dim[1];

	if(dim[2] > 3)
		mexWarnMsgTxt("Only using first 3 values of 3rd dimension");
	else if(dim[2] < 3)
		mexErrMsgTxt("Third dimension must have size = 3");

	return((GLubyte*)mxGetData(img));
}


float *ScaleTexVert(float *vert, int Nvert, int width, int height)
{
	int k;
	float *tvert, xval, yval;

	if((tvert = (float*)mxCalloc(2 * Nvert, sizeof(float))) == NULL)
		mexErrMsgTxt("Unable to allocate texture vertices\n");

	for(k = 0; k < Nvert; k++) {
		xval = vert[k*2];
		yval = vert[(k*2)+1];

		tvert[k*2] = xval / (float)width;
		tvert[(k*2)+1] = yval / (float)height;
	}
	return(tvert);
}



void ParseVert(float *vert, int nvert, int *opwidth, int *opheight)
{
	float maxx = -10000.0, maxy = -10000.0;
	float x, y;
	int k;
	
	for(k = 0; k < nvert; k++) {
		x = vert[k*2];
		y = vert[(k*2)+1];
		
		if (maxx < x)
			maxx = x;
		if (maxy < y)
			maxy = y;
	}
	
	// Return the number of pixels in the output image
	*opwidth = (int)floor(maxx + 0.5);
	*opheight = (int)floor(maxy + 0.5);
}


