相信很多朋友和我一樣,經常由於這或那的原因,需制作一些特定格式的圖像。如開發過程中需要給菜單、工具條及按鈕等添加對應的圖形標識,通過代碼或資源導入方式加載這些圖像時往往會有較高的格式要求。
比如,為按鈕添加"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文件自動生成並保存。
整個流程大致就是如此,對圖像處理方面的要求比較高一點。
