// ==========================================================
// Upsampling / downsampling classes
//
// Design and implementation by
// - Hervé Drolon (drolon@infonie.fr)
// - Detlev Vendt (detlev.vendt@brillit.de)
//
// 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 "Resize.h"

/**
  Filter weights table.
  This class stores contribution information for an entire line (row or column).
*/
CWeightsTable::CWeightsTable(CGenericFilter *pFilter, unsigned uDstSize, unsigned uSrcSize) {
	unsigned u;
	double dWidth;
	double dFScale = 1.0;
	const double dFilterWidth = pFilter->GetWidth();

	// scale factor
	const double dScale = double(uDstSize) / double(uSrcSize);

	if(dScale < 1.0) {
		// minification
		dWidth = dFilterWidth / dScale; 
		dFScale = dScale; 
	} else {
		// magnification
		dWidth= dFilterWidth; 
	}

	// allocate a new line contributions structure
	//
	// window size is the number of sampled pixels
	m_WindowSize = 2 * (int)ceil(dWidth) + 1; 
	m_LineLength = uDstSize; 
	 // allocate list of contributions 
	m_WeightTable = (Contribution*)malloc(m_LineLength * sizeof(Contribution));
	for(u = 0 ; u < m_LineLength ; u++) {
		// allocate contributions for every pixel
		m_WeightTable[u].Weights = (double*)malloc(m_WindowSize * sizeof(double));
	}

	// offset for discrete to continuous coordinate conversion
	const double dOffset = (0.5 / dScale) - 0.5;


	for(u = 0; u < m_LineLength; u++) {
		// scan through line of contributions
		const double dCenter = (double)u / dScale + dOffset;   // reverse mapping
		// find the significant edge points that affect the pixel
		int iLeft = MAX (0, (int)floor (dCenter - dWidth)); 
		int iRight = MIN ((int)ceil (dCenter + dWidth), int(uSrcSize) - 1); 

		// cut edge points to fit in filter window in case of spill-off
		if((iRight - iLeft + 1) > int(m_WindowSize)) {
			if(iLeft < (int(uSrcSize) - 1 / 2)) {
				iLeft++; 
			} else {
				iRight--; 
			}
		}

		m_WeightTable[u].Left = iLeft; 
		m_WeightTable[u].Right = iRight;

		int iSrc = 0;
		double dTotalWeight = 0;  // zero sum of weights
		for(iSrc = iLeft; iSrc <= iRight; iSrc++) {
			// calculate weights
			const double weight = dFScale * pFilter->Filter(dFScale * (dCenter - (double)iSrc));
			m_WeightTable[u].Weights[iSrc-iLeft] = weight;
			dTotalWeight += weight;
		}
		if((dTotalWeight > 0) && (dTotalWeight != 1)) {
			// normalize weight of neighbouring points
			for(iSrc = iLeft; iSrc <= iRight; iSrc++) {
				// normalize point
				m_WeightTable[u].Weights[iSrc-iLeft] /= dTotalWeight; 
			}
			// simplify the filter, discarding null weights at the right
			iSrc = iRight - iLeft;
			while(m_WeightTable[u].Weights[iSrc] == 0){
				m_WeightTable[u].Right--;
				iSrc--;
				if(m_WeightTable[u].Right == m_WeightTable[u].Left)
					break;
			}

		}
   } 
}

CWeightsTable::~CWeightsTable() {
	for(unsigned u = 0; u < m_LineLength; u++) {
		// free contributions for every pixel
		free(m_WeightTable[u].Weights);
	}
	// free list of pixels contributions
	free(m_WeightTable);
}

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

/**
 CResizeEngine<br>
 This class performs filtered zoom. It scales an image to the desired dimensions with 
 any of the CGenericFilter derived filter class.<br>
 It works with 8-, 24- and 32-bit buffers.<br><br>

 <b>References</b> : <br>
 [1] Paul Heckbert, C code to zoom raster images up or down, with nice filtering. 
 UC Berkeley, August 1989. [online] http://www-2.cs.cmu.edu/afs/cs.cmu.edu/Web/People/ph/heckbert.html
 [2] Eran Yariv, Two Pass Scaling using Filters. The Code Project, December 1999. 
 [online] http://www.codeproject.com/bitmap/2_pass_scaling.asp

*/

FIBITMAP* CResizeEngine::scale(FIBITMAP *src, unsigned dst_width, unsigned dst_height) { 
	unsigned src_width  = FreeImage_GetWidth(src); 
	unsigned src_height = FreeImage_GetHeight(src);

	unsigned redMask	= FreeImage_GetRedMask(src);
	unsigned greenMask	= FreeImage_GetGreenMask(src);
	unsigned blueMask	= FreeImage_GetBlueMask(src);

	unsigned bpp = FreeImage_GetBPP(src);
	if(bpp == 1) {
		// convert output to 8-bit
		bpp = 8;
	}

	FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src);

	// allocate the dst image
	FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, bpp, redMask, greenMask, blueMask);
	if(!dst) return NULL;
	
	if(bpp == 8) {
		if(FreeImage_GetColorType(src) == FIC_MINISWHITE) {
			// build an inverted greyscale palette
			RGBQUAD *dst_pal = FreeImage_GetPalette(dst);
			for(unsigned i = 0; i < 256; i++) {
				dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)(255 - i);
			}
		} else {
			// build a greyscale palette
			RGBQUAD *dst_pal = FreeImage_GetPalette(dst);
			for(unsigned i = 0; i < 256; i++) {
				dst_pal[i].rgbRed = dst_pal[i].rgbGreen = dst_pal[i].rgbBlue = (BYTE)i;
			}
		}
	}

	/**
	Decide which filtering order (xy or yx) is faster for this mapping. 
	--- The theory ---
	Try to minimize calculations by counting the number of convolution multiplies
	if(dst_width*src_height <= src_width*dst_height) {
		// xy filtering
	} else {
		// yx filtering
	}
	--- The practice ---
	Try to minimize calculations by counting the number of vertical convolutions (the most time consuming task)
	if(dst_width*dst_height <= src_width*dst_height) {
		// xy filtering
	} else {
		// yx filtering
	}
	*/

	if(dst_width <= src_width) {
		// xy filtering
		// -------------

		// allocate a temporary image
		FIBITMAP *tmp = FreeImage_AllocateT(image_type, dst_width, src_height, bpp, redMask, greenMask, blueMask);
		if(!tmp) {
			FreeImage_Unload(dst);
			return NULL;
		}

		// scale source image horizontally into temporary image
		horizontalFilter(src, src_width, src_height, tmp, dst_width, src_height);

		// scale temporary image vertically into result image    
		verticalFilter(tmp, dst_width, src_height, dst, dst_width, dst_height);

		// free temporary image
		FreeImage_Unload(tmp);

	} else {
		// yx filtering
		// -------------

		// allocate a temporary image
		FIBITMAP *tmp = FreeImage_AllocateT(image_type, src_width, dst_height, bpp, redMask, greenMask, blueMask);
		if(!tmp) {
			FreeImage_Unload(dst);
			return NULL;
		}

		// scale source image vertically into temporary image
		verticalFilter(src, src_width, src_height, tmp, src_width, dst_height);

		// scale temporary image horizontally into result image    
		horizontalFilter(tmp, src_width, dst_height, dst, dst_width, dst_height);

		// free temporary image
		FreeImage_Unload(tmp);
	}

	return dst;
} 


/// Performs horizontal image filtering
void CResizeEngine::horizontalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) { 
	if(dst_width == src_width) {
		// no scaling required, just copy
		switch(FreeImage_GetBPP(src)) {
			case 1:
			{
				if(FreeImage_GetBPP(dst) != 8) break;
				for(unsigned y = 0; y < dst_height; y++) {
					// convert each row 
					BYTE *src_bits = FreeImage_GetScanLine(src, y);
					BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
					FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width);
				}
			}
			break;

			default:
			{
				const BYTE *src_bits = FreeImage_GetBits(src);
				BYTE *dst_bits = FreeImage_GetBits(dst);
				memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst));
			}
			break;
		}
	}
	else {

		// allocate and calculate the contributions
		CWeightsTable weightsTable(m_pFilter, dst_width, src_width); 
		
		// step through rows
		switch(FreeImage_GetImageType(src)) {
			case FIT_BITMAP:
			{
				switch(FreeImage_GetBPP(src)) {
					case 1:
					{
						// scale and convert to 8-bit
						if(FreeImage_GetBPP(dst) != 8) break;

						for(unsigned y = 0; y < dst_height; y++) {
							// scale each row 
							const BYTE *src_bits = FreeImage_GetScanLine(src, y);
							BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

							for(unsigned x = 0; x < dst_width; x++) {
								// loop through row
								double value = 0;
								const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary
								const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary

								for(unsigned i = iLeft; i <= iRight; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(x, i-iLeft);
									
									const BYTE pixel = (src_bits[i >> 3] & (0x80 >> (i & 0x07))) != 0;
									value += (weight * (double)pixel);
								} 
								value *= 255;

								// clamp and place result in destination pixel
								dst_bits[x] = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 255);
							} 
						}
					}
					break;

					case 8:
					case 24:
					case 32:
					{
						// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit)
						const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src);

						for(unsigned y = 0; y < dst_height; y++) {
							// scale each row 
							const BYTE *src_bits = FreeImage_GetScanLine(src, y);
							BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

							for(unsigned x = 0; x < dst_width; x++) {
								// loop through row
								double value[4] = {0, 0, 0, 0};					// 4 = 32 bpp max
								const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary
								const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary

								for(unsigned i = iLeft; i <= iRight; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(x, i-iLeft);
									
									unsigned index = i * bytespp;	// pixel index
									for (unsigned j = 0; j < bytespp; j++) {
										value[j] += (weight * (double)src_bits[index++]); 
									}
								} 

								// clamp and place result in destination pixel
								for (unsigned j = 0; j < bytespp; j++) {
									dst_bits[j] = (BYTE)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFF);
								}

								dst_bits += bytespp;
							} 
						}
					}
					break;
				}
			}
			break;

			case FIT_UINT16:
			case FIT_RGB16:
			case FIT_RGBA16:
			{
				// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit)
				const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD);

				for(unsigned y = 0; y < dst_height; y++) {
					// scale each row 
					const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, y);
					WORD *dst_bits = (WORD*)FreeImage_GetScanLine(dst, y);

					for(unsigned x = 0; x < dst_width; x++) {
						// loop through row
						double value[4] = {0, 0, 0, 0};					// 4 = 64 bpp max
						const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary
						const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary

						for(unsigned i = iLeft; i <= iRight; i++) {
							// scan between boundaries
							// accumulate weighted effect of each neighboring pixel
							const double weight = weightsTable.getWeight(x, i-iLeft);
							
							unsigned index = i * wordspp;	// pixel index
							for (unsigned j = 0; j < wordspp; j++) {
								value[j] += (weight * (double)src_bits[index++]); 
							}
						} 

						// clamp and place result in destination pixel
						for (unsigned j = 0; j < wordspp; j++) {
							dst_bits[j] = (WORD)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFFFF);
						}

						dst_bits += wordspp;
					} 
				}
			}
			break;

			case FIT_FLOAT:
			case FIT_RGBF:
			case FIT_RGBAF:
			{
				// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit)
				const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float);

				for(unsigned y = 0; y < dst_height; y++) {
					// scale each row 
					const float *src_bits = (float*)FreeImage_GetScanLine(src, y);
					float *dst_bits = (float*)FreeImage_GetScanLine(dst, y);

					for(unsigned x = 0; x < dst_width; x++) {
						// loop through row
						double value[4] = {0, 0, 0, 0};					// 4 = 128 bpp max
						const unsigned iLeft = weightsTable.getLeftBoundary(x);    // retrieve left boundary
						const unsigned iRight = weightsTable.getRightBoundary(x);  // retrieve right boundary

						for(unsigned i = iLeft; i <= iRight; i++) {
							// scan between boundaries
							// accumulate weighted effect of each neighboring pixel
							const double weight = weightsTable.getWeight(x, i-iLeft);
							
							unsigned index = i * floatspp;	// pixel index
							for (unsigned j = 0; j < floatspp; j++) {
								value[j] += (weight * (double)src_bits[index++]); 
							}
						} 

						// place result in destination pixel
						for (unsigned j = 0; j < floatspp; j++) {
							dst_bits[j] = (float)value[j];
						}

						dst_bits += floatspp;
					} 
				}
			}
			break;

		}
	}
} 

/// Performs vertical image filtering
void CResizeEngine::verticalFilter(FIBITMAP *src, unsigned src_width, unsigned src_height, FIBITMAP *dst, unsigned dst_width, unsigned dst_height) { 
	if(src_height == dst_height) {
		// no scaling required, just copy
		switch(FreeImage_GetBPP(src)) {
			case 1:
			{
				if(FreeImage_GetBPP(dst) != 8) break;
				for(unsigned y = 0; y < dst_height; y++) {
					// convert each row 
					BYTE *src_bits = FreeImage_GetScanLine(src, y);
					BYTE *dst_bits = FreeImage_GetScanLine(dst, y);
					FreeImage_ConvertLine1To8(dst_bits, src_bits, dst_width);
				}
			}
			break;

			default:
			{
				const BYTE *src_bits = FreeImage_GetBits(src);
				BYTE *dst_bits = FreeImage_GetBits(dst);
				memcpy(dst_bits, src_bits, dst_height * FreeImage_GetPitch(dst));
			}
			break;
		}

	}
	else {

		// allocate and calculate the contributions
		CWeightsTable weightsTable(m_pFilter, dst_height, src_height); 

		// step through columns
		switch(FreeImage_GetImageType(src)) {
			case FIT_BITMAP:
			{
				switch(FreeImage_GetBPP(src)) {
					case 1:
					{
						// scale and convert to 8-bit
						if(FreeImage_GetBPP(dst) != 8) break;

						const unsigned src_pitch = FreeImage_GetPitch(src);
						const unsigned dst_pitch = FreeImage_GetPitch(dst);

						for(unsigned x = 0; x < dst_width; x++) {

							// work on column x in dst
							BYTE *dst_bits = FreeImage_GetBits(dst) + x;

							// scale each column
							for(unsigned y = 0; y < dst_height; y++) {
								// loop through column
								double value = 0;
								const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary
								const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary

								BYTE *src_bits = FreeImage_GetScanLine(src, iLeft);

								for(unsigned i = iLeft; i <= iRight; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(y, i-iLeft);
									
									const BYTE pixel = (src_bits[x >> 3] & (0x80 >> (x & 0x07))) != 0;
									value += (weight * (double)pixel);

									src_bits += src_pitch;
								}
								value *= 255;

								// clamp and place result in destination pixel
								*dst_bits = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);

								dst_bits += dst_pitch;
							}
						}
					}
					break;

					case 8:
					case 24:
					case 32:
					{
						// Calculate the number of bytes per pixel (1 for 8-bit, 3 for 24-bit or 4 for 32-bit)
						const unsigned bytespp = FreeImage_GetLine(src) / FreeImage_GetWidth(src);

						const unsigned src_pitch = FreeImage_GetPitch(src);
						const unsigned dst_pitch = FreeImage_GetPitch(dst);

						for(unsigned x = 0; x < dst_width; x++) {
							const unsigned index = x * bytespp;	// pixel index

							// work on column x in dst
							BYTE *dst_bits = FreeImage_GetBits(dst) + index;

							// scale each column
							for(unsigned y = 0; y < dst_height; y++) {
								// loop through column
								double value[4] = {0, 0, 0, 0};					// 4 = 32 bpp max
								const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary
								const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary

								const BYTE *src_bits = FreeImage_GetScanLine(src, iLeft) + index;

								for(unsigned i = iLeft; i <= iRight; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(y, i-iLeft);
									for (unsigned j = 0; j < bytespp; j++) {
										value[j] += (weight * (double)src_bits[j]);
									}

									src_bits += src_pitch;
								}

								// clamp and place result in destination pixel
								for (unsigned j = 0; j < bytespp; j++) {
									dst_bits[j] = (BYTE)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFF);
								}

								dst_bits += dst_pitch;
							}
						}
					}
					break;
				}
			}
			break;

			case FIT_UINT16:
			case FIT_RGB16:
			case FIT_RGBA16:
			{
				// Calculate the number of words per pixel (1 for 16-bit, 3 for 48-bit or 4 for 64-bit)
				const unsigned wordspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(WORD);

				const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
				const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD);

				for(unsigned x = 0; x < dst_width; x++) {
					const unsigned index = x * wordspp;	// pixel index

					// work on column x in dst
					WORD *dst_bits = (WORD*)FreeImage_GetBits(dst) + index;

					// scale each column
					for(unsigned y = 0; y < dst_height; y++) {
						// loop through column
						double value[4] = {0, 0, 0, 0};					// 4 = 64 bpp max
						const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary
						const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary

						const WORD *src_bits = (WORD*)FreeImage_GetScanLine(src, iLeft) + index;

						for(unsigned i = iLeft; i <= iRight; i++) {
							// scan between boundaries
							// accumulate weighted effect of each neighboring pixel
							const double weight = weightsTable.getWeight(y, i-iLeft);
							for (unsigned j = 0; j < wordspp; j++) {
								value[j] += (weight * (double)src_bits[j]);
							}

							src_bits += src_pitch;
						}

						// clamp and place result in destination pixel
						for (unsigned j = 0; j < wordspp; j++) {
							dst_bits[j] = (WORD)CLAMP<int>((int)(value[j] + 0.5), 0, 0xFFFF);
						}

						dst_bits += dst_pitch;
					}
				}
			}
			break;

			case FIT_FLOAT:
			case FIT_RGBF:
			case FIT_RGBAF:
			{
				// Calculate the number of floats per pixel (1 for 32-bit, 3 for 96-bit or 4 for 128-bit)
				const unsigned floatspp = (FreeImage_GetLine(src) / FreeImage_GetWidth(src)) / sizeof(float);

				const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(float);
				const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(float);

				for(unsigned x = 0; x < dst_width; x++) {
					const unsigned index = x * floatspp;	// pixel index

					// work on column x in dst
					float *dst_bits = (float*)FreeImage_GetBits(dst) + index;

					// scale each column
					for(unsigned y = 0; y < dst_height; y++) {
						// loop through column
						double value[4] = {0, 0, 0, 0};					// 4 = 128 bpp max
						const unsigned iLeft = weightsTable.getLeftBoundary(y);    // retrieve left boundary
						const unsigned iRight = weightsTable.getRightBoundary(y);  // retrieve right boundary

						const float *src_bits = (float*)FreeImage_GetScanLine(src, iLeft) + index;

						for(unsigned i = iLeft; i <= iRight; i++) {
							// scan between boundaries
							// accumulate weighted effect of each neighboring pixel
							const double weight = weightsTable.getWeight(y, i-iLeft);
							for (unsigned j = 0; j < floatspp; j++) {
								value[j] += (weight * (double)src_bits[j]);
							}

							src_bits += src_pitch;
						}

						// clamp and place result in destination pixel
						for (unsigned j = 0; j < floatspp; j++) {
							dst_bits[j] = (float)value[j];
						}

						dst_bits += dst_pitch;
					}
				}
			}
			break;

		}
	}
} 

