// ==========================================================
// fipWinImage class implementation
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
//
// This file is part of FreeImage 3
//
// COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
// THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE
// OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED
// CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT
// THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY
// SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL
// PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER
// THIS DISCLAIMER.
//
// Use at your own risk!
// ==========================================================

#include "FreeImagePlus.h"

#ifdef _WIN32

// marker used for clipboard copy / paste

static inline void 
SET_FREEIMAGE_MARKER(BITMAPINFOHEADER *bmih, FIBITMAP *dib) {
	// Windows constants goes from 0L to 5L
	// Add 0xFF to avoid conflicts
	bmih->biCompression = 0xFF + FreeImage_GetImageType(dib);
}

static inline FREE_IMAGE_TYPE 
GET_FREEIMAGE_MARKER(BITMAPINFOHEADER *bmih) {
	return (FREE_IMAGE_TYPE)(bmih->biCompression - 0xFF);
}

///////////////////////////////////////////////////////////////////
// Construction / Destruction

fipWinImage::fipWinImage(FREE_IMAGE_TYPE image_type, unsigned width, unsigned height, unsigned bpp) : fipImage(image_type, width, height, bpp) {
	_display_dib = NULL;
	_bDeleteMe = FALSE;
	// default tone mapping operator
	_tmo = FITMO_DRAGO03;
	_tmo_param_1 = 0;
	_tmo_param_2 = 0;
	_tmo_param_3 = 1;
	_tmo_param_4 = 0;
}

fipWinImage::~fipWinImage() { 
	if(_bDeleteMe) {
		FreeImage_Unload(_display_dib);
	}
}

void fipWinImage::clear() {
	// delete _display_dib
	if(_bDeleteMe) {
		FreeImage_Unload(_display_dib);
	}
	_display_dib = NULL;
	_bDeleteMe = FALSE;
	// delete base class data
	fipImage::clear();
}

BOOL fipWinImage::isValid() const {
	return fipImage::isValid();
}

///////////////////////////////////////////////////////////////////
// Copying

fipWinImage& fipWinImage::operator=(const fipImage& Image) {
	// delete _display_dib
	if(_bDeleteMe) {
		FreeImage_Unload(_display_dib);
	}
	_display_dib = NULL;
	_bDeleteMe = FALSE;
	// clone the base class
	fipImage::operator=(Image);

	return *this;
}

fipWinImage& fipWinImage::operator=(const fipWinImage& Image) {
	if(this != &Image) {
		// delete _display_dib
		if(_bDeleteMe) {
			FreeImage_Unload(_display_dib);
		}
		_display_dib = NULL;
		_bDeleteMe = FALSE;
		// copy tmo data
		_tmo = Image._tmo;
		_tmo_param_1 = Image._tmo_param_1;
		_tmo_param_2 = Image._tmo_param_2;
		_tmo_param_3 = Image._tmo_param_3;
		_tmo_param_4 = Image._tmo_param_4;

		// clone the base class
		fipImage::operator=(Image);
	}
	return *this;
}

HANDLE fipWinImage::copyToHandle() const {
	HANDLE hMem = NULL;

	if(_dib) {

		// Get equivalent DIB size
		long dib_size = sizeof(BITMAPINFOHEADER);
		dib_size += FreeImage_GetColorsUsed(_dib) * sizeof(RGBQUAD);
		dib_size += FreeImage_GetPitch(_dib) * FreeImage_GetHeight(_dib);

		// Allocate a DIB
		hMem = GlobalAlloc(GHND, dib_size);
		BYTE *dib = (BYTE*)GlobalLock(hMem);

		memset(dib, 0, dib_size);

		BYTE *p_dib = (BYTE*)dib;

		// Copy the BITMAPINFOHEADER

		BITMAPINFOHEADER *bih = FreeImage_GetInfoHeader(_dib);
		memcpy(p_dib, bih, sizeof(BITMAPINFOHEADER));
		if(FreeImage_GetImageType(_dib) != FIT_BITMAP) {
			// this hack is used to store the bitmap type in the biCompression member of the BITMAPINFOHEADER
			SET_FREEIMAGE_MARKER((BITMAPINFOHEADER*)p_dib, _dib);
		}
		p_dib += sizeof(BITMAPINFOHEADER);

		// Copy the palette

		RGBQUAD *pal = FreeImage_GetPalette(_dib);
		memcpy(p_dib, pal, FreeImage_GetColorsUsed(_dib) * sizeof(RGBQUAD));
		p_dib += FreeImage_GetColorsUsed(_dib) * sizeof(RGBQUAD);

		// Copy the bitmap

		BYTE *bits = FreeImage_GetBits(_dib);
		memcpy(p_dib, bits, FreeImage_GetPitch(_dib) * FreeImage_GetHeight(_dib));

		GlobalUnlock(hMem);
	}

	return hMem;
}

BOOL fipWinImage::copyFromHandle(HANDLE hMem) {
	BYTE *lpVoid = NULL;
	BITMAPINFOHEADER *pHead = NULL;
	RGBQUAD *pPalette = NULL;
	BYTE *bits = NULL;
	DWORD bitfields[3] = {0, 0, 0};

	// Get a pointer to the bitmap
	lpVoid = (BYTE *)GlobalLock(hMem);

	// Get a pointer to the bitmap header
	pHead = (BITMAPINFOHEADER *)lpVoid;

	// Get a pointer to the palette
	if(pHead->biBitCount < 16)
		pPalette = (RGBQUAD *)(((BYTE *)pHead) + sizeof(BITMAPINFOHEADER));

	// Get a pointer to the pixels
	bits = ((BYTE*)pHead + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * pHead->biClrUsed);

	if(pHead->biCompression == BI_BITFIELDS) {
		// Take into account the color masks that specify the red, green, and blue components (16- and 32-bit)
		unsigned mask_size = 3 * sizeof(DWORD);
		memcpy(&bitfields[0], bits, mask_size);
		bits += mask_size;
	} 

	if(lpVoid) {

		// Allocate a new FIBITMAP

		FREE_IMAGE_TYPE image_type = FIT_BITMAP;
		// Use a hack to decide if the clipboard contains non standard bitmaps ...
		switch(GET_FREEIMAGE_MARKER(pHead)) {
			case FIT_UINT16:
			case FIT_INT16:
			case FIT_UINT32:
			case FIT_INT32:
			case FIT_FLOAT:
			case FIT_DOUBLE:
			case FIT_COMPLEX:
			case FIT_RGB16:
			case FIT_RGBA16:
			case FIT_RGBF:
			case FIT_RGBAF:
				image_type = GET_FREEIMAGE_MARKER(pHead);
				break;
		}
		if(!setSize(image_type, (WORD)pHead->biWidth, (WORD)pHead->biHeight, pHead->biBitCount, bitfields[2], bitfields[1], bitfields[0])) {
			GlobalUnlock(lpVoid);
			return FALSE;
		}

		// Copy the bitmap header
		memcpy(FreeImage_GetInfoHeader(_dib), pHead, sizeof(BITMAPINFOHEADER));


		// Copy the palette
		memcpy(FreeImage_GetPalette(_dib), pPalette, pHead->biClrUsed * sizeof(RGBQUAD));

		// Copy the bitmap
		memcpy(FreeImage_GetBits(_dib), bits, FreeImage_GetPitch(_dib) * FreeImage_GetHeight(_dib));

		GlobalUnlock(lpVoid);

		return TRUE;
	}

	return FALSE;
}

BOOL fipWinImage::copyFromBitmap(HBITMAP hbmp) {
	if(hbmp) { 
		int Success;
        BITMAP bm;
		// Get informations about the bitmap
        GetObject(hbmp, sizeof(BITMAP), (LPSTR) &bm);
		// Create the image
        setSize(FIT_BITMAP, (WORD)bm.bmWidth, (WORD)bm.bmHeight, (WORD)bm.bmBitsPixel);

		// The GetDIBits function clears the biClrUsed and biClrImportant BITMAPINFO members (dont't know why) 
		// So we save these infos below. This is needed for palettized images only. 
		int nColors = FreeImage_GetColorsUsed(_dib);

		// Create a device context for the bitmap
        HDC dc = GetDC(NULL);
		// Copy the pixels
		Success = GetDIBits(dc,								// handle to DC
								hbmp,						// handle to bitmap
								0,							// first scan line to set
								FreeImage_GetHeight(_dib),	// number of scan lines to copy
								FreeImage_GetBits(_dib),	// array for bitmap bits
								FreeImage_GetInfo(_dib),	// bitmap data buffer
								DIB_RGB_COLORS				// RGB 
								);
		if(Success == 0) {
			FreeImage_OutputMessageProc(FIF_UNKNOWN, "Error : GetDIBits failed");
			ReleaseDC(NULL, dc);
			return FALSE;
        }
        ReleaseDC(NULL, dc);

		// restore BITMAPINFO members
		
		FreeImage_GetInfoHeader(_dib)->biClrUsed = nColors;
		FreeImage_GetInfoHeader(_dib)->biClrImportant = nColors;

		return TRUE;
    }

	return FALSE;
}

BOOL fipWinImage::copyToClipboard(HWND hWndNewOwner) const {
	HANDLE hDIB = copyToHandle();

	if(OpenClipboard(hWndNewOwner)) {
		if(EmptyClipboard()) {
			if(SetClipboardData(CF_DIB, hDIB) == NULL) {
				MessageBox(hWndNewOwner, "Unable to set Clipboard data", "FreeImage", MB_ICONERROR);
				CloseClipboard();
				return FALSE;
			}
		}
	}
	CloseClipboard();

	return TRUE;
}

BOOL fipWinImage::pasteFromClipboard() {
	if(!IsClipboardFormatAvailable(CF_DIB))
		return FALSE;

	if(OpenClipboard(NULL)) {
		HANDLE hDIB = GetClipboardData(CF_DIB);
		copyFromHandle(hDIB);
		CloseClipboard();
		return TRUE;
	}
	CloseClipboard();

	return FALSE;
}

///////////////////////////////////////////////////////////////////
// Screen capture

BOOL fipWinImage::captureWindow(HWND hWndApplicationWindow, HWND hWndSelectedWindow) {
	int xScreen, yScreen, xshift, yshift;
	RECT r;

	// Get window size
	GetWindowRect(hWndSelectedWindow, &r);

	// Check if the window is out of the screen or maximixed
	xshift = 0;
	yshift = 0;
	xScreen = GetSystemMetrics(SM_CXSCREEN);
	yScreen = GetSystemMetrics(SM_CYSCREEN);
	if(r.right > xScreen)
		   r.right = xScreen;
	if(r.bottom > yScreen)
		   r.bottom = yScreen;
	if(r.left < 0) {
		   xshift = -r.left;
		   r.left = 0;
	}
	if(r.top < 0){
		   yshift = -r.top;
		   r.top = 0;
	}
	
	int width  = r.right  - r.left;
	int height = r.bottom - r.top;

	if(width <= 0 || height <= 0)
		return FALSE;

	// Hide the application window. 
	ShowWindow(hWndApplicationWindow, SW_HIDE); 
	// Bring the window at the top most level
	SetWindowPos(hWndSelectedWindow, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
	// Give enough time to refresh the window
	Sleep(500);

	// Prepare the DCs
	HDC dstDC = GetDC(NULL);
    HDC srcDC = GetWindowDC(hWndSelectedWindow); // full window (GetDC(hWndSelectedWindow) = clientarea)
	HDC memDC = CreateCompatibleDC(dstDC);
	
	// Copy the screen to the bitmap
	HBITMAP bm = CreateCompatibleBitmap(dstDC, width, height);
	HBITMAP oldbm = (HBITMAP)SelectObject(memDC, bm);
	BitBlt(memDC, 0, 0, width, height, srcDC, xshift, yshift, SRCCOPY);

	// Redraw the application window. 
	ShowWindow(hWndApplicationWindow, SW_SHOW); 

	// Restore the position
	SetWindowPos(hWndSelectedWindow, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
	SetWindowPos(hWndApplicationWindow, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
	
	// Convert the HBITMAP to a FIBITMAP
	copyFromBitmap(bm);

	// Free objects
	DeleteObject(SelectObject(memDC, oldbm));
	DeleteDC(memDC);

	// Convert 32-bit images to 24-bit
	if(getBitsPerPixel() == 32) {
		convertTo24Bits();
	}

	return TRUE;
}


///////////////////////////////////////////////////////////////////
// Painting operations

void fipWinImage::drawEx(HDC hDC, RECT& rcDest, BOOL useFileBkg, RGBQUAD *appBkColor, FIBITMAP *bg) const {
	// Convert to a standard bitmap if needed
	if(_bHasChanged) {
		if(_bDeleteMe) {
			FreeImage_Unload(_display_dib);
			_display_dib = NULL;
			_bDeleteMe = FALSE;
		}

		FREE_IMAGE_TYPE image_type = getImageType();
		if(image_type == FIT_BITMAP) {
			BOOL bHasBackground = FreeImage_HasBackgroundColor(_dib);
			BOOL bIsTransparent = FreeImage_IsTransparent(_dib);

			if(!bIsTransparent && (!bHasBackground || !useFileBkg)) {
				// Copy pointer
				_display_dib = _dib;
			}
			else {
				// Create the transparent / alpha blended image
				_display_dib = FreeImage_Composite(_dib, useFileBkg, appBkColor, bg);
				if(_display_dib) {
					// Remember to delete _display_dib
					_bDeleteMe = TRUE;
				} else {
					// Something failed: copy pointers
					_display_dib = _dib;
				}
			}
		} else {
			// Convert to a standard dib for display

			if(image_type == FIT_COMPLEX) {
				// Convert to type FIT_DOUBLE
				FIBITMAP *dib_double = FreeImage_GetComplexChannel(_dib, FICC_MAG);
				// Convert to a standard bitmap (linear scaling)
				_display_dib = FreeImage_ConvertToStandardType(dib_double, TRUE);
				// Free image of type FIT_DOUBLE
				FreeImage_Unload(dib_double);
			} else if((image_type == FIT_RGBF) || (image_type == FIT_RGBAF) || (image_type == FIT_RGB16)) {
				// Apply a tone mapping algorithm and convert to 24-bit 
				switch(_tmo) {
					case FITMO_REINHARD05:
						_display_dib = FreeImage_TmoReinhard05Ex(_dib, _tmo_param_1, _tmo_param_2, _tmo_param_3, _tmo_param_4);
						break;
					default:
						_display_dib = FreeImage_ToneMapping(_dib, _tmo, _tmo_param_1, _tmo_param_2);
						break;
				}
			} else if(image_type == FIT_RGBA16) {
				// Convert to 32-bit
				FIBITMAP *dib32 = FreeImage_ConvertTo32Bits(_dib);
				if(dib32) {
					// Create the transparent / alpha blended image
					_display_dib = FreeImage_Composite(dib32, useFileBkg, appBkColor, bg);
					FreeImage_Unload(dib32);
				}
			} else {
				// Other cases: convert to a standard bitmap (linear scaling)
				_display_dib = FreeImage_ConvertToStandardType(_dib, TRUE);
			}
			// Remember to delete _display_dib
			_bDeleteMe = TRUE;
		}

		_bHasChanged = FALSE;
	}

	// Draw the dib
	SetStretchBltMode(hDC, COLORONCOLOR);	
	StretchDIBits(hDC, rcDest.left, rcDest.top, 
		rcDest.right-rcDest.left, rcDest.bottom-rcDest.top, 
		0, 0, FreeImage_GetWidth(_display_dib), FreeImage_GetHeight(_display_dib),
		FreeImage_GetBits(_display_dib), FreeImage_GetInfo(_display_dib), DIB_RGB_COLORS, SRCCOPY);

}

void fipWinImage::setToneMappingOperator(FREE_IMAGE_TMO tmo, double first_param, double second_param, double third_param, double fourth_param) {
	// avoid costly operations if possible ...
	if((_tmo != tmo) || (_tmo_param_1 != first_param) || (_tmo_param_2 != second_param) || (_tmo_param_3 != third_param) || (_tmo_param_4 != fourth_param)) {
		_tmo = tmo;
		_tmo_param_1 = first_param;
		_tmo_param_2 = second_param;
		_tmo_param_3 = third_param;
		_tmo_param_4 = fourth_param;

		FREE_IMAGE_TYPE image_type = getImageType();
		switch(image_type) {
			case FIT_RGBF:
			case FIT_RGBAF:
			case FIT_RGB16:
			case FIT_RGBA16:
				_bHasChanged = TRUE;
				break;
			default:
				break;
		}
	}
}

void fipWinImage::getToneMappingOperator(FREE_IMAGE_TMO *tmo, double *first_param, double *second_param, double *third_param, double *fourth_param) const {
	*tmo = _tmo;
	*first_param = _tmo_param_1;
	*second_param = _tmo_param_2;
	*third_param = _tmo_param_3;
	*fourth_param = _tmo_param_4;
}


#endif // _WIN32
