网上很多位图旋转的程序,但是一般都是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结束