// ==========================================================
// Bitmap conversion routines
//
// Design and implementation by
// - Floris van den Berg (flvdberg@wxs.nl)
// - Hervé Drolon (drolon@infonie.fr)
// - Jani Kajala (janik@remedy.fi)
//
// 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"
#include "Quantizers.h"

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

#define CONVERT(from, to) case to : FreeImage_ConvertLine##from##To##to(bits, scanline, FreeImage_GetWidth(dib)); break;
#define CONVERTWITHPALETTE(from, to) case to : FreeImage_ConvertLine##from##To##to(bits, scanline, FreeImage_GetWidth(dib), FreeImage_GetPalette(dib)); break;

#define CONVERTTO16(from) \
	case 16 : \
		if ((red_mask == FI16_555_RED_MASK) && (green_mask == FI16_555_GREEN_MASK) && (blue_mask == FI16_555_BLUE_MASK)) { \
			FreeImage_ConvertLine##from##To16_555(bits, scanline, FreeImage_GetWidth(dib)); \
		} else { \
			FreeImage_ConvertLine##from##To16_565(bits, scanline, FreeImage_GetWidth(dib)); \
		} \
		break;

#define CONVERTTO16WITHPALETTE(from) \
	case 16 : \
		if ((red_mask == FI16_555_RED_MASK) && (green_mask == FI16_555_GREEN_MASK) && (blue_mask == FI16_555_BLUE_MASK)) { \
			FreeImage_ConvertLine##from##To16_555(bits, scanline, FreeImage_GetWidth(dib), FreeImage_GetPalette(dib)); \
		} else { \
			FreeImage_ConvertLine##from##To16_565(bits, scanline, FreeImage_GetWidth(dib), FreeImage_GetPalette(dib)); \
		} \
		break;

// ==========================================================

FIBITMAP * DLL_CALLCONV
FreeImage_ColorQuantize(FIBITMAP *dib, FREE_IMAGE_QUANTIZE quantize) {
	return FreeImage_ColorQuantizeEx(dib, quantize);
}

FIBITMAP * DLL_CALLCONV
FreeImage_ColorQuantizeEx(FIBITMAP *dib, FREE_IMAGE_QUANTIZE quantize, int PaletteSize, int ReserveSize, RGBQUAD *ReservePalette) {
	if( PaletteSize < 2 ) PaletteSize = 2;
	if( PaletteSize > 256 ) PaletteSize = 256;
	if( ReserveSize < 0 ) ReserveSize = 0;
	if( ReserveSize > PaletteSize ) ReserveSize = PaletteSize;
	if (dib) {
		if (FreeImage_GetBPP(dib) == 24) {
			switch(quantize) {
				case FIQ_WUQUANT :
				{
					try {
						WuQuantizer Q (dib);
						FIBITMAP *dst = Q.Quantize(PaletteSize, ReserveSize, ReservePalette);
						if(dst) {
							// copy metadata from src to dst
							FreeImage_CloneMetadata(dst, dib);
						}
						return dst;
					} catch (const char *) {
						return NULL;
					}
				}
				case FIQ_NNQUANT :
				{
					// sampling factor in range 1..30. 
					// 1 => slower (but better), 30 => faster. Default value is 1
					const int sampling = 1;

					NNQuantizer Q(PaletteSize);
					FIBITMAP *dst = Q.Quantize(dib, ReserveSize, ReservePalette, sampling);
					if(dst) {
						// copy metadata from src to dst
						FreeImage_CloneMetadata(dst, dib);
					}
					return dst;
				}
			}
		}
	}

	return NULL;
}

// ==========================================================

FIBITMAP * DLL_CALLCONV
FreeImage_ConvertFromRawBits(BYTE *bits, int width, int height, int pitch, unsigned bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask, BOOL topdown) {
	FIBITMAP *dib = FreeImage_Allocate(width, height, bpp, red_mask, green_mask, blue_mask);

	if (dib != NULL) {
		if (topdown) {
			for (int i = height - 1; i >= 0; --i) {
				memcpy(FreeImage_GetScanLine(dib, i), bits, FreeImage_GetLine(dib));
				bits += pitch;
			}
		} else {
			for (int i = 0; i < height; ++i) {			
				memcpy(FreeImage_GetScanLine(dib, i), bits, FreeImage_GetLine(dib));
				bits += pitch;
			}
		}
	}

	return dib;
}

void DLL_CALLCONV
FreeImage_ConvertToRawBits(BYTE *bits, FIBITMAP *dib, int pitch, unsigned bpp, unsigned red_mask, unsigned green_mask, unsigned blue_mask, BOOL topdown) {
	if ((dib != NULL) && (bits != NULL)) {
		for (unsigned i = 0; i < FreeImage_GetHeight(dib); ++i) {
			BYTE *scanline = FreeImage_GetScanLine(dib, topdown ? (FreeImage_GetHeight(dib) - i - 1) : i);

			if ((bpp == 16) && (FreeImage_GetBPP(dib) == 16)) {
				// convert 555 to 565 or vice versa

				if ((red_mask == FI16_555_RED_MASK) && (green_mask == FI16_555_GREEN_MASK) && (blue_mask == FI16_555_BLUE_MASK)) {
					if ((FreeImage_GetRedMask(dib) == FI16_565_RED_MASK) && (FreeImage_GetGreenMask(dib) == FI16_565_GREEN_MASK) && (FreeImage_GetBlueMask(dib) == FI16_565_BLUE_MASK)) {
						FreeImage_ConvertLine16_565_To16_555(bits, scanline, FreeImage_GetWidth(dib));
					} else {
						memcpy(bits, scanline, FreeImage_GetLine(dib));
					}
				} else {
					if ((FreeImage_GetRedMask(dib) == FI16_555_RED_MASK) && (FreeImage_GetGreenMask(dib) == FI16_555_GREEN_MASK) && (FreeImage_GetBlueMask(dib) == FI16_555_BLUE_MASK)) {
						FreeImage_ConvertLine16_555_To16_565(bits, scanline, FreeImage_GetWidth(dib));
					} else {
						memcpy(bits, scanline, FreeImage_GetLine(dib));
					}
				}
			} else if (FreeImage_GetBPP(dib) != bpp) {
				switch(FreeImage_GetBPP(dib)) {
					case 1 :
						switch(bpp) {
							CONVERT(1, 8)
							CONVERTTO16WITHPALETTE(1)
							CONVERTWITHPALETTE(1, 24)
							CONVERTWITHPALETTE(1, 32)
						};

						break;

					case 4 :
						switch(bpp) {
							CONVERT(4, 8)
							CONVERTTO16WITHPALETTE(4)
							CONVERTWITHPALETTE(4, 24)
							CONVERTWITHPALETTE(4, 32)
						};

						break;

					case 8 :
						switch(bpp) {
							CONVERTTO16WITHPALETTE(8)
							CONVERTWITHPALETTE(8, 24)
							CONVERTWITHPALETTE(8, 32)
						};

						break;

					case 24 :
						switch(bpp) {
							CONVERT(24, 8)
							CONVERTTO16(24)
							CONVERT(24, 32)
						};

						break;

					case 32 :
						switch(bpp) {
							CONVERT(32, 8)
							CONVERTTO16(32)
							CONVERT(32, 24)
						};

						break;
				};
			} else {
				memcpy(bits, scanline, FreeImage_GetLine(dib));
			}

			bits += pitch;
		}
	}
}
