目录:
1 导言
2 用GID函数抓屏
3  用DirectX方式抓屏
4 用Windows Media API抓屏

1 导言
有时候我们需要编程抓取整个屏幕上的内容,下面我将介绍抓屏是如何实现的。典型地,我们可以用GID和DirectX来完成,另外一个选择是 Windows Media API,在这篇文章我会逐一加以分析。在每一种方法里,一旦我们把屏幕的内容保存到了程序定义的内存块或bitmap文件里,我们就可以进一步利用它们来生成动画和电影,这个过程你可以参考“从HBitmap创建电影”一文中,以获得更多的帮助。

2 用GDI函数抓屏
如果我们不太在意抓屏的效率,并且我们想要的只是一个屏幕快照的话,可以考虑使用GDI方式。这种抓屏机制是以“桌面也是一个窗口,桌面也有一个窗口句柄(HWND)”这个简单的常识为基础的,如果我们得到了桌面的设备上下文(DC),就可以利用blit(复制)它的内容到我们创建的DC中。我们可以用 GetDeskWindow()得到桌面的窗口句柄,从句柄得到DC也是很容易的。具体的实现步骤为:

1.          通过GetDesktopWindow()函数得到桌面的窗口句柄
2.          用GetDC()取得桌面窗口的DC
3.          创建和屏幕DC兼容的位图和DC(CreateCompatibleBitmap ()和CreateCompatibleDC()),并把这个位图选进该DC(SelectObject())
4.          当你准备好抓屏时,就复制桌面窗口DC的内容到兼容DC,你就完成的抓屏过程,兼容位图中就是抓屏时刻的屏幕内容
5.          完成后别忘了释放你创建的对象,内存是宝贵的(对别的程序来说)

示例代码:
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)。关于如何保存抓到的位图到文件和如何置到剪贴板,请参看附带的源代码,很简单的。示例代码每隔一段时间就通过上述技术抓屏,并把图像序列保存到动画。

3 DirectX方式
用DreictX进行抓屏也是很简单的,DirectX提供了很优雅的实现。
每个DirectX程序都包含一个被我们称作缓冲的内存区域,其中保存了和该程序有关的显存内容,这在程序中被称作后台缓冲(Back Buffer),有些程序有不止一个的后台缓冲。还有一个缓冲,在默认情况下每个程序都可以访问-前台缓冲。前台缓冲保存了和桌面相关的显存内容,实质上就是屏幕图像。
我们的程序通过访问前台缓冲就可以捕捉到当前屏幕的内容。由DirectX的底层优化机制做保证,我们的抓屏效率是很高的,至少比GDI方式高。
在DirectX程序中访问前台缓冲是很简单的,IDirect3DDevice8接口提供了GetFrontBuffer()方法,它接收一个 IDirect3DSurface8对象指针做参数,并复制前台缓冲的内容到该Surface。IDirect3DSurfce8对象可以用 IDirect3DDevice8::CreateImageSurface()得到。一旦屏幕内容被保存到了这个surface,我们就可以用 D3DXSaveSurfaceToFile()方法直接把内容保存到磁盘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 是一个初始化好的IDirect3DDevice对象,这个例子直接把捕捉到的图像保存到文件。然而,有时候我们想访问直接这个图像中的各个位,我们可以使用IDirect3DSurface8::LockRect(),它给我们一个执行surface内存的指针,也就是捕捉到的图像的数据。我们复制这些数据到程序定义的内存中就可以操作它了。看下面的代码:
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的宽度和被捕捉的屏幕宽度不一样。由于内存对齐的原因(按WORD对齐的内存通常在访问时效率较高),surface在每行结尾处可能会有多余的bits以使它对齐到word边界上。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);
}
这对于从top-down位图到bottom-up位图很有用。

我们还可以使用IDirect3DSurface9的 GetDC()方法取得DirectX surface的GDI兼容DC,然后复制它的内容到我们的兼容DC。如果你用的是DirectX9,试试吧。

最后,需要注意的一点,文档提到:FrontBuffer是一个比较慢的操作,设计就是如此,所以在效率很关键的程序中应避免使用。已经警告你了!本文附带的源代码用这种技术定时捕捉屏幕,并保存为动画。

4 用Windows Media API抓屏
Windows Media 9.0 支持用Windows Media Encoder 9 API来抓屏。它有一个编码器叫Windows Media Video 9 Screen codec,特别为抓屏优化过。Windows Media Encoder API提供了一个IWMEncoder2接口可以用来高效地捕捉屏幕图像。

用这种技术进行抓屏也很简单,首先我们用CoCreateInstance()创建一个IWMEncoder2对象:
IWMEncoder2* g_pEncoder=NULL;
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);
这个Encoder对象包含了捕捉屏幕的所需的全部操作,然而为了正确地工作,编码器对象的行为取决于被称作profile的设置。一个profile只是一个包含了所有控制编码操作设置的文件,我们可以根据被捕捉的数据的特性在运行时创建包含自定义设置的profile。为了在你的抓屏程序中使用 profile,我们基于Windows Media Video 9 Screen codec来创建自定义的profile。自定义的profile对象从IWMEncProfile2开始就被支持了。我们可以用 CoCreateInstance来创建自定义profile
IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);
我需要在profile里指定编码器的听众(audience)。每个profile可以包含多个听众配置,它们是IWMEncAudienceObj接口对象。这里我们为profile使用一个听众。我们可以通过IWMEncProfile::AddAudience()为我们的profile创建听众,这个函数返回一个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是针对每个编码器的唯一的标识,Windows Media Video 9 Screen codec的fourcc为MSS2。IWMEncAudienceObj::put_VideoCodec()接受profile索引来组织一个 profile,索引可以用IWMEncProfile::GetCodecIndexFromFourCC()取得。

一旦我们配置完毕一个profile对象,我们就可以用IWMEncSourceGroup :: put_Profile()选择这个profile到我们的编码器。一个源组(SourceGruop)是一组视频流来源或音频流来源,或html来源。每个编码器可以使用许多源组,并从中取得输入数据。由于我们的程序仅仅使用视频流中是视频来源。这个视频来源需要用 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);

现在,一旦编码器对象的一切所需配置都完成后,我们就可以用IWMEncoder::Start()开始抓屏。IWMEncoder::Stop() 和 IWMEncoder::Pause可以用来停止和暂停捕捉。
这些适用于全屏捕捉,我们也可以通过调整输入视频来源流的属性来选择一个区域进行捕捉。我们可以用IWmEnVideoSource2的IPropertyBag 接口来实现:
#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 );

本文附带的源码实现此中技术的抓屏。除去生成的动画质量很好外,一个有意思的地方是鼠标指针也被抓到了(GDI和DirectX默认是不抓取鼠标指针的)。

注意,为了适用WindowMedia9.0 API,你的电脑必须安装Windows Media9.0 SDK,你可以用下面地址下载:
http://msdn.microsoft.com/library/default.asp?url=/downloads/list/winmedia.asp
最终用户必须安装Windows Media Encoder 9 系列才能运行你的程序。在发布基于Windows Media Encoder SDK的程序时,Windows Media Encoder软件也必须附带上去,要么在你的软件安装时自动安装Windows Media Encoder要么让用户自己下载安装。

Windows Encoder 9.0可以从下面地址下载:
http://www.microsoft.com/windows/windowsmedia/9series/encoder/default.aspx

结论
上面讨论的各种方法都是基于一个目标-抓取屏幕的内容。然而适用不同的技术,得到的结果也不一样。如果我们需要的只是偶尔的抓屏,GDI方式是个好的选择,因为它简单。然而如果你想得到更专业的结果,可以使用Windows Media。一个可能没有意义的要点是,这些技术捕捉到的内容的质量很大程度上决于你的系统设置,比如进制硬件加速会大大提高抓屏的质量和程序的运行效率。

VC 抓屏的各种方法相关推荐

  1. 抓屏的各种方法(http://www.codeproject.com/KB/dialog/screencap.aspx)

    文章翻译自 P.GopalaKrishna 的 Various methods for capturing the screen 一文,原版地址见下面.本文章版权归原作者所有.     如果转载该译文 ...

  2. linuxpython拍照_linux下python抓屏实现方法 -电脑资料

    作者:zevolo 字体:[增加 减小] 类型:转载 这篇文章主要介绍了linux下python抓屏实现方法,涉及Python操作屏幕截取的相关技巧,需要的朋友可以参考下 本文实例讲述了linux下p ...

  3. JS应用之禁止抓屏、复制、打印

    作者:Randy 原载:JS应用之禁止抓屏.复制.打印 版权所有,转载时必须以链接形式注明作者和原始出处及本声明 JS应用之禁止抓屏.复制.打印 项目需要禁止抓屏.复制.打印的要求,复制.打印做起来可 ...

  4. 【1.6万字】连续抓屏保存为Gif动图 【keyboard库、PIL库、imageio库和pygifsicle库 探索】

    目录 一.抓屏保存为Gif 二.keyboard库 三.PIL 库 3.0 安装Pillow 3.1 打开本地图片 3.2 创建一张新图片 3.3 Image模块的常用属性 3.4 图片的模式和模式转 ...

  5. 截取计算机全屏画面的方法有,电脑怎么截图全屏 详细方法介绍

    电脑想必大家应该了解了吧!因为电脑现在来说已经非常地普遍了,几乎家家户户都拥有一台电脑.不仅如此,电脑的在其他领域的应用也非常地广泛,电脑有很多的功能,这一点大家在平时也体会到了吧!电脑的功能在各个领 ...

  6. 摸鱼软件1:自动抓屏截图PPT软件

    摸鱼软件1:自动抓屏截图PPT软件 前言 事情主要起源于数据库的网课,我那时候知道我们课后会有一个限时的测试,于是就产生了一个需求就是说自动截屏获得老师上课的PPT,上网搜了一圈,但是发现并没有现成的 ...

  7. 笔记1:VC获取系统时间的方法

    笔记1:VC 获取系统时间的方法 推荐的获取方法 获取时间方法种类 各种获取时间方法详解 方法1:time_t变量 和 time(time_t*)方法 方法2:system(" time&q ...

  8. 基于ffmpeg与rtsp的抓屏推流拉流

    主要实现功能 主要想实现出的demo样式为,一台设备抓屏并且将抓到的屏幕传输到另一台设备上.主要实现的环境如下: windows抓屏推流 虚拟机上搭建rtsp流媒体服务器(centos7) windo ...

  9. C#图像处理(各种旋转、改变大小、柔化、锐化、雾化、底片、浮雕、黑白、滤镜效果,滤波,图像截取) 对图片的处理 : 亮度调整 抓屏 翻转 随鼠标画矩形

    C#图像处理(各种旋转.改变大小.柔化.锐化.雾化.底片.浮雕.黑白.滤镜效果) C#图像处理(各种旋转.改变大小.柔化.锐化.雾化.底片.浮雕.黑白.滤镜效果,滤波,图像截取) 对图片的处理 : 亮 ...

最新文章

  1. 在Ubuntu 14.04 64bit上安装Master PDF Editor 3.2.81
  2. webstorm常用快捷键
  3. c++ 使用 math库笔记
  4. 在网页中调用本地的应用程序
  5. 轻松搭建基于 Serverless 的 ThinkPHP 应用
  6. @即将开学的你,请收好这份必读论文清单
  7. echarts map
  8. 【论文复现】使用fastText进行文本分类
  9. 关于GDAL180中文路径不能打开的问题分析与解决
  10. hdu 6397 Character Encoding
  11. python源码保护之cython
  12. zabbix 3.0 的web界面出现乱码如何解决?
  13. GBK编码/GBK内码/Big5
  14. 【自动驾驶轨迹规划之RRT算法】
  15. C语言游戏开发——打飞机游戏1.0
  16. 网络第三方投票工具搭建的代码应该怎么写?
  17. 计算机恢复桌面,桌面图标打开方式怎么还原_电脑图标打开方式恢复方法-win7之家...
  18. python剪切文件_python剪切文件 - osc_42713235的个人空间 - OSCHINA - 中文开源技术交流社区...
  19. 3d视觉效果html,jQuery炫酷网页3D背景视觉差特效
  20. 利用matlab的interp1()对矩阵进行插值

热门文章

  1. python扫雷_Python轻松实现扫雷
  2. 卡巴斯基实验室警告去年第四季有70%黑客攻击落在微软的Office
  3. Facebook:等待验证的神话
  4. 热血篮球维修服务器是怎么回事,热血篮球 12月24日11点维护更新公告
  5. 对话快手 | 如何从0到1建立商业化品牌的海外影响力?
  6. 索航科技技术类开发人员简历-本人
  7. 如何用webgl(three.js)搭建不规则建筑模型,客流量热力图模拟
  8. Bentley正洞与斜井
  9. 用jdl 生成微服务
  10. jQuery中append()和appendTo()的区别