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结束


以下是一个简单的bmp单色位图抗锯齿算法MFC代码实现: ``` void CMyView::OnDraw(CDC* pDC) { CRect rect; GetClientRect(&rect); // 创建一个内存DC,用于绘制位图 CDC memDC; memDC.CreateCompatibleDC(pDC); // 创建一个位图对象 CBitmap bmp; bmp.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); CBitmap* pOldBitmap = memDC.SelectObject(&bmp); // 绘制图形 memDC.FillSolidRect(&rect, RGB(255, 255, 255)); memDC.MoveTo(10, 10); memDC.LineTo(100, 100); // 对位图进行抗锯齿处理 Antialiasing(&memDC, rect); // 将位图绘制到屏幕上 pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); // 清理资 memDC.SelectObject(pOldBitmap); bmp.DeleteObject(); memDC.DeleteDC(); } void CMyView::Antialiasing(CDC* pDC, const CRect& rect) { // 获取位图信息 BITMAPINFO bmpInfo = { 0 }; bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmpInfo.bmiHeader.biWidth = rect.Width(); bmpInfo.bmiHeader.biHeight = -rect.Height(); bmpInfo.bmiHeader.biPlanes = 1; bmpInfo.bmiHeader.biBitCount = 32; bmpInfo.bmiHeader.biCompression = BI_RGB; // 创建一个DIBSection,用于读取位图像素 void* pBits = nullptr; HBITMAP hDib = CreateDIBSection(pDC->GetSafeHdc(), &bmpInfo, DIB_RGB_COLORS, &pBits, nullptr, 0); if (hDib == nullptr) return; // 将位图复制到DIBSection CDC dibDC; dibDC.CreateCompatibleDC(pDC); HBITMAP hOldDib = dibDC.SelectObject(hDib); dibDC.BitBlt(0, 0, rect.Width(), rect.Height(), pDC, 0, 0, SRCCOPY); // 对DIBSection像素进行抗锯齿处理 for (int y = 0; y < rect.Height(); y++) { for (int x = 0; x < rect.Width(); x++) { // 获取当前像素的颜色 COLORREF color = ((COLORREF*)pBits)[y * rect.Width() + x]; // 对颜色进行抗锯齿处理 COLORREF newColor = AntialiasingColor(color, x, y, rect.Width(), rect.Height(), pBits); // 将新颜色写回到DIBSection ((COLORREF*)pBits)[y * rect.Width() + x] = newColor; } } // 将DIBSection复制回位图 pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dibDC, 0, 0, SRCCOPY); // 清理资 dibDC.SelectObject(hOldDib); DeleteObject(hDib); } COLORREF CMyView::AntialiasingColor(COLORREF color, int x, int y, int width, int height, void* pBits) { // 获取当前像素的颜色分量 int r = GetRValue(color); int g = GetGValue(color); int b = GetBValue(color); // 周围像素的颜色分量之和 int sumR = 0, sumG = 0, sumB = 0; int count = 0; // 计算周围像素的颜色分量之和 for (int i = -1; i <= 1; i++) { for (int j = -1; j <= 1; j++) { int newX = x + i; int newY = y + j; // 判断像素是否在图像范围内 if (newX >= 0 && newX < width && newY >= 0 && newY < height) { // 获取周围像素的颜色 COLORREF newColor = ((COLORREF*)pBits)[newY * width + newX]; // 将颜色分量加到总和 sumR += GetRValue(newColor); sumG += GetGValue(newColor); sumB += GetBValue(newColor); // 计数器加1 count++; } } } // 计算平均颜色分量 int avgR = sumR / count; int avgG = sumG / count; int avgB = sumB / count; // 计算新颜色 return RGB(avgR, avgG, avgB); } ``` 该代码实现了一个简单的bmp单色位图抗锯齿算法,通过对位图像素进行平滑处理,使图像看起来更加平滑自然。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值