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文件自动生成并保存。

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

你可能感兴趣的:(vs2008)