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