Chinaunix首页 | 论坛 | 博客
  • 博客访问: 157029
  • 博文数量: 31
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 360
  • 用 户 组: 普通用户
  • 注册时间: 2017-02-28 08:37
个人简介

没有绝活,怎能风骚.....

文章分类

全部博文(31)

文章存档

2017年(31)

我的朋友

分类: C/C++

2017-05-12 17:39:03

一、文章撰写缘由
    主要是分享一下自己搞了挺久才解决的问题,相信大家也看了网上好多人写过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方法来获取你所需要的缓冲区的大小。
    这里通过控制台程序举获取支持哪些编码器的例子,支持哪些解码器的代码你们自己下去写写:
  1. #include <windows.h>
  2. #include <gdiplus.h>
  3. #include <stdio.h>
  4. using namespace Gdiplus;
  5. INT main()
  6. {
  7. // Initialize GDI+.
  8. GdiplusStartupInput gdiplusStartupInput;
  9. ULONG_PTR gdiplusToken;
  10. GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
  11. UINT num; // number of image encoders
  12. UINT size; // size, in bytes, of the image encoder array
  13. ImageCodecInfo* pImageCodecInfo;
  14. // How many encoders are there?
  15. // How big (in bytes) is the array of all ImageCodecInfo objects?
  16. GetImageEncodersSize(&num, &size);
  17. // Create a buffer large enough to hold the array of ImageCodecInfo
  18. // objects that will be returned by GetImageEncoders.
  19. pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
  20. // GetImageEncoders creates an array of ImageCodecInfo objects
  21. // and copies that array into a previously allocated buffer.
  22. // The third argument, imageCodecInfo, is a pointer to that buffer.
  23. GetImageEncoders(num, size, pImageCodecInfo);
  24. // Display the graphics file format (MimeType)
  25. // for each ImageCodecInfo object.
  26. for(UINT j = 0; j < num; ++j)
  27. {
  28. wprintf(L"%s\n", pImageCodecInfo[j].MimeType);
  29. }
  30. free(pImageCodecInfo);
  31. GdiplusShutdown(gdiplusToken);
  32. return 0;
  33. }
    运行前面的控制台程序,结果如下面,列出了已安装的编码器:
    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。
  1. int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
  2. {
  3.     UINT num = 0; // number of image encoders
  4.     UINT size = 0; // size of the image encoder array in bytes
  5.     ImageCodecInfo* pImageCodecInfo = NULL;
  6.     GetImageEncodersSize(&num, &size);
  7.     if(size == 0)
  8.         return -1; // Failure
  9.     pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
  10.     if(pImageCodecInfo == NULL)
  11.         return -1; // Failure
  12.     GetImageEncoders(num, size, pImageCodecInfo);
  13.     for(UINT j = 0; j < num; ++j)
  14.     {
  15.         if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
  16.         {
  17.             *pClsid = pImageCodecInfo[j].Clsid;
  18.             free(pImageCodecInfo);
  19.             return j; // Success
  20.         }
  21.     }
  22.     free(pImageCodecInfo);
  23.     return -1; // Failure
  24. }
    下面举个控制台程序调用GetEncoderClsid函数获取PNG编码器的CLSID的例子:

  1. #include <windows.h>
  2. #include <gdiplus.h>
  3. #include <stdio.h>
  4. using namespace Gdiplus;
  5. #include "GdiplusHelperFunctions.h"
  6. INT main()
  7. {
  8. // Initialize GDI+.
  9. GdiplusStartupInput gdiplusStartupInput;
  10. ULONG_PTR gdiplusToken;
  11. GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
  12. CLSID encoderClsid;
  13. INT result;
  14. WCHAR strGuid[39];
  15. result = GetEncoderClsid(L"image/png", &encoderClsid);
  16. if(result < 0)
  17. {
  18. printf("The PNG encoder is not installed.\n");
  19. }
  20. else
  21. {
  22. StringFromGUID2(encoderClsid, strGuid, 39);
  23. printf("An ImageCodecInfo object representing the PNG encoder\n");
  24. printf("was found at position %d in the array.\n", result);
  25. wprintf(L"The CLSID of the PNG encoder is %s.\n", strGuid);
  26. }
  27. GdiplusShutdown(gdiplusToken);
  28. return 0;
  29. }
    运行前面的控制台程序,输出类似的结果为:
    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函数添加如下代码即可:
  1. CClientDC dc(this);
  2. Graphics graphics(dc); //在dc画布上构建graphics画笔
  3. Image image(L"13-main按下.jpg"); //加载jpg图片
  4. //因为图片旋转以图片左上角的坐标为旋转中心点进行逆时针旋转,所以先把它的坐标平移到dc画布的(图片高度,0)坐标处,实现逆时针旋转90度后在(0,0)处绘制显示图片
  5. graphics.TranslateTransform(image.GetHeight(),0)
  6. graphics.RotateTransform(90); //设置图片旋转显示的角度为90度
  7. //graphics.RotateTransform(180)//设置图片旋转显示的角度为180度
  8. //graphics.RotateTransform(270)//设置图片旋转显示的角度为270度
  9. graphics.DrawImage(&image, 0, 0,image.GetWidth(),image.GetHeight());

  10. CLSID jpgClsid; 
  11. GetEncoderClsid(L"image/jpeg", &jpgClsid); //这个是上面我们自己实现的获取编码器的类标识符函数

  12. EncoderParameters encoderParameters; //编码器保存jpg图片的参数设定
  13. ULONG transformation;
  14. encoderParameters.Count = 1;
  15. encoderParameters.Parameter[0].Guid = EncoderTransformation; //设置Guid的值为编码器实现图片变换
  16. encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
  17. encoderParameters.Parameter[0].NumberOfValues = 1;
  18.         
  19. // Rotate and save the image.
  20. transformation = EncoderValueTransformRotate90; //设置保存jpg文件的旋转角度为90度
  21. //transformation = EncoderValueTransformRotate180; //设置保存jpg文件的旋转角度为1800度
  22. //transformation = EncoderValueTransformRotate270; //设置保存jpg文件的旋转角度为270度
  23. encoderParameters.Parameter[0].Value = &transformation;
  24. image.Save(L"13-main按下_90.jpg",&jpgClsid,&encoderParameters); //保存为指定名字的jpg格式图片
    图片效果如下:
    
    下面讲一下程序中可以扩展的几个地方:
    ①获取编码器的类标识符:其实我们不使用12行中的GetEncoderClsid函数获取也可以,可以使用下面直接设定编码器类标识符的方法:  
  1. //Save to JPG
  2. CLSID jpgClsid;
  3. CLSIDFromString(L"{557cf401-1a04-11d3-9a73-0000f81ef32e}", &jpgClsid);
  4. bmp.Save(L"file.jpg", &pngClsid, NULL);
  5. //and here's IDs for other formats:
  6. bmp: {557cf400-1a04-11d3-9a73-0000f81ef32e}
  7. jpg: {557cf401-1a04-11d3-9a73-0000f81ef32e}
  8. gif: {557cf402-1a04-11d3-9a73-0000f81ef32e}
  9. tif: {557cf405-1a04-11d3-9a73-0000f81ef32e}
  10. 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图片的旋转和保存,经过测试,图片效果很好,没有失真。
  1. Image image(L"13-main按下.bmp"); //加载bmp图片
  2. Bitmap *pBitmap = new Bitmap(image.GetHeight(),image.GetWidth()); //创建旋转90度、原图片大小一样的画布
  3. if (!pBitmap)
  4. {
  5.     return;
  6. }
  7. Graphics *imageGraphics = Graphics::FromImage(pBitmap); //在pBitmap画布上构建iamgeGraphics画笔
  8. //因为图片旋转以图片左上角的坐标为旋转中心点进行逆时针旋转,所以先把它的坐标平移到pBitmap画布的(图片高度,0)坐标处,实现逆时针旋转90度后在(0,0)处绘制显示图片
  9. imageGraphics->TranslateTransform(image.GetHeight(),0)
  10. //imageGraphics->TranslateTransform(image.GetWidth(),image.GetHeight()); //180度对应的平移坐标
  11. //imageGraphics->TranslateTransform(0,image.GetWidth()); //270度对应的平移坐标
  12. imageGraphics->RotateTransform(90); //将bmp图片以左上角的坐标旋转为中心逆时针旋转90度
  13. //graphics.RotateTransform(180)//设置图片旋转显示的角度为180度
  14. //graphics.RotateTransform(270)//设置图片旋转显示的角度为270度
  15. imageGraphics->DrawImage(&image,0,0,image.GetWidth(),image.GetHeight()); //将bmp图片按原来大小绘制到pBitmap画布
  16.  
  17. CLSID bmpClsid;
  18. if (!pBitmap)
  19. {
  20.     return;
  21. }
  22. GetEncoderClsid(L"image/bmp",&bmpClsid); //选择bmp编码器类标识符
  23. pBitmap->Save(L"13-main按下_90.bmp",&bmpClsid,NULL); //保存为bmp格式的图片
  24. //pBitmap->Save(L"13-main按下__180.bmp",&bmpClsid,NULL);
  25. //pBitmap->Save(L"13-main按下_270.bmp",&bmpClsid,NULL);
  26. delete pBitmap;
    图片效果如下:
    

总结:
    使用GDI+旋转后的图片效果很好,没失真,使用起来也很方便,具体不同怎么配置GDI+实用环境的可以参考我另外一篇博文:http://blog.chinaunix.net/uid-31439230-id-5763484.html。
阅读(6102) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~