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 2 #pragma comment(lib, "gdiplus.lib") 3 using namespace Gdiplus;

2、在"CreateGIF.h"中添加成员变量,GDI初始化时用:
1 private: 2GdiplusStartupInput m_GdiplusStartupInput; 3ULONG_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(){ 2GdiplusShutdown(m_pGdiToken); 3return 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 { 5return; 6 } 7 8 HBITMAP hBmp = (HBITMAP)LoadImage(NULL,dlg.GetPathName(),IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_CREATEDIBSECTION); 9 10 if(hBmp == NULL) 11 { 12return; 13 } 14 15 16 BYTE *palette = NULL; 17 BYTE *pData = https://www.it610.com/article/NULL; 18 int nWidth,nHeight; 19 BYTE bitsPixel = 8; 20 21 if(GetData(hBmp,&palette,&pData,&bitsPixel,&nWidth,&nHeight) == FALSE) 22 { 23DeleteObject(hBmp); 24return ; 25 } 26 27 DeleteObject(hBmp); 28 29 CFile file; 30 if(file.Open(m_sSavePath,CFile::typeBinary|CFile::modeCreate|CFile::modeWrite) == FALSE) 31 { 32return; 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 BitmaptempBmp(FileName.AllocSysString()); 2 ColorbackColor; 3 HBITMAPHBitmap; 4 tempBmp.GetHBITMAP(backColor,&HBitmap); 5 returnHBitmap;

但是这样做总是不行,个人估计是"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 { 9image.Save(ToWChar(m_sBMPSavePath.GetBuffer(m_sBMPSavePath.GetLength())), &clsid, NULL); 10 }

注意,上面用到的两个函数:ToWCchar()与GetImageCLSID()并不是自带的,而是要自己实现。
1 WCHAR* CCreateGIFDlg::ToWChar(char *str) 2 { 3//在 GDI+中,有关字符的参数类型全部都是 WCHAR 类型的 4//该函数是将传统字符串进行转换 5const int nnn = 1024; 6static WCHAR buffer[nnn]; 7wcsset(buffer,0); 8MultiByteToWideChar(CP_ACP,0,str,strlen(str),buffer,nnn); 9return buffer; 10 } 11 12 int CCreateGIFDlg::GetImageCLSID(const WCHAR *format, CLSID *pCLSID) 13 { 14UINT num=0; 15UINT size=0; 16 17ImageCodecInfo* pImageCodecInfo=NULL; 18 19GetImageEncodersSize(&num,&size); 20 21if(size==0) 22return FALSE; // 编码信息不可用 23// 分配内存 24 25pImageCodecInfo=(ImageCodecInfo*)(malloc(size)); 26 27if(pImageCodecInfo==NULL) 28return FALSE; // 分配失败 29// 获得系统中可用的编码方式的所有信息 30GetImageEncoders(num,size,pImageCodecInfo); 31// 在可用编码信息中查找 format 格式是否被支持 32 33for(UINT i=0; i
执行之后,就可以看见转换好的"bmp"图像了。

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

在GIF生成函数OnCreateGIF()中使用了while循环机制,图像选择文件对话框会一直跳出,即用户可以不断地添加图像。当点击取消时终止图像添加过程,对话框自动关闭,动画——GIF文件自动生成并保存。
【VS2008+GDI实现多幅图像的GIF动画制作】整个流程大致就是如此,对图像处理方面的要求比较高一点。

    推荐阅读