一、 簡介
屏幕抓圖程序在處理圖形中應用廣泛。作為Windows XP及以后版本操作系統的圖形處理內核,GDI+在二維幾何圖形處理、圖像顯示與轉換和字符排版等方面簡直是傳統GDI程序員的一種解脫。但是,至少在目前情況下,GDI+尚不能完全代替GDI。與GDI相比,它至少還存在以下不足:
不支持從內存到屏幕的位傳輸操作;
不支持光柵“位運算”操作;
如果程序性能、速度要求比較嚴格,在圖片輸出方面的表現較差時,GDI往往能取代實現高性能的輸出。
本文通過對流行的屏幕抓圖程序工作原理的剖析,力圖向讀者闡明GDI+與GDI各自在圖形處理方面的優缺點,并給出相應的VC++ .NET代碼實現。
二、 GDI在抓圖中的關鍵作用
要實現屏幕抓圖,關鍵有兩點:一是獲取圖片所在窗口的窗口句柄,即在何處捕獲圖片;二是保存抓取的圖片,實現這一點正是GDI+的強項。
對于問題一,可以利用SetCapture函數,它能夠追蹤鼠標指針的移動(包括在屏幕抓圖程序窗口之外的窗口)。在移動鼠標的過程中,它還可以根據鼠標的指針所在位置來判斷當前窗口的窗口句柄。我們還可以使用函數WindowFromPoint,這個函數能夠找出鼠標指針當前位置所對應的窗口句柄。
使用過知名的抓圖軟件SnagIT的讀者都知道,在選擇抓圖窗口時,鼠標指針所在位置的窗口都會出現加粗的紅色邊框,以提醒目前所選擇的窗口,這個功能實現起來有些復雜。下面介紹在GDI中如何使這個紅色邊框出現。
【注意】正是由于這個紅色邊框的實現,讀者才能發現GDI+在這方面的弱點。
在GDI中,一個最基本的概念就是設備環境(DC),每一個窗口都具有自己的DC。如果能夠找到窗口的DC,那么,用戶就能夠在該窗口的任何位置繪圖。然而,在屏幕抓圖程序中,由于用戶所選擇的窗口不固定,所以,要想得到鼠標指針所處窗口的DC并不容易。這一問題的答案在于GetDC函數。下面是GetDC的函數聲明:
HDC GetDC(HWND hWnd);
這里,hWnd是DC對應的窗口句柄。注意,當hWnd為空時,該函數返回的是整個屏幕的設備環境句柄。這就意味著,開發人員可以在屏幕上的任何位置進行任意的繪圖操作。
在鼠標指針所處的窗口繪圖時,繪圖的目的只是為了提醒用戶目前所選擇的窗口,所以,在繪圖時,必須保證不會破壞窗口原有的畫面。這時可將窗口的繪圖模式設為RS_NOTXORPEN,將畫筆顏色與屏幕顏色進行異或運算之后,再對屏幕顏色取反即可。RS_NOTXORPEN運算方式的特點在于:對同一像素進行兩次RS_NOTXORPEN運算后,像素值并不會發生變化。這樣,在同一個地方進行兩次繪圖后,窗口的畫面并不會發生任何變化。
【注意】這些功能在GDI+中很難實現。
三、 編碼實現
由上可知,屏幕抓圖至少分為3個步驟:
(1) 啟用鼠標指針捕獲。
(2) 在鼠標指針所在處的窗口進行繪圖,提示抓圖的目標。
(3) 選定目標窗口時,將目標窗口的畫面保存為自定義的位圖并終止鼠標指針捕獲。
以下是具體的編程步驟:
(1)在Visual C++ .NET中按照GDI+程序的框架新建一個基于對話框的項目ScreenCapture,然后準備好一個外形為相機的光標文件(*.cur),將之引入資源管理器(IDC_CAMERA)。接著在CScreenCaptureDlg類中加入以下兩個全局變量:
HWND hwndCapture;
Crect rectCapture;
(2)通過類向導加入對WM_MOUSEMOVE及WM_LBUTTONUP事件的響應函數,分別如下所示。
void CScreenCaptureDlg::OnMouseMove(UINT nFlags, CPoint point)
{
//如果用戶按隹鼠標左鍵不放,則開始抓取圖片
if(nFlags==MK_LBUTTON){
//隱藏程序窗口,以免影響在抓取時的“視野”
ShowWindow(SW_HIDE);
//載入“照相機”鼠標指針,開始追蹤鼠標指針的移動
HCURSOR cur=LoadCursor(AfxGetInstanceHandle(),MAKEINTRESOURCE(IDC_CAMERA));
SetCursor(cur);
SetCapture();
//獲得鼠標指針所在窗口的句柄
this->ClientToScreen(&point);
hwndCapture=(HWND)::WindowFromPoint(point);
//取得屏幕的設備環境句柄,以便在屏幕的任何位置繪圖
HDC hDC=::GetDC(NULL);
//建立一個紅色的畫筆
HPEN hPen=CreatePen(PS_INSIDEFRAME,6,RGB(255,0,0));
//將繪圖模式設為R2_NOTXORPEN,在繪圖時可以不破壞原有的背景
int nMode=SetROP2(hDC,R2_NOTXORPEN);
HPEN hpenOld=(HPEN)SelectObject(hDC,hPen);
//得到鼠標指針所在窗口的區域
::GetWindowRect(hwndCapture,&rectCapture);
//在鼠標指針所在處的窗口四周畫一紅色的矩形,做為選定時的提示
POINT pt[5];
pt[0]=CPoint(rectCapture.left,rectCapture.top);
pt[1]=CPoint(rectCapture.right,rectCapture.top);
pt[2]=CPoint(rectCapture.right,rectCapture.bottom);
pt[3]=CPoint(rectCapture.left,rectCapture.bottom);
pt[4]=CPoint(rectCapture.left,rectCapture.top);
::Polyline(hDC,pt,5);
//延時后再重繪紅色矩形,這樣不會破壞原有的內容
Sleep(100);
::Polyline(hDC,pt,5);
::SelectObject(hDC,hpenOld);
::ReleaseDC(NULL,hDC);
}
CDialog::OnMouseMove(nFlags, point);
}
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
// 得到鼠標指針所在窗口的區域寬、高
int nWidth=rectCapture.Width();
int nHeight=rectCapture.Height();
HDC hdcScreen,hMemDC;
HBITMAP hBitmap,hOldBitmap;
//建立一個屏幕設備環境句柄
hdcScreen=CreateDC("DISPLAY",NULL,NULL,NULL);
hMemDC=CreateCompatibleDC(hdcScreen);
//建立一個與屏幕設備環境句柄兼容、與鼠標指針所在窗口的區域等大的位圖
hBitmap=CreateCompatibleBitmap(hdcScreen,nWidth,nHeight);
//把新位圖選到內存設備描述表中
hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap);
//把屏幕設備描述表拷貝到內存設備描述表中
BitBlt(hMemDC,0,0,nWidth,nHeight,hdcScreen,rectCapture.left,rectCapture.top,SRCCOPY);
DeleteDC(hdcScreen);
DeleteDC(hMemDC);
//返回位圖句柄
//打開剪貼板,并將位圖拷到剪貼板上
OpenClipboard();
EmptyClipboard();
SetClipboardData(CF_BITMAP,hBitmap);
//關閉剪貼板
CloseClipboard();
MessageBox("屏幕內容已經拷到剪貼板!");
ReleaseCapture();
//恢復窗口顯示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
至此,一個具有專業效果的屏幕抓圖程序的核心已經搞定。
四、 用GDI+實現畫面的保存
經過上面兩步,如果用戶在對話框中按住鼠標左鍵不放,程序便開始“抓圖”。當選擇好抓圖的目標后,松開鼠標左鍵,抓圖的目標窗口的畫面就自動保存到剪貼板中了。但是,把畫面保存到文件中更為重要。如果用GDI的方式來操作,需要對各種類位圖的結構有詳盡的了解,極其麻煩。但如果用GDI+來實現之則極為容易。下面介紹如何將已經抓到的圖片保存到一個BMP文件中。
由上面知,抓圖程序已經得到了所捕獲的窗口的位圖句柄,接下來要將位圖句柄保存為相應的位圖文件。這一切歸功于GDI+的Bitmap類,詳見下列代碼。
void CScreenCaptureDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
//……省略
if(GetSaveFileName(&ofn))
{
CLSID pngClsid;
Bitmap bmp(hBitmap,NULL);
//獲取BMP文件的編碼方式
GetEncoderClsid(L"image/bmp",&pngClsid);//幫助函數
CString tmp(ofn.lpstrFile);
CStringW filename((LPCSTR)tmp);
//保存所截取的屏幕圖片
bmp.Save(filename,&pngClsid);
}
ReleaseCapture();
MessageBox("屏幕內容已經保存到文件中!");
//恢復窗口顯示模式
ShowWindow(SW_NORMAL);
CDialog::OnLButtonUp(nFlags, point);
}
五、 小結
本文通過一個專業的屏幕抓圖程序的核心實現,對比分析了GDI與GDI+各自的優缺點。但我們相信,GDI+作為新一代圖形引擎,隨著版本的不斷升級,其遲早要淘汰掉GDI。本人拙見,不足處還望讀者指正。
另外,本文源碼在Windows 2000/VC++.NET 2003環境中調試通過。調試過程中注意:
確保工程對GDI+庫的正確引用:在頭文件stdafx.h中要加入相應引用;在應用程序類的InitInstance成員函數前后及其析構函數中加適當的操作;工程編譯時要加入對gdiplus.lib的引用(“項目”|“添加現有項”,我的機器上是在C:\Program Files\Microsoft Visual Studio.NET\vc7\platformSDK\lib下找到庫文件)。
posted on 2008-08-13 23:02
幽幽 閱讀(2020)
評論(0) 編輯 收藏 引用