windows抓屏(截屏)实现方法

         在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 supported
	D3D_DRIVER_TYPE DriverTypes[] =
	{
		D3D_DRIVER_TYPE_HARDWARE,
		D3D_DRIVER_TYPE_WARP,
		D3D_DRIVER_TYPE_REFERENCE,
	};
	UINT NumDriverTypes = ARRAYSIZE(DriverTypes);

	// Feature levels supported
	D3D_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 thread
	BOOL 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才可以编译。



相关代码:点这里         

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值