VS2008+GDI实现多幅图像的GIF动画制作

  

  相信很多朋友和我一样,经常由于这或那的原因,需制作一些特定格式的图像。如开发过程中需要给菜单、工具条及按钮等添加对应的图形标识,通过代码或资源导入方式加载这些图像时往往会有较高的格式要求。

  比如,为按钮添加"bmp"类型图标,而手头只有"jpg"格式的图像,此时若是简单地在图像编辑器里改变图像大小或保存为后缀"bmp"格式,很多情况是会读取失败并终止程序的。

  当然,在如今这个移动互联网如此发达的时代,早就有很多在线图像制作及转换的网站。普遍遇到的图像转换问题在那里几乎都能解决,方便快捷,只要有网络。

  言归正传,近日我又遇到了类似的问题:将多幅图像(格式可不同)生成GIF动画!

  马上在网上转悠一圈,现成的工具很多,下载挺方便,用的感觉也还过得去。

  问题是慢慢地发现,分享的代码很少,基于VC的就更少了。

  兴致到此,没办法,自己动手丰衣足食吧。但也不可操之过急,毕竟关于GIF的格式及数据添加方法不是很熟悉,于是就把网上能找到的关于这方面的代码先理解,主要有多幅"bmp"图像生成GIF,"jpg"、"tif"等转"bmp"的。

  不过靠谱的很少,大家多少也懂一点,下载前信心满满,后面就......

  好了,接下来说说我的GIF制作过程,用到的语言工具为VS2008(MFC+GDI),方法有些是借鉴前辈分享的资料。

  因为GDI一般都会在安装VS时自动载入,所以使用前只需进行简单的配置就可(其实更准确地说只是进行初始化)。

  建立MFC工程时就取名称"CreateGIF",对应的对话框类名"CreateGIFDlg"。

  1、在头文件"StdAfx.h"中添加以下代码,也可以在其他头文件中添加:

1 #include <gdiplus.h>
2 #pragma comment(lib, "gdiplus.lib") 
3 using namespace Gdiplus;           

  2、在"CreateGIF.h"中添加成员变量,GDI初始化时用:

1 private:
2     GdiplusStartupInput m_GdiplusStartupInput;  
3     ULONG_PTR m_pGdiToken;

  3、重载父类虚函数,用以结束GDI:

virtual int ExitInstance();

  4、在"CreateGIF.cpp"源文件的初始化函数InitInstance()中添加GDI初始化语句,注意该语句必须放在对话框生成语句之前,否则在对话框中操作时会因为GDI未初始化而出错。

1 GdiplusStartup(&m_pGdiToken,&m_GdiplusStartupInput,NULL);

  5、在"CreateGIF.cpp"中添加虚函数ExitInstance()的定义:

1 int CCreateGIFApp::ExitInstance(){  
2     GdiplusShutdown(m_pGdiToken);  
3     return CWinApp::ExitInstance();  
4 }

  到此,GDI的初始化工作算是完成,可以直接使用其库中的资源了——图像类及处理功能,如Image。

  由于位图(BMP)是比较标准的图像格式,将其数据写入GIF文件中,不是难事,以下是实现过程:

  (变量m_sSavePath为事先指定的完整保存路径,类型CString)

 1 CFileDialog dlg(TRUE,"BMP",NULL,0,"图像文件(*.bmp)|*.bmp||",this);
 2 
 3 if(dlg.DoModal() != IDOK)
 4 {
 5     return;
 6 }
 7 
 8 HBITMAP hBmp = (HBITMAP)LoadImage(NULL,dlg.GetPathName(),IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
 9 
10 if(hBmp == NULL)
11 {
12     return;
13 }
14         
15             
16 BYTE *palette = NULL;
17 BYTE *pData = NULL;
18 int nWidth,nHeight;
19 BYTE bitsPixel = 8;
20 
21 if(GetData(hBmp,&palette,&pData,&bitsPixel,&nWidth,&nHeight) == FALSE)
22 {
23     DeleteObject(hBmp);
24     return ;
25 }
26 
27 DeleteObject(hBmp);
28 
29 CFile file;
30 if(file.Open(m_sSavePath,CFile::typeBinary|CFile::modeCreate|CFile::modeWrite) == FALSE)
31 {
32     return;
33 }
34 
35 CreateGIFHeard(file,nWidth,nHeight,bitsPixel);
36 
37 short int nTransparentColorIndex = -1;
38 
39 AddImageToGIF(file,pData,palette,nWidth,nHeight,bitsPixel,m_nDelay,nTransparentColorIndex);
40 delete []pData; 
41 delete []palette;

  上述代码实现的功能是:从文件打开对话框中选择一幅"bmp"格式的图像,读取其数据信息,打开"gif"文件创建头信息,将"bmp"图像数据写入到"gif"文件中。

  可以看到结尾处并没有关闭"gif"打开的文件,所以要想再添加一幅或若干幅"bmp"图像只需重复上述过程。完成之后,添加以下代码结束文件的写操作。GIF文件就生成并保存在了指定路径中。

1 CloseGIF(file);
2 file.Close();

  那接下的问题就是:如何将其他格式的图像数据读取并写入到"gif"文件,参与动画制作的大家庭。

  最初在网上找了一篇文章,说可以直接通过文件名将"jpg"等格式图像读取成Bitmap对象,进一步提取出HBITMAP信息,然后就可以同上述过程进行添加了。代码大致如下:

1 Bitmap   tempBmp(FileName.AllocSysString());   
2 Color    backColor;         
3 HBITMAP  HBitmap;     
4 tempBmp.GetHBITMAP(backColor,&HBitmap);   
5 return   HBitmap; 

  但是这样做总是不行,个人估计是"Bitmap tempBmp(FileName.AllocSysString());"这个地方并不能真正地将其他类型的图像转为"bmp"。

  后来又找到了一种方法,通过上面提到过的Image类,先将图像读取进来,然后保存为"bmp"格式。当然,读取的时候也可以读取"bmp"类型。指定源图像文件及目标图像文件的路径之后,便可以实现方便、快捷的隐式转换。

 1 Graphics graphics(GetDC()->m_hDC);
 2   
 3 Image image(ToWChar(m_sOpenPath.GetBuffer(m_sOpenPath.GetLength())));       
 4 
 5 CLSID clsid;  
 6 
 7 if(GetImageCLSID(L"image/bmp", &clsid))  
 8 {  
 9     image.Save(ToWChar(m_sBMPSavePath.GetBuffer(m_sBMPSavePath.GetLength())), &clsid, NULL);   
10 }   

  注意,上面用到的两个函数:ToWCchar()与GetImageCLSID()并不是自带的,而是要自己实现。

 1 WCHAR* CCreateGIFDlg::ToWChar(char *str)  
 2 {  
 3     //在 GDI+中,有关字符的参数类型全部都是 WCHAR 类型的  
 4     //该函数是将传统字符串进行转换  
 5     const int nnn = 1024;
 6     static WCHAR buffer[nnn];  
 7     wcsset(buffer,0);  
 8     MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,nnn);  
 9     return buffer;  
10 }
11 
12 int CCreateGIFDlg::GetImageCLSID(const WCHAR *format, CLSID *pCLSID)  
13 {  
14     UINT num=0;  
15     UINT size=0;  
16 
17     ImageCodecInfo* pImageCodecInfo=NULL;  
18 
19     GetImageEncodersSize(&num,&size);    
20 
21     if(size==0)  
22         return FALSE; // 编码信息不可用  
23     // 分配内存  
24 
25     pImageCodecInfo=(ImageCodecInfo*)(malloc(size));  
26 
27     if(pImageCodecInfo==NULL)  
28         return FALSE; // 分配失败  
29     // 获得系统中可用的编码方式的所有信息  
30     GetImageEncoders(num,size,pImageCodecInfo);  
31     // 在可用编码信息中查找 format 格式是否被支持  
32 
33     for(UINT i=0;i<num;++i)  
34     {  
35         //MimeType: 编码方式的具体描述  
36         if (wcscmp(pImageCodecInfo[ i] .MimeType,format)==0)  
37         {  
38             *pCLSID=pImageCodecInfo[i].Clsid;  
39             free(pImageCodecInfo);  
40             return TRUE;  
41         }  
42     }  
43     free(pImageCodecInfo);  
44     return FALSE;  
45 }

  执行之后,就可以看见转换好的"bmp"图像了。

  离真相不远了,接下来需要做的就是将上述的生成"gif"与"bmp"这两个过程合并。具体如下:

  1 void CCreateGIFDlg::OnCreateGIF() 
  2 {
  3     // TODO: Add extra validation here
  4     if(!UpdateData())
  5     {
  6         return;
  7     }
  8 
  9     CFileDialog dlg(TRUE,"(*.*)|*.*",NULL,0,"图像文件 (*.*)|*.*||",this); 
 10 
 11     if(dlg.DoModal() != IDOK)
 12     {
 13         return;
 14     }
 15 
 16     m_sOpenPath = dlg.GetPathName();
 17     m_sBMPSavePath = dlg.GetFileTitle()+"1.bmp";
 18 
 19     OnFileSave();
 20 
 21     HBITMAP hBmp = (HBITMAP)LoadImage(NULL,m_sBMPSavePath,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
 22 
 23     if(hBmp == NULL)
 24     {
 25         return;
 26     }
 27             
 28     BYTE *palette = NULL;
 29     BYTE *pData = NULL;
 30     int nWidth,nHeight;
 31     BYTE bitsPixel = 8;
 32 
 33     //为位图生成调色板,得到索引数据、宽、高
 34     if(GetData(hBmp,&palette,&pData,&bitsPixel,&nWidth,&nHeight) == FALSE)
 35     {
 36         DeleteObject(hBmp);
 37         return ;
 38     }
 39 
 40     DeleteObject(hBmp);
 41 
 42     //创建GIF文件
 43     CFile file;
 44     if(file.Open(m_sSavePath,CFile::typeBinary|CFile::modeCreate|CFile::modeWrite) == FALSE)
 45     {
 46         return;
 47     }
 48 
 49     //写GIF头
 50     CreateGIFHeard(file,nWidth,nHeight,bitsPixel);
 51 
 52     short int nTransparentColorIndex = -1;
 53 
 54     //加入第一幅图片
 55     AddImageToGIF(file,pData,palette,nWidth,nHeight,bitsPixel,m_nDelay,nTransparentColorIndex);
 56     delete []pData;    //这两个变量在此相当于二维数组
 57     delete []palette;
 58 
 59     //////////////////////////////////////////////////////////////////////////////////
 60     while(1)
 61     {
 62         CFileDialog dlg(TRUE,"(*.*)|*.*",NULL,0,"图像文件 (*.*)|*.*||",this); 
 63         if(dlg.DoModal() != IDOK)
 64         {
 65             CDialog::OnOK();
 66             return;
 67         }
 68 
 69         m_sOpenPath = dlg.GetPathName();
 70         m_sBMPSavePath = dlg.GetFileTitle()+"1.bmp";
 71 
 72         OnFileSave();
 73 
 74         HBITMAP hBmp2 = (HBITMAP)LoadImage(NULL,m_sBMPSavePath,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION);
 75 
 76         if(hBmp2 == NULL)
 77         {
 78             CloseGIF(file);
 79             file.Close();
 80             return;
 81         }
 82 
 83         if(GetData(hBmp2,&palette,&pData,&bitsPixel,&nWidth,&nHeight) == FALSE)
 84         {
 85             DeleteObject(hBmp2);
 86             CloseGIF(file);
 87             file.Close();
 88             return ;
 89         }
 90         DeleteObject(hBmp2);
 91 
 92         nTransparentColorIndex = -1;
 93 
 94         //加入其它图片
 95         AddImageToGIF(file,pData,palette,nWidth,nHeight,bitsPixel,m_nDelay,nTransparentColorIndex);
 96         delete []pData;
 97         delete []palette;
 98     }
 99 
100     //结束GIF
101     CloseGIF(file);
102 
103     file.Close();
104 
105     CDialog::OnOK();
106 }  
107 
108 WCHAR* CCreateGIFDlg::ToWChar(char *str)  
109 {  
110     //在 GDI+中,有关字符的参数类型全部都是 WCHAR 类型的  
111     //该函数是将传统字符串进行转换  
112     const int nnn = 1024;
113     static WCHAR buffer[nnn];  
114     wcsset(buffer,0);  
115     MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,nnn);  
116     return buffer;  
117 }
118 
119 int CCreateGIFDlg::GetImageCLSID(const WCHAR *format, CLSID *pCLSID)  
120 {  
121     UINT num=0;  
122     UINT size=0;  
123 
124     ImageCodecInfo* pImageCodecInfo=NULL;  
125 
126     GetImageEncodersSize(&num,&size);    
127 
128     if(size==0)  
129         return FALSE; // 编码信息不可用  
130     // 分配内存  
131 
132     pImageCodecInfo=(ImageCodecInfo*)(malloc(size));  
133 
134     if(pImageCodecInfo==NULL)  
135         return FALSE; // 分配失败  
136     // 获得系统中可用的编码方式的所有信息  
137     GetImageEncoders(num,size,pImageCodecInfo);  
138     // 在可用编码信息中查找 format 格式是否被支持  
139 
140     for(UINT i=0;i<num;++i)  
141     {  
142         //MimeType: 编码方式的具体描述  
143         if (wcscmp(pImageCodecInfo[ i] .MimeType,format)==0)  
144         {  
145             *pCLSID=pImageCodecInfo[i].Clsid;  
146             free(pImageCodecInfo);  
147             return TRUE;  
148         }  
149     }  
150     free(pImageCodecInfo);  
151     return FALSE;  
152 }  
153 
154 void CCreateGIFDlg::OnFileSave()  
155 {   
156     Graphics graphics(GetDC()->m_hDC);
157   
158     Image image(ToWChar(m_sOpenPath.GetBuffer(m_sOpenPath.GetLength())));       
159 
160     CLSID clsid;  
161 
162     if(GetImageCLSID(L"image/bmp", &clsid))  
163     {  
164         image.Save(ToWChar(m_sBMPSavePath.GetBuffer(m_sBMPSavePath.GetLength())), &clsid, NULL);   
165     }   
166 }

  在GIF生成函数OnCreateGIF()中使用了while循环机制,图像选择文件对话框会一直跳出,即用户可以不断地添加图像。当点击取消时终止图像添加过程,对话框自动关闭,动画——GIF文件自动生成并保存。

  整个流程大致就是如此,对图像处理方面的要求比较高一点。

posted @ 2014-07-16 18:08  路上的脚印  阅读(3347)  评论(1编辑  收藏  举报