http://download.csdn.net/detail/a514534316/6340539 用VC6編寫抓圖程式 |
摘 要:本文給出了設計一個抓圖程式的完整過程,對一個抓圖程式所應擁有的最基本的三大功能即:熱鍵激活、螢幕抓圖及存儲所抓取的圖形等功能的實作作了詳細的介紹。 關鍵詞:熱鍵 螢幕抓圖 DDB DIB 在遊戲程式的設計中,抓圖功能是比較重要的一個功能。其實抓圖程式的設計并不太複雜,仔細分析一個典型的抓圖程式,它主要有以下功能:①具有熱鍵激活的功能;②螢幕抓圖的功能;③存儲所抓取的圖形。以下介紹在VC6中抓圖程式的實作過程。 在VC6中建立一單文檔工程,可命名為BmpCapture,為簡化程式設計不選取工具條、狀态條、ActiveX支援、3D控件和列印及列印預覽等選項。注意在AppWizard對話框的Step6中,選取視圖類的基類為CScrollView,因為不同的機器上,螢幕分辨率有可能不一樣,是以應該建立一滾動視圖,以适應不同分辨率大小的圖形。 當然要完全支援滾動視圖并沒有這麼簡單,我們還必須手工加入代碼。 在文檔類中加入一保護型成員變量protected: CSize m_SizeDoc,該成員變量用于儲存實際圖形的分辨率的大小,也就是文檔的尺寸;因為該成員被設定為保護型成員,故不能由與該文檔相連的視圖直接處理,而為能夠根據實際圖形分辨率大小改變該成員變量,應在文檔類中相應定義兩成員函數: ①CSize GetDocSize() {return m_SizeDoc;} 該函數用以擷取實際圖形分辨率大小; ②void SetDocSize(CSize size) {m_SizeDoc=size;} 該函數用于根據實際圖形分辨率大小來設定m_SizeDoc成員變量的值; 那麼如何根據不同視窗對象客戶區的大小來改變m_sizeDoc的大小呢? 用GetClientRect函數來獲得視窗對象客戶區的大小 CWnd *m_pWndShow=GetDesktopWindow();//得到桌面視窗 CRect rect; m_pWndShow->GetClientRect(rect); size.cx=rect.Width(); size.cy=rect.Height(); 然後進行修改如下: CBmpCaptureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->SetDocSize(size); 記得在每次修改了m_sizeDoc之後要調用視圖類的SetScrollSizes成員函數以傳遞該尺寸。 SetScrollSizes(MM_TEXT,pDoc->GetDocSize()); 除了這些工作以外,還需要重載CBmpCaptureView類的成員函數OnInitialUpdate,該函數在視圖第一次與文檔相連時被調用。通過重載這個函數,能把文檔的尺寸在最初更新視圖前通知給視圖。 void CBmpCaptureView::OnInitialUpdate() { CScrollView::OnInitialUpdate(); CSize sizeTotal; // TODO: calculate the total size of this view sizeTotal.cx = sizeTotal.cy = 100; SetScrollSizes(MM_TEXT, sizeTotal); } 所抓取的圖形應首先存于一記憶體DC的CBitmap對象中,然後在OnDraw函數中重繪。為此,在CBmpCaptureDoc類中加入一CBitmap型成員變量,如下: public: CBitmap m_bitmap; 完成了以上的工作以後,現在開始在程式中加入熱鍵激活的功能。在抓圖程式中所謂熱鍵激活是指當程式被最小化後,按下熱鍵後即可完成抓圖工作,同時恢複抓圖程式界面,并在架構視窗的客戶區顯示所抓取的圖形。 由于在ClassWizard中并沒有封裝熱鍵處理消息(WM_HOTKEY),是以必須手工加入所有的代碼。 ①首先在BmpCaptureView.h檔案中,加入熱鍵消息響應函數的聲明: protected: //{{AFX_MSG(CBmpCaptureView) afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg LRESULT OnHotKey(WPARAM wParam,LPARAM lParam); //}}AFX_MSG DECLARE_MESSAGE_MAP() ②然後在BmpCaptureView.cpp檔案中,找到消息映射的定義處,加入以下語句: BEGIN_MESSAGE_MAP(CBmpCaptureView, CScrollView) //{{AFX_MSG_MAP(CBmpCaptureView) ON_WM_CREATE() //}}AFX_MSG_MAP ON_MESSAGE(WM_HOTKEY,OnHotKey) //消息和函數發生關聯 END_MESSAGE_MAP() ③下一步在OnCreate函數中加入初始化代碼并用RegisterHotKey函數向系統登記熱鍵。初始化部分包括将m_bitmap對象初始化為合适的大小,并與視圖視窗相相容同時将位圖對象清空。重載OnCreate函數如下: int CBmpCaptureView::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CScrollView::OnCreate(lpCreateStruct) == -1) return -1; RegisterHotKey(m_hWnd,1001,MOD_CONTROL|MOD_ALT,’Y’);//此處登記熱鍵 //為”Ctrl+Alt+Y” // TODO: Add your specialized creation code here CWnd *m_pWndShow=GetDesktopWindow();//得到桌面視窗 CClientDC windowDC(this); CBmpCaptureDoc* pDoc = GetDocument(); CRect rect; m_pWndShow->GetClientRect(rect); int cx=rect.Width(),cy=rect.Height(); pDoc->m_bitmap.CreateCompatibleBitmap(&windowDC, cx,cy); CDC memoryDC; memoryDC.CreateCompatibleDC(&windowDC); CBitmap* pOldBitmap = memoryDC.SelectObject(&pDoc->m_bitmap); CBrush* pWhiteBrush = new CBrush(RGB(255,255,255)); CRect rect1(0, 0, cx-1, cy-1); memoryDC.FillRect(rect1, pWhiteBrush);//将位圖清空 memoryDC.SelectObject(pOldBitmap); delete pWhiteBrush; return 0; } ④接下來當然得編寫響應熱鍵消息的函數OnHotKey了,在這裡首先通過檢查參數wParam來判斷是否是所期望的熱鍵,然後擷取桌面視窗DC并使用BitBlt函數将其内容拷至m_bitmap對象中,最後還有一段将被最小化的視窗恢複顯示的代碼。因為代碼中用到了CMainFrame類指針,是以需要在BmpCaptureView.cpp檔案中加入語句#include "MainFrm.h" LRESULT CBmpCaptureView::OnHotKey(WPARAM wParam,LPARAM lParam) { if (wParam==1001) { CWnd *m_pWndShow=GetDesktopWindow();//得到桌面視窗 ASSERT(m_pWndShow!=NULL); CDC *pdc_Showed=m_pWndShow->GetDC(); CRect rect; m_pWndShow->GetClientRect(rect); CSize size; size.cx=rect.Width(); size.cy=rect.Height(); CBmpCaptureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDoc->SetDocSize(size); SetScrollSizes(MM_TEXT,pDoc->GetDocSize()); CClientDC cdc(this); CDC memoryDC; memoryDC.CreateCompatibleDC(pdc_Showed); CBitmap* pOldBitmap = memoryDC.SelectObject(&pDoc->m_bitmap); size=pDoc->GetDocSize(); memoryDC.BitBlt (0,0,size.cx,size.cy,pdc_Showed,0,0,SRCCOPY); Invalidate();//使視窗重畫 memoryDC.SelectObject(pOldBitmap); //以下重新顯示被最小化的視窗 CMainFrame* pwnd=(CMainFrame*)AfxGetApp()->m_pMainWnd; if(pwnd->SetForegroundWindow()) { pwnd->ShowWindow(SW_SHOWNORMAL); pwnd->UpdateWindow(); } } return 0; } 以上OnHotKey函數中對 Invalidate() 的調用将迫使視圖視窗重畫,使視圖類調用OnDraw函數,以下為OnDraw函數的代碼,其功能為在視圖客戶區内顯示m_bitmap對象: void CBmpCaptureView::OnDraw(CDC* pDC) { CBmpCaptureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CDC memoryDC; memoryDC.CreateCompatibleDC(pDC); CBitmap* pOldBitmap = memoryDC.SelectObject(&pDoc->m_bitmap); CSize size=pDoc->GetDocSize(); pDC->BitBlt(0, 0,size.cx, size.cy, &memoryDC, 0, 0, SRCCOPY); memoryDC.SelectObject(pOldBitmap); } 到這裡已經實作了:①熱鍵激活的功能;②螢幕抓圖的功能。剩下的工作是将所抓取的圖形儲存起來。 由于CBitmap類對象是一個DDB(裝置相關位圖),而我們将要儲存的BMP位圖則是DIB(裝置無關位圖)。是以首先得将DDB格式轉換為DIB格式,然後再進行儲存。 在CBmpCaptureDoc類中增加一個SaveBmp成員函數,代碼如下: BOOL CBmpCaptureDoc::SaveBmp(HBITMAP hBitmap, CFile& file) { if (hBitmap == NULL) return FALSE; HDC hDC; int iBits; //目前顯示分辨率下每個像素所占位元組數 WORD wBitCount; //位圖中每個像素所占位元組數 //定義調色闆大小, 位圖中像素位元組大小 , //位圖大小 DWORD dwPaletteSize=0, dwBmBitsSize, dwDIBSize; BITMAP Bitmap; HANDLE hDib; HPALETTE hPal,hOldPal=NULL; //位圖屬性結構 BITMAPFILEHEADER bmfHdr; // 位圖檔案頭 LPBITMAPINFOHEADER lpBI; // 位圖頭指針 BITMAPINFOHEADER bi; //位圖資訊頭結構 //DWORD dwDIBSize; //計算位圖檔案每個像素所占位元組數 hDC = CreateDC("DISPLAY",NULL,NULL,NULL); iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); DeleteDC(hDC); if (iBits<=1) wBitCount=1; else if (iBits<=4) wBitCount=4; else if (iBits<=8) wBitCount=8; else if (iBits<=16) wBitCount=16; else if (iBits<=24) wBitCount=24; //計算調色闆大小 if (wBitCount<=8) dwPaletteSize=(1<<wBitCount) * sizeof(RGBQUAD); GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); bi.biSize= sizeof(BITMAPINFOHEADER); bi.biWidth= Bitmap.bmWidth; bi.biHeight= Bitmap.bmHeight; bi.biPlanes= 1; bi.biBitCount= wBitCount; bi.biCompression= BI_RGB; bi.biSizeImage= 0; bi.biXPelsPerMeter= 0; bi.biYPelsPerMeter= 0; bi.biClrUsed= 0; bi.biClrImportant= 0; dwBmBitsSize=((Bitmap.bmWidth * wBitCount+31)/32)* 4 *Bitmap.bmHeight ; hDib=GlobalAlloc(GHND,dwBmBitsSize+ dwPaletteSize+sizeof(BITMAPINFOHEADER)); lpBI=(LPBITMAPINFOHEADER)GlobalLock(hDib); *lpBI=bi; // 處理調色闆 hPal=(HPALETTE)GetStockObject(DEFAULT_PALETTE); if (hPal) { hDC=GetDC(NULL); hOldPal=(HPALETTE)SelectPalette(hDC,hPal,FALSE); RealizePalette(hDC); } // 擷取該調色闆下新的像素值 GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, (LPSTR)lpBI+sizeof(BITMAPINFOHEADER) +dwPaletteSize,(BITMAPINFO*) lpBI, DIB_RGB_COLORS); //恢複調色闆 if (hOldPal) { SelectPalette(hDC,hOldPal, TRUE); RealizePalette(hDC); ReleaseDC(NULL, hDC); } bmfHdr.bfType = 0x4d42; // "BM" dwDIBSize = sizeof(BITMAPINFOHEADER) + dwPaletteSize + dwBmBitsSize; bmfHdr.bfSize = dwDIBSize+sizeof(BITMAPFILEHEADER); bmfHdr.bfReserved1 = 0; bmfHdr.bfReserved2 = 0; bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) + (DWORD)sizeof(BITMAPINFOHEADER) + dwPaletteSize; TRY { // 寫檔案頭 file.Write((LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER)); // // 寫DIB頭及資料位 // file.WriteHuge((LPSTR)lpBI, dwDIBSize); } CATCH (CFileException, e) { ::GlobalUnlock((HGLOBAL) hDib); THROW_LAST(); } END_CATCH ::GlobalUnlock((HGLOBAL) hDib); return TRUE; } 隻要熟悉了BMP位圖的檔案格式,以上的代碼就不難了解。由于在很多書籍中對BMP位圖的檔案格式都有詳細的介紹,故本文不再贅述。函數的最後是一段異常處理代碼。 最後重載CBmpCaptureDoc類的OnSaveDocument成員函數,在該函數中調用以上的SaveBmp函數。代碼如下: BOOL CBmpCaptureDoc::OnSaveDocument(LPCTSTR lpszPathName) { // TODO: Add your specialized code here and/or call the base class CFile file; CFileException fe; if (!file.Open(lpszPathName, CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive, &fe)) { ReportSaveLoadException(lpszPathName, &fe, TRUE, AFX_IDP_INVALID_FILENAME); return FALSE; } BOOL bSuccess = FALSE; TRY { BeginWaitCursor(); HBITMAP m_hBMP=(HBITMAP)m_bitmap; bSuccess = SaveBmp(m_hBMP,file); file.Close(); } CATCH (CException, eSave) { file.Abort(); EndWaitCursor(); ReportSaveLoadException(lpszPathName, eSave, TRUE, AFX_IDP_FAILED_TO_SAVE_DOC); return FALSE; } END_CATCH EndWaitCursor(); SetModifiedFlag(FALSE); if (!bSuccess) { CString strMsg; strMsg.LoadString(IDS_CANNOT_SAVE_BMP); MessageBox(NULL, strMsg, NULL, MB_ICONINFORMATION | MB_OK); } return bSuccess; } 其中IDS_CANNOT_SAVE_BMP是字元串”Can not save bmp”的ID。可以看到OnSaveDocument中除了對SaveBmp函數的調用外,還有許多異常處理代碼。 好了,現在可以Build這個工程來看看這個程式的功能了。 先将生成的執行程式最小化,然後按下熱鍵”Ctrl+Alt+Y”,你會發現你的桌面已經被抓到BmpCapture中了,從“檔案”菜單中選取“儲存”子菜單就可以将你的桌面儲存為Bmp位圖了。 以上程式在VC6,WIN98中調試通過。 |