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

  整個流程大致就是如此,對圖像處理方面的要求比較高一點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM