windows抓屏(截屏)实现方法
来源:互联网 发布:matlab矩阵转化列向量 编辑:程序博客网 时间:2024/06/15 10:36
在windows系统中,抓取当前桌面的屏幕有很多方法,比较常用的是GDI和mirror两种方式,除此以外,利用ddraw和dxgi(windows7以上系统支持)方式也可以抓取屏幕。由于mirror的方式牵扯到驱动,并且也不是所有系统都支持,本文不会介绍这种抓屏方式,这里将着重介绍GDI, DDRAW和DXGI这三种抓屏方式,并给出其相应的实现代码。
GDI抓屏
这种方式比较通用,所有的windows版本都支持此方式抓屏,而且不依赖其他API,仅仅使用user32中的API即可完成抓屏;但是这种抓屏方式相对比较慢,抓取一帧1080p的桌面需要5~8ms左右的时间。单纯的从抓屏来说,这个时间还是可以接受的,但是抓屏后往往需要进行很多图像处理,所以这个用时就显得不那么友好了。而且在vista以后的系统上,如果启用Aero特效的话,抓取一帧的用时会增加到300~500ms,基本上属于不可用的级别。
下面给出实现代码:
HDC m_hMemDC, m_hRootDC;HBITMAP m_hBitmapMem;DEVMODE m_origDevMode;BITMAPINFO m_bitmapInfo;void *m_pvBits;int m_iWidth = 1920,m_iHeight = 1080;BOOL InitBitmapInfo(void){ if (m_hRootDC){return FALSE;}m_hRootDC = CreateDC(("DISPLAY"), NULL, NULL, NULL);if (!m_hRootDC){return FALSE;}m_hMemDC = CreateCompatibleDC(m_hRootDC); if (!m_hMemDC){ return FALSE; } m_hBitmapMem = CreateCompatibleBitmap(m_hRootDC, m_iWidth, m_iHeight); if (!m_hBitmapMem){ return FALSE; } return TRUE;}BOOL CreateDIBBuffers(void){m_bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); m_bitmapInfo.bmiHeader.biBitCount = 0; if (GetDIBits(m_hMemDC, m_hBitmapMem, 0, 1, NULL, &m_bitmapInfo, DIB_RGB_COLORS) == 0){ return FALSE; }m_bitmapInfo.bmiHeader.biCompression = BI_RGB;m_bitmapInfo.bmiHeader.biHeight = -abs(m_bitmapInfo.bmiHeader.biHeight); // 因为抓取的图片是上下颠倒的,所以这里需要将高度颠倒一下 HBITMAP hBitmapTmp = CreateDIBSection(m_hMemDC, &m_bitmapInfo, DIB_RGB_COLORS, &m_pvBits, NULL, 0); if (!hBitmapTmp){ ("Can not use fast blit!\n");} if (m_hBitmapMem != NULL){ DeleteObject(m_hBitmapMem); m_hBitmapMem = NULL; }m_hBitmapMem = hBitmapTmp; return TRUE;}void ByteAlign(RECT &rect){int nWidth = rect.right - rect.left;int nHeight = rect.bottom - rect.top;//// 对齐4字节边界// 因为GDI的原因,分辨率必须要是4字节的边界,否则抓出来的图像重新渲染的话,// 会出现花屏现象,这里处理的目的就是为了保证是4字节的边界。——yshen on 2012-11-25//int nMod = nWidth % 4;if (nMod){rect.left += (nMod / 2);rect.right -= (nMod / 2);}nMod = nHeight % 4;if (nMod){rect.top += (nMod / 2);rect.bottom -= (nMod / 2);}}int CaptureScreen(const RECT &rect){ ByteAlign(rect);// 检测和启动时的分辨率是否一致,如果不一致,则停止抓屏INT nWidth = GetSystemMetrics(SM_CXSCREEN); INT nHeight = GetSystemMetrics(SM_CYSCREEN);if ((m_iWidth != nWidth) && (m_iHeight != nHeight)){return -1;}GdiFlush();HBITMAP hOldBitmap = (HBITMAP)SelectObject(m_hMemDC, m_hBitmapMem);if (hOldBitmap){BitBlt(m_hMemDC, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, m_hRootDC, rect.left, rect.top, SRCCOPY | CAPTUREBLT);SelectObject(m_hMemDC, hOldBitmap);}return 0;}BOOL Init(void){if (!InitBitmapInfo()){ return FALSE; } if (!CreateDIBBuffers()){ return FALSE; } return TRUE;}
调用示例:
// 初始化抓屏 init(); // 抓取一帧 RECT rect(0, 0, 1920, 1080); CaptureScreen(rect);
DDRAW抓屏
这种方式抓屏速度比GDI要快一些,抓取一帧图像大概需要4~5ms左右,但是缺点也和GDI一样,如果启用了特效,速度也非常慢,基本也在400ms以上。
实现代码:
LPDIRECTDRAW m_lpDDraw;LPDIRECTDRAWSURFACE m_lpDDSPrime;LPDIRECTDRAWSURFACE m_lpDDSBack;DDSURFACEDESC m_DDSdesc;BOOL Init(){HMODULE hDll = LoadLibrary("ddraw.dll");if (hDll == NULL){("无法载入ddraw.dll\n");return FALSE;}// 载入ddraw的导入函数PFN_DirectDrawCreate DirectDrawCreateFunc = (PFN_DirectDrawCreate)GetProcAddress(hDll, "DirectDrawCreate");if (DirectDrawCreateFunc == NULL){("无法找到访问点:DirectDrawCreate\n");return FALSE;}HRESULT hr = DirectDrawCreateFunc(NULL, &m_lpDDraw, NULL);if (FAILED(hr)){("DirectDrawCreate失败\n");return FALSE;}hr = m_lpDDraw->SetCooperativeLevel(NULL, DDSCL_NORMAL);if (FAILED(hr)){("SetCooperativeLevel失败\n");return FALSE; }DDSURFACEDESC DDSdesc;ZeroMemory(&DDSdesc, sizeof(DDSdesc));DDSdesc.dwSize = sizeof(DDSdesc);DDSdesc.dwFlags = DDSD_CAPS;DDSdesc.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;hr = m_lpDDraw->CreateSurface(&DDSdesc, &m_lpDDSPrime, NULL);if (FAILED(hr)){("CreateSurface 主表面失败\n");return FALSE; }ZeroMemory(&DDSdesc, sizeof(DDSdesc));DDSdesc.dwSize = sizeof(DDSdesc);DDSdesc.dwFlags = DDSD_ALL;hr = m_lpDDSPrime->GetSurfaceDesc(&DDSdesc);if (FAILED(hr)){("GetSurfaceDesc失败\n");return FALSE;}// 备份描述信息memcpy(&m_DDSdesc, &DDSdesc, sizeof(DDSdesc));DDSdesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT |DDSD_WIDTH; DDSdesc.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;hr = m_lpDDraw->CreateSurface(&DDSdesc, &m_lpDDSBack, 0);if (FAILED(hr)){("CreateSurface 后备表面失败\n");return FALSE;}return TRUE;}BOOL CaptureImage(RECT &rect, void *pData, INT &nLen){if (m_lpDDSBack == NULL){("DDraw对象未初始化\n");return FALSE;}HRESULT hr = m_lpDDSBack->BltFast(rect.left, rect.top, m_lpDDSPrime, &rect, DDBLTFAST_NOCOLORKEY | DDBLTFAST_WAIT);if (FAILED(hr)){("BltFast失败\n");return FALSE;}DDSURFACEDESC surfDesc;ZeroMemory(&surfDesc, sizeof(surfDesc)); surfDesc.dwSize = sizeof(surfDesc);hr = m_lpDDSBack->Lock(&rect, &surfDesc, DDLOCK_READONLY | DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR , NULL);if (FAILED(hr)){("Lock失败\n");return FALSE;}// 这里拷贝的是32位数据memcpy(pBuf, (BYTE*)surfDesc.lpSurface, surfDesc.dwWidth * surfDesc.dwHeight * surfDesc.ddpfPixelFormat.dwRGBBitCount / 8); m_lpDDSBack->Unlock(surfDesc.lpSurface);return TRUE;}
DXGI抓屏
这种抓屏方式,速度非常快,通常一帧图像能够在2~3ms内完成,而且即使启用了Aero特效,抓屏效率也一样。尤其在windows10以后的系统上,当桌面没有变化时,你是抓取不到任何图像的,只有在桌面有变化时,你才能抓取到图像。这会带来更高的抓屏效率和更少的系统开销。当然,它也不是完美的,它只能在vista以上的系统上才可以使用,老旧的xp是不支持这种新技术的。
实现代码:
ID3D11Device *m_hDevice;ID3D11DeviceContext *m_hContext;IDXGIOutputDuplication *m_hDeskDupl;DXGI_OUTPUT_DESC m_dxgiOutDesc;BOOL Init(){HRESULT hr = S_OK;// Driver types supportedD3D_DRIVER_TYPE DriverTypes[] ={D3D_DRIVER_TYPE_HARDWARE,D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_REFERENCE,};UINT NumDriverTypes = ARRAYSIZE(DriverTypes);// Feature levels supportedD3D_FEATURE_LEVEL FeatureLevels[] ={D3D_FEATURE_LEVEL_11_0,D3D_FEATURE_LEVEL_10_1,D3D_FEATURE_LEVEL_10_0,D3D_FEATURE_LEVEL_9_1};UINT NumFeatureLevels = ARRAYSIZE(FeatureLevels);D3D_FEATURE_LEVEL FeatureLevel;//// Create D3D device//for (UINT DriverTypeIndex = 0; DriverTypeIndex < NumDriverTypes; ++DriverTypeIndex){hr = D3D11CreateDevice(NULL, DriverTypes[DriverTypeIndex], NULL, 0, FeatureLevels, NumFeatureLevels, D3D11_SDK_VERSION, &m_hDevice, &FeatureLevel, &m_hContext);if (SUCCEEDED(hr)){break;}}if (FAILED(hr)){return FALSE;}//// Get DXGI device//IDXGIDevice *hDxgiDevice = NULL;hr = m_hDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&hDxgiDevice));if (FAILED(hr)){return FALSE;}//// Get DXGI adapter//IDXGIAdapter *hDxgiAdapter = NULL;hr = hDxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&hDxgiAdapter));RESET_OBJECT(hDxgiDevice);if (FAILED(hr)){return FALSE;}//// Get output//INT nOutput = 0;IDXGIOutput *hDxgiOutput = NULL;hr = hDxgiAdapter->EnumOutputs(nOutput, &hDxgiOutput);RESET_OBJECT(hDxgiAdapter);if (FAILED(hr)){return FALSE;}//// get output description struct//hDxgiOutput->GetDesc(&m_dxgiOutDesc);//// QI for Output 1//IDXGIOutput1 *hDxgiOutput1 = NULL;hr = hDxgiOutput->QueryInterface(__uuidof(hDxgiOutput1), reinterpret_cast<void**>(&hDxgiOutput1));RESET_OBJECT(hDxgiOutput);if (FAILED(hr)){return FALSE;}//// Create desktop duplication//hr = hDxgiOutput1->DuplicateOutput(m_hDevice, &m_hDeskDupl);RESET_OBJECT(hDxgiOutput1);if (FAILED(hr)){return FALSE;}return TRUE;}BOOL AttatchToThread(VOID){HDESK hCurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);if (!hCurrentDesktop){return FALSE;}// Attach desktop to this threadBOOL bDesktopAttached = SetThreadDesktop(hCurrentDesktop);CloseDesktop(hCurrentDesktop);hCurrentDesktop = NULL;return bDesktopAttached;}BOOL VideoDXGICaptor::QueryFrame(void *pImgData, INT &nImgSize){if (!AttatchToThread()){return FALSE;}nImgSize = 0;IDXGIResource *hDesktopResource = NULL;DXGI_OUTDUPL_FRAME_INFO FrameInfo;HRESULT hr = m_hDeskDupl->AcquireNextFrame(500, &FrameInfo, &hDesktopResource);if (FAILED(hr)){//// 在一些win10的系统上,如果桌面没有变化的情况下,// 这里会发生超时现象,但是这并不是发生了错误,而是系统优化了刷新动作导致的。// 所以,这里没必要返回FALSE,返回不带任何数据的TRUE即可//return TRUE;}//// query next frame staging buffer//ID3D11Texture2D *hAcquiredDesktopImage = NULL;hr = hDesktopResource->QueryInterface(__uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&hAcquiredDesktopImage));RESET_OBJECT(hDesktopResource);if (FAILED(hr)){return FALSE;}//// copy old description//D3D11_TEXTURE2D_DESC frameDescriptor;hAcquiredDesktopImage->GetDesc(&frameDescriptor);//// create a new staging buffer for fill frame image//ID3D11Texture2D *hNewDesktopImage = NULL;frameDescriptor.Usage = D3D11_USAGE_STAGING;frameDescriptor.CPUAccessFlags = D3D11_CPU_ACCESS_READ;frameDescriptor.BindFlags = 0;frameDescriptor.MiscFlags = 0;frameDescriptor.MipLevels = 1;frameDescriptor.ArraySize = 1;frameDescriptor.SampleDesc.Count = 1;hr = m_hDevice->CreateTexture2D(&frameDescriptor, NULL, &hNewDesktopImage);if (FAILED(hr)){RESET_OBJECT(hAcquiredDesktopImage);m_hDeskDupl->ReleaseFrame();return FALSE;}//// copy next staging buffer to new staging buffer//m_hContext->CopyResource(hNewDesktopImage, hAcquiredDesktopImage);RESET_OBJECT(hAcquiredDesktopImage);m_hDeskDupl->ReleaseFrame();//// create staging buffer for map bits//IDXGISurface *hStagingSurf = NULL;hr = hNewDesktopImage->QueryInterface(__uuidof(IDXGISurface), (void **)(&hStagingSurf));RESET_OBJECT(hNewDesktopImage);if (FAILED(hr)){return FALSE;}//// copy bits to user space//DXGI_MAPPED_RECT mappedRect;hr = hStagingSurf->Map(&mappedRect, DXGI_MAP_READ);if (SUCCEEDED(hr)){nImgSize = GetWidth() * GetHeight() * 3;PrepareBGR24From32(mappedRect.pBits, (BYTE*)pImgData, m_dxgiOutDesc.DesktopCoordinates);hStagingSurf->Unmap();}RESET_OBJECT(hStagingSurf);return SUCCEEDED(hr);}
DXGI只能使用vs2012以上的IDE才可以编译。
相关代码:点这里
- windows抓屏(截屏)实现方法
- WINDOWS抓屏的各种方法
- Windows 定时抓屏
- Windows环境 WinPcap远程抓包方法
- windows下抓本机环回包的方法
- windows 2012 抓明文密码方法
- windows抓包简单的实现
- c实现网络抓包windows下
- 抓屏的各种方法
- 抓屏的各种方法
- windows远程桌面实现之一 (抓屏技术总览 MirrorDriver,DXGI,GDI)
- Windows远程桌面实现之二(抓屏技术之MirrorDriver镜像驱动开发)
- windows 抓屏 基于RDP Mirror Driver
- 网络技术-windows下抓本机环回包的方法
- 【转】windows下抓本机环回包的方法
- Windows平台下Android应用抓包挖掘漏洞方法
- winfrom 截屏、抓屏
- [翻译]抓屏的各种方法
- Xcode报错总结
- 算法训练 表达式计算
- seession存入memcache
- D. Artsem and Saunders
- PAT甲级练习1028. List Sorting (25)
- windows抓屏(截屏)实现方法
- 社群管理难,变现差,死亡快,5招教你掌握社群精细化运营精髓
- HTTP(Hyper Text Transfer Protocol 超文本传输协议)学习笔记
- SVN-TortoiseSVN
- ZOJ2314 Reactor Cooling(无源汇上下界可行流)
- JavaSE_25th_数组——声明和初始化
- C到C++的升级
- nigx 负载均衡
- 自由存储区内存管理