本文地址 : http://www.codeproject.com/dialog/screencap.asp
有时候我们需要在程序中截取屏幕,下面我将解释如何来实现。其中最直接的方式是使用 GDI 或者 DX,除此之外,还可以考虑 Windows Media API。 在这里我们会分别介绍他们的实现方法。
GDI 方式:
如果不考虑效果或者仅是随便的截图,我们可以考虑GDI。他的机制是利用了桌面也是一个窗口,因此都会有窗口句柄以及设备环境(DC)
如果我们获取了该DC,只需要把他的内容 blit 到我们的程序DC中即可,而知道了窗口句柄后,DC则是探囊取物。桌面的窗口句柄可以通过函数 GetDesktopWindow() 获取。
因此所涉及的步骤有:
1. 通过XX 函数获得窗口句柄
2. 通过句柄获得设备环境
3. 为桌面DC创建一个兼容DC以及创建兼容位图到该DC。可以使用CreateCompatibleDC() 和 CreateCompatibleBitmap();SelectObject()实现
4. 准备好了? OK, 把桌面DC BLIT 到 刚创建的兼容DC中 就搞定了(刚才创建的位图便是当前的桌面了)
5 记得释放内存。
Example
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返回屏幕高。 至于如何保存所获取的位图以及如何发送到
粘贴板,没什么好说的 HOHO。
DX :
通过DX截取屏幕是一件非常简单的事情。
每个 DX 应用程序都有一个缓冲来存放本程序相关的显存,也叫做背面缓冲。有些可能不止一个背面缓冲。还有另外一个可以直接存取的
缓冲——前台缓冲,他包含了于桌面相关的显存——实际上就是桌面的图片。
通过读取前台缓冲,我们便可以截取桌面了,而DX的内部机制可以确保得到优化的效果——至少比GDI强。
通过DX我们可以很轻松的对前台缓冲进行操作,接口 IDirect3DDevice8 提供了函数 GetFrontBuffer() ,带一个参数(IDirect3DSurface8),
该参数用于获得前台缓冲的内容。而 IDirect3DSurface8 对象可以通过函数 CreateImageSurface() 创建,一旦屏幕被截取到 surface 中,我们可以
用 D3DXSaveSurfaceFile() 把 surface 保存为位图格式,下面是截图代码:
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 是IDirect3DDevice的对象,并假设已经被初始化。 上面的代码是直接保存图片到本地磁盘,如果我们只是想对图片的象素进行操作,我们
可以使用 IDirect3DSurface8::LockRect(), 他让一个指针指向所截取图片的内存单元,我们可以把他拷贝到我们的程序定义的内存并进行操作,下面是
实现拷贝操作的代码段:
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 是一个 void 指针,在拷贝之前要却被他有足够的内存。 BITSPERPIXEL 典型的值是每个象素32位,但他可能因显示器的设置而改变。
这里特别需要注意的是,surface 的宽度并不等同于截取的屏幕图片的宽度。 由于内存对齐处理(以字对齐的方式存取快于非对齐内存),surface 可能会在每行的后面进行多余的填充。 不过 lockedRect.Pitch 确切的告诉我们两行之间的位数,也就是说我们可以通过 pitch 而非 width 获得正确的结果。
字对齐。
下面的代码段是以反序拷贝 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);
}
在 DX8 中, LockRect 是唯一的方式可以对截取的图片进行存取操作,而在 DX9 中,可以使用 GetDC() 来获得该图片的GDI兼容设备环境,
这样问题又回归到了第一种方式……
最后一点废话(文档的注意信息):GetFrontBuffer() 被设计为一个较慢的操作,在性能较差的机器的应用程序中不与考虑。
独子饿了! ……
有错误的理解请指正!谢谢