天天看點

編寫抓圖程式

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中調試通過。

繼續閱讀