DIB(設備無關位圖)旋轉任意角度算法(單色位圖)

      網上很多位圖旋轉的程序,但是一般都是8位、24位、32位位圖的旋轉,這些大於8位的位圖每個像素都可以用整個字節表示,所以用char數組很容易實現對應像素複製。但是要對單色位圖進行旋轉的話,就涉及到按位複製,因爲每個像素是用一個字節中的某一位表示的。

      我自己寫了一個單色位圖旋轉的算法:

#include "math.h"
#define PI 3.14159
//角度到弧度轉化的宏
#define RADIAN(angle) ((angle)*PI/180.0) 
HGLOBAL WINAPI RotateDIB(LPSTR lpDIB, int iRotateAngle)//單色位圖旋轉
{	
	LONG	lWidth,lHeight;// 源圖像的寬度和高度	
	LONG	lNewWidth,lNewHeight;// 旋轉後圖像的寬度和高度

	LONG	lLineBytes;// 圖像每行的字節數
	LONG	lNewLineBytes;//旋轉後圖像的寬度(lNewWidth',必須是4的倍數)	
	
	LPSTR	lpDIBBits; //指向源圖像的指針		
	LPSTR	lpNewDIBBits;
	
	LPSTR	lpSrc;    // 指向源象素的指針	
	LPSTR	lpDst;// 指向旋轉圖像對應象素的指針	
	
	HDIB	hDIB;// 旋轉後新DIB句柄	
	LPSTR	lpNewDIB;// 指向旋轉圖像的指針(源圖像指針是參數lpDIB)

	LPBITMAPINFOHEADER lpbmi;// 指向BITMAPINFO結構的指針(Win3.0)
	LPBITMAPCOREHEADER lpbmc;// 指向BITMAPCOREINFO結構的指針
		
	LONG	i,j;// 循環變量(象素在新DIB中的座標)
	LONG	i0,j0;// 象素在源DIB中的座標
	
	float	fRotateAngle;// 旋轉角度(弧度)
	float	fSina, fCosa;// 旋轉角度的正弦和餘弦
	
	// 源圖四個角的座標(以圖像中心爲座標系原點)
	float	fSrcX1,fSrcY1,fSrcX2,fSrcY2,fSrcX3,fSrcY3,fSrcX4,fSrcY4;	
	float	fDstX1,fDstY1,fDstX2,fDstY2,fDstX3,fDstY3,fDstX4,fDstY4;// 旋轉後四個角的座標(以圖像中心爲座標系原點)
	
	// 兩個中間常量
	float	f1,f2;
	
	lpDIBBits = ::FindDIBBits(lpDIB);// 找到源DIB圖像象素起始位置		
	lWidth = ::DIBWidth(lpDIB);  // 獲取圖像的"寬度"(4的倍數)		
	lHeight = ::DIBHeight(lpDIB);// 獲取圖像的高度	
	lLineBytes = WIDTHBYTES(lWidth);//計算圖像每行的字節數(單色位圖)
	
	fRotateAngle = (float) RADIAN(iRotateAngle);// 將旋轉角度從度轉換到弧度		
	fSina = (float) sin((double)fRotateAngle);// 計算旋轉角度的正弦
	fCosa = (float) cos((double)fRotateAngle);// 計算旋轉角度的餘弦
	
	// 計算原圖的四個角的座標(以圖像中心爲座標系原點)
	fSrcX1 = (float) (- (lWidth  - 1) / 2);
	fSrcY1 = (float) (  (lHeight - 1) / 2);
	fSrcX2 = (float) (  (lWidth  - 1) / 2);
	fSrcY2 = (float) (  (lHeight - 1) / 2);
	fSrcX3 = (float) (- (lWidth  - 1) / 2);
	fSrcY3 = (float) (- (lHeight - 1) / 2);
	fSrcX4 = (float) (  (lWidth  - 1) / 2);
	fSrcY4 = (float) (- (lHeight - 1) / 2);
	
	// 計算新圖四個角的座標(以圖像中心爲座標系原點)
	fDstX1 =  fCosa * fSrcX1 + fSina * fSrcY1;
	fDstY1 = -fSina * fSrcX1 + fCosa * fSrcY1;
	fDstX2 =  fCosa * fSrcX2 + fSina * fSrcY2;
	fDstY2 = -fSina * fSrcX2 + fCosa * fSrcY2;
	fDstX3 =  fCosa * fSrcX3 + fSina * fSrcY3;
	fDstY3 = -fSina * fSrcX3 + fCosa * fSrcY3;
	fDstX4 =  fCosa * fSrcX4 + fSina * fSrcY4;
	fDstY4 = -fSina * fSrcX4 + fCosa * fSrcY4;	
	
	lNewWidth  = (LONG) ( max( fabs(fDstX4 - fDstX1), fabs(fDstX3 - fDstX2) ) + 0.5);// 計算旋轉後的圖像實際寬度		
	lNewHeight = (LONG) ( max( fabs(fDstY4 - fDstY1), fabs(fDstY3 - fDstY2) )  + 0.5);// 計算旋轉後的圖像高度
	lNewLineBytes = WIDTHBYTES(lNewWidth);// 計算新圖像每行的字節數(單色圖)
	
	// 兩個常數,這樣不用以後每次都計算了
	f1 = (float) (-0.5 * (lNewWidth - 1) * fCosa - 0.5 * (lNewHeight - 1) * fSina + 0.5 * (lWidth  - 1));
	f2 = (float) ( 0.5 * (lNewWidth - 1) * fSina - 0.5 * (lNewHeight - 1) * fCosa + 0.5 * (lHeight - 1));
	
	
	// 分配內存,以保存新DIB
	hDIB = (HDIB) ::GlobalAlloc(GHND, lNewLineBytes * lNewHeight + *(LPDWORD)lpDIB + ::PaletteSize(lpDIB));	
	if (hDIB == NULL)// 判斷是否內存分配失敗
	{		
		return NULL;// 分配內存失敗
	}
	// 鎖定內存
	lpNewDIB =  (char * )::GlobalLock((HGLOBAL) hDIB);	
	memcpy(lpNewDIB, lpDIB, *(LPDWORD)lpDIB + ::PaletteSize(lpDIB));// 複製DIB信息頭和調色板
	
	lpNewDIBBits = ::FindDIBBits(lpNewDIB);// 找到新DIB象素起始位置
	lpbmi = (LPBITMAPINFOHEADER)lpNewDIB;// 獲取指針
	lpbmc = (LPBITMAPCOREHEADER)lpNewDIB;
	// 更新DIB中圖像的高度和寬度
	if (IS_WIN30_DIB(lpNewDIB))// 對於Windows 3.0 DIB
	{	
		lpbmi->biWidth = lNewWidth;
		lpbmi->biHeight = lNewHeight;
	}
	else// 對於其它格式的DIB
	{		
		lpbmc->bcWidth = (unsigned short) lNewWidth;
		lpbmc->bcHeight = (unsigned short) lNewHeight;
	}

	for(i = 0; i < lNewHeight; i++)// 針對圖像每行進行操作
	{	
		// 注意此處寬度和高度是新DIB的寬度和高度
		lpDst = (char *)lpNewDIBBits + lNewLineBytes * (lNewHeight - 1 - i);//第i行的數據(DIB是從下向上存儲圖像的)
		for(j = 0; j < lNewWidth; j++)// 針對圖像每列進行操作
		{		 
		    int NewBytes = j/8;//一行中第幾個字節
			int NewBits = j%8;//確定是在該字節中的第幾位
			BYTE byNewByte = lpDst[NewBytes];//取一個字節,即8個象素	
			
			// 計算該象素在源DIB中的座標
			i0 = (LONG) (-((float) j) * fSina + ((float) i) * fCosa + f2 + 0.5);//旋轉前的目標行
			j0 = (LONG) ( ((float) j) * fCosa + ((float) i) * fSina + f1 + 0.5);//旋轉前的目標列		
			// 判斷是否在源圖範圍內
			if( (j0 >= 0) && (j0 < lWidth) && (i0 >= 0) && (i0 < lHeight))
			{				
				lpSrc = (char *)lpDIBBits + lLineBytes * (lHeight - 1 - i0);//指向源圖像中第i0行
				int SrcBytes = j0/8;//該像素在這一行的第幾個字節
				int SrcBits  = j0%8;//第幾位
				BYTE bySrcByte = lpSrc[SrcBytes];//對應源圖中的字節
				BYTE byTmp = (bySrcByte>>(7-SrcBits))&0x01;//取出源圖像中的位,7-0		
			
				//注意:最高位代表最左邊的象素!
				switch(NewBits)
				{
				case 0:
					byNewByte = (byTmp<<7)|byNewByte; //賦值給新DIB中對應的位
					break;
				case 1:
					byNewByte = (byTmp<<6)|byNewByte; 
					break;
				case 2:
					byNewByte = (byTmp<<5)|byNewByte; 
					break;
				case 3:
					byNewByte = (byTmp<<4)|byNewByte; 
					break;
				case 4:
					byNewByte = (byTmp<<3)|byNewByte; 
					break;
				case 5:
					byNewByte = (byTmp<<2)|byNewByte; 
					break;
				case 6:
					byNewByte = (byTmp<<1)|byNewByte; 
					break;
				case 7:
					byNewByte = byTmp|byNewByte; 
					break;
				}

				lpDst[NewBytes] = byNewByte;		
			}
			else
			{		
				lpDst[NewBytes] = 255;// 對於源圖中沒有的象素,直接賦值爲255
			}
			
		}//列j結束	
	}//行i結束
	return hDIB;// 返回
}

具體調用可以參考我上傳的資源 :  VS2010實現單色位圖旋轉


上面這種算法邊緣鋸齒非常嚴重,下面是邊緣處理稍微好一些的算法:

memset( lpNewDIBBits, 0xff, lNewLineBytes*lNewHeight );//先將每個像素設爲白色
for(i = 0; i < lNewHeight; i++)// 針對圖像每行進行操作
{	
	for(j = 0; j < lNewWidth; j++)// 針對圖像每列進行操作
	{		 
		// 計算該象素在源DIB中的座標
		i0 = (LONG) (-((float) j) * fSina + ((float) i) * fCosa + f2 + 0.5);//旋轉前的目標行
		j0 = (LONG) ( ((float) j) * fCosa + ((float) i) * fSina + f1 + 0.5);//旋轉前的目標列		
		// 判斷是否在源圖範圍內
		if( (j0 >= 0) && (j0 < lWidth) && (i0 >= 0) && (i0 < lHeight))
		{				
			BYTE mask = *((char *)lpDIBBits+lLineBytes*i0+j0/8)&(0x80>>j0%8);
			mask = mask ? (0x80 >> j%8) : 0;
			//lpSrc = (char *)lpDIBBits + lLineBytes * (lHeight - 1 - i0);//指向源圖像中第i0行
			*((char*)lpNewDIBBits + lNewLineBytes*(i) + (j/8)) &= ~(0x80 >> j%8);
			*((char*)lpNewDIBBits + lNewLineBytes*(i) + (j/8)) |= mask;
		}
	}//列j結束	
}//行i結束


發佈了75 篇原創文章 · 獲贊 95 · 訪問量 54萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章