抓屏的各种方法(http://www.codeproject.com/KB/dialog/screencap.aspx)
文章翻譯自 P.GopalaKrishna 的 Various methods for capturing the screen 一文,原版地址見下面。本文章版權(quán)歸原作者所有。
??? 如果轉(zhuǎn)載該譯文 , 請保證文章的完整性,并注明來自 www.farproc.com
袁曉輝 ??
2005/6/12
原版地址: http://www.codeproject.com/dialog/screencap.asp#Windows%20Media%20API%20for%20Capturing%20the%20Screen%20:
?
本文附帶源碼 1 下載 39K
本文附帶源碼 2 下載 135.5K
本文附帶源碼 3 下載 59.8K
?
目錄:
?
l????????? 導(dǎo)言
l????????? 用 GID 函數(shù)抓屏
l????????? 用 DirectX 方式抓屏
l????????? 用 Windows Media API 抓屏
?
導(dǎo)言
?
有時(shí)候我們需要編程抓取整個(gè)屏幕上的內(nèi)容,下面我將介紹抓屏是如何實(shí)現(xiàn)的。典型地,我們可以用 GID 和 DirectX 來完成,另外一個(gè)選擇是 Windows Media API ,在這篇文章我會(huì)逐一加以分析。在每一種方法里,一旦我們把屏幕的內(nèi)容保存到了程序定義的內(nèi)存塊或 bitmap 文件里,我們就可以進(jìn)一步利用它們來生成動(dòng)畫和電影,這個(gè)過程你可以參考“ 從 HBitmap 創(chuàng)建電影 ”一文中,以獲得更多的幫助。
?
用 GDI 函數(shù)抓屏
如果我們不太在意抓屏的效率,并且我們想要的只是一個(gè)屏幕快照的話,可以考慮使用 GDI 方式。這種抓屏機(jī)制是以“桌面也是一個(gè)窗口,桌面也有一個(gè)窗口句柄( HWND )”這個(gè)簡單的常識(shí)為基礎(chǔ)的,如果我們得到了桌面的設(shè)備上下文( DC ),就可以利用 blit (復(fù)制)它的內(nèi)容到我們創(chuàng)建的 DC 中。我們可以用 GetDeskWindow ()得到桌面的窗口句柄,從句柄得到 DC 也是很容易的。具體的實(shí)現(xiàn)步驟為:
?
1.????????? 通過 GetDesktopWindow ()函數(shù)得到桌面的窗口句柄
2.????????? 用 GetDC ()取得桌面窗口的 DC
3.????????? 創(chuàng)建和屏幕 DC 兼容的位圖和 DC ( CreateCompatibleBitmap ()和 CreateCompatibleDC ()),并把這個(gè)位圖選進(jìn)該 DC ( SelectObject ())
4.????????? 當(dāng)你準(zhǔn)備好抓屏?xí)r,就復(fù)制桌面窗口 DC 的內(nèi)容到兼容 DC ,你就完成的抓屏過程,兼容位圖中就是抓屏?xí)r刻的屏幕內(nèi)容
5.????????? 完成后別忘了釋放你創(chuàng)建的對象,內(nèi)存是寶貴的(對別的程序來說)
?
示例代碼:
void CaptureScreen()
{
??? int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
??? int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
??? HWND hDesktopWnd = GetDesktopWindow();
??? HDC hDesktopDC = GetDC(hDesktopWnd);
??? HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
??? HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC,
??????? nScreenWidth, nScreenHeight);
??? SelectObject(hCaptureDC,hCaptureBitmap);
??? BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,hDesktopDC,0,0,SRCCOPY);
??? SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
??????????????????????????????? //here to save the captured image to disk
??? ReleaseDC(hDesktopWnd,hDesktopDC);
??? DeleteDC(hCaptureDC);
??? DeleteObject(hCaptureBitmap);
}
?
上面代碼段中, GetSystemMetrics ()返回屏幕的寬度( SM_CXSCREEN )和高度( SM_CYSCREEN )。關(guān)于如何保存抓到的位圖到文件和如何置到剪貼板,請參看附帶的源代碼,很簡單的。示例代碼每隔一段時(shí)間就通過上述技術(shù)抓屏,并把圖像序列保存到動(dòng)畫。
?
DirectX 方式
用 DreictX 進(jìn)行抓屏也是很簡單的, DirectX 提供了很優(yōu)雅的實(shí)現(xiàn)。
每個(gè) DirectX 程序都包含一個(gè)被我們稱作緩沖的內(nèi)存區(qū)域,其中保存了和該程序有關(guān)的顯存內(nèi)容,這在程序中被稱作后臺(tái)緩沖( Back Buffer ),有些程序有不止一個(gè)的后臺(tái)緩沖。還有一個(gè)緩沖,在默認(rèn)情況下每個(gè)程序都可以訪問-前臺(tái)緩沖。前臺(tái)緩沖保存了和桌面相關(guān)的顯存內(nèi)容,實(shí)質(zhì)上就是屏幕圖像。
我們的程序通過訪問前臺(tái)緩沖就可以捕捉到當(dāng)前屏幕的內(nèi)容。由 DirectX 的底層優(yōu)化機(jī)制做保證,我們的抓屏效率是很高的,至少比 GDI 方式高。
在 DirectX 程序中訪問前臺(tái)緩沖是很簡單的, IDirect3DDevice8 接口提供了 GetFrontBuffer() 方法,它接收一個(gè) IDirect3DSurface8 對象指針做參數(shù),并復(fù)制前臺(tái)緩沖的內(nèi)容到該 Surface 。 IDirect3DSurfce8 對象可以用 IDirect3DDevice8::CreateImageSurface() 得到。一旦屏幕內(nèi)容被保存到了這個(gè) surface ,我們就可以用 D3DXSaveSurfaceToFile() 方法直接把內(nèi)容保存到磁盤 bmp 文件。示例代碼如下:
extern IDirect3DDevice8* g_pd3dDevice;
Void CaptureScreen()
{
??? IDirect3DSurface8 * pSurface;
??? g_pd3dDeviceàCreateImageSurface(ScreenWidth,ScreenHeight,
??????? D3DFMT_A8R8G8B8,&pSurface);
??? g_pd3dDevice->GetFrontBuffer(pSurface);
??? D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,
??????? NULL,NULL);
??? pSurface->Release();
}
上面, g_pd3dDevice 是一個(gè)初始化好的 IDirect3DDevice 對象,這個(gè)例子直接把捕捉到的圖像保存到文件。然而,有時(shí)候我們想訪問直接這個(gè)圖像中的各個(gè)位,我們可以使用 IDirect3DSurface8::LockRect() ,它給我們一個(gè)執(zhí)行 surface 內(nèi)存的指針,也就是捕捉到的圖像的數(shù)據(jù)。我們復(fù)制這些數(shù)據(jù)到程序定義的內(nèi)存中就可以操作它了。看下面的代碼:
extern void* pBits;
extern IDirect3DDevice8* g_pd3dDevice;
IDirect3DSurface8 * pSurface;
g_pd3dDeviceàCreateImageSurface(ScreenWidth,ScreenHeight,
??????????????????????????????? D3DFMT_A8R8G8B8,&pSurface);
g_pd3dDevice->GetFrontBuffer(pSurface);
D3DLOCKED_RECT lockedRect;
pSurfaceàLockRect(&lockedRect,NULL,
????????????????? D3DLOCK_NO_DIRTY_UPDATE|D3DLOCK_NOSYSLOCK|
????????????????? D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
??? memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 ,
??????? (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
??????? ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();
?
上面的 pBits 是一個(gè) void* ,請保證為先為它分配組足夠的內(nèi)存空間。 BITSPERPIXEL 一般用 32 位色即可,它也取決于你的顯示器當(dāng)前配置。一個(gè)需要注意的是, surface 的寬度和被捕捉的屏幕寬度不一樣。由于內(nèi)存對齊的原因 ( 按 WORD 對齊的內(nèi)存通常在訪問時(shí)效率較高 ) , surface 在每行結(jié)尾處可能會(huì)有多余的 bits 以使它對齊到 word 邊界上。 lockedRect.Pitch 給我們提供了兩個(gè)連續(xù)行的開端之間的字節(jié)數(shù)。也就是說我們在讀取一行時(shí)要向后移動(dòng)指針 Pitch 字節(jié)而不是 Width 字節(jié)。你可以用下面的代碼反序復(fù)制 surface :
for( int i=0 ; i < ScreenHeight ; i++)
{
??? memcpy((BYTE*) pBits +( ScreenHeight - i - 1) *
??????? ScreenWidth * BITSPERPIXEL/8 ,
??????? (BYTE*) lockedRect.pBits + i* lockedRect.Pitch ,
??????? ScreenWidth* BITSPERPIXEL/8);
}
這對于從 top-down 位圖到 bottom-up 位圖很有用。
?
我們還可以使用 IDirect3DSurface9 的 GetDC() 方法取得 DirectX surface 的 GDI 兼容 DC ,然后復(fù)制它的內(nèi)容到我們的兼容 DC 。如果你用的是 DirectX9 ,試試吧。
最后,需要注意的一點(diǎn),文檔提到: FrontBuffer 是一個(gè)比較慢的操作,設(shè)計(jì)就是如此,所以在效率很關(guān)鍵的程序中應(yīng)避免使用。已經(jīng)警告你了!本文附帶的源代碼用這種技術(shù)定時(shí)捕捉屏幕,并保存為動(dòng)畫。
用 Windows Media API 抓屏
Windows Media 9.0 支持用 Windows Media Encoder 9 API 來抓屏。它有一個(gè)編碼器叫 Windows Media Video 9 Screen codec ,特別為抓屏優(yōu)化過。 Windows Media Encoder API 提供了一個(gè) IWMEncoder2 接口可以用來高效地捕捉屏幕圖像。
?
用這種技術(shù)進(jìn)行抓屏也很簡單,首先我們用 CoCreateInstance() 創(chuàng)建一個(gè) IWMEncoder2 對象:
IWMEncoder2* g_pEncoder=NULL;
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
??????? IID_IWMEncoder2,(void**)&g_pEncoder);
這個(gè) Encoder 對象包含了捕捉屏幕的所需的全部操作,然而為了正確地工作,編碼器對象的行為取決于被稱作 profile 的設(shè)置。一個(gè) profile 只是一個(gè)包含了所有控制編碼操作設(shè)置的文件,我們可以根據(jù)被捕捉的數(shù)據(jù)的特性在運(yùn)行時(shí)創(chuàng)建包含自定義設(shè)置的 profile 。為了在你的抓屏程序中使用 profile ,我們基于 Windows Media Video 9 Screen codec 來創(chuàng)建自定義的 profile 。自定義的 profile 對象從 IWMEncProfile2 開始就被支持了。我們可以用 CoCreateInstance 來創(chuàng)建自定義 profile
IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
??????? IID_IWMEncProfile2,(void**)&g_pProfile);
我需要在 profile 里指定編碼器的聽眾( audience )。每個(gè) profile 可以包含多個(gè)聽眾配置,它們是 IWMEncAudienceObj 接口對象。這里我們?yōu)?/span> profile 使用一個(gè)聽眾。我們可以通過 IWMEncProfile::AddAudience() 為我們的 profile 創(chuàng)建聽眾,這個(gè)函數(shù)返回一個(gè) IWMEncAudienceObj 指針,可以用來配置視頻編碼器 ( IWMEncAudienceObj::put_VideoCodec() ) ,視頻幀對象 ( IWMEncAudienceObj::put_VideoHeight() 和 IWMEncAudienceObj::put_VideoWidth() ) 我們用下面的代碼來配置視頻編碼器:
extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2')
??? //MSS2 is the fourcc for the screen codec
?
long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
??? &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);
?
fourcc 是針對每個(gè)編碼器的唯一的標(biāo)識(shí), Windows Media Video 9 Screen codec 的 fourcc 為 MSS2 。 IWMEncAudienceObj::put_VideoCodec() 接受 profile 索引來組織一個(gè) profile ,索引可以用 IWMEncProfile::GetCodecIndexFromFourCC() 取得。
?
一旦我們配置完畢一個(gè) profile 對象,我們就可以用 IWMEncSourceGroup :: put_Profile() 選擇這個(gè) profile 到我們的編碼器。一個(gè)源組( SourceGruop )是一組視頻流來源或音頻流來源,或 html 來源。每個(gè)編碼器可以使用許多源組,并從中取得輸入數(shù)據(jù)。由于我們的程序僅僅使用視頻流中是視頻來源。這個(gè)視頻來源需要用 IWMEncVideoSource2::SetInput(BSTR) Screen Device 來配置為輸入來源:
extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");
?
目的輸出可以用 IWMEncFile::put_LocalFileName() 配置為保存到視頻文件( wmv 文件)。 IWMEncFile 對象可以用 IWMEncoder::get_File() 得到:
IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);
?
現(xiàn)在,一旦編碼器對象的一切所需配置都完成后,我們就可以用 IWMEncoder::Start() 開始抓屏。 IWMEncoder::Stop() 和 IWMEncoder::Pause 可以用來停止和暫停捕捉。
這些適用于全屏捕捉,我們也可以通過調(diào)整輸入視頻來源流的屬性來選擇一個(gè)區(qū)域進(jìn)行捕捉。我們可以用 IWmEnVideoSource2 的 I PropertyBag 接口來實(shí)現(xiàn):
#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );
?
本文附帶的源碼實(shí)現(xiàn)此中技術(shù)的抓屏。除去生成的動(dòng)畫質(zhì)量很好外,一個(gè)有意思的地方是鼠標(biāo)指針也被抓到了( GDI 和 DirectX 默認(rèn)是不抓取鼠標(biāo)指針的)。
?
注意,為了適用 WindowMedia9.0 API ,你的電腦必須安裝 Windows Media9.0 SDK ,你可以用下面地址下載:
- http://msdn.microsoft.com/library/default.asp?url=/downloads/list/winmedia.asp
最終用戶必須安裝 Windows Media Encoder 9 系列才能運(yùn)行你的程序。在發(fā)布基于 Windows Media Encoder SDK 的程序時(shí), Windows Media Encoder 軟件也必須附帶上去,要么在你的軟件安裝時(shí)自動(dòng)安裝 Windows Media Encoder 要么讓用戶自己下載安裝。
?
Windows Encoder 9.0 可以從下面地址下載:
http://www.microsoft.com/windows/windowsmedia/ 9series/encoder/default.aspx
?
結(jié)論
上面討論的各種方法都是基于一個(gè)目標(biāo)-抓取屏幕的內(nèi)容。然而適用不同的技術(shù),得到的結(jié)果也不一樣。如果我們需要的只是偶爾的抓屏, GDI 方式是個(gè)好的選擇,因?yàn)樗唵巍H欢绻阆氲玫礁鼘I(yè)的結(jié)果,可以使用 Windows Media 。一個(gè)可能沒有意義的要點(diǎn)是,這些技術(shù)捕捉到的內(nèi)容的質(zhì)量很大程度上決于你的系統(tǒng)設(shè)置,比如進(jìn)制硬件加速會(huì)大大提高抓屏的質(zhì)量和程序的運(yùn)行效率。 ?
?
?
?
?
?
?
?
?
?
?
?
?
?
總結(jié)
以上是生活随笔為你收集整理的抓屏的各种方法(http://www.codeproject.com/KB/dialog/screencap.aspx)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Wireshark抓包常见问题解析(转)
- 下一篇: 2017书单3