很多朋友可能都会遇到图片旋转的相关编程,一般的图像处理的书里面都会用一个自己写的函数对一个大的数组进行操作来解决旋转问题,这些函数往往还需要很多输入参数,如图片的大小,位的深度之类的,为了处理多种格式的图片我们往往需要重复编写代码,这样非常麻烦。
其实Windows 本身就提供了一个API函数SetWorldTransForm来解决图片旋转、位移及其他变形,这个函数是对一个设备上下文DC进行操作,通过坐标转换来实现各种功能的。
SetWorldTransForm内部的算法其实相当于用线性代数里矩阵与一个向量相乘的办法来解决图形的变换,只要我们知道要乘上的变换方程是哪一个,就能进行各种变换(不止是旋转)。
参考这篇文章的内容:Using SetWorldTransform() to rotate basic shapes by any angle - CodeProject
msdn对于SetWorldTransForm的参考说明
对于选定DC的任何坐标(x, y)将被SetWindowsForm设定的变换转换为坐标(x’, y’),两个坐标的对应关系是
x’ = x * eM11 + y * eM12 + eDx; y’ = x * eM12 + y * eM22 + eDy;对于一个转换的操作,数学上的转换方式是:
x2 = cos(q)*(x1-x0) – sin(q)*(y1-y0) + x0; y2 = sin(q)*(x1-x0) + cos(q)*(y1-y0) + y0;于是,我们可以得到SetWorldTransForm的一组参数:
xform.eM11 = cos(q); xform.eM12 = sin(q); xform.eM21 = -sin(q); xform.eM22 = cos(q); xform.eDx = x0 – cos(q)*x0 + sin(q)*y0; xform.eDy = y0 – cos(q)*y0 - sin(q)*x0;
#include <math.h> // 将DC旋转一定的角度 inline int RotateDC(HDC hDc, int iAngle, POINT centerPt) { int nGraphicsMode = SetGraphicsMode(hDc, GM_ADVANCED); XFORM xform; if ( iAngle != 0 ) { double fangle = (double)iAngle / 180. * 3.1415926; xform.eM11 = (float)cos(fangle); xform.eM12 = (float)sin(fangle); xform.eM21 = (float)-sin(fangle); xform.eM22 = (float)cos(fangle); xform.eDx = (float)(centerPt.x - cos(fangle)*centerPt.x + sin(fangle)*centerPt.y); xform.eDy = (float)(centerPt.y - cos(fangle)*centerPt.y - sin(fangle)*centerPt.x); SetWorldTransform(hDc, &xform); } return nGraphicsMode; } // 恢复旋转过的DC inline void RestoreRotatedDC(HDC hDc, int nGraphicsMode) { XFORM xform; xform.eM11 = (float)1.0; xform.eM12 = (float)0; xform.eM21 = (float)0; xform.eM22 = (float)1.0; xform.eDx = (float)0; xform.eDy = (float)0; SetWorldTransform(hDc, &xform); SetGraphicsMode(hDc, nGraphicsMode); }
@1.如何将一个图片转换后显示出来,或者得到旋转过的Bitmat对象?
此部分代码取自我自己的部分程序代码,是用GDI+绘制图片的,前面一半代码是绘制旋转的图片,后面是按正常坐标(不旋转)画一个扇形。
// 获取控件尺寸 CRect rc; GetClientRect(rc); CDC MemDC; MemDC.CreateCompatibleDC(pDC); // 创建内存DC CBitmap MemBmp; MemBmp.CreateCompatibleBitmap(pDC, rc.Width(), rc.Height()); CBitmap *pOldBmp = MemDC.SelectObject(&MemBmp); Graphics graphics(MemDC.m_hDC); // 绘制背景图片 CPoint centerPt((INT)((rc.left + rc.right) * 0.5), (INT)((rc.top + rc.bottom) * 0.5)); // 获取一个旋转过的Graphics对象 int nGraphicsMode = RotateDC(MemDC.m_hDC, 35, centerPt); Graphics rotatedGraphics(MemDC.m_hDC); RestoreRotatedDC(MemDC.m_hDC, nGraphicsMode); // 绘制图片 Image image(m_pStream); rotatedGraphics.DrawImage(&image, (INT)rc.left, (INT)rc.top, (INT)(rc.right-rc.left), (INT)(rc.bottom-rc.top)); // 扇形的外接长方开左上角坐标计算 INT ellipseWidth = (INT)((rc.right-rc.left) * TYRE_SCALE); INT ellipseHeight = (INT)((rc.right-rc.left) * TYRE_SCALE); INT ellipseX = (INT)((rc.left + rc.right - ellipseWidth) * 0.5); INT ellipseY = (INT)((rc.top + rc.bottom - ellipseHeight) * 0.5); // 绘制当前位置 SolidBrush redBrush(Color(128, 255, 0, 0)); SolidBrush greenBrush(Color(128, 0, 255, 0)); Rect ellipseRect(ellipseX, ellipseY, ellipseWidth, ellipseHeight); REAL startAngle = -90.0f; REAL sweepAngle = 90.0f; graphics.FillPie(&greenBrush, ellipseRect, startAngle, m_fCurrentDegree); pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &MemDC, 0, 0, SRCCOPY); MemDC.SelectObject(pOldBmp); MemBmp.DeleteObject(); MemDC.DeleteDC();
在经过这个处理之后,MemBmp里的内容就是旋转后的图片。如果直接调用BitBlt将MemDC的内容拷贝到显示DC中,可以直接显示旋转后的图片。
@2.在GDI+中的特别注意事项
注意Graphics对象如果是在RotateDC前创建的,那使用那个对象绘制的任何图形都不没有旋转的(如上面代码中第11行的graphics,尽管下面使用了RotateDC,但它绘制的效果并不旋转,只有像下面这段代码中的graphics对象绘制的效果才翻转,尽管后面的RestoreRotatedDC已经将MemDC恢复成不旋转,但这不影响rotatedGraphics的绘制)
// 获取一个旋转过的Graphics对象 int nGraphicsMode = RotateDC(MemDC.m_hDC, 35, centerPt); Graphics rotatedGraphics(MemDC.m_hDC); RestoreRotatedDC(MemDC.m_hDC, nGraphicsMode);
@3.可执行的演示程序
请参考Using SetWorldTransform() to rotate basic shapes by any angle - CodeProject