// ==========================================================
// Bitmap conversion routines
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Tanner Helland (tannerhelland@users.sf.net)
//
// 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 "FreeImage.h"
#include "Utilities.h"

// ----------------------------------------------------------

/** Convert a greyscale image of type Tsrc to type Tdst.
	Conversion is done using standard C language casting convention.
*/
template<class Tdst, class Tsrc>
class CONVERT_TYPE
{
public:
	FIBITMAP* convert(FIBITMAP *src, FREE_IMAGE_TYPE dst_type);
};

template<class Tdst, class Tsrc> FIBITMAP* 
CONVERT_TYPE<Tdst, Tsrc>::convert(FIBITMAP *src, FREE_IMAGE_TYPE dst_type) {

	FIBITMAP *dst = NULL;

	unsigned width	= FreeImage_GetWidth(src);
	unsigned height = FreeImage_GetHeight(src);
	unsigned bpp	= FreeImage_GetBPP(src);

	// allocate dst image

	dst = FreeImage_AllocateT(dst_type, width, height, bpp, 
			FreeImage_GetRedMask(src), FreeImage_GetGreenMask(src), FreeImage_GetBlueMask(src));
	if(!dst) return NULL;

	// convert from src_type to dst_type
	
	for(unsigned y = 0; y < height; y++) {
		const Tsrc *src_bits = reinterpret_cast<Tsrc*>(FreeImage_GetScanLine(src, y));
		Tdst *dst_bits = reinterpret_cast<Tdst*>(FreeImage_GetScanLine(dst, y));

		for(unsigned x = 0; x < width; x++) {
			*dst_bits++ = static_cast<Tdst>(*src_bits++);
		}
	}

	return dst;
}


/** Convert a greyscale image of type Tsrc to a 8-bit grayscale dib.
	Conversion is done using either a linear scaling from [min, max] to [0, 255]
	or a rounding from src_pixel to (BYTE) MIN(255, MAX(0, q)) where int q = int(src_pixel + 0.5); 
*/
template<class Tsrc>
class CONVERT_TO_BYTE
{
public:
	FIBITMAP* convert(FIBITMAP *src, BOOL scale_linear);
};

template<class Tsrc> FIBITMAP* 
CONVERT_TO_BYTE<Tsrc>::convert(FIBITMAP *src, BOOL scale_linear) {
	FIBITMAP *dst = NULL;
	unsigned x, y;

	unsigned width	= FreeImage_GetWidth(src);
	unsigned height = FreeImage_GetHeight(src);

	// allocate a 8-bit dib

	dst = FreeImage_AllocateT(FIT_BITMAP, width, height, 8, 0, 0, 0);
	if(!dst) return NULL;

	// build a greyscale palette
	RGBQUAD *pal = FreeImage_GetPalette(dst);
	for(int i = 0; i < 256; i++) {
		pal[i].rgbRed = (BYTE)i;
		pal[i].rgbGreen = (BYTE)i;
		pal[i].rgbBlue = (BYTE)i;
	}

	// convert the src image to dst
	// (FIBITMAP are stored upside down)
	if(scale_linear) {
		Tsrc max, min;
		double scale;

		// find the min and max value of the image
		Tsrc l_min, l_max;
		min = 255, max = 0;
		for(y = 0; y < height; y++) {
			Tsrc *bits = reinterpret_cast<Tsrc*>(FreeImage_GetScanLine(src, y));
			MAXMIN(bits, width, l_max, l_min);
			if(l_max > max) max = l_max;
			if(l_min < min) min = l_min;
		}
		if(max == min) {
			max = 255; min = 0;
		}

		// compute the scaling factor
		scale = 255 / (double)(max - min);

		// scale to 8-bit
		for(y = 0; y < height; y++) {
			Tsrc *src_bits = reinterpret_cast<Tsrc*>(FreeImage_GetScanLine(src, y));
			BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
			for(x = 0; x < width; x++) {
				dst_bits[x] = (BYTE)( scale * (src_bits[x] - min) + 0.5);
			}
		}
	} else {
		for(y = 0; y < height; y++) {
			Tsrc *src_bits = reinterpret_cast<Tsrc*>(FreeImage_GetScanLine(src, y));
			BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
			for(x = 0; x < width; x++) {
				// rounding
				int q = int(src_bits[x] + 0.5);
				dst_bits[x] = (BYTE) MIN(255, MAX(0, q));
			}
		}
	}

	return dst;
}

/** Convert a greyscale image of type Tsrc to a FICOMPLEX dib.
*/
template<class Tsrc>
class CONVERT_TO_COMPLEX
{
public:
	FIBITMAP* convert(FIBITMAP *src);
};

template<class Tsrc> FIBITMAP* 
CONVERT_TO_COMPLEX<Tsrc>::convert(FIBITMAP *src) {
	FIBITMAP *dst = NULL;

	unsigned width	= FreeImage_GetWidth(src);
	unsigned height = FreeImage_GetHeight(src);

	// allocate dst image

	dst = FreeImage_AllocateT(FIT_COMPLEX, width, height);
	if(!dst) return NULL;

	// convert from src_type to FIT_COMPLEX
	
	for(unsigned y = 0; y < height; y++) {
		const Tsrc *src_bits = reinterpret_cast<Tsrc*>(FreeImage_GetScanLine(src, y));
		FICOMPLEX *dst_bits = (FICOMPLEX *)FreeImage_GetScanLine(dst, y);

		for(unsigned x = 0; x < width; x++) {
			dst_bits[x].r = (double)src_bits[x];
			dst_bits[x].i = 0;
		}
	}

	return dst;
}

// ----------------------------------------------------------

// Convert from type BYTE to type X
CONVERT_TYPE<unsigned short, BYTE>	convertByteToUShort;
CONVERT_TYPE<short, BYTE>			convertByteToShort;
CONVERT_TYPE<DWORD, BYTE>			convertByteToULong;
CONVERT_TYPE<LONG, BYTE>			convertByteToLong;
CONVERT_TYPE<float, BYTE>			convertByteToFloat;
CONVERT_TYPE<double, BYTE>			convertByteToDouble;

// Convert from type X to type BYTE
CONVERT_TO_BYTE<unsigned short>	convertUShortToByte;
CONVERT_TO_BYTE<short>			convertShortToByte;
CONVERT_TO_BYTE<DWORD>			convertULongToByte;
CONVERT_TO_BYTE<LONG>			convertLongToByte;
CONVERT_TO_BYTE<float>			convertFloatToByte;
CONVERT_TO_BYTE<double>			convertDoubleToByte;

// Convert from type X to type float
CONVERT_TYPE<float, unsigned short>	convertUShortToFloat;
CONVERT_TYPE<float, short>			convertShortToFloat;
CONVERT_TYPE<float, DWORD>			convertULongToFloat;
CONVERT_TYPE<float, LONG>			convertLongToFloat;

// Convert from type X to type double
CONVERT_TYPE<double, unsigned short>	convertUShortToDouble;
CONVERT_TYPE<double, short>				convertShortToDouble;
CONVERT_TYPE<double, DWORD>				convertULongToDouble;
CONVERT_TYPE<double, LONG>				convertLongToDouble;
CONVERT_TYPE<double, float>				convertFloatToDouble;

// Convert from type X to type FICOMPLEX
CONVERT_TO_COMPLEX<BYTE>			convertByteToComplex;
CONVERT_TO_COMPLEX<unsigned short>	convertUShortToComplex;
CONVERT_TO_COMPLEX<short>			convertShortToComplex;
CONVERT_TO_COMPLEX<DWORD>			convertULongToComplex;
CONVERT_TO_COMPLEX<LONG>			convertLongToComplex;
CONVERT_TO_COMPLEX<float>			convertFloatToComplex;
CONVERT_TO_COMPLEX<double>			convertDoubleToComplex;

// ----------------------------------------------------------

// ----------------------------------------------------------
//   smart convert X to standard FIBITMAP
// ----------------------------------------------------------

/** Convert image of any type to a standard 8-bit greyscale image.
For standard images, a clone of the input image is returned.
When the scale_linear parameter is TRUE, conversion is done by scaling linearly 
each pixel to an integer value between [0..255]. When it is FALSE, conversion is done 
by rounding each float pixel to an integer between [0..255]. 
For complex images, the magnitude is extracted as a double image, then converted according to the scale parameter. 
@param image Image to convert
@param scale_linear Linear scaling / rounding switch
*/
FIBITMAP* DLL_CALLCONV
FreeImage_ConvertToStandardType(FIBITMAP *src, BOOL scale_linear) {
	FIBITMAP *dst = NULL;

	if(!src) return NULL;

	// convert from src_type to FIT_BITMAP

	const FREE_IMAGE_TYPE src_type = FreeImage_GetImageType(src);

	switch(src_type) {
		case FIT_BITMAP:	// standard image: 1-, 4-, 8-, 16-, 24-, 32-bit
			dst = FreeImage_Clone(src);
			break;
		case FIT_UINT16:	// array of unsigned short: unsigned 16-bit
			dst = convertUShortToByte.convert(src, scale_linear);
			break;
		case FIT_INT16:		// array of short: signed 16-bit
			dst = convertShortToByte.convert(src, scale_linear);
			break;
		case FIT_UINT32:	// array of unsigned long: unsigned 32-bit
			dst = convertULongToByte.convert(src, scale_linear);
			break;
		case FIT_INT32:		// array of long: signed 32-bit
			dst = convertLongToByte.convert(src, scale_linear);
			break;
		case FIT_FLOAT:		// array of float: 32-bit
			dst = convertFloatToByte.convert(src, scale_linear);
			break;
		case FIT_DOUBLE:	// array of double: 64-bit
			dst = convertDoubleToByte.convert(src, scale_linear);
			break;
		case FIT_COMPLEX:	// array of FICOMPLEX: 2 x 64-bit
			{
				// Convert to type FIT_DOUBLE
				FIBITMAP *dib_double = FreeImage_GetComplexChannel(src, FICC_MAG);
				if(dib_double) {
					// Convert to a standard bitmap (linear scaling)
					dst = convertDoubleToByte.convert(dib_double, scale_linear);
					// Free image of type FIT_DOUBLE
					FreeImage_Unload(dib_double);
				}
			}
			break;
		case FIT_RGB16:		// 48-bit RGB image: 3 x 16-bit
			break;
		case FIT_RGBA16:	// 64-bit RGBA image: 4 x 16-bit
			break;
		case FIT_RGBF:		// 96-bit RGB float image: 3 x 32-bit IEEE floating point
			break;
		case FIT_RGBAF:		// 128-bit RGBA float image: 4 x 32-bit IEEE floating point
			break;
	}

	if(NULL == dst) {
		FreeImage_OutputMessageProc(FIF_UNKNOWN, "FREE_IMAGE_TYPE: Unable to convert from type %d to type %d.\n No such conversion exists.", src_type, FIT_BITMAP);
	} else {
		// copy metadata from src to dst
		FreeImage_CloneMetadata(dst, src);
	}
	
	return dst;
}



// ----------------------------------------------------------
//   smart convert X to Y
// ----------------------------------------------------------

FIBITMAP* DLL_CALLCONV
FreeImage_ConvertToType(FIBITMAP *src, FREE_IMAGE_TYPE dst_type, BOOL scale_linear) {
	FIBITMAP *dst = NULL;

	if(!FreeImage_HasPixels(src)) return NULL;

	// convert from src_type to dst_type

	const FREE_IMAGE_TYPE src_type = FreeImage_GetImageType(src);

	if(src_type == dst_type) {
		return FreeImage_Clone(src);
	}

	const unsigned src_bpp = FreeImage_GetBPP(src);

	switch(src_type) {
		case FIT_BITMAP:
			switch(dst_type) {
				case FIT_UINT16:
					dst = FreeImage_ConvertToUINT16(src);
					break;
				case FIT_INT16:
					dst = (src_bpp == 8) ? convertByteToShort.convert(src, dst_type) : NULL;
					break;
				case FIT_UINT32:
					dst = (src_bpp == 8) ? convertByteToULong.convert(src, dst_type) : NULL;
					break;
				case FIT_INT32:
					dst = (src_bpp == 8) ? convertByteToLong.convert(src, dst_type) : NULL;
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					dst = (src_bpp == 8) ? convertByteToDouble.convert(src, dst_type) : NULL;
					break;
				case FIT_COMPLEX:
					dst = (src_bpp == 8) ? convertByteToComplex.convert(src) : NULL;
					break;
				case FIT_RGB16:
					dst = FreeImage_ConvertToRGB16(src);
					break;
				case FIT_RGBA16:
					dst = FreeImage_ConvertToRGBA16(src);
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_UINT16:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					dst = convertUShortToDouble.convert(src, dst_type);
					break;
				case FIT_COMPLEX:
					dst = convertUShortToComplex.convert(src);
					break;
				case FIT_RGB16:
					dst = FreeImage_ConvertToRGB16(src);
					break;
				case FIT_RGBA16:
					dst = FreeImage_ConvertToRGBA16(src);
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_INT16:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_UINT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = convertShortToFloat.convert(src, dst_type);
					break;
				case FIT_DOUBLE:
					dst = convertShortToDouble.convert(src, dst_type);
					break;
				case FIT_COMPLEX:
					dst = convertShortToComplex.convert(src);
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					break;
				case FIT_RGBAF:
					break;
			}
			break;
		case FIT_UINT32:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = convertULongToFloat.convert(src, dst_type);
					break;
				case FIT_DOUBLE:
					dst = convertULongToDouble.convert(src, dst_type);
					break;
				case FIT_COMPLEX:
					dst = convertULongToComplex.convert(src);
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					break;
				case FIT_RGBAF:
					break;
			}
			break;
		case FIT_INT32:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_FLOAT:
					dst = convertLongToFloat.convert(src, dst_type);
					break;
				case FIT_DOUBLE:
					dst = convertLongToDouble.convert(src, dst_type);
					break;
				case FIT_COMPLEX:
					dst = convertLongToComplex.convert(src);
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					break;
				case FIT_RGBAF:
					break;
			}
			break;
		case FIT_FLOAT:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_DOUBLE:
					dst = convertFloatToDouble.convert(src, dst_type);
					break;
				case FIT_COMPLEX:
					dst = convertFloatToComplex.convert(src);
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_DOUBLE:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertToStandardType(src, scale_linear);
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					break;
				case FIT_COMPLEX:
					dst = convertDoubleToComplex.convert(src);
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					break;
				case FIT_RGBAF:
					break;
			}
			break;
		case FIT_COMPLEX:
			switch(dst_type) {
				case FIT_BITMAP:
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					break;
				case FIT_DOUBLE:
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					break;
				case FIT_RGBAF:
					break;
			}
			break;
		case FIT_RGB16:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertTo24Bits(src);
					break;
				case FIT_UINT16:
					dst = FreeImage_ConvertToUINT16(src);
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					break;
				case FIT_COMPLEX:
					break;
				case FIT_RGBA16:
					dst = FreeImage_ConvertToRGBA16(src);
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_RGBA16:
			switch(dst_type) {
				case FIT_BITMAP:
					dst = FreeImage_ConvertTo32Bits(src);
					break;
				case FIT_UINT16:
					dst = FreeImage_ConvertToUINT16(src);
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					break;
				case FIT_COMPLEX:
					break;
				case FIT_RGB16:
					dst = FreeImage_ConvertToRGB16(src);
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_RGBF:
			switch(dst_type) {
				case FIT_BITMAP:
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					break;
				case FIT_COMPLEX:
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBAF:
					dst = FreeImage_ConvertToRGBAF(src);
					break;
			}
			break;
		case FIT_RGBAF:
			switch(dst_type) {
				case FIT_BITMAP:
					break;
				case FIT_UINT16:
					break;
				case FIT_INT16:
					break;
				case FIT_UINT32:
					break;
				case FIT_INT32:
					break;
				case FIT_FLOAT:
					dst = FreeImage_ConvertToFloat(src);
					break;
				case FIT_DOUBLE:
					break;
				case FIT_COMPLEX:
					break;
				case FIT_RGB16:
					break;
				case FIT_RGBA16:
					break;
				case FIT_RGBF:
					dst = FreeImage_ConvertToRGBF(src);
					break;
			}
			break;
	}

	if(NULL == dst) {
		FreeImage_OutputMessageProc(FIF_UNKNOWN, "FREE_IMAGE_TYPE: Unable to convert from type %d to type %d.\n No such conversion exists.", src_type, dst_type);
	} else {
		// copy metadata from src to dst
		FreeImage_CloneMetadata(dst, src);
	}

	return dst;
}
