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

/**
Returns the color type of a bitmap. In contrast to FreeImage_GetColorType,
this function optionally supports a boolean OUT parameter, that receives TRUE,
if the specified bitmap is greyscale, that is, it consists of grey colors only.
Although it returns the same value as returned by FreeImage_GetColorType for all
image types, this extended function primarily is intended for palletized images,
since the boolean pointed to by 'bIsGreyscale' remains unchanged for RGB(A/F)
images. However, the outgoing boolean is properly maintained for palletized images,
as well as for any non-RGB image type, like FIT_UINTxx and FIT_DOUBLE, for example.
@param dib A pointer to a FreeImage bitmap to calculate the extended color type for
@param bIsGreyscale A pointer to a boolean, that receives TRUE, if the specified bitmap
is greyscale, that is, it consists of grey colors only. This parameter can be NULL.
@return the color type of the specified bitmap
*/
static FREE_IMAGE_COLOR_TYPE
GetExtendedColorType(FIBITMAP *dib, BOOL *bIsGreyscale) {
	const unsigned bpp = FreeImage_GetBPP(dib);
	const unsigned size = CalculateUsedPaletteEntries(bpp);
	const RGBQUAD * const pal = FreeImage_GetPalette(dib);
	FREE_IMAGE_COLOR_TYPE color_type = FIC_MINISBLACK;
	BOOL bIsGrey = TRUE;

	switch (bpp) {
		case 1:
		{
			for (unsigned i = 0; i < size; i++) {
				if ((pal[i].rgbRed != pal[i].rgbGreen) || (pal[i].rgbRed != pal[i].rgbBlue)) {
					color_type = FIC_PALETTE;
					bIsGrey = FALSE;
					break;
				}
			}
			if (bIsGrey) {
				if (pal[0].rgbBlue == 255 && pal[1].rgbBlue == 0) {
					color_type = FIC_MINISWHITE;
				} else if (pal[0].rgbBlue != 0 || pal[1].rgbBlue != 255) {
					color_type = FIC_PALETTE;
				}
			}
			break;
		}

		case 4:
		case 8:
		{
			for (unsigned i = 0; i < size; i++) {
				if ((pal[i].rgbRed != pal[i].rgbGreen) || (pal[i].rgbRed != pal[i].rgbBlue)) {
					color_type = FIC_PALETTE;
					bIsGrey = FALSE;
					break;
				}
				if (color_type != FIC_PALETTE && pal[i].rgbBlue != i) {
					if ((size - i - 1) != pal[i].rgbBlue) {
						color_type = FIC_PALETTE;
						if (!bIsGreyscale) {
							// exit loop if we're not setting
							// bIsGreyscale parameter
							break;
						}
					} else {
						color_type = FIC_MINISWHITE;
					}
				}
			}
			break;
		}

		default:
		{
			color_type = FreeImage_GetColorType(dib);
			bIsGrey = (color_type == FIC_MINISBLACK) ? TRUE : FALSE;
			break;
		}

	}
	if (bIsGreyscale) {
		*bIsGreyscale = bIsGrey;
	}

	return color_type;
}

/**
Returns a pointer to an RGBA palette, created from the specified bitmap.
The RGBA palette is a copy of the specified bitmap's palette, that, additionally
contains the bitmap's transparency information in the rgbReserved member
of the palette's RGBQUAD elements.
@param dib A pointer to a FreeImage bitmap to create the RGBA palette from.
@param buffer A pointer to the buffer to store the RGBA palette.
@return A pointer to the newly created RGBA palette or NULL, if the specified
bitmap is no palletized standard bitmap. If non-NULL, the returned value is
actually the pointer passed in parameter 'buffer'.
*/
static inline RGBQUAD *
GetRGBAPalette(FIBITMAP *dib, RGBQUAD * const buffer) {
	// clone the palette
	const unsigned ncolors = FreeImage_GetColorsUsed(dib);
	if (ncolors == 0) {
		return NULL;
	}
	memcpy(buffer, FreeImage_GetPalette(dib), ncolors * sizeof(RGBQUAD));
	// merge the transparency table
	const unsigned ntransp = MIN(ncolors, FreeImage_GetTransparencyCount(dib));
	const BYTE * const tt = FreeImage_GetTransparencyTable(dib);
	for (unsigned i = 0; i < ntransp; i++) {
		buffer[i].rgbReserved = tt[i];
	}
	for (unsigned i = ntransp; i < ncolors; i++) {
		buffer[i].rgbReserved = 255;
	}
	return buffer;
}

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

CWeightsTable::CWeightsTable(CGenericFilter *pFilter, unsigned uDstSize, unsigned uSrcSize) {
	double dWidth;
	double dFScale;
	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; 
		dFScale = 1.0; 
	}

	// allocate a new line contributions structure
	//
	// window size is the number of sampled pixels
	m_WindowSize = 2 * (int)ceil(dWidth) + 1; 
	// length of dst line (no. of rows / cols) 
	m_LineLength = uDstSize; 

	 // allocate list of contributions 
	m_WeightTable = (Contribution*)malloc(m_LineLength * sizeof(Contribution));
	for(unsigned 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);

	for(unsigned u = 0; u < m_LineLength; u++) {
		// scan through line of contributions

		// inverse mapping (discrete dst 'u' to continous src 'dCenter')
		const double dCenter = (double)u / dScale + dOffset;

		// find the significant edge points that affect the pixel
		const int iLeft = MAX(0, (int)(dCenter - dWidth + 0.5));
		const int iRight = MIN((int)(dCenter + dWidth + 0.5), int(uSrcSize));

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

		double dTotalWeight = 0;  // sum of weights (initialized to zero)
		for(int iSrc = iLeft; iSrc < iRight; iSrc++) {
			// calculate weights
			const double weight = dFScale * pFilter->Filter(dFScale * ((double)iSrc + 0.5 - dCenter));
			// assert((iSrc-iLeft) < m_WindowSize);
			m_WeightTable[u].Weights[iSrc-iLeft] = weight;
			dTotalWeight += weight;
		}
		if((dTotalWeight > 0) && (dTotalWeight != 1)) {
			// normalize weight of neighbouring points
			for(int iSrc = iLeft; iSrc < iRight; iSrc++) {
				// normalize point
				m_WeightTable[u].Weights[iSrc-iLeft] /= dTotalWeight; 
			}
		}

		// simplify the filter, discarding null weights at the right
		{			
			int iTrailing = iRight - iLeft - 1;
			while(m_WeightTable[u].Weights[iTrailing] == 0) {
				m_WeightTable[u].Right--;
				iTrailing--;
				if(m_WeightTable[u].Right == m_WeightTable[u].Left) {
					break;
				}
			}
			
		}

	} // next dst pixel
}

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);
}

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

FIBITMAP* CResizeEngine::scale(FIBITMAP *src, unsigned dst_width, unsigned dst_height, unsigned src_left, unsigned src_top, unsigned src_width, unsigned src_height, unsigned flags) {

	const FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(src);
	const unsigned src_bpp = FreeImage_GetBPP(src);

	// determine the image's color type
	BOOL bIsGreyscale = FALSE;
	FREE_IMAGE_COLOR_TYPE color_type;
	if (src_bpp <= 8) {
		color_type = GetExtendedColorType(src, &bIsGreyscale);
	} else {
		color_type = FIC_RGB;
	}

	// determine the required bit depth of the destination image
	unsigned dst_bpp;
	unsigned dst_bpp_s1 = 0;
	if (color_type == FIC_PALETTE && !bIsGreyscale) {
		// non greyscale FIC_PALETTE images require a high-color destination
		// image (24- or 32-bits depending on the image's transparent state)
		dst_bpp = FreeImage_IsTransparent(src) ? 32 : 24;
	} else if (src_bpp <= 8) {
		// greyscale images require an 8-bit destination image
		// (or a 32-bit image if the image is transparent);
		// however, if flag FI_RESCALE_TRUE_COLOR is set, we will return
		// a true color (24 bpp) image
		if (FreeImage_IsTransparent(src)) {
			dst_bpp = 32;
			// additionally, for transparent images we always need a
			// palette including transparency information (an RGBA palette)
			// so, set color_type accordingly
			color_type = FIC_PALETTE;
		} else {
			dst_bpp = ((flags & FI_RESCALE_TRUE_COLOR) == FI_RESCALE_TRUE_COLOR) ? 24 : 8;
			// in any case, we use a fast 8-bit temporary image for the
			// first filter operation (stage 1, either horizontal or
			// vertical) and implicitly convert to 24 bpp (if requested
			// by flag FI_RESCALE_TRUE_COLOR) during the second filter
			// operation
			dst_bpp_s1 = 8;
		}
	} else if (src_bpp == 16 && image_type == FIT_BITMAP) {
		// 16-bit 555 and 565 RGB images require a high-color destination
		// image (fixed to 24 bits, since 16-bit RGBs don't support
		// transparency in FreeImage)
		dst_bpp = 24;
	} else {
		// bit depth remains unchanged for all other images
		dst_bpp = src_bpp;
	}

	// make 'stage 1' bpp a copy of the destination bpp if it
	// was not explicitly set
	if (dst_bpp_s1 == 0) {
		dst_bpp_s1 = dst_bpp;
	}

	// early exit if destination size is equal to source size
	if ((src_width == dst_width) && (src_height == dst_height)) {
		FIBITMAP *out = src;
		FIBITMAP *tmp = src;
		if ((src_width != FreeImage_GetWidth(src)) || (src_height != FreeImage_GetHeight(src))) {
			out = FreeImage_Copy(tmp, src_left, src_top, src_left + src_width, src_top + src_height);
			tmp = out;
		}
		if (src_bpp != dst_bpp) {
			switch (dst_bpp) {
				case 8:
					out = FreeImage_ConvertToGreyscale(tmp);
					break;
				case 24:
					out = FreeImage_ConvertTo24Bits(tmp);
					break;
				case 32:
					out = FreeImage_ConvertTo32Bits(tmp);
					break;
				default:
					break;
			}
			if (tmp != src) {
				FreeImage_Unload(tmp);
				tmp = NULL;
			}
		}

		return (out != src) ? out : FreeImage_Clone(src);
	}

	RGBQUAD pal_buffer[256];
	RGBQUAD *src_pal = NULL;

	// provide the source image's palette to the rescaler for
	// FIC_PALETTE type images (this includes palletized greyscale
	// images with an unordered palette as well as transparent images)
	if (color_type == FIC_PALETTE) {
		if (dst_bpp == 32) {
			// a 32-bit destination image signals transparency, so
			// create an RGBA palette from the source palette
			src_pal = GetRGBAPalette(src, pal_buffer);
		} else {
			src_pal = FreeImage_GetPalette(src);
		}
	}

	// allocate the dst image
	FIBITMAP *dst = FreeImage_AllocateT(image_type, dst_width, dst_height, dst_bpp, 0, 0, 0);
	if (!dst) {
		return NULL;
	}
	
	if (dst_bpp == 8) {
		RGBQUAD * const dst_pal = FreeImage_GetPalette(dst);
		if (color_type == FIC_MINISWHITE) {
			// build an inverted greyscale palette
			CREATE_GREYSCALE_PALETTE_REVERSE(dst_pal, 256);
		} 
		/*
		else {
			// build a default greyscale palette
			// Currently, FreeImage_AllocateT already creates a default
			// greyscale palette for 8 bpp images, so we can skip this here.
			CREATE_GREYSCALE_PALETTE(dst_pal, 256);
		}
		*/
	}

	// calculate x and y offsets; since FreeImage uses bottom-up bitmaps, the
	// value of src_offset_y is measured from the bottom of the image
	unsigned src_offset_x = src_left;
	unsigned src_offset_y = FreeImage_GetHeight(src) - src_height - src_top;

	/*
	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
		// -------------

		FIBITMAP *tmp = NULL;

		if (src_width != dst_width) {
			// source and destination widths are different so, we must
			// filter horizontally
			if (src_height != dst_height) {
				// source and destination heights are also different so, we need
				// a temporary image
				tmp = FreeImage_AllocateT(image_type, dst_width, src_height, dst_bpp_s1, 0, 0, 0);
				if (!tmp) {
					FreeImage_Unload(dst);
					return NULL;
				}
			} else {
				// source and destination heights are equal so, we can directly
				// scale into destination image (second filter method will not
				// be invoked)
				tmp = dst;
			}

			// scale source image horizontally into temporary (or destination) image
			horizontalFilter(src, src_height, src_width, src_offset_x, src_offset_y, src_pal, tmp, dst_width);

			// set x and y offsets to zero for the second filter method
			// invocation (the temporary image only contains the portion of
			// the image to be rescaled with no offsets)
			src_offset_x = 0;
			src_offset_y = 0;

			// also ensure, that the second filter method gets no source
			// palette (the temporary image is palletized only, if it is
			// greyscale; in that case, it is an 8-bit image with a linear
			// palette so, the source palette is not needed or will even be
			// mismatching, if the source palette is unordered)
			src_pal = NULL;
		} else {
			// source and destination widths are equal so, just copy the
			// image pointer
			tmp = src;
		}

		if (src_height != dst_height) {
			// source and destination heights are different so, scale
			// temporary (or source) image vertically into destination image
			verticalFilter(tmp, dst_width, src_height, src_offset_x, src_offset_y, src_pal, dst, dst_height);
		}

		// free temporary image, if not pointing to either src or dst
		if (tmp != src && tmp != dst) {
			FreeImage_Unload(tmp);
		}

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

		// Remark:
		// The yx filtering branch could be more optimized by taking into,
		// account that (src_width != dst_width) is always true, which
		// follows from the above condition, which selects filtering order.
		// Since (dst_width <= src_width) == TRUE selects xy filtering,
		// both widths must be different when performing yx filtering.
		// However, to make the code more robust, not depending on that
		// condition and more symmetric to the xy filtering case, these
		// (src_width != dst_width) conditions are still in place.

		FIBITMAP *tmp = NULL;

		if (src_height != dst_height) {
			// source and destination heights are different so, we must
			// filter vertically
			if (src_width != dst_width) {
				// source and destination widths are also different so, we need
				// a temporary image
				tmp = FreeImage_AllocateT(image_type, src_width, dst_height, dst_bpp_s1, 0, 0, 0);
				if (!tmp) {
					FreeImage_Unload(dst);
					return NULL;
				}
			} else {
				// source and destination widths are equal so, we can directly
				// scale into destination image (second filter method will not
				// be invoked)
				tmp = dst;
			}

			// scale source image vertically into temporary (or destination) image
			verticalFilter(src, src_width, src_height, src_offset_x, src_offset_y, src_pal, tmp, dst_height);

			// set x and y offsets to zero for the second filter method
			// invocation (the temporary image only contains the portion of
			// the image to be rescaled with no offsets)
			src_offset_x = 0;
			src_offset_y = 0;

			// also ensure, that the second filter method gets no source
			// palette (the temporary image is palletized only, if it is
			// greyscale; in that case, it is an 8-bit image with a linear
			// palette so, the source palette is not needed or will even be
			// mismatching, if the source palette is unordered)
			src_pal = NULL;

		} else {
			// source and destination heights are equal so, just copy the
			// image pointer
			tmp = src;
		}

		if (src_width != dst_width) {
			// source and destination heights are different so, scale
			// temporary (or source) image horizontally into destination image
			horizontalFilter(tmp, dst_height, src_width, src_offset_x, src_offset_y, src_pal, dst, dst_width);
		}

		// free temporary image, if not pointing to either src or dst
		if (tmp != src && tmp != dst) {
			FreeImage_Unload(tmp);
		}
	}

	return dst;
} 

void CResizeEngine::horizontalFilter(FIBITMAP *const src, unsigned height, unsigned src_width, unsigned src_offset_x, unsigned src_offset_y, const RGBQUAD *const src_pal, FIBITMAP *const dst, unsigned dst_width) {

	// 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:
				{
					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// transparently convert the 1-bit non-transparent greyscale image to 8 bpp
							src_offset_x >>= 3;
							if (src_pal) {
								// we have got a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE * const dst_bits = FreeImage_GetScanLine(dst, y);

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

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

										// clamp and place result in destination pixel
										dst_bits[x] = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
									}
								}
							} else {
								// we do not have a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE * const dst_bits = FreeImage_GetScanLine(dst, y);

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

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

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

						case 24:
						{
							// transparently convert the non-transparent 1-bit image to 24 bpp
							src_offset_x >>= 3;
							if (src_pal) {
								// we have got a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

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

										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 unsigned pixel = (src_bits[i >> 3] & (0x80 >> (i & 0x07))) != 0;
											const BYTE * const entry = (BYTE *)&src_pal[pixel];
											r += (weight * (double)entry[FI_RGBA_RED]);
											g += (weight * (double)entry[FI_RGBA_GREEN]);
											b += (weight * (double)entry[FI_RGBA_BLUE]);
										}

										// clamp and place result in destination pixel
										dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
										dst_bits += 3;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

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

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

										// clamp and place result in destination pixel
										const BYTE bval = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_RED]	= bval;
										dst_bits[FI_RGBA_GREEN]	= bval;
										dst_bits[FI_RGBA_BLUE]	= bval;
										dst_bits += 3;
									}
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 1-bit image to 32 bpp; 
							// we always have got a palette here
							src_offset_x >>= 3;

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

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

									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 unsigned pixel = (src_bits[i >> 3] & (0x80 >> (i & 0x07))) != 0;
										const BYTE * const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += 4;
								}
							}
						}
						break;
					}
				}
				break;

				case 4:
				{
					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// transparently convert the non-transparent 4-bit greyscale image to 8 bpp; 
							// we always have got a palette for 4-bit images
							src_offset_x >>= 1;

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

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

									for (unsigned i = iLeft; i < iRight; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const unsigned pixel = i & 0x01 ? src_bits[i >> 1] & 0x0F : src_bits[i >> 1] >> 4;
										value += (weightsTable.getWeight(x, i - iLeft) * (double)*(BYTE *)&src_pal[pixel]);
									}

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

						case 24:
						{
							// transparently convert the non-transparent 4-bit image to 24 bpp; 
							// we always have got a palette for 4-bit images
							src_offset_x >>= 1;

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

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

									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 unsigned pixel = i & 0x01 ? src_bits[i >> 1] & 0x0F : src_bits[i >> 1] >> 4;
										const BYTE * const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits += 3;
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 4-bit image to 32 bpp; 
							// we always have got a palette for 4-bit images
							src_offset_x >>= 1;

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

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

									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 unsigned pixel = i & 0x01 ? src_bits[i >> 1] & 0x0F : src_bits[i >> 1] >> 4;
										const BYTE * const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += 4;
								}
							}
						}
						break;
					}
				}
				break;

				case 8:
				{
					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// scale the 8-bit non-transparent greyscale image
							// into an 8 bpp destination image
							if (src_pal) {
								// we have got a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE * const dst_bits = FreeImage_GetScanLine(dst, y);

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

										// for(i = iLeft to iRight)
										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(x, i) * (double)*(BYTE *)&src_pal[pixel[i]]);
										}

										// clamp and place result in destination pixel
										dst_bits[x] = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
									}
								}
							} else {
								// we do not have a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE * const dst_bits = FreeImage_GetScanLine(dst, y);

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

										// for(i = iLeft to iRight)
										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(x, i) * (double)pixel[i]);
										}

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

						case 24:
						{
							// transparently convert the non-transparent 8-bit image to 24 bpp
							if (src_pal) {
								// we have got a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

									for (unsigned x = 0; x < dst_width; x++) {
										// loop through row
										const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
										const BYTE * const pixel = src_bits + iLeft;
										double r = 0, g = 0, b = 0;

										// for(i = iLeft to iRight)
										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											const double weight = weightsTable.getWeight(x, i);
											const BYTE *const entry = (BYTE *)&src_pal[pixel[i]];
											r += (weight * (double)entry[FI_RGBA_RED]);
											g += (weight * (double)entry[FI_RGBA_GREEN]);
											b += (weight * (double)entry[FI_RGBA_BLUE]);
										}

										// clamp and place result in destination pixel
										dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
										dst_bits += 3;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned y = 0; y < height; y++) {
									// scale each row
									const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
									BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

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

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

										// clamp and place result in destination pixel
										const BYTE bval = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_RED]	= bval;
										dst_bits[FI_RGBA_GREEN]	= bval;
										dst_bits[FI_RGBA_BLUE]	= bval;
										dst_bits += 3;
									}
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 8-bit image to 32 bpp; 
							// we always have got a palette here
							for (unsigned y = 0; y < height; y++) {
								// scale each row
								const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
								BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

								for (unsigned x = 0; x < dst_width; x++) {
									// loop through row
									const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
									const BYTE * const pixel = src_bits + iLeft;
									double r = 0, g = 0, b = 0, a = 0;

									// for(i = iLeft to iRight)
									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const double weight = weightsTable.getWeight(x, i);
										const BYTE * const entry = (BYTE *)&src_pal[pixel[i]];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += 4;
								}
							}
						}
						break;
					}
				}
				break;

				case 16:
				{
					// transparently convert the 16-bit non-transparent image to 24 bpp
					if (IS_FORMAT_RGB565(src)) {
						// image has 565 format
						for (unsigned y = 0; y < height; y++) {
							// scale each row
							const WORD * const src_bits = (WORD *)FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x / sizeof(WORD);
							BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

							for (unsigned x = 0; x < dst_width; x++) {
								// loop through row
								const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
								const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
								const WORD *pixel = src_bits + iLeft;
								double r = 0, g = 0, b = 0;

								// for(i = iLeft to iRight)
								for (unsigned i = 0; i < iLimit; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(x, i);
									r += (weight * (double)((*pixel & FI16_565_RED_MASK) >> FI16_565_RED_SHIFT));
									g += (weight * (double)((*pixel & FI16_565_GREEN_MASK) >> FI16_565_GREEN_SHIFT));
									b += (weight * (double)((*pixel & FI16_565_BLUE_MASK) >> FI16_565_BLUE_SHIFT));
									pixel++;
								}

								// clamp and place result in destination pixel
								dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(((r * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(((g * 0xFF) / 0x3F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(((b * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits += 3;
							}
						}
					} else {
						// image has 555 format
						for (unsigned y = 0; y < height; y++) {
							// scale each row
							const WORD * const src_bits = (WORD *)FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x;
							BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

							for (unsigned x = 0; x < dst_width; x++) {
								// loop through row
								const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
								const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
								const WORD *pixel = src_bits + iLeft;
								double r = 0, g = 0, b = 0;

								// for(i = iLeft to iRight)
								for (unsigned i = 0; i < iLimit; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(x, i);
									r += (weight * (double)((*pixel & FI16_555_RED_MASK) >> FI16_555_RED_SHIFT));
									g += (weight * (double)((*pixel & FI16_555_GREEN_MASK) >> FI16_555_GREEN_SHIFT));
									b += (weight * (double)((*pixel & FI16_555_BLUE_MASK) >> FI16_555_BLUE_SHIFT));
									pixel++;
								}

								// clamp and place result in destination pixel
								dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(((r * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(((g * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(((b * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits += 3;
							}
						}
					}
				}
				break;

				case 24:
				{
					// scale the 24-bit non-transparent image into a 24 bpp destination image
					for (unsigned y = 0; y < height; y++) {
						// scale each row
						const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x * 3;
						BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

						for (unsigned x = 0; x < dst_width; x++) {
							// loop through row
							const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
							const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
							const BYTE * pixel = src_bits + iLeft * 3;
							double r = 0, g = 0, b = 0;

							// for(i = iLeft to iRight)
							for (unsigned i = 0; i < iLimit; i++) {
								// scan between boundaries
								// accumulate weighted effect of each neighboring pixel
								const double weight = weightsTable.getWeight(x, i);
								r += (weight * (double)pixel[FI_RGBA_RED]);
								g += (weight * (double)pixel[FI_RGBA_GREEN]);
								b += (weight * (double)pixel[FI_RGBA_BLUE]);
								pixel += 3;
							}

							// clamp and place result in destination pixel
							dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
							dst_bits += 3;
						}
					}
				}
				break;

				case 32:
				{
					// scale the 32-bit transparent image into a 32 bpp destination image
					for (unsigned y = 0; y < height; y++) {
						// scale each row
						const BYTE * const src_bits = FreeImage_GetScanLine(src, y + src_offset_y) + src_offset_x * 4;
						BYTE *dst_bits = FreeImage_GetScanLine(dst, y);

						for (unsigned x = 0; x < dst_width; x++) {
							// loop through row
							const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
							const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
							const BYTE *pixel = src_bits + iLeft * 4;
							double r = 0, g = 0, b = 0, a = 0;

							// for(i = iLeft to iRight)
							for (unsigned i = 0; i < iLimit; i++) {
								// scan between boundaries
								// accumulate weighted effect of each neighboring pixel
								const double weight = weightsTable.getWeight(x, i);
								r += (weight * (double)pixel[FI_RGBA_RED]);
								g += (weight * (double)pixel[FI_RGBA_GREEN]);
								b += (weight * (double)pixel[FI_RGBA_BLUE]);
								a += (weight * (double)pixel[FI_RGBA_ALPHA]);
								pixel += 4;
							}

							// clamp and place result in destination pixel
							dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
							dst_bits += 4;
						}
					}
				}
				break;
			}
		}
		break;

		case FIT_UINT16:
		{
			// 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) / src_width) / sizeof(WORD);

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

				for (unsigned x = 0; x < dst_width; x++) {
					// loop through row
					const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
					const WORD *pixel = src_bits + iLeft * wordspp;
					double value = 0;

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

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

		case FIT_RGB16:
		{
			// 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) / src_width) / sizeof(WORD);

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

				for (unsigned x = 0; x < dst_width; x++) {
					// loop through row
					const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
					const WORD *pixel = src_bits + iLeft * wordspp;
					double r = 0, g = 0, b = 0;

					// for(i = iLeft to iRight)
					for (unsigned i = 0; i < iLimit; i++) {
						// scan between boundaries
						// accumulate weighted effect of each neighboring pixel
						const double weight = weightsTable.getWeight(x, i);						
						r += (weight * (double)pixel[0]);
						g += (weight * (double)pixel[1]);
						b += (weight * (double)pixel[2]);
						pixel += wordspp;
					}

					// clamp and place result in destination pixel
					dst_bits[0] = (WORD)CLAMP<int>((int)(r + 0.5), 0, 0xFFFF);
					dst_bits[1] = (WORD)CLAMP<int>((int)(g + 0.5), 0, 0xFFFF);
					dst_bits[2] = (WORD)CLAMP<int>((int)(b + 0.5), 0, 0xFFFF);
					dst_bits += wordspp;
				}
			}
		}
		break;

		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) / src_width) / sizeof(WORD);

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

				for (unsigned x = 0; x < dst_width; x++) {
					// loop through row
					const unsigned iLeft = weightsTable.getLeftBoundary(x);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(x) - iLeft;	// retrieve right boundary
					const WORD *pixel = src_bits + iLeft * wordspp;
					double r = 0, g = 0, b = 0, a = 0;

					// for(i = iLeft to iRight)
					for (unsigned i = 0; i < iLimit; i++) {
						// scan between boundaries
						// accumulate weighted effect of each neighboring pixel
						const double weight = weightsTable.getWeight(x, i);						
						r += (weight * (double)pixel[0]);
						g += (weight * (double)pixel[1]);
						b += (weight * (double)pixel[2]);
						a += (weight * (double)pixel[3]);
						pixel += wordspp;
					}

					// clamp and place result in destination pixel
					dst_bits[0] = (WORD)CLAMP<int>((int)(r + 0.5), 0, 0xFFFF);
					dst_bits[1] = (WORD)CLAMP<int>((int)(g + 0.5), 0, 0xFFFF);
					dst_bits[2] = (WORD)CLAMP<int>((int)(b + 0.5), 0, 0xFFFF);
					dst_bits[3] = (WORD)CLAMP<int>((int)(a + 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) / src_width) / sizeof(float);

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

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

					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 *const src, unsigned width, unsigned src_height, unsigned src_offset_x, unsigned src_offset_y, const RGBQUAD *const src_pal, FIBITMAP *const dst, unsigned dst_height) {

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

	// step through columns
	switch(FreeImage_GetImageType(src)) {
		case FIT_BITMAP:
		{
			const unsigned dst_pitch = FreeImage_GetPitch(dst);
			BYTE * const dst_base = FreeImage_GetBits(dst);

			switch(FreeImage_GetBPP(src)) {
				case 1:
				{
					const unsigned src_pitch = FreeImage_GetPitch(src);
					const BYTE * const src_base = FreeImage_GetBits(src) + src_offset_y * src_pitch + (src_offset_x >> 3);

					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// transparently convert the 1-bit non-transparent greyscale image to 8 bpp
							if (src_pal) {
								// we have got a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x;
									const unsigned index = x >> 3;
									const unsigned mask = 0x80 >> (x & 0x07);

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + index;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											const unsigned pixel = (*src_bits & mask) != 0;
											value += (weightsTable.getWeight(y, i) * (double)*(BYTE *)&src_pal[pixel]);
											src_bits += src_pitch;
										}
										value *= 0xFF;

										// clamp and place result in destination pixel
										*dst_bits = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits += dst_pitch;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x;
									const unsigned index = x >> 3;
									const unsigned mask = 0x80 >> (x & 0x07);

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + index;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(y, i) * (double)((*src_bits & mask) != 0));
											src_bits += src_pitch;
										}
										value *= 0xFF;

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

						case 24:
						{
							// transparently convert the non-transparent 1-bit image to 24 bpp
							if (src_pal) {
								// we have got a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x * 3;
									const unsigned index = x >> 3;
									const unsigned mask = 0x80 >> (x & 0x07);

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + index;
										double r = 0, g = 0, b = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											const double weight = weightsTable.getWeight(y, i);
											const unsigned pixel = (*src_bits & mask) != 0;
											const BYTE * const entry = (BYTE *)&src_pal[pixel];
											r += (weight * (double)entry[FI_RGBA_RED]);
											g += (weight * (double)entry[FI_RGBA_GREEN]);
											b += (weight * (double)entry[FI_RGBA_BLUE]);
											src_bits += src_pitch;
										}

										// clamp and place result in destination pixel
										dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
										dst_bits += dst_pitch;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x * 3;
									const unsigned index = x >> 3;
									const unsigned mask = 0x80 >> (x & 0x07);

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + index;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(y, i) * (double)((*src_bits & mask) != 0));
											src_bits += src_pitch;
										}
										value *= 0xFF;

										// clamp and place result in destination pixel
										const BYTE bval = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_RED]	= bval;
										dst_bits[FI_RGBA_GREEN]	= bval;
										dst_bits[FI_RGBA_BLUE]	= bval;
										dst_bits += dst_pitch;
									}
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 1-bit image to 32 bpp; 
							// we always have got a palette here
							for (unsigned x = 0; x < width; x++) {
								// work on column x in dst
								BYTE *dst_bits = dst_base + x * 4;
								const unsigned index = x >> 3;
								const unsigned mask = 0x80 >> (x & 0x07);

								// scale each column
								for (unsigned y = 0; y < dst_height; y++) {
									// loop through column
									const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
									const BYTE *src_bits = src_base + iLeft * src_pitch + index;
									double r = 0, g = 0, b = 0, a = 0;

									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const double weight = weightsTable.getWeight(y, i);
										const unsigned pixel = (*src_bits & mask) != 0;
										const BYTE * const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
										src_bits += src_pitch;
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += dst_pitch;
								}
							}
						}
						break;
					}
				}
				break;

				case 4:
				{
					const unsigned src_pitch = FreeImage_GetPitch(src);
					const BYTE *const src_base = FreeImage_GetBits(src) + src_offset_y * src_pitch + (src_offset_x >> 1);

					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// transparently convert the non-transparent 4-bit greyscale image to 8 bpp; 
							// we always have got a palette for 4-bit images
							for (unsigned x = 0; x < width; x++) {
								// work on column x in dst
								BYTE *dst_bits = dst_base + x;
								const unsigned index = x >> 1;

								// scale each column
								for (unsigned y = 0; y < dst_height; y++) {
									// loop through column
									const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
									const BYTE *src_bits = src_base + iLeft * src_pitch + index;
									double value = 0;

									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const unsigned pixel = x & 0x01 ? *src_bits & 0x0F : *src_bits >> 4;
										value += (weightsTable.getWeight(y, i) * (double)*(BYTE *)&src_pal[pixel]);
										src_bits += src_pitch;
									}

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

						case 24:
						{
							// transparently convert the non-transparent 4-bit image to 24 bpp; 
							// we always have got a palette for 4-bit images
							for (unsigned x = 0; x < width; x++) {
								// work on column x in dst
								BYTE *dst_bits = dst_base + x * 3;
								const unsigned index = x >> 1;

								// scale each column
								for (unsigned y = 0; y < dst_height; y++) {
									// loop through column
									const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
									const BYTE *src_bits = src_base + iLeft * src_pitch + index;
									double r = 0, g = 0, b = 0;

									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const double weight = weightsTable.getWeight(y, i);
										const unsigned pixel = x & 0x01 ? *src_bits & 0x0F : *src_bits >> 4;
										const BYTE *const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										src_bits += src_pitch;
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits += dst_pitch;
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 4-bit image to 32 bpp; 
							// we always have got a palette for 4-bit images
							for (unsigned x = 0; x < width; x++) {
								// work on column x in dst
								BYTE *dst_bits = dst_base + x * 4;
								const unsigned index = x >> 1;

								// scale each column
								for (unsigned y = 0; y < dst_height; y++) {
									// loop through column
									const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
									const BYTE *src_bits = src_base + iLeft * src_pitch + index;
									double r = 0, g = 0, b = 0, a = 0;

									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const double weight = weightsTable.getWeight(y, i);
										const unsigned pixel = x & 0x01 ? *src_bits & 0x0F : *src_bits >> 4;
										const BYTE *const entry = (BYTE *)&src_pal[pixel];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
										src_bits += src_pitch;
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += dst_pitch;
								}
							}
						}
						break;
					}
				}
				break;

				case 8:
				{
					const unsigned src_pitch = FreeImage_GetPitch(src);
					const BYTE *const src_base = FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x;

					switch(FreeImage_GetBPP(dst)) {
						case 8:
						{
							// scale the 8-bit non-transparent greyscale image into an 8 bpp destination image
							if (src_pal) {
								// we have got a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x;

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + x;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(y, i) * (double)*(BYTE *)&src_pal[*src_bits]);
											src_bits += src_pitch;
										}

										// clamp and place result in destination pixel
										*dst_bits = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits += dst_pitch;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x;

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + x;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(y, i) * (double)*src_bits);
											src_bits += src_pitch;
										}

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

						case 24:
						{
							// transparently convert the non-transparent 8-bit image to 24 bpp
							if (src_pal) {
								// we have got a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x * 3;

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + x;
										double r = 0, g = 0, b = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											const double weight = weightsTable.getWeight(y, i);
											const BYTE * const entry = (BYTE *)&src_pal[*src_bits];
											r += (weight * (double)entry[FI_RGBA_RED]);
											g += (weight * (double)entry[FI_RGBA_GREEN]);
											b += (weight * (double)entry[FI_RGBA_BLUE]);
											src_bits += src_pitch;
										}

										// clamp and place result in destination pixel
										dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
										dst_bits += dst_pitch;
									}
								}
							} else {
								// we do not have a palette
								for (unsigned x = 0; x < width; x++) {
									// work on column x in dst
									BYTE *dst_bits = dst_base + x * 3;

									// scale each column
									for (unsigned y = 0; y < dst_height; y++) {
										// loop through column
										const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
										const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
										const BYTE *src_bits = src_base + iLeft * src_pitch + x;
										double value = 0;

										for (unsigned i = 0; i < iLimit; i++) {
											// scan between boundaries
											// accumulate weighted effect of each neighboring pixel
											value += (weightsTable.getWeight(y, i) * (double)*src_bits);
											src_bits += src_pitch;
										}

										// clamp and place result in destination pixel
										const BYTE bval = (BYTE)CLAMP<int>((int)(value + 0.5), 0, 0xFF);
										dst_bits[FI_RGBA_RED]	= bval;
										dst_bits[FI_RGBA_GREEN]	= bval;
										dst_bits[FI_RGBA_BLUE]	= bval;
										dst_bits += dst_pitch;
									}
								}
							}
						}
						break;

						case 32:
						{
							// transparently convert the transparent 8-bit image to 32 bpp; 
							// we always have got a palette here
							for (unsigned x = 0; x < width; x++) {
								// work on column x in dst
								BYTE *dst_bits = dst_base + x * 4;

								// scale each column
								for (unsigned y = 0; y < dst_height; y++) {
									// loop through column
									const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
									const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
									const BYTE *src_bits = src_base + iLeft * src_pitch + x;
									double r = 0, g = 0, b = 0, a = 0;

									for (unsigned i = 0; i < iLimit; i++) {
										// scan between boundaries
										// accumulate weighted effect of each neighboring pixel
										const double weight = weightsTable.getWeight(y, i);
										const BYTE * const entry = (BYTE *)&src_pal[*src_bits];
										r += (weight * (double)entry[FI_RGBA_RED]);
										g += (weight * (double)entry[FI_RGBA_GREEN]);
										b += (weight * (double)entry[FI_RGBA_BLUE]);
										a += (weight * (double)entry[FI_RGBA_ALPHA]);
										src_bits += src_pitch;
									}

									// clamp and place result in destination pixel
									dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(r + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(g + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(b + 0.5), 0, 0xFF);
									dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int)(a + 0.5), 0, 0xFF);
									dst_bits += dst_pitch;
								}
							}
						}
						break;
					}
				}
				break;

				case 16:
				{
					// transparently convert the 16-bit non-transparent image to 24 bpp
					const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
					const WORD *const src_base = (WORD *)FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x;

					if (IS_FORMAT_RGB565(src)) {
						// image has 565 format
						for (unsigned x = 0; x < width; x++) {
							// work on column x in dst
							BYTE *dst_bits = dst_base + x * 3;

							// scale each column
							for (unsigned y = 0; y < dst_height; y++) {
								// loop through column
								const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
								const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
								const WORD *src_bits = src_base + iLeft * src_pitch + x;
								double r = 0, g = 0, b = 0;

								for (unsigned i = 0; i < iLimit; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(y, i);
									r += (weight * (double)((*src_bits & FI16_565_RED_MASK) >> FI16_565_RED_SHIFT));
									g += (weight * (double)((*src_bits & FI16_565_GREEN_MASK) >> FI16_565_GREEN_SHIFT));
									b += (weight * (double)((*src_bits & FI16_565_BLUE_MASK) >> FI16_565_BLUE_SHIFT));
									src_bits += src_pitch;
								}

								// clamp and place result in destination pixel
								dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(((r * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(((g * 0xFF) / 0x3F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(((b * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits += dst_pitch;
							}
						}
					} else {
						// image has 555 format
						for (unsigned x = 0; x < width; x++) {
							// work on column x in dst
							BYTE *dst_bits = dst_base + x * 3;

							// scale each column
							for (unsigned y = 0; y < dst_height; y++) {
								// loop through column
								const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
								const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
								const WORD *src_bits = src_base + iLeft * src_pitch + x;
								double r = 0, g = 0, b = 0;

								for (unsigned i = 0; i < iLimit; i++) {
									// scan between boundaries
									// accumulate weighted effect of each neighboring pixel
									const double weight = weightsTable.getWeight(y, i);
									r += (weight * (double)((*src_bits & FI16_555_RED_MASK) >> FI16_555_RED_SHIFT));
									g += (weight * (double)((*src_bits & FI16_555_GREEN_MASK) >> FI16_555_GREEN_SHIFT));
									b += (weight * (double)((*src_bits & FI16_555_BLUE_MASK) >> FI16_555_BLUE_SHIFT));
									src_bits += src_pitch;
								}

								// clamp and place result in destination pixel
								dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int)(((r * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int)(((g * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int)(((b * 0xFF) / 0x1F) + 0.5), 0, 0xFF);
								dst_bits += dst_pitch;
							}
						}
					}
				}
				break;

				case 24:
				{
					// scale the 24-bit transparent image into a 24 bpp destination image
					const unsigned src_pitch = FreeImage_GetPitch(src);
					const BYTE *const src_base = FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x * 3;

					for (unsigned x = 0; x < width; x++) {
						// work on column x in dst
						const unsigned index = x * 3;
						BYTE *dst_bits = dst_base + index;

						// scale each column
						for (unsigned y = 0; y < dst_height; y++) {
							// loop through column
							const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
							const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
							const BYTE *src_bits = src_base + iLeft * src_pitch + index;
							double r = 0, g = 0, b = 0;

							for (unsigned i = 0; i < iLimit; i++) {
								// scan between boundaries
								// accumulate weighted effect of each neighboring pixel
								const double weight = weightsTable.getWeight(y, i);
								r += (weight * (double)src_bits[FI_RGBA_RED]);
								g += (weight * (double)src_bits[FI_RGBA_GREEN]);
								b += (weight * (double)src_bits[FI_RGBA_BLUE]);
								src_bits += src_pitch;
							}

							// clamp and place result in destination pixel
							dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int) (r + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int) (g + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int) (b + 0.5), 0, 0xFF);
							dst_bits += dst_pitch;
						}
					}
				}
				break;

				case 32:
				{
					// scale the 32-bit transparent image into a 32 bpp destination image
					const unsigned src_pitch = FreeImage_GetPitch(src);
					const BYTE *const src_base = FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x * 4;

					for (unsigned x = 0; x < width; x++) {
						// work on column x in dst
						const unsigned index = x * 4;
						BYTE *dst_bits = dst_base + index;

						// scale each column
						for (unsigned y = 0; y < dst_height; y++) {
							// loop through column
							const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
							const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
							const BYTE *src_bits = src_base + iLeft * src_pitch + index;
							double r = 0, g = 0, b = 0, a = 0;

							for (unsigned i = 0; i < iLimit; i++) {
								// scan between boundaries
								// accumulate weighted effect of each neighboring pixel
								const double weight = weightsTable.getWeight(y, i);
								r += (weight * (double)src_bits[FI_RGBA_RED]);
								g += (weight * (double)src_bits[FI_RGBA_GREEN]);
								b += (weight * (double)src_bits[FI_RGBA_BLUE]);
								a += (weight * (double)src_bits[FI_RGBA_ALPHA]);
								src_bits += src_pitch;
							}

							// clamp and place result in destination pixel
							dst_bits[FI_RGBA_RED]	= (BYTE)CLAMP<int>((int) (r + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_GREEN]	= (BYTE)CLAMP<int>((int) (g + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_BLUE]	= (BYTE)CLAMP<int>((int) (b + 0.5), 0, 0xFF);
							dst_bits[FI_RGBA_ALPHA]	= (BYTE)CLAMP<int>((int) (a + 0.5), 0, 0xFF);
							dst_bits += dst_pitch;
						}
					}
				}
				break;
			}
		}
		break;

		case FIT_UINT16:
		{
			// 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) / width) / sizeof(WORD);

			const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD);
			WORD *const dst_base = (WORD *)FreeImage_GetBits(dst);

			const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
			const WORD *const src_base = (WORD *)FreeImage_GetBits(src)	+ src_offset_y * src_pitch + src_offset_x * wordspp;

			for (unsigned x = 0; x < width; x++) {
				// work on column x in dst
				const unsigned index = x * wordspp;	// pixel index
				WORD *dst_bits = dst_base + index;

				// scale each column
				for (unsigned y = 0; y < dst_height; y++) {
					// loop through column
					const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
					const WORD *src_bits = src_base + iLeft * src_pitch + index;
					double value = 0;

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

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

					dst_bits += dst_pitch;
				}
			}
		}
		break;

		case FIT_RGB16:
		{
			// 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) / width) / sizeof(WORD);

			const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD);
			WORD *const dst_base = (WORD *)FreeImage_GetBits(dst);

			const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
			const WORD *const src_base = (WORD *)FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x * wordspp;

			for (unsigned x = 0; x < width; x++) {
				// work on column x in dst
				const unsigned index = x * wordspp;	// pixel index
				WORD *dst_bits = dst_base + index;

				// scale each column
				for (unsigned y = 0; y < dst_height; y++) {
					// loop through column
					const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
					const WORD *src_bits = src_base + iLeft * src_pitch + index;
					double r = 0, g = 0, b = 0;

					for (unsigned i = 0; i < iLimit; i++) {
						// scan between boundaries
						// accumulate weighted effect of each neighboring pixel
						const double weight = weightsTable.getWeight(y, i);					
						r += (weight * (double)src_bits[0]);
						g += (weight * (double)src_bits[1]);
						b += (weight * (double)src_bits[2]);

						src_bits += src_pitch;
					}

					// clamp and place result in destination pixel
					dst_bits[0] = (WORD)CLAMP<int>((int)(r + 0.5), 0, 0xFFFF);
					dst_bits[1] = (WORD)CLAMP<int>((int)(g + 0.5), 0, 0xFFFF);
					dst_bits[2] = (WORD)CLAMP<int>((int)(b + 0.5), 0, 0xFFFF);

					dst_bits += dst_pitch;
				}
			}
		}
		break;

		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) / width) / sizeof(WORD);

			const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(WORD);
			WORD *const dst_base = (WORD *)FreeImage_GetBits(dst);

			const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(WORD);
			const WORD *const src_base = (WORD *)FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x * wordspp;

			for (unsigned x = 0; x < width; x++) {
				// work on column x in dst
				const unsigned index = x * wordspp;	// pixel index
				WORD *dst_bits = dst_base + index;

				// scale each column
				for (unsigned y = 0; y < dst_height; y++) {
					// loop through column
					const unsigned iLeft = weightsTable.getLeftBoundary(y);				// retrieve left boundary
					const unsigned iLimit = weightsTable.getRightBoundary(y) - iLeft;	// retrieve right boundary
					const WORD *src_bits = src_base + iLeft * src_pitch + index;
					double r = 0, g = 0, b = 0, a = 0;

					for (unsigned i = 0; i < iLimit; i++) {
						// scan between boundaries
						// accumulate weighted effect of each neighboring pixel
						const double weight = weightsTable.getWeight(y, i);					
						r += (weight * (double)src_bits[0]);
						g += (weight * (double)src_bits[1]);
						b += (weight * (double)src_bits[2]);
						a += (weight * (double)src_bits[3]);

						src_bits += src_pitch;
					}

					// clamp and place result in destination pixel
					dst_bits[0] = (WORD)CLAMP<int>((int)(r + 0.5), 0, 0xFFFF);
					dst_bits[1] = (WORD)CLAMP<int>((int)(g + 0.5), 0, 0xFFFF);
					dst_bits[2] = (WORD)CLAMP<int>((int)(b + 0.5), 0, 0xFFFF);
					dst_bits[3] = (WORD)CLAMP<int>((int)(a + 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) / width) / sizeof(float);

			const unsigned dst_pitch = FreeImage_GetPitch(dst) / sizeof(float);
			float *const dst_base = (float *)FreeImage_GetBits(dst);

			const unsigned src_pitch = FreeImage_GetPitch(src) / sizeof(float);
			const float *const src_base = (float *)FreeImage_GetBits(src) + src_offset_y * src_pitch + src_offset_x * floatspp;

			for (unsigned x = 0; x < width; x++) {
				// work on column x in dst
				const unsigned index = x * floatspp;	// pixel index
				float *dst_bits = (float *)dst_base + index;

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

					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;
					}

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