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

文章翻译自 P.GopalaKrishna 的 Various methods for capturing the screen 一文,原版地址见下面。本文章版权归原作者所有。
   
如果转载该译文 , 请保证文章的完整性,并注明来自 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          导言

l          用 GID 函数抓屏

l          用 DirectX 方式抓屏

l          用 Windows Media API 抓屏

 

导言

 

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

 

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

 

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 是一个比较慢的操作,设计就是如此,所以在效率很关键的程序中应避免使用。已经警告你了!本文附带的源代码用这种技术定时捕捉屏幕,并保存为动画。

用 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 的 I PropertyBag 接口来实现:

#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 。一个可能没有意义的要点是,这些技术捕捉到的内容的质量很大程度上决于你的系统设置,比如进制硬件加速会大大提高抓屏的质量和程序的运行效率。  

 

 

 

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/news/255034.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

与 OpenCV 1 同时使用

与 OpenCV 1 同时使用 目的 对于OpenCV的开发团队来说&#xff0c;持续稳定地提高代码库非常重要。我们一直在思考如何在使其易用的同时保持灵活性。新的C接口即为此而来。尽管如此&#xff0c;向下兼容仍然十分重要。我们并不想打断你基于早期OpenCV库的开发。因此&am…

第五周 Leetcode 99. Recover Binary Search Tree (HARD)

Leetcode99 给定一个 二叉搜索树&#xff0c;其中两个节点被交换&#xff0c;写一个程序恢复这颗BST. 只想到了时间复杂度O&#xff08;n&#xff09;空间复杂度O&#xff08;h&#xff09; h为树高的解法&#xff0c;还没想到空间O(1&#xff09;的解法。 交换的情况只有两种&…

Fedora15安装NVIDIA显卡驱动全过程

Fedora安装N卡驱动全过程 Fedora安装NVIDIA显卡全过程&#xff0c;经过自己亲自安装 折腾了一个上午&#xff0c;搞定了N卡驱动安装&#xff0c;现将安装步骤整理如下&#xff1a; 1、首先访问Nvidia官网下载最新的Linux驱动&#xff1a;http://www.nvidia.cn/Download/index…

板邓:wordpress自定义登录页面实现用户登录

首先检查用户是否已经登录&#xff0c;如果已经登录就返回info目录下的页面。 <?phpglobal $current_user;$loginuserid $current_user->ID;if($loginuserid){//如果已经登录header("Location:".get_bloginfo(url)."/info/"); exit;} 如果用户未登…

机器学习(machine learning)之AdaBoost算法

转自&#xff1a;http://blog.csdn.net/haidao2009/article/details/7514787 浅谈 Adaboost 算法 机器学习是利用一些方法来使机器实现人的学习行为&#xff0c;以便获取新的知识或技能&#xff0c;重新组织已有的知识结构使之不断改善自身的性能。 AdaBoost全名“adaptive B…

交换两个整形变量的数值

课堂问题一: #include<stdio.h>void swap(int *p,int *q) {int *m;printf("m%d\n",m);printf("%s\n",*m);*m*p;*p*q;*q*m; } int main(){int a,b;scanf("%d,%d",&a,&b);swap(&a,&b);printf("a%d b%d\n",a,b);re…

使用CodeFirst创建并更新数据库

本文主要介绍如何使用CodeFirst模式来新建并更新数据库 在使用Entity Framwork的三种方式&#xff08;ModelFist、DBFirst、CodeFirst&#xff09;中&#xff0c;CodeFirst方式书写的代码最为干净。 至于CodeFist方式的详细优缺点请各位读者自行搜索&#xff0c;这里不多赘述。…

fedora 15怎么修改运行级别?

inittab改了已经在fedora15中&#xff0c;你vim它就可以看到更改说明&#xff0c;就是说都改到/etc/systemd/system/default.target这里了&#xff0c;就是缺省的设置。如果你要改变缺省值就把对应的runlevel移动过去覆盖了。 To 3 字符 [root15 system]# rm -rf /etc/systemd…

浅析人脸检测之Haar分类器方法

由于工作需要&#xff0c;我开始研究人脸检测部分的算法&#xff0c;这期间断断续续地学习Haar分类器的训练以及检测过程&#xff0c;在这里根据各种论文、网络资源的查阅和对代码的理解做一个简单的总结。我试图概括性的给出算法的起源、全貌以及细节的来龙去脉&#xff0c;但…

利用微软平台生成报表,线性图,柱形图

说来惭愧,以前的工作中一直借助第三方dll进行报表制作,比如线性图,柱形图. 因为现在工作的这家公司不允许随便引入第三方dll,听同事说起可以建rdl类型文件进行引入到winform窗体中,窗体上使用reportViewer控件进行关联展示.下面是我今天摸索3个小时的结果分享. 第一步. 首先找到…

Linux ffmpeg的安装编译过程

Linux ffmpeg的安装编译过程 1、下载ffmpeg。    在网上搜索一下,或者到官方网站下载2、解压   tar命令解压3、配置  ./configure --enable-shared --prefix/usr/local/ffmpeg  其中&#xff1a;--enable-shared 是允许其编译产生动态库&#xff0c;在以后的编程中…

opencv 模板匹配(cvMatchTemplate)

opencv 模板匹配(cvMatchTemplate) 模板匹配是通过在输入图像上滑动模板图像块对实际的图像块和输入图像进行匹配&#xff0c;并且可以利用函数cvMinMaxLoc()找到最佳匹配的位置。例如在工业应用中&#xff0c;可以锁定图像中零部件的位置&#xff0c;并根据具体的位置&…

爬虫系统Lucene分词

思路&#xff1a;查询数据库中信息&#xff0c;查询出id和name把那么进行分词存入文件 package com.open1111.index; import java.io.IOException;import java.nio.file.Paths;import java.sql.Connection;import java.sql.PreparedStatement;import java.sql.ResultSet; impor…

[BZOJ1880] [Sdoi2009] Elaxia的路线 (SPFA 拓扑排序)

Description 最近&#xff0c;Elaxia和w**的关系特别好&#xff0c;他们很想整天在一起&#xff0c;但是大学的学习太紧张了&#xff0c;他们 必须合理地安排两个人在一起的时间。Elaxia和w**每天都要奔波于宿舍和实验室之间&#xff0c;他们 希望在节约时间的前提下&#xff0…

ffmpeg的编译大全

ffmpeg的编译大全 最近互联网视频共享的网站很火&#xff0c;公司也想搞类似的网站&#xff0c;初步是用fmsffmpeg形式 fms负责在线录制&#xff0c;播放&#xff0c;ffmpeg则在后台处理上传的资源转换成一定的格式。 为了让ffmpeg支持的格式尽量多&#xff0c;所以特把我的编译…

用OPENCV视觉解数独

用OPENCV视觉解数独 2010-06-29 看到增强视觉网站上介绍老外用视觉解SUDOKU(http://www.cvchina.info/2011/05/29/video-sudoku-solver/)&#xff0c;觉得应该不难&#xff0c;于是用OPENCV和训练好的数字分类器&#xff0c;也试着做一个&#xff0c;纯属娱乐 基本思路如下&…

集成ffmpeg/x264:ERROR: libx264 not found的问题

集成ffmpeg/x264:ERROR: libx264 not found的问题--拔剑集成ffmpeg/x264碰到如下问题&#xff1a; ERROR: libx264 not found察看config.log,详细信息如下&#xff1a;check_lib x264.h x264_encoder_encode -lx264check_header x264.hcheck_cppBEGIN/tmp/ffconf.isuazGlg.c1 …

[ActionScript 3.0] AS3.0 下雨及涟漪效果

帧代码&#xff1a; stage.frameRate 80;function init(x1:Number,y1:Number) {var mc:MovieClipnew MovieClip();addChild(mc);mc.x x1;mc.y y1;mc.graphics.lineStyle(0.5,0xbbffff,0.6);mc.graphics.drawEllipse(-1,-0.3,2,0.6);mc.addEventListener(Event.ENTER_FRAME,f…

JS Math.round()方法原理

请先测试代码&#xff1a; 1 <!doctype html>2 <html lang"en">3 4 <head>5 <meta charset"UTF-8" />6 <title>Math.round方法</title>7 <style type"text/css">8 …

一个通用Makefile的编写

我们在 LinuxLinux Linux是一套免费使用和自由传播的操作系统&#xff0c;它主要用于基于Intel系列CPU的计算机上。这个系统是由全世界各地的成千上万的程序员设计和实现的&#xff0c;其目的是建立不受任何商品化软件的版权制约的、全世界都能自由使用的Unix兼容产品。 环境下…