CImage一個比CBitmap更優秀的類
來源:互聯網
上載者:User
一個比CBitmap更優秀的類前兩天有個朋友讓我幫他編一個圖象處理方面的程式,需要支援目前常見的映像格式.這可把我難住了.一直以來都只會用Visual C++的CBitmap類載入各種位元影像資源,然後處理.Visual C++的CBitmap類的功能是比較弱的,它只能顯示出在資源中的表徵圖、位元影像、游標以及圖元檔案的內容,而不像VB中的Image控制項可以顯示出絕大多數的外部影像檔(BMP、GIF、JPEG等)。如果想要在對話方塊或其他視窗中顯示外部影像檔則只能藉助於第三方提供的控制項或代碼,未免過於繁瑣.這陣子在學C#,裝了visual studio2003.索性在網上找了一下,竟然發現.net引入了一個功能非常強大的新類 ----- CImage.有了CImage類,Visual C++在映像方面的缺憾將一去不複返。CImage是MFC和ATL共用的新類,它能從外部磁碟中調入一個JPEG、GIF、BMP和PNG格式的影像檔加以顯示,而且這些檔案格式可以相互轉換。例如通過簡單的幾句,就可以實現CImage類和CBitmap類執行個體的:HBITMAP hBitmap=image.Detach();CBitmap bmp;bmp.Attach(hBitmap);這樣一來,就又迴歸到以前操縱CBitmap的方式了.CImage本身封裝了DIB(裝置無關位元影像)的功能,因而能夠處理每個位元影像像素。它具有下列最酷特性: 1、AlphaBlend支援像素級的顏色混合,從而實現透明和半透明的效果。 2、PlgBlt能使一個矩形地區的位元影像映射到一個平行四邊形地區中,而且還可能使用位屏蔽操作。 3、TransparentBlt在目的地區域中產生透明映像,SetTransparentColor用來設定某種顏色是透明色。 4、MaskBlt在目的地區域中產生源位元影像與屏蔽位元影像合成的效果。由於CImage在不同的Windows作業系統中其某些效能是不一樣的,因此在使用時要特別注意。例如,CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能運行在Windows 95/98 應用程式中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000運行程式還必須將stdafx.h檔案中的WINVER和_WIN32_WINNT的預定義修改成0x0500才能正常使用。使用CImage的一般方法 使用CImage的一般方法是這樣的過程: (1) 開啟應用程式的stdafx.h檔案添加CImage類的包含檔案:#include <atlimage.h> (2) 定義一個CImage類對象,然後調用CImage::Load方法裝載一個外部影像檔。 (3) 調用CImage::Draw方法繪製映像。Draw方法具有如下定義:BOOL Draw( HDC hDestDC, int xDest, int yDest,int nDestWidth, int nDestHeight, int xSrc, int ySrc,int nSrcWidth, int nSrcHeight );BOOL Draw( HDC hDestDC, const RECT& rectDest, const RECT& rectSrc );BOOL Draw( HDC hDestDC, int xDest, int yDest );BOOL Draw( HDC hDestDC, const POINT& pointDest );BOOL Draw( HDC hDestDC, int xDest, int yDest,int nDestWidth, int nDestHeight );BOOL Draw( HDC hDestDC, const RECT& rectDest ); 其中,hDestDC用來指定繪製的目標裝置環境控制代碼,(xDest, yDest)和pointDest用來指定映像顯示的位置,這個位置和源映像的左上方點相對應。nDestWidth和nDestHeight分別指定映像要顯示的高度和寬度,xSrc、ySrc、nSrcWidth和nSrcHeight用來指定要顯示的源映像的某個部分所在的位置和大小。rectDest和rectSrc分別用來指定目標裝置環境上和源映像所要顯示的某個部分的位置和大小。 需要說明的是,Draw方法綜合了StretchBlt、TransparentBlt和AlphaBlend函數的功能。預設時,Draw的功能和StretchBlt相同。但當映像含有透明色或Alpha通道時,它的功能又和TransparentBlt、AlphaBlend相同。因此,在一般情況下,我們都應該盡量調用CImage::Draw方法來繪製映像。 例如,下面的樣本Ex_Image是實現這樣的功能:當選擇"檔案"ò"開啟"功能表命令後,彈出一個檔案開啟對話方塊。當選定一個影像檔後,就會在視窗客戶區中顯示該影像檔內容。這個樣本的具體步驟如下: (1) 建立一個預設的單文檔程式項目Ex_Image。 (2) 開啟stdafx.h檔案中添加CImage類的包含檔案atlimage.h。 (3) 在CEx_ImageView類添加ID_FILE_OPEN的COMMAND事件對應程式,並添加下列代碼:void CEx_ImageView::OnFileOpen(){ CString strFilter; CSimpleArray<GUID> aguidFileTypes; HRESULT hResult; // 擷取CImage支援的影像檔的過濾字串 hResult = m_Image.GetExporterFilterString(strFilter,aguidFileTypes,_T( "All Image Files") ); if (FAILED(hResult)) { MessageBox("GetExporterFilter調用失敗!"); return; } CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, strFilter); if(IDOK != dlg.DoModal()) return; m_Image.Destroy(); // 將外部影像檔裝載到CImage對象中 hResult = m_Image.Load(dlg.GetFileName()); if (FAILED(hResult)) { MessageBox("調用影像檔失敗!"); return; } // 設定主視窗標題列內容 CString str; str.LoadString(AFX_IDS_APP_TITLE); AfxGetMainWnd()->SetWindowText(str + " - " +dlg.GetFileName()); Invalidate(); // 強制調用OnDraw} (4) 定位到CEx_ImageView::OnDraw函數處,添加下列代碼:void CEx_ImageView::OnDraw(CDC* pDC){ CEx_ImageDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!m_Image.IsNull()) { m_Image.Draw(pDC->m_hDC,0,0); }} (5) 開啟Ex_ImageView.h檔案,添加一個公用的成員資料m_Image:public:CImage m_Image; (6) 編譯並運行。單擊"開啟"工具按鈕,在彈出的對話方塊中指定一個影像檔後,單擊"開啟"按鈕,其結果7.21所示。將圖片用其它格式儲存 CImage::Save方法能將一個影像檔按另一種格式來儲存,它的原型如下:HRESULT Save( LPCTSTR pszFileName, REFGUID guidFileType= GUID_NULL); 其中,pszFileName用來指定一個檔案名稱,guidFileType用來指定要儲存的影像檔格式,當為GUID_NULL時,其檔案格式由檔案的副檔名來決定,這也是該函數的預設值。它還可以是GUID_BMPFile(BMP檔案格式)、GUID_PNGFile(PNG檔案格式)、GUID_JPEGFile(JPEG檔案格式)和GUID_GIFFile(GIF檔案格式)。 例如,下面的過程是在Ex_Image樣本基礎上進行的,我們在CEx_ImageView類添加ID_FILE_SAVE_AS的COMMAND事件對應程式,並添加下列代碼:void CEx_ImageView::OnFileSaveAs(){ if (m_Image.IsNull()) { MessageBox("你還沒有開啟一個要儲存的影像檔!"); return; } CString strFilter; strFilter = "位元影像檔案|*.bmp|JPEG 影像檔|*.jpg| /GIF 影像檔|*.gif|PNG 影像檔|*.png||"; CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter); if ( IDOK != dlg.DoModal()) return; // 如果使用者沒有指定副檔名,則為其添加一個 CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { switch (dlg.m_ofn.nFilterIndex) { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; default: break; } strFileName = strFileName + '.' + strExtension; } // 映像儲存 HRESULT hResult = m_Image.Save(strFileName); if (FAILED(hResult)) MessageBox("儲存影像檔失敗!");將圖片用其它格式儲存 CImage::Save方法能將一個影像檔按另一種格式來儲存,它的原型如下:HRESULT Save( LPCTSTR pszFileName, REFGUID guidFileType= GUID_NULL); 其中,pszFileName用來指定一個檔案名稱,guidFileType用來指定要儲存的影像檔格式,當為GUID_NULL時,其檔案格式由檔案的副檔名來決定,這也是該函數的預設值。它還可以是GUID_BMPFile(BMP檔案格式)、GUID_PNGFile(PNG檔案格式)、GUID_JPEGFile(JPEG檔案格式)和GUID_GIFFile(GIF檔案格式)。 例如,下面的過程是在Ex_Image樣本基礎上進行的,我們在CEx_ImageView類添加ID_FILE_SAVE_AS的COMMAND事件對應程式,並添加下列代碼:void CEx_ImageView::OnFileSaveAs(){ if (m_Image.IsNull()) { MessageBox("你還沒有開啟一個要儲存的影像檔!"); return; } CString strFilter; strFilter = "位元影像檔案|*.bmp|JPEG 影像檔|*.jpg| /GIF 影像檔|*.gif|PNG 影像檔|*.png||"; CFileDialog dlg(FALSE,NULL,NULL,NULL,strFilter); if ( IDOK != dlg.DoModal()) return; // 如果使用者沒有指定副檔名,則為其添加一個 CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; if (dlg.m_ofn.nFileExtension == 0) { switch (dlg.m_ofn.nFilterIndex) { case 1: strExtension = "bmp"; break; case 2: strExtension = "jpg"; break; case 3: strExtension = "gif"; break; case 4: strExtension = "png"; break; default: break; } strFileName = strFileName + '.' + strExtension; } // 映像儲存 HRESULT hResult = m_Image.Save(strFileName); if (FAILED(hResult)) MessageBox("儲存影像檔失敗!");變成黑白圖片 由於許多影像檔使用顏色表來發揮顯示裝置的色彩顯示能力,因而將一張彩色圖片變成黑色圖片時需要調用CImage::IsIndexed來判斷是否使用顏色表,若是則修改顏色表,否則直接將像素進行顏色設定。例如下面的代碼:void CEx_ImageView::MakeBlackAndwhite(CImage* image){ if (image->IsNull()) return; if (!image->IsIndexed()) { // 直接修改像素顏色 COLORREF pixel; int maxY = image->GetHeight(), maxX = image->GetWidth(); byte r,g,b,avg; for (int x=0; x<maxX; x++) { for (int y=0; y<maxY; y++) { pixel = image->GetPixel(x,y); r = GetRValue(pixel); g = GetGValue(pixel); b = GetBValue(pixel); avg = (int)((r + g + b)/3); image->SetPixelRGB(x,y,avg,avg,avg); } } } else { // 擷取並修改顏色表 int MaxColors = image->GetMaxColorTableEntries(); RGBQUAD* ColorTable; ColorTable = new RGBQUAD[MaxColors]; image->GetColorTable(0,MaxColors,ColorTable); for (int i=0; i<MaxColors; i++) { int avg = (ColorTable[i].rgbBlue + ColorTable[i].rgbGreen + ColorTable[i].rgbRed)/3; ColorTable[i].rgbBlue = avg; ColorTable[i].rgbGreen = avg; ColorTable[i].rgbRed = avg; } image->SetColorTable(0,MaxColors,ColorTable); delete(ColorTable); }} 至此,我們介紹了GDI+和CImage的一般使用方法和技巧。當然,它們本身還有許多更深入的方法,由於篇幅所限,這裡不再一一討論。