一、文章撰写缘由
主要是分享一下自己搞了挺久才解决的问题,相信大家也看了网上好多人写过GDI+实现图片旋转的相关博客和帖子,他们实现的功能主要有:
①实现jpg/bmp/png等格式图片的旋转与显示
②只能实现jpg图片格式的旋转并保存为文件,bmp/png等都不可以
1.相信大家都会有这个疑问,为什么只有jpg可以实现图片旋转并保存,而bmp/png等格式不可以?
答:原因在于JPEG编码器支持Transformation参数类型,而BMP和PNG等编码器不支持这种参数类型;所以jpg图片实现旋转90/180/270度后保存为jpg格式文件很简单,只需要设置完
Transformation参数类型,再设置对应旋转角度参数即可完成,如:90度旋转参数为EncoderValueTransformRotate90,这里只做个简单介绍,后面会详细讲到。
二、图像编码器和解码器
GDI+提供了Image类和Bitmap类用于存储和处理内存图像。GDI+借助于图像编码器写入磁盘文件,借助图像解码器从磁盘文件中载入图像。编码器将Image或Bitmap对象的数据转换为指定的磁盘文件格式。解码器则磁盘文件数据转换为Image和Bitmap对象所需的格式。GDI+内建编码器和解码器支持如下文件类型:BMP GIF JPEG PNG TIFF。
GDI+提供GetImageEncoders函数用于判断您的计算机支持哪些图像编码器;提供GetImageDecoders函数用于判断您的计算机支持哪些图像解码器。两个函数都返回的是ImageCodecInfo对象数组,所以在调用函数之前,必须分配足够的缓冲区来容纳这个数组,可以调用GetImageEncodersSize或GetImageDecodersSize方法来获取你所需要的缓冲区的大小。
这里通过控制台程序举获取支持哪些编码器的例子,支持哪些解码器的代码你们自己下去写写:
-
#include <windows.h>
-
#include <gdiplus.h>
-
#include <stdio.h>
-
using namespace Gdiplus;
-
INT main()
-
{
-
// Initialize GDI+.
-
GdiplusStartupInput gdiplusStartupInput;
-
ULONG_PTR gdiplusToken;
-
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
-
UINT num; // number of image encoders
-
UINT size; // size, in bytes, of the image encoder array
-
ImageCodecInfo* pImageCodecInfo;
-
// How many encoders are there?
-
// How big (in bytes) is the array of all ImageCodecInfo objects?
-
GetImageEncodersSize(&num, &size);
-
// Create a buffer large enough to hold the array of ImageCodecInfo
-
// objects that will be returned by GetImageEncoders.
-
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
-
// GetImageEncoders creates an array of ImageCodecInfo objects
-
// and copies that array into a previously allocated buffer.
-
// The third argument, imageCodecInfo, is a pointer to that buffer.
-
GetImageEncoders(num, size, pImageCodecInfo);
-
// Display the graphics file format (MimeType)
-
// for each ImageCodecInfo object.
-
for(UINT j = 0; j < num; ++j)
-
{
-
wprintf(L"%s\n", pImageCodecInfo[j].MimeType);
-
}
-
free(pImageCodecInfo);
-
GdiplusShutdown(gdiplusToken);
-
return 0;
-
}
运行前面的控制台程序,结果如下面,列出了已安装的编码器:
image/bmp
image/jpeg
image/gif
image/tiff
image/png
三、获取编码器的类标识符
大家看到这个“获取编码器的类标识符”的名称,感觉很奇怪,觉得为什么需要这个东西?其实我们在保存图片文件的时候,必须知道你要用什么格式的编码器去进行编码,保存为对应格式的图片。由于GDI+中没有实现这个GetEncoderClsid函数,来获取编码器的类标识符,所以需要我们自己实现。
下面的GetEncoderClsid函数就是用来获取一个编码器的MIME(多用途网际邮件扩充协议)类型,然后返回该编码器的类标识符(CLSID)。GDI+中编码器的MIME类型如下:
image/bmp
image/jpeg
image/gif
image/tiff
image/png
GetImageEncoders函数获取一个ImageCodeInfo对象数组。如果数组中的某个ImageCodecInfo对象是你所需要的编码器,函数就返回该ImageCodecInfo对象的索引值,然后将它的CLISD复制到pClisd指针变量中,;如果函数调用失败,返回-1。
-
int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
-
{
-
UINT num = 0; // number of image encoders
-
UINT size = 0; // size of the image encoder array in bytes
-
ImageCodecInfo* pImageCodecInfo = NULL;
-
GetImageEncodersSize(&num, &size);
-
if(size == 0)
-
return -1; // Failure
-
pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
-
if(pImageCodecInfo == NULL)
-
return -1; // Failure
-
GetImageEncoders(num, size, pImageCodecInfo);
-
for(UINT j = 0; j < num; ++j)
-
{
-
if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
-
{
-
*pClsid = pImageCodecInfo[j].Clsid;
-
free(pImageCodecInfo);
-
return j; // Success
-
}
-
}
-
free(pImageCodecInfo);
-
return -1; // Failure
-
}
下面举个控制台程序调用GetEncoderClsid函数获取PNG编码器的CLSID的例子:
-
#include <windows.h>
-
#include <gdiplus.h>
-
#include <stdio.h>
-
using namespace Gdiplus;
-
#include "GdiplusHelperFunctions.h"
-
INT main()
-
{
-
// Initialize GDI+.
-
GdiplusStartupInput gdiplusStartupInput;
-
ULONG_PTR gdiplusToken;
-
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
-
CLSID encoderClsid;
-
INT result;
-
WCHAR strGuid[39];
-
result = GetEncoderClsid(L"image/png", &encoderClsid);
-
if(result < 0)
-
{
-
printf("The PNG encoder is not installed.\n");
-
}
-
else
-
{
-
StringFromGUID2(encoderClsid, strGuid, 39);
-
printf("An ImageCodecInfo object representing the PNG encoder\n");
-
printf("was found at position %d in the array.\n", result);
-
wprintf(L"The CLSID of the PNG encoder is %s.\n", strGuid);
-
}
-
GdiplusShutdown(gdiplusToken);
-
return 0;
-
}
运行前面的控制台程序,输出类似的结果为:
An ImageCodecInfo object representing the PNG encoder
was found at position 4 in the array.
The CLSID of the PNG encoder is {557CF406-1A04-11D3-9A73-0000F81EF32E}.
四、JPG格式图片旋转90/180/270度并保存为文件
在前面也提到了jgp格式图片实现旋转并保存,只需要EncoderParameters编码参数Guid设置为Transformation参数类型,再设置参数Value为对应旋转角度即可完成我们的需求,下面是经过用对话框程序测试的代码,如在OnPaint函数添加如下代码即可:
-
CClientDC dc(this);
-
Graphics graphics(dc); //在dc画布上构建graphics画笔
-
Image image(L"13-main按下.jpg"); //加载jpg图片
-
//因为图片旋转以图片左上角的坐标为旋转中心点进行逆时针旋转,所以先把它的坐标平移到dc画布的(图片高度,0)坐标处,实现逆时针旋转90度后在(0,0)处绘制显示图片
-
graphics.TranslateTransform(image.GetHeight(),0);
-
graphics.RotateTransform(90); //设置图片旋转显示的角度为90度
-
//graphics.RotateTransform(180); //设置图片旋转显示的角度为180度
-
//graphics.RotateTransform(270); //设置图片旋转显示的角度为270度
-
graphics.DrawImage(&image, 0, 0,image.GetWidth(),image.GetHeight());
-
-
CLSID jpgClsid;
-
GetEncoderClsid(L"image/jpeg", &jpgClsid); //这个是上面我们自己实现的获取编码器的类标识符函数
-
-
EncoderParameters encoderParameters; //编码器保存jpg图片的参数设定
-
ULONG transformation;
-
encoderParameters.Count = 1;
-
encoderParameters.Parameter[0].Guid = EncoderTransformation; //设置Guid的值为编码器实现图片变换
-
encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
-
encoderParameters.Parameter[0].NumberOfValues = 1;
-
-
// Rotate and save the image.
-
transformation = EncoderValueTransformRotate90; //设置保存jpg文件的旋转角度为90度
-
//transformation = EncoderValueTransformRotate180; //设置保存jpg文件的旋转角度为1800度
-
//transformation = EncoderValueTransformRotate270; //设置保存jpg文件的旋转角度为270度
-
encoderParameters.Parameter[0].Value = &transformation;
-
image.Save(L"13-main按下_90.jpg",&jpgClsid,&encoderParameters); //保存为指定名字的jpg格式图片
图片效果如下:
下面讲一下程序中可以扩展的几个地方:
①获取编码器的类标识符:其实我们不使用12行中的GetEncoderClsid函数获取也可以,可以使用下面直接设定编码器类标识符的方法:
-
//Save to JPG
-
CLSID jpgClsid;
-
CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &jpgClsid);
-
bmp.Save(L"file.jpg", &pngClsid, NULL);
-
//and here's IDs for other formats:
-
bmp: {557cf400-1a04-11d3-9a73-0000f81ef32e}
-
jpg: {557cf401-1a04-11d3-9a73-0000f81ef32e}
-
gif: {557cf402-1a04-11d3-9a73-0000f81ef32e}
-
tif: {557cf405-1a04-11d3-9a73-0000f81ef32e}
-
png: {557cf406-1a04-11d3-9a73-0000f81ef32e}
②第9行的DrawImage函数:
如果我们使用graphics.DrawImage(&image,0,0);在[0,0]坐标处画出的图像会比原图变大,这是我们需要注意的,我开始自己也很疑惑到底是为什么?后来查了一下,才知道DrawImage是与设备相关的函数,即DrawImage会把屏幕的参数带上。所以它绘制图像的DPI基本都是96,而我们的图片一般是72DPI的。
所以我们需要使用graphics.DrawImage(&image,0,0,image.GetWidth(),image.GetHeight());加上原图的宽度和高度去解决这个问题。
五、BMP格式图片旋转90/180/270度并保存为文件
有些同学可能觉得直接将上面代码改一下一些参数就可以实现bmp图片的旋转和保存,如修改:
Image image(L"13-main按下.bmp"); //加载bmp图片
GetEncoderClsid(L"image/bmp", &jpgClsid); //获取bmp的编码器类标识符
image.Save(L"13-main按下_90.bmp",&jpgClsid,&encoderParameters); //保存为指定名字的bmp格式图片
这样修改真的大功告成了吗?
答案肯定是不是的,保存的bmp图片还是没旋转的图片,不过庆幸的是显示的图片是旋转了90度显示。
大家心里又多了个问题,那就是为什么显示的是90度的图片,保存的图片还是原图呢?
原因就是因为EncoderParameters编码参数设置只适用于JPEG编码器,不适用与BMP编码器,所以无论我们怎么设置旋转角度参数都实现不了保存为旋转后的bmp文件。
相信大家跟我一样,有这样的想法,既然能旋转显示出来,那就肯定能把旋转后的bmp图片保存为文件,我在网上查阅了大量资料,都没有找到我需要的。最后看到一个标题是“GDI+在内存中绘图”,这个字眼吸引了我,恍然大悟,没错,我们可以把旋转后的bmp图片绘制在内存中,再直接将内存中旋转好的bmp图片保存为文件就可以了。
下面就是根据这个想法,实现的bmp图片的旋转和保存,经过测试,图片效果很好,没有失真。
-
Image image(L"13-main按下.bmp"); //加载bmp图片
-
Bitmap *pBitmap = new Bitmap(image.GetHeight(),image.GetWidth()); //创建旋转90度、原图片大小一样的画布
-
if (!pBitmap)
-
{
-
return;
-
}
-
Graphics *imageGraphics = Graphics::FromImage(pBitmap); //在pBitmap画布上构建iamgeGraphics画笔
-
//因为图片旋转以图片左上角的坐标为旋转中心点进行逆时针旋转,所以先把它的坐标平移到pBitmap画布的(图片高度,0)坐标处,实现逆时针旋转90度后在(0,0)处绘制显示图片
-
imageGraphics->TranslateTransform(image.GetHeight(),0);
-
//imageGraphics->TranslateTransform(image.GetWidth(),image.GetHeight()); //180度对应的平移坐标
-
//imageGraphics->TranslateTransform(0,image.GetWidth()); //270度对应的平移坐标
-
imageGraphics->RotateTransform(90); //将bmp图片以左上角的坐标旋转为中心逆时针旋转90度
-
//graphics.RotateTransform(180); //设置图片旋转显示的角度为180度
-
//graphics.RotateTransform(270); //设置图片旋转显示的角度为270度
-
imageGraphics->DrawImage(&image,0,0,image.GetWidth(),image.GetHeight()); //将bmp图片按原来大小绘制到pBitmap画布上
-
-
CLSID bmpClsid;
-
if (!pBitmap)
-
{
-
return;
-
}
-
GetEncoderClsid(L"image/bmp",&bmpClsid); //选择bmp编码器类标识符
-
pBitmap->Save(L"13-main按下_90.bmp",&bmpClsid,NULL); //保存为bmp格式的图片
-
//pBitmap->Save(L"13-main按下__180.bmp",&bmpClsid,NULL);
-
//pBitmap->Save(L"13-main按下_270.bmp",&bmpClsid,NULL);
-
delete pBitmap;
图片效果如下:
总结:
使用GDI+旋转后的图片效果很好,没失真,使用起来也很方便,具体不同怎么配置GDI+实用环境的可以参考我另外一篇博文:http://blog.chinaunix.net/uid-31439230-id-5763484.html。
阅读(6102) | 评论(0) | 转发(0) |