本文将阐释如何使用GDI+将图像连续顺时针或逆时针旋转90度。文中给出的代码只在内存中旋转了图像,没有包含显示部分,需要包含的头文件和使用GDI+需要的初始化和关闭也略去了。
代码如下:
if (m_bitmap == nullptr)
return;
int width = m_bitmap->GetWidth();
int height = m_bitmap->GetHeight();
Gdiplus::Bitmap* rotatedBitmap = new Gdiplus::Bitmap(height, width, PixelFormat32bppARGB);
Gdiplus::Graphics graphics(rotatedBitmap);
Gdiplus::RectF aRect(-(width - height) / 2.0, -(height - width) / 2.0, width, height);
Gdiplus::Matrix matrix;
matrix.RotateAt(rotateAngle, Gdiplus::PointF(height / 2.0, width/2.0));
graphics.SetTransform(&matrix);
graphics.DrawImage(m_bitmap, aRect);
graphics.ResetTransform();
delete m_bitmap;
m_bitmap = rotatedBitmap;
代码中m_bitmap是指向待旋转位图的指针。类型是Bitmap*
。width
和height
分别是它的宽度(水平长度)和长度(竖直长度)。
接下来我们将一行一行地理解以上代码。
-
Gdiplus::Bitmap* rotatedBitmap = new Gdiplus::Bitmap(height, width, PixelFormat32bppARGB);
这一行我们创建了一个新的Bitmap
对象,用于保存旋转后的图像,并让rotatedBitmap
作为指向该对象的指针。由于使用了new
关键字在堆上分配了内存,之后需要使用delete
关键字释放它。调用
Bitmap
的构造函数时,我们传递了三个参数:height, width, PixelFormat32bppARGB
。其中,height
和width
分别指定了新Bitmap
对象的宽和高,注意这里用height指定宽度,而用width指定了高度,这是因为旋转90度后的图像,长和宽是互换了的。作为存放旋转后的图像的容器,这里的新Bitmap
对象的长和宽就应该是原来图像长和宽的互换。PixelFormat32bppARGB
指定了每个像素用32比特保存,顺序是ARGB。 -
Gdiplus::Graphics graphics(rotatedBitmap);
这一行我们创建了一个Graphics
对象,并将其绑定到rotatedBitmap
指向的位图对象。Graphics
对象十分重要,其内部封装了一个设备上下文,对新Bitmap位图的绘图操作都要通过graphics
来实现。 -
Gdiplus::RectF aRect(-(width - height) / 2.0, -(height - width) / 2.0, width, height);
这一步创建的aRect
是一个矩形区域,将用于指定我们将在新位图的哪个区域绘制原位图。创建该矩形区域时传入了四个参数,它们分别指定了:以新位图的左上角为原点,向右为x正半轴,向下为y正半轴时,矩形区域左上角的x和y坐标;矩形区域的宽度,即x轴上的长度;矩形区域的长度,即y轴上的长度。这里有一些工作细节。当我们执行graphics.DrawImage(m_bitmap, aRect);
将原来的位图绘制到新位图上时,GDI+首先会比较原来位图与aRect
表示的矩形区域的长和宽,根据比较结果先对原位图进行缩放。然后GDI+再将缩放后原位图的左上角与aRect
表示的矩形区域的左上角重合放置。之后,GDI+才会根据对graphics
的设置开始对原位图进行变换,并将变换后的图像垂直投影到新位图的区域中,绘制完毕。所以,若我们想让原位图旋转后能够恰到好处地嵌入到新位图的区域中,我们创建矩形区域需要传递的参数是-(width - height) / 2.0, -(height - width) / 2.0, width, height
。这个矩形区域的中心与新位图区域的中心重合,同时width
是矩形的宽,height
是矩形的高。这样保证了原位图不会先被缩放然后再被旋转,而是会保持原来的长和宽。就这样,原位图在没有被改动的情况下,将正好被嵌入到aRect
指定的矩形区域中。 -
Gdiplus::Matrix matrix; matrix.RotateAt(rotateAngle, Gdiplus::PointF(height / 2.0, width/2.0)); graphics.SetTransform(&matrix);
第一行,我们创建了一个用于变换图像的
Matrix
对象matrix
。
第二行,我们使用了matrix
的RotateAT()
方法,传入了两个参数,分别是旋转角度rotateAngle
和旋转点PointF
。这个PointF
是在与graphics
关联的坐标系,也就是新位图区域的坐标系中指定的(以新位图的左上角为原点,向右为x轴正方向,向下为y轴正方向)。可以看出,该旋转点正是新位图也是aRect
指定的矩形区域的中心点。当rotatedAngle
为90(顺时针旋转90度)或270(逆时针旋转90度)时,旋转后的位图将可以恰到好处地嵌入到新位图中。
第三行,将设置好的旋转矩阵应用于graphics
。 -
graphics.DrawImage(m_bitmap, aRect); graphics.ResetTransform(); delete m_bitmap; m_bitmap = rotatedBitmap;
第一行,调用
DrawImage()
方法将原位图绘制到新位图上。该过程中原位图将被旋转。
第二行,将应用于graphics
上的变换重置。
第三行,释放m_bitmap
指向的存放原位图的堆内存。
第四行,使m_bitmap
指向存放新位图的堆内存。
以一张图片更好地理解此过程:
图中灰色区域是新位图的区域,蓝色区域是aRect
指定的矩形区域。aRect
前两个参数指定的点就是图中的点A。当执行graphics.DrawImage(m_bitmap, aRect);
时,GDI+首先比较原位图和aRect
指定的矩形区域的大小,在这里它们是一样大的,所以GDI+不会将原位图缩放。然后GDI+将原位图的左上角与A点重合,将原位图嵌入了aRect
指定的区域中。接下来,GDI+对原位图应用之前对graphics
应用的变换。图中C点是两个矩形区域的中心,也是RotateAt()
方法中第二个参数对应的点。根据对matrix
的设置,位于蓝色区域的原位图将按C点被旋转90度,从而正好与灰色区域重合。最后,GDI+将旋转后的位图垂直投影到灰色区域中。由于这里旋转后的位图将与灰色区域重合,所以这一步就不会发生图像的损失。
到此,代码的功能和工作原理介绍完毕。