// xImaTran.cpp : Transformation functions
/* 07/08/2001 v1.00 - ing.davide.pizzolato@libero.it
 * CxImage version 5.71 25/Apr/2003
 */

#include "ximage.h"

#if CXIMAGE_SUPPORT_BASICTRANSFORMATIONS
////////////////////////////////////////////////////////////////////////////////
bool CxImage::GrayScale()
{
	if (!pDib) return false;
	if (head.biBitCount<=8){
		RGBQUAD* ppal=GetPalette();
		int gray;
		//converts the colors to gray, use the blue channel only
		for(DWORD i=0;i<head.biClrUsed;i++){
			gray=(int)RGB2GRAY(ppal[i].rgbRed,ppal[i].rgbGreen,ppal[i].rgbBlue);
			ppal[i].rgbBlue = (BYTE)gray;
		}
		// preserve transparency
		if (info.nBkgndIndex != -1) info.nBkgndIndex = ppal[info.nBkgndIndex].rgbBlue;
		//create a "real" 8 bit gray scale image
		if (head.biBitCount==8){
			BYTE *img=info.pImage;
			for(DWORD i=0;i<head.biSizeImage;i++) img[i]=ppal[img[i]].rgbBlue;
			SetGrayPalette();
		}
		//transform to 8 bit gray scale
		if (head.biBitCount==4 || head.biBitCount==1){
			CxImage ima(head.biWidth,head.biHeight,8,info.dwType);
			ima.SetGrayPalette();
			BYTE *img=ima.GetBits();
			long l=ima.GetEffWidth();
			for (long y=0;y<head.biHeight;y++){
				for (long x=0;x<head.biWidth; x++){
					img[x+y*l]=ppal[GetPixelIndex(x,y)].rgbBlue;
				}
			}
			Transfer(ima);
		}
	} else { //from RGB to 8 bit gray scale
		BYTE *iSrc=info.pImage;
		CxImage ima(head.biWidth,head.biHeight,8,info.dwType);
		ima.SetGrayPalette();
#if CXIMAGE_SUPPORT_ALPHA
		ima.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA
		BYTE *img=ima.GetBits();
		long l8=ima.GetEffWidth();
		long l=head.biWidth * 3;
		for(long y=0; y < head.biHeight; y++) {
			for(long x=0,x8=0; x < l; x+=3,x8++) {
				img[x8+y*l8]=(BYTE)RGB2GRAY(*(iSrc+x+2),*(iSrc+x+1),*(iSrc+x+0));
			}
			iSrc+=info.dwEffWidth;
		}
		Transfer(ima);
	}
	return true;
}
bool CxImage::Flip()
{
	if (!pDib) return false;
	CxImage* imatmp = new CxImage(*this,false,false,true);
	if (!imatmp) return false;
	BYTE *iSrc,*iDst;
	iSrc=info.pImage + (head.biHeight-1)*info.dwEffWidth;
	iDst=imatmp->info.pImage;
    for(long y=0; y < head.biHeight; y++){
		memcpy(iDst,iSrc,info.dwEffWidth);
		iSrc-=info.dwEffWidth;
		iDst+=info.dwEffWidth;
	}

#if CXIMAGE_SUPPORT_ALPHA
	imatmp->AlphaFlip();
#endif //CXIMAGE_SUPPORT_ALPHA

	Transfer(*imatmp);
	delete imatmp;
	return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Mirror()
{
	if (!pDib) return false;

	CxImage* imatmp = new CxImage(*this,false,false,true);
	if (!imatmp) return false;
	BYTE *iSrc,*iDst;
	long wdt=(head.biWidth-1) * (head.biBitCount==24 ? 3:1);
	iSrc=info.pImage + wdt;
	iDst=imatmp->info.pImage;
	long x,y;
	switch (head.biBitCount){
	case 24:
		for(y=0; y < head.biHeight; y++){
			for(x=0; x <= wdt; x+=3){
				*(iDst+x)=*(iSrc-x);
				*(iDst+x+1)=*(iSrc-x+1);
				*(iDst+x+2)=*(iSrc-x+2);
			}
			iSrc+=info.dwEffWidth;
			iDst+=info.dwEffWidth;
		}
		break;
	case 8:
		for(y=0; y < head.biHeight; y++){
			for(x=0; x <= wdt; x++)
				*(iDst+x)=*(iSrc-x);
			iSrc+=info.dwEffWidth;
			iDst+=info.dwEffWidth;
		}
		break;
	default:
		for(y=0; y < head.biHeight; y++){
			for(x=0; x <= wdt; x++)
				imatmp->SetPixelIndex(x,y,GetPixelIndex(wdt-x,y));
		}
	}

#if CXIMAGE_SUPPORT_ALPHA
	imatmp->AlphaMirror();
#endif //CXIMAGE_SUPPORT_ALPHA

	Transfer(*imatmp);
	delete imatmp;
	return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::RotateLeft(CxImage* iDst)
{
	if (!pDib) return false;

	long newWidth = GetHeight();
	long newHeight = GetWidth();

	CxImage imgDest;
	imgDest.CopyInfo(*this);
	imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
	imgDest.SetPalette(GetPalette());

	long x,x2,y,dlineup;
	
	// Speedy rotate for BW images <Robert Abram>
	if (head.biBitCount == 1) {
	
		BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp;
		div_t div_r;

		BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits();
		dbitsmax = bdest + imgDest.head.biSizeImage - 1;
		dlineup = 8 * imgDest.info.dwEffWidth - imgDest.head.biWidth;

		imgDest.Clear(0);
		for (y = 0; y < head.biHeight; y++) {
			// Figure out the Column we are going to be copying to
			div_r = div(y + dlineup, 8);
			// set bit pos of src column byte				
			bitpos = 1 << div_r.rem;
			srcdisp = bsrc + y * info.dwEffWidth;
			for (x = 0; x < (long)info.dwEffWidth; x++) {
				// Get Source Bits
				sbits = srcdisp + x;
				// Get destination column
				nrow = bdest + (x * 8) * imgDest.info.dwEffWidth + imgDest.info.dwEffWidth - 1 - div_r.quot;
				for (long z = 0; z < 8; z++) {
				   // Get Destination Byte
					dbits = nrow + z * imgDest.info.dwEffWidth;
					if ((dbits < bdest) || (dbits > dbitsmax)) break;
					if (*sbits & (128 >> z)) *dbits |= bitpos;
				}
			}
		}
	} else {
		for (x = 0; x < newWidth; x++){
			info.nProgress = (long)(100*x/newWidth); //<Anatoly Ivasyuk>
			x2=newWidth-x-1;
			for (y = 0; y < newHeight; y++){
				if(head.biClrUsed==0)	//RGB
					imgDest.SetPixelColor(x, y, GetPixelColor(y, x2));
				else					//PALETTE
					imgDest.SetPixelIndex(x, y, GetPixelIndex(y, x2));
			}
		}
	}

#if CXIMAGE_SUPPORT_ALPHA
	if (AlphaIsValid()){
		imgDest.AlphaCreate();
		for (x = 0; x < newWidth; x++){
			x2=newWidth-x-1;
			for (y = 0; y < newHeight; y++){
				imgDest.AlphaSet(x,y,AlphaGet(y, x2));
			}
		}
	}
#endif //CXIMAGE_SUPPORT_ALPHA

	//select the destination
	if (iDst) iDst->Transfer(imgDest);
	else Transfer(imgDest);
	return true;
}

////////////////////////////////////////////////////////////////////////////////
bool CxImage::RotateRight(CxImage* iDst)
{
	if (!pDib) return false;

	long newWidth = GetHeight();
	long newHeight = GetWidth();

	CxImage imgDest;
	imgDest.CopyInfo(*this);
	imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
	imgDest.SetPalette(GetPalette());

	long x,y,y2;
	// Speedy rotate for BW images <Robert Abram>
	if (head.biBitCount == 1) {
	
		BYTE *sbits, *dbits, *dbitsmax, bitpos, *nrow,*srcdisp;
		div_t div_r;

		BYTE *bsrc = GetBits(), *bdest = imgDest.GetBits();
		dbitsmax = bdest + imgDest.head.biSizeImage - 1;

		imgDest.Clear(0);
		for (y = 0; y < head.biHeight; y++) {
			// Figure out the Column we are going to be copying to
			div_r = div(y, 8);
			// set bit pos of src column byte				
			bitpos = 128 >> div_r.rem;
			srcdisp = bsrc + y * info.dwEffWidth;
			for (x = 0; x < (long)info.dwEffWidth; x++) {
				// Get Source Bits
				sbits = srcdisp + x;
				// Get destination column
				nrow = bdest + (imgDest.head.biHeight-1-(x*8)) * imgDest.info.dwEffWidth + div_r.quot;
				for (long z = 0; z < 8; z++) {
				   // Get Destination Byte
					dbits = nrow - z * imgDest.info.dwEffWidth;
					if ((dbits < bdest) || (dbits > dbitsmax)) break;
					if (*sbits & (128 >> z)) *dbits |= bitpos;
				}
			}
		}
	} else {
		for (y = 0; y < newHeight; y++){
			info.nProgress = (long)(100*y/newHeight); //<Anatoly Ivasyuk>
			y2=newHeight-y-1;
			for (x = 0; x < newWidth; x++){
				if(head.biClrUsed==0)	//RGB
					imgDest.SetPixelColor(x, y, GetPixelColor(y2, x));
				else					//PALETTE
					imgDest.SetPixelIndex(x, y, GetPixelIndex(y2, x));
			}
		}
	}

#if CXIMAGE_SUPPORT_ALPHA
	if (AlphaIsValid()){
		imgDest.AlphaCreate();
		for (y = 0; y < newHeight; y++){
			y2=newHeight-y-1;
			for (x = 0; x < newWidth; x++){
				imgDest.AlphaSet(x,y,AlphaGet(y2, x));
			}
		}
	}
#endif //CXIMAGE_SUPPORT_ALPHA

	//select the destination
	if (iDst) iDst->Transfer(imgDest);
	else Transfer(imgDest);
	return true;
}

////////////////////////////////////////////////////////////////////////////////
bool CxImage::Negative()
{
	if (!pDib) return false;

	if (head.biBitCount<=8){
		if (IsGrayScale()){ //GRAYSCALE, selection
			if (pSelection){
				for(long y=info.rSelectionBox.bottom; y<info.rSelectionBox.top; y++){
					for(long x=info.rSelectionBox.left; x<info.rSelectionBox.right; x++){
#if CXIMAGE_SUPPORT_SELECTION
						if (SelectionIsInside(x,y))
#endif //CXIMAGE_SUPPORT_SELECTION
						{
							SetPixelIndex(x,y,(BYTE)(255-GetPixelIndex(x,y)));
						}
					}
				}
			} else {
				for(long y=0; y<head.biHeight; y++){
					for(long x=0; x<head.biWidth; x++){
						SetPixelIndex(x,y,(BYTE)(255-GetPixelIndex(x,y)));
					}
				}
			}
		} else { //PALETTE, full image
			RGBQUAD* ppal=GetPalette();
			for(DWORD i=0;i<head.biClrUsed;i++){
				ppal[i].rgbBlue =(BYTE)(255-ppal[i].rgbBlue);
				ppal[i].rgbGreen =(BYTE)(255-ppal[i].rgbGreen);
				ppal[i].rgbRed =(BYTE)(255-ppal[i].rgbRed);
			}
		}
	} else {
		if (pSelection==NULL){ //RGB, full image
			BYTE *iSrc=info.pImage;
			for(unsigned long i=0; i < head.biSizeImage ; i++){
				*iSrc=(BYTE)~(*(iSrc));
				iSrc++;
			}
		} else { // RGB with selection
			RGBQUAD color;
			for(long y=info.rSelectionBox.bottom; y<info.rSelectionBox.top; y++){
				for(long x=info.rSelectionBox.left; x<info.rSelectionBox.right; x++){
#if CXIMAGE_SUPPORT_SELECTION
					if (SelectionIsInside(x,y))
#endif //CXIMAGE_SUPPORT_SELECTION
					{
						color = GetPixelColor(x,y);
						color.rgbRed = (BYTE)(255-color.rgbRed);
						color.rgbGreen = (BYTE)(255-color.rgbGreen);
						color.rgbBlue = (BYTE)(255-color.rgbBlue);
						SetPixelColor(x,y,color);
					}
				}
			}
		}
		//<DP> invert transparent color too
		info.nBkgndColor.rgbBlue = (BYTE)(255-info.nBkgndColor.rgbBlue);
		info.nBkgndColor.rgbGreen = (BYTE)(255-info.nBkgndColor.rgbGreen);
		info.nBkgndColor.rgbRed = (BYTE)(255-info.nBkgndColor.rgbRed);
	}
	return true;
}

////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_BASICTRANSFORMATIONS
////////////////////////////////////////////////////////////////////////////////
#if CXIMAGE_SUPPORT_TRANSFORMATION
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
bool CxImage::Rotate(float angle, CxImage* iDst)
{
	if (!pDib) return false;

//  $Id: ximatran.cpp,v 1.1 2003/10/12 15:57:18 sirob Exp $
//  Copyright (c) 1996-1998 Ulrich von Zadow

	// Negative the angle, because the y-axis is negative.
	double ang = -angle*acos((float)0)/90;
	int newWidth, newHeight;
	int nWidth = GetWidth();
	int nHeight= GetHeight();
	double cos_angle = cos(ang);
	double sin_angle = sin(ang);

	// Calculate the size of the new bitmap
	POINT p1={0,0};
	POINT p2={nWidth,0};
	POINT p3={0,nHeight};
	POINT p4={nWidth-1,nHeight};
	POINT newP1,newP2,newP3,newP4, leftTop, rightTop, leftBottom, rightBottom;

	newP1.x = p1.x;
	newP1.y = p1.y;
	newP2.x = (long)(p2.x*cos_angle - p2.y*sin_angle);
	newP2.y = (long)(p2.x*sin_angle + p2.y*cos_angle);
	newP3.x = (long)(p3.x*cos_angle - p3.y*sin_angle);
	newP3.y = (long)(p3.x*sin_angle + p3.y*cos_angle);
	newP4.x = (long)(p4.x*cos_angle - p4.y*sin_angle);
	newP4.y = (long)(p4.x*sin_angle + p4.y*cos_angle);

	leftTop.x = min(min(newP1.x,newP2.x),min(newP3.x,newP4.x));
	leftTop.y = min(min(newP1.y,newP2.y),min(newP3.y,newP4.y));
	rightBottom.x = max(max(newP1.x,newP2.x),max(newP3.x,newP4.x));
	rightBottom.y = max(max(newP1.y,newP2.y),max(newP3.y,newP4.y));
	leftBottom.x = leftTop.x;
	leftBottom.y = rightBottom.y;
	rightTop.x = rightBottom.x;
	rightTop.y = leftTop.y;

	newWidth = rightTop.x - leftTop.x;
	newHeight= leftBottom.y - leftTop.y;
	CxImage imgDest;
	imgDest.CopyInfo(*this);
	imgDest.Create(newWidth,newHeight,GetBpp(),GetType());
	imgDest.SetPalette(GetPalette());

#if CXIMAGE_SUPPORT_ALPHA
	if(AlphaIsValid())	//MTA: Fix for rotation problem when the image has an alpha channel
	{
		imgDest.AlphaCreate();
		imgDest.AlphaClear();
	}
#endif //CXIMAGE_SUPPORT_ALPHA

	int x,y,newX,newY,oldX,oldY;

	if (head.biClrUsed==0){ //RGB
		for (y = leftTop.y, newY = 0; y<=leftBottom.y; y++,newY++){
			info.nProgress = (long)(100*newY/newHeight);
			if (info.nEscape) break;
			for (x = leftTop.x, newX = 0; x<=rightTop.x; x++,newX++){
				oldX = (long)(x*cos_angle + y*sin_angle - 0.5);
				oldY = (long)(y*cos_angle - x*sin_angle - 0.5);
				imgDest.SetPixelColor(newX,newY,GetPixelColor(oldX,oldY));
#if CXIMAGE_SUPPORT_ALPHA
				imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY));				//MTA: copy the alpha value
#endif //CXIMAGE_SUPPORT_ALPHA
			}
		}
	} else { //PALETTE
		for (y = leftTop.y, newY = 0; y<=leftBottom.y; y++,newY++){
			info.nProgress = (long)(100*newY/newHeight);
			if (info.nEscape) break;
			for (x = leftTop.x, newX = 0; x<=rightTop.x; x++,newX++){
				oldX = (long)(x*cos_angle + y*sin_angle - 0.5);
				oldY = (long)(y*cos_angle - x*sin_angle - 0.5);
				imgDest.SetPixelIndex(newX,newY,GetPixelIndex(oldX,oldY));
#if CXIMAGE_SUPPORT_ALPHA
				imgDest.AlphaSet(newX,newY,AlphaGet(oldX,oldY));				//MTA: copy the alpha value
#endif //CXIMAGE_SUPPORT_ALPHA
			}
		}
	}
	//select the destination
	if (iDst) iDst->Transfer(imgDest);
	else Transfer(imgDest);

	return true;
}

////////////////////////////////////////////////////////////////////////////////
bool CxImage::Rotate180(CxImage* iDst)
{
	if (!pDib) return false;

	long wid = GetWidth();
	long ht = GetHeight();

	CxImage imgDest;
	imgDest.CopyInfo(*this);
	imgDest.Create(wid,ht,GetBpp(),GetType());
	imgDest.SetPalette(GetPalette());

#if CXIMAGE_SUPPORT_ALPHA
	if (AlphaIsValid())	imgDest.AlphaCreate();
#endif //CXIMAGE_SUPPORT_ALPHA

	long x,y,y2;
	for (y = 0; y < ht; y++){
		info.nProgress = (long)(100*y/ht); //<Anatoly Ivasyuk>
		y2=ht-y-1;
		for (x = 0; x < wid; x++){
			if(head.biClrUsed==0)//RGB
				imgDest.SetPixelColor(wid-x-1, y2, GetPixelColor(x, y));
			else  //PALETTE
				imgDest.SetPixelIndex(wid-x-1, y2, GetPixelIndex(x, y));

#if CXIMAGE_SUPPORT_ALPHA
			if (AlphaIsValid())	imgDest.AlphaSet(wid-x-1, y2,AlphaGet(x, y));
#endif //CXIMAGE_SUPPORT_ALPHA

		}
	}

	//select the destination
	if (iDst) iDst->Transfer(imgDest);
	else Transfer(imgDest);
	return true;
}

////////////////////////////////////////////////////////////////////////////////
bool CxImage::Resample(long newx, long newy, int mode, CxImage* iDst)
{
	if (newx==0 || newy==0) return false;

	if (head.biWidth==newx && head.biHeight==newy){
		if (iDst) iDst->Copy(*this);
		return true;
	}

	float xScale, yScale, fX, fY;
	xScale = (float)head.biWidth  / (float)newx;
	yScale = (float)head.biHeight / (float)newy;

	CxImage newImage;
	newImage.CopyInfo(*this);
	newImage.Create(newx,newy,head.biBitCount,GetType());
	newImage.SetPalette(GetPalette());
	if (!newImage.IsValid()) return false;

	if (head.biWidth==newx && head.biHeight==newy){
		Transfer(newImage);
		return true;
	}

	switch (mode) {
	case 1: // nearest pixel
	{ 
		for(long y=0; y<newy; y++){
			info.nProgress = (long)(100*y/newy);
			if (info.nEscape) break;
			fY = y * yScale;
			for(long x=0; x<newx; x++){
				fX = x * xScale;
				newImage.SetPixelColor(x,y,GetPixelColor((long)fX,(long)fY));
			}
		}
		break;
	}
	case 2: // bicubic interpolation by Blake L. Carlson <blake-carlson(at)uiowa(dot)edu
	{
		float f_x, f_y, a, b, rr, gg, bb, r1, r2;
		int   i_x, i_y, xx, yy;
		RGBQUAD rgb;
		for(long y=0; y<newy; y++){
			info.nProgress = (long)(100*y/newy);
			if (info.nEscape) break;
			f_y = (float) y * yScale;
			i_y = (int) floor(f_y);
			a   = f_y - (float)floor(f_y);
			for(long x=0; x<newx; x++){
				f_x = (float) x * xScale;
				i_x = (int) floor(f_x);
				b   = f_x - (float)floor(f_x);

				rr = gg = bb = 0.0F;
				for(int m=-1; m<3; m++) {
					r1 = b3spline((float) m - a);
					for(int n=-1; n<3; n++) {
						r2 = b3spline(-1.0F*((float)n - b)); 
						xx = i_x+n+2;
						yy = i_y+m+2;
						if (xx<0) xx=0;
						if (yy<0) yy=0;
						if (xx>=head.biWidth) xx=head.biWidth-1;
						if (yy>=head.biHeight) yy=head.biHeight-1;
						rgb = GetPixelColor(xx,yy);
						rr += rgb.rgbRed * r1 * r2;
						gg += rgb.rgbGreen * r1 * r2;
						bb += rgb.rgbBlue * r1 * r2;
					}
				}
				newImage.SetPixelColor(x,y,RGB(rr,gg,bb));
			}
		}
		break;
	}
	default: // bilinear interpolation
		if (!(head.biWidth>newx && head.biHeight>newy && head.biBitCount==24)) {
			//© 1999 Steve McMahon (steve@dogma.demon.co.uk)
			long ifX, ifY, ifX1, ifY1, xmax, ymax;
			float ir1, ir2, ig1, ig2, ib1, ib2, dx, dy;
			BYTE r,g,b;
			RGBQUAD rgb1, rgb2, rgb3, rgb4;
			xmax = head.biWidth-1;
			ymax = head.biHeight-1;
			for(long y=0; y<newy; y++){
				info.nProgress = (long)(100*y/newy);
				if (info.nEscape) break;
				fY = y * yScale;
				ifY = (int)fY;
				ifY1 = min(ymax, ifY+1);
				dy = fY - ifY;
				for(long x=0; x<newx; x++){
					fX = x * xScale;
					ifX = (int)fX;
					ifX1 = min(xmax, ifX+1);
					dx = fX - ifX;
					// Interpolate using the four nearest pixels in the source
					if (head.biClrUsed){
						rgb1=GetPaletteColor(GetPixelIndex(ifX,ifY));
						rgb2=GetPaletteColor(GetPixelIndex(ifX1,ifY));
						rgb3=GetPaletteColor(GetPixelIndex(ifX,ifY1));
						rgb4=GetPaletteColor(GetPixelIndex(ifX1,ifY1));
					}
					else {
						BYTE* iDst;
						iDst = info.pImage + ifY*info.dwEffWidth + ifX*3;
						rgb1.rgbBlue = *iDst++;	rgb1.rgbGreen= *iDst++;	rgb1.rgbRed =*iDst;
						iDst = info.pImage + ifY*info.dwEffWidth + ifX1*3;
						rgb2.rgbBlue = *iDst++;	rgb2.rgbGreen= *iDst++;	rgb2.rgbRed =*iDst;
						iDst = info.pImage + ifY1*info.dwEffWidth + ifX*3;
						rgb3.rgbBlue = *iDst++;	rgb3.rgbGreen= *iDst++;	rgb3.rgbRed =*iDst;
						iDst = info.pImage + ifY1*info.dwEffWidth + ifX1*3;
						rgb4.rgbBlue = *iDst++;	rgb4.rgbGreen= *iDst++;	rgb4.rgbRed =*iDst;
					}
					// Interplate in x direction:
					ir1 = rgb1.rgbRed   * (1 - dy) + rgb3.rgbRed   * dy;
					ig1 = rgb1.rgbGreen * (1 - dy) + rgb3.rgbGreen * dy;
					ib1 = rgb1.rgbBlue  * (1 - dy) + rgb3.rgbBlue  * dy;
					ir2 = rgb2.rgbRed   * (1 - dy) + rgb4.rgbRed   * dy;
					ig2 = rgb2.rgbGreen * (1 - dy) + rgb4.rgbGreen * dy;
					ib2 = rgb2.rgbBlue  * (1 - dy) + rgb4.rgbBlue  * dy;
					// Interpolate in y:
					r = (BYTE)(ir1 * (1 - dx) + ir2 * dx);
					g = (BYTE)(ig1 * (1 - dx) + ig2 * dx);
					b = (BYTE)(ib1 * (1 - dx) + ib2 * dx);
					// Set output
					newImage.SetPixelColor(x,y,RGB(r,g,b));
				}
			} 
		} else {
			//high resolution shrink, thanks to Henrik Stellmann <henrik.stellmann@volleynet.de>
			const long ACCURACY = 1000;
			long i,j; // index for faValue
			long x,y; // coordinates in  source image
			BYTE* pSource;
			BYTE* pDest = newImage.info.pImage;
			long* naAccu  = new long[3 * newx + 3];
			long* naCarry = new long[3 * newx + 3];
			long* naTemp;
			long  nWeightX,nWeightY;
			float fEndX;
			long nScale = (long)(ACCURACY * xScale * yScale);

			memset(naAccu,  0, sizeof(long) * 3 * newx);
			memset(naCarry, 0, sizeof(long) * 3 * newx);

			int u, v = 0; // coordinates in dest image
			float fEndY = yScale - 1.0f;
			for (y = 0; y < head.biHeight; y++){
				info.nProgress = (long)(100*y/head.biHeight); //<Anatoly Ivasyuk>
				if (info.nEscape) break;
				pSource = info.pImage + y * info.dwEffWidth;
				u = i = 0;
				fEndX = xScale - 1.0f;
				if ((float)y < fEndY) {       // complete source row goes into dest row
					for (x = 0; x < head.biWidth; x++){
						if ((float)x < fEndX){       // complete source pixel goes into dest pixel
							for (j = 0; j < 3; j++)	naAccu[i + j] += (*pSource++) * ACCURACY;
						} else {       // source pixel is splitted for 2 dest pixels
							nWeightX = (long)(((float)x - fEndX) * ACCURACY);
							for (j = 0; j < 3; j++){
								naAccu[i] += (ACCURACY - nWeightX) * (*pSource);
								naAccu[3 + i++] += nWeightX * (*pSource++);
							}
							fEndX += xScale;
							u++;
						}
					}
				} else {       // source row is splitted for 2 dest rows       
					nWeightY = (long)(((float)y - fEndY) * ACCURACY);
					for (x = 0; x < head.biWidth; x++){
						if ((float)x < fEndX){       // complete source pixel goes into 2 pixel
							for (j = 0; j < 3; j++){
								naAccu[i + j] += ((ACCURACY - nWeightY) * (*pSource));
								naCarry[i + j] += nWeightY * (*pSource++);
							}
						} else {       // source pixel is splitted for 4 dest pixels
							nWeightX = (int)(((float)x - fEndX) * ACCURACY);
							for (j = 0; j < 3; j++) {
								naAccu[i] += ((ACCURACY - nWeightY) * (ACCURACY - nWeightX)) * (*pSource) / ACCURACY;
								*pDest++ = (BYTE)(naAccu[i] / nScale);
								naCarry[i] += (nWeightY * (ACCURACY - nWeightX) * (*pSource)) / ACCURACY;
								naAccu[i + 3] += ((ACCURACY - nWeightY) * nWeightX * (*pSource)) / ACCURACY;
								naCarry[i + 3] = (nWeightY * nWeightX * (*pSource)) / ACCURACY;
								i++;
								pSource++;
							}
							fEndX += xScale;
							u++;
						}
					}
					if (u < newx){ // possibly not completed due to rounding errors
						for (j = 0; j < 3; j++) *pDest++ = (BYTE)(naAccu[i++] / nScale);
					}
					naTemp = naCarry;
					naCarry = naAccu;
					naAccu = naTemp;
					memset(naCarry, 0, sizeof(int) * 3);    // need only to set first pixel zero
					pDest = newImage.info.pImage + (++v * newImage.info.dwEffWidth);
					fEndY += yScale;
				}
			}
			if (v < newy){	// possibly not completed due to rounding errors
				for (i = 0; i < 3 * newx; i++) *pDest++ = (BYTE)(naAccu[i] / nScale);
			}
			delete [] naAccu;
			delete [] naCarry;
		}
	}

#if CXIMAGE_SUPPORT_ALPHA
	if (AlphaIsValid()){
		newImage.AlphaCreate();
		for(long y=0; y<newy; y++){
			fY = y * yScale;
			for(long x=0; x<newx; x++){
				fX = x * xScale;
				newImage.AlphaSet(x,y,AlphaGet((long)fX,(long)fY));
			}
		}
	}
#endif //CXIMAGE_SUPPORT_ALPHA

	//select the destination
	if (iDst) iDst->Transfer(newImage);
	else Transfer(newImage);

	return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::DecreaseBpp(DWORD nbit, bool errordiffusion, RGBQUAD* ppal)
{
	if (!pDib) return false;
	if (head.biBitCount <  nbit) return false;
	if (head.biBitCount == nbit) return true;

	long er,eg,eb;
	RGBQUAD c,ce;

	CxImage tmp;
	tmp.CopyInfo(*this);
	tmp.Create(head.biWidth,head.biHeight,(WORD)nbit,info.dwType);

#if CXIMAGE_SUPPORT_SELECTION
	tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
	tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA

	switch (tmp.head.biBitCount){
	case 1:
		if (ppal) tmp.SetPalette(ppal,16);
		else {
			tmp.SetPaletteColor(0,0,0,0);
			tmp.SetPaletteColor(1,255,255,255);
		}
		break;
	case 4:
		if (ppal) tmp.SetPalette(ppal,16);
		else tmp.SetStdPalette();
		break;
	case 8:
		if (ppal) tmp.SetPalette(ppal);
		else tmp.SetStdPalette();
		break;
	default:
		return false;
	}

	for (long y=0;y<head.biHeight;y++){
		if (info.nEscape) break;
		info.nProgress = (long)(100*y/head.biHeight);
		for (long x=0;x<head.biWidth;x++){
			if (!errordiffusion){
				tmp.SetPixelColor(x,y,GetPixelColor(x,y));
			} else {
				c=GetPixelColor(x,y);
				tmp.SetPixelColor(x,y,c);

				ce=tmp.GetPixelColor(x,y);
				er=(long)c.rgbRed - (long)ce.rgbRed;
				eg=(long)c.rgbGreen - (long)ce.rgbGreen;
				eb=(long)c.rgbBlue - (long)ce.rgbBlue;

				c = GetPixelColor(x+1,y);
				c.rgbRed = (BYTE)min(255L,max(0L,(long)c.rgbRed + ((er*7)/16)));
				c.rgbGreen = (BYTE)min(255L,max(0L,(long)c.rgbGreen + ((eg*7)/16)));
				c.rgbBlue = (BYTE)min(255L,max(0L,(long)c.rgbBlue + ((eb*7)/16)));
				SetPixelColor(x+1,y,c);
				int coeff;
				for(int i=-1; i<2; i++){
					switch(i){
					case -1:
						coeff=2; break;
					case 0:
						coeff=4; break;
					case 1:
						coeff=1; break;
					}
					c = GetPixelColor(x+i,y+1);
					c.rgbRed = (BYTE)min(255L,max(0L,(long)c.rgbRed + ((er * coeff)/16)));
					c.rgbGreen = (BYTE)min(255L,max(0L,(long)c.rgbGreen + ((eg * coeff)/16)));
					c.rgbBlue = (BYTE)min(255L,max(0L,(long)c.rgbBlue + ((eb * coeff)/16)));
					SetPixelColor(x+i,y+1,c);
				}
			}
		}
	}

	if (head.biBitCount==1){
		tmp.SetPaletteColor(0,0,0,0);
		tmp.SetPaletteColor(1,255,255,255);
	}

	Transfer(tmp);
	return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::IncreaseBpp(DWORD nbit)
{
	if (!pDib) return false;
	switch (nbit){
	case 4:
		{
			if (head.biBitCount==4) return true;
			if (head.biBitCount>4) return false;

			CxImage tmp;
			tmp.CopyInfo(*this);
			tmp.Create(head.biWidth,head.biHeight,4,info.dwType);
			tmp.SetPalette(GetPalette(),GetNumColors());


#if CXIMAGE_SUPPORT_SELECTION
			tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
			tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA

			for (long y=0;y<head.biHeight;y++){
				if (info.nEscape) break;
				for (long x=0;x<head.biWidth;x++){
					tmp.SetPixelIndex(x,y,GetPixelIndex(x,y));
				}
			}
			Transfer(tmp);
			return true;
		}
	case 8:
		{
			if (head.biBitCount==8) return true;
			if (head.biBitCount>8) return false;

			CxImage tmp;
			tmp.CopyInfo(*this);
			tmp.Create(head.biWidth,head.biHeight,8,info.dwType);
			tmp.SetPalette(GetPalette(),GetNumColors());

#if CXIMAGE_SUPPORT_SELECTION
			tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
			tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA

			for (long y=0;y<head.biHeight;y++){
				if (info.nEscape) break;
				for (long x=0;x<head.biWidth;x++){
					tmp.SetPixelIndex(x,y,GetPixelIndex(x,y));
				}
			}
			Transfer(tmp);
			return true;
		}
	case 24:
		{
			if (head.biBitCount==24) return true;
			if (head.biBitCount>24) return false;

			CxImage tmp;
			tmp.CopyInfo(*this);
			tmp.Create(head.biWidth,head.biHeight,24,info.dwType);

#if CXIMAGE_SUPPORT_SELECTION
			tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
			tmp.AlphaCopy(*this);
			if (AlphaPaletteIsValid() && !AlphaIsValid()) tmp.AlphaCreate();
#endif //CXIMAGE_SUPPORT_ALPHA

			for (long y=0;y<head.biHeight;y++){
				if (info.nEscape) break;
				for (long x=0;x<head.biWidth;x++){
					tmp.SetPixelColor(x,y,GetPixelColor(x,y),true);
				}
			}
			Transfer(tmp);
			return true;
		}
	}
	return false;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Dither(long method)
{
	if (!pDib) return false;
	if (head.biBitCount == 1) return true;
	
	switch (method){
	case 1:
	{
		// Multi-Level Ordered-Dithering by Kenny Hoff (Oct. 12, 1995)
		#define NumRows 4
		#define NumCols 4
		#define NumIntensityLevels 2
		#define NumRowsLessOne (NumRows-1)
		#define NumColsLessOne (NumCols-1)
		#define RowsXCols (NumRows*NumCols)
		#define MaxIntensityVal 255
		#define MaxDitherIntensityVal (NumRows*NumCols*(NumIntensityLevels-1))

		int DitherMatrix[NumRows][NumCols] = {{0,8,2,10}, {12,4,14,6}, {3,11,1,9}, {15,7,13,5} };
		
		unsigned char Intensity[NumIntensityLevels] = { 0,1 };                       // 2 LEVELS B/W
		//unsigned char Intensity[NumIntensityLevels] = { 0,255 };                       // 2 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,127,255 };                   // 3 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,85,170,255 };                // 4 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,63,127,191,255 };            // 5 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,51,102,153,204,255 };        // 6 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,42,85,127,170,213,255 };     // 7 LEVELS
		//unsigned char Intensity[NumIntensityLevels] = { 0,36,73,109,145,182,219,255 }; // 8 LEVELS
		int DitherIntensity, DitherMatrixIntensity, Offset, DeviceIntensity;
		unsigned char DitherValue;
  
		GrayScale();

		CxImage tmp(head.biWidth,head.biHeight,1,info.dwType);

#if CXIMAGE_SUPPORT_SELECTION
			tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
			tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA

		for (long y=0;y<head.biHeight;y++){
			info.nProgress = (long)(100*y/head.biHeight);
			if (info.nEscape) break;
			for (long x=0;x<head.biWidth;x++){

				DeviceIntensity = GetPixelIndex(x,y);
				DitherIntensity = DeviceIntensity*MaxDitherIntensityVal/MaxIntensityVal;
				DitherMatrixIntensity = DitherIntensity % RowsXCols;
				Offset = DitherIntensity / RowsXCols;
				if (DitherMatrix[y&NumRowsLessOne][x&NumColsLessOne] < DitherMatrixIntensity)
					DitherValue = Intensity[1+Offset];
				else
					DitherValue = Intensity[0+Offset];

				tmp.SetPixelIndex(x,y,DitherValue);
			}
		}
		tmp.SetPaletteColor(0,0,0,0);
		tmp.SetPaletteColor(1,255,255,255);
		Transfer(tmp);
		break;
	}
	default:
	{
		// Floyd-Steinberg error diffusion (Thanks to Steve McMahon)
		long error,nlevel,coeff;
		BYTE level;

		GrayScale();

		CxImage tmp(head.biWidth,head.biHeight,1,info.dwType);

#if CXIMAGE_SUPPORT_SELECTION
		tmp.SelectionCopy(*this);
#endif //CXIMAGE_SUPPORT_SELECTION

#if CXIMAGE_SUPPORT_ALPHA
		tmp.AlphaCopy(*this);
#endif //CXIMAGE_SUPPORT_ALPHA

		for (long y=0;y<head.biHeight;y++){
			info.nProgress = (long)(100*y/head.biHeight);
			if (info.nEscape) break;
			for (long x=0;x<head.biWidth;x++){

				level=GetPixelIndex(x,y);
				if (level > 128){
					tmp.SetPixelIndex(x,y,1);
					error = level-255;
				} else {
					tmp.SetPixelIndex(x,y,0);
					error = level;
				}

				nlevel = GetPixelIndex(x+1,y) + (error * 7)/16;
				level = (BYTE)min(255,max(0,(int)nlevel));
				SetPixelIndex(x+1,y,level);
				for(int i=-1; i<2; i++){
					switch(i){
					case -1:
						coeff=3; break;
					case 0:
						coeff=5; break;
					case 1:
						coeff=1; break;
					}
					nlevel = GetPixelIndex(x+i,y+1) + (error * coeff)/16;
					level = (BYTE)min(255,max(0,(int)nlevel));
					SetPixelIndex(x+i,y+1,level);
				}
			}
		}
		tmp.SetPaletteColor(0,0,0,0);
		tmp.SetPaletteColor(1,255,255,255);
		Transfer(tmp);
	}
	}
	return true;
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Crop(long left, long top, long right, long bottom, CxImage* iDst)
{
	if (!pDib) return false;

	long startx = max(0L,min(left,head.biWidth));
	long endx = max(0L,min(right,head.biWidth));
	long starty = head.biHeight - max(0L,min(top,head.biHeight));
	long endy = head.biHeight - max(0L,min(bottom,head.biHeight));

	if (startx==endx || starty==endy) return false;

	if (startx>endx) {long tmp=startx; startx=endx; endx=tmp;}
	if (starty>endy) {long tmp=starty; starty=endy; endy=tmp;}

	CxImage tmp(endx-startx,endy-starty,head.biBitCount,info.dwType);
	tmp.SetPalette(GetPalette(),head.biClrUsed);
	tmp.info.nBkgndIndex = info.nBkgndIndex;
	tmp.info.nBkgndColor = info.nBkgndColor;

	switch (head.biBitCount) {
	case 1:
	case 4:
	{
		for(long y=starty, yd=0; y<endy; y++, yd++){
			info.nProgress = (long)(100*y/endy); //<Anatoly Ivasyuk>
			for(long x=startx, xd=0; x<endx; x++, xd++){
				tmp.SetPixelIndex(xd,yd,GetPixelIndex(x,y));
			}
		}
		break;
	}
	case 8:
	case 24:
	{
		BYTE* pDest = tmp.info.pImage;
		BYTE* pSrc = info.pImage + starty * info.dwEffWidth + (startx*head.biBitCount >> 3);
		for(long y=starty; y<endy; y++){
			info.nProgress = (long)(100*y/endy); //<Anatoly Ivasyuk>
			memcpy(pDest,pSrc,tmp.info.dwEffWidth);
			pDest+=tmp.info.dwEffWidth;
			pSrc+=info.dwEffWidth;
		}
    }
	}
	//select the destination
	if (iDst) iDst->Transfer(tmp);
	else Transfer(tmp);

	return true;
}
////////////////////////////////////////////////////////////////////////////////
float CxImage::b3spline(float x)
{
	float a, b, c, d;

	if((x + 2.0f) <= 0.0f) a = 0.0f; else a = (float)pow((x + 2.0f), 3.0f);
	if((x + 1.0f) <= 0.0f) b = 0.0f; else b = (float)pow((x + 1.0f), 3.0f);
	if(x <= 0) c = 0.0f; else c = (float)pow(x, 3.0f);  
	if((x - 1.0f) <= 0.0f) d = 0.0f; else d = (float)pow((x - 1.0f), 3.0f);

	return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d)));
}
////////////////////////////////////////////////////////////////////////////////
bool CxImage::Skew(float xgain, float ygain, long xpivot, long ypivot)
{
	if (!pDib) return false;
	long nx,ny;
	CxImage tmp(*this,pSelection!=0,true,true);
	long xmin,xmax,ymin,ymax;
	if (pSelection){
		xmin = info.rSelectionBox.left; xmax = info.rSelectionBox.right;
		ymin = info.rSelectionBox.bottom; ymax = info.rSelectionBox.top;
	} else {
		xmin = ymin = 0;
		xmax = head.biWidth; ymax=head.biHeight;
	}
	for(long y=ymin; y<ymax; y++){
		info.nProgress = (long)(100*y/head.biHeight);
		if (info.nEscape) break;
		for(long x=xmin; x<xmax; x++){
#if CXIMAGE_SUPPORT_SELECTION
			if (SelectionIsInside(x,y))
#endif //CXIMAGE_SUPPORT_SELECTION
			{
				nx = x + (long)(xgain*(y - ypivot));
				ny = y + (long)(ygain*(x - xpivot));
				if (head.biClrUsed==0){
					tmp.SetPixelColor(x,y,GetPixelColor(nx,ny));
				} else {
					tmp.SetPixelIndex(x,y,GetPixelIndex(nx,ny));
				}
#if CXIMAGE_SUPPORT_ALPHA
				tmp.AlphaSet(x,y,AlphaGet(nx,ny));
#endif //CXIMAGE_SUPPORT_ALPHA
			}
		}
	}
	Transfer(tmp);
	return true;
}
////////////////////////////////////////////////////////////////////////////////
#endif //CXIMAGE_SUPPORT_TRANSFORMATION
