VC dxgi 截屏保存bmp文件

介绍

    win8 dxgi截屏保存为bmp文件。

代码

    其中用到的d3d,dxgi头文件和库都是win8或者win10sdk中所包含的。

#include <stdio.h>
#include <assert.h>
#include <process.h>
#include "D3D11.h"
#include "DXGI1_2.h"
#include "DXGICaptor.h"

#pragma comment(lib, "D3D11.lib")
#pragma comment(lib, "DXGI.lib")

#define RESET_OBJECT(obj) { if(obj) obj->Release(); obj = NULL; }

ID3D11Device           *m_hDevice = nullptr;
ID3D11DeviceContext    *m_hContext = nullptr;
IDXGIOutputDuplication *m_hDeskDupl = nullptr;

int iWidth  = 0;
int iHeight = 0;

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
	//
	DXGI_OUTPUT_DESC        m_dxgiOutDesc;
	hDxgiOutput->GetDesc(&m_dxgiOutDesc);
	iWidth = m_dxgiOutDesc.DesktopCoordinates.right - m_dxgiOutDesc.DesktopCoordinates.left;
	iHeight = m_dxgiOutDesc.DesktopCoordinates.bottom - m_dxgiOutDesc.DesktopCoordinates.top;
	//
	// 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;
}
void Finit()
{
	RESET_OBJECT(m_hDeskDupl);
	RESET_OBJECT(m_hContext);
	RESET_OBJECT(m_hDevice);
}

BOOL AttatchToThread(VOID)
{
	HDESK hold = GetThreadDesktop(GetCurrentThreadId());  
	HDESK hCurrentDesktop = OpenInputDesktop(0, FALSE, GENERIC_ALL);
	if (!hCurrentDesktop)
	{
		return FALSE;
	}

	// Attach desktop to this thread
	BOOL bDesktopAttached = SetThreadDesktop(hCurrentDesktop) ? true : false;
	int err = GetLastError();
	CloseDesktop(hold);
	CloseDesktop(hCurrentDesktop);
	//hCurrentDesktop = NULL;


	return bDesktopAttached;
}

BOOL QueryFrame(const char *fileName, void *pImgData, INT &nImgSize)
{
	if (!AttatchToThread())
	{
		return FALSE;
	}

	nImgSize = 0;

	IDXGIResource *hDesktopResource = NULL;
	DXGI_OUTDUPL_FRAME_INFO FrameInfo;
	m_hDeskDupl->ReleaseFrame();
	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;
	}
	DXGI_SURFACE_DESC hStagingSurfDesc;
	// BGRA8
	hStagingSurf->GetDesc(&hStagingSurfDesc);
	//
	// copy bits to user space
	//
	DXGI_MAPPED_RECT mappedRect;
	hr = hStagingSurf->Map(&mappedRect, DXGI_MAP_READ);
	int imgSize = iWidth * iHeight *4;
	if (SUCCEEDED(hr))
	{
		nImgSize = imgSize;
		memcpy(pImgData, mappedRect.pBits, imgSize);
		hStagingSurf->Unmap();
	}else {
		printf("failed.\n");
	}
	printf("finish.\n");
	RESET_OBJECT(hStagingSurf);
	return SUCCEEDED(hr);
}

void rotateImg(unsigned char *pImgData)
{
	unsigned int* pImg32 = (unsigned int*)pImgData;
	int iCount = iWidth * iHeight;
	unsigned int* pRotateImg32 = new unsigned int[iCount];
	for (int i = 0; i < iCount; ++ i) {
		pRotateImg32[i] = pImg32[iCount - i - 1];
	}

	unsigned int* pRotateImg32_r = (unsigned int*)pImgData;
	for (int i = 0; i < iHeight; ++ i) {
		for (int j = 0; j < iWidth; ++ j) {
			pRotateImg32_r[j + i * iWidth] = pRotateImg32[iWidth - j - 1 + i * iWidth];
		}
	}
	delete [] pRotateImg32;
}

void saveBmpFile(const char *fileName, unsigned char *pImgData, int imgLength)
{
	BITMAPFILEHEADER bmheader;
	memset(&bmheader, 0, sizeof(bmheader));
	bmheader.bfType = 0x4d42;     //图像格式。必须为'BM'格式。  
	bmheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //从文件开头到数据的偏移量  
	bmheader.bfSize = imgLength + bmheader.bfOffBits;//文件大小  

	BITMAPINFOHEADER bmInfo;
	memset(&bmInfo, 0, sizeof(bmInfo));
	bmInfo.biSize = sizeof(bmInfo);
	bmInfo.biWidth = iWidth;
	bmInfo.biHeight = iHeight;
	bmInfo.biPlanes = 1;
	bmInfo.biBitCount = 32;
	bmInfo.biCompression = BI_RGB;

	HANDLE hFile = CreateFileA(fileName, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0);
	if (hFile != INVALID_HANDLE_VALUE) {
		DWORD dwWritten;
		BOOL bRet = WriteFile(hFile, &bmheader, sizeof(BITMAPFILEHEADER), &dwWritten, NULL);
		assert(TRUE == bRet);
		bRet = WriteFile(hFile, &bmInfo, sizeof(BITMAPINFOHEADER), &dwWritten, NULL);
		assert(TRUE == bRet);
		bRet =WriteFile(hFile, pImgData, imgLength, &dwWritten, NULL);
		assert(TRUE == bRet);
		CloseHandle(hFile);
	}
}

unsigned __stdcall capture(void *arg) 
{
	unsigned char *pImgData = new unsigned char[iWidth * iHeight * 4];
	int imgLength = iWidth * iHeight * 4;
	int i = 1;
	while (true) {
		char buf[10] = {0};
		sprintf(buf, "%d.bmp", i);
		QueryFrame(buf, pImgData, imgLength);
		rotateImg(pImgData);
		saveBmpFile(buf, pImgData, imgLength);
		++ i;
		Sleep(500);
	}
	return 0;
}


int main(int argc, TCHAR *argv[]) 
{
	if (!Init()) {
		Finit();
		printf("not support dxgi.");
		return -1;
	}
	HANDLE handle = (HANDLE)_beginthreadex(NULL, 0, capture, NULL, 0, NULL);
	CloseHandle(handle);
	getchar();
	Finit();
	return 0;
}

总结

    以上代码,在测试中发现几个问题:

  • 第一张图片截屏的总是黑屏,暂时不知道原因。
  • 最初的时候在截屏完成并写入到bmp文件时图片全是倒置的,并没有找到原因或者是在哪里有设置可以使图片显示正常,所以只好自己就写了一个函数rotateImg直接把整个数组和左右一并给倒置了,这样图片保存到bmp文件时才显示正常。
  • 当程序运行在双显卡机器时可能出现问题,因为当时测试时使用的是笔记本双显卡机器,在高性能处理器处于激活状态时,在调用函数DuplicateOutput时出错,错误提示不支持,不知道是因为是这款显卡不支持又或者是其它原因,而如果把显卡激活到集成显卡时,程序就能正常运行。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用Qt和DXGI进行屏幕截图并保存的示例代码: ```c++ #include <QCoreApplication> #include <QImage> #include <QFile> #include <QDebug> #include <d3d11.h> #include <dxgi1_2.h> // Helper function to check HRESULTs #define CHECK_HR(hr, msg) \ if (FAILED(hr)) { \ qDebug() << msg << "failed with HRESULT" << hr; \ return false; \ } bool saveScreenshot(const QString& filePath) { HRESULT hr = S_OK; // Create DXGI factory IDXGIFactory1* pFactory = nullptr; hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory); CHECK_HR(hr, "Failed to create DXGI factory"); // Get primary adapter IDXGIAdapter1* pAdapter = nullptr; hr = pFactory->EnumAdapters1(0, &pAdapter); CHECK_HR(hr, "Failed to get primary adapter"); // Create D3D device and swap chain D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0; D3D_FEATURE_LEVEL featureLevels[] = { featureLevel }; DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 }; swapChainDesc.BufferCount = 1; swapChainDesc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.OutputWindow = GetDesktopWindow(); swapChainDesc.SampleDesc.Count = 1; swapChainDesc.Windowed = TRUE; ID3D11Device* pDevice = nullptr; ID3D11DeviceContext* pContext = nullptr; IDXGISwapChain* pSwapChain = nullptr; hr = D3D11CreateDeviceAndSwapChain(pAdapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, featureLevels, 1, D3D11_SDK_VERSION, &swapChainDesc, &pSwapChain, &pDevice, &featureLevel, &pContext); CHECK_HR(hr, "Failed to create D3D device and swap chain"); // Get back buffer texture ID3D11Texture2D* pBackBuffer = nullptr; hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); CHECK_HR(hr, "Failed to get back buffer texture"); // Create a CPU-readable texture D3D11_TEXTURE2D_DESC textureDesc = { 0 }; textureDesc.Width = GetSystemMetrics(SM_CXSCREEN); textureDesc.Height = GetSystemMetrics(SM_CYSCREEN); textureDesc.MipLevels = 1; textureDesc.ArraySize = 1; textureDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; textureDesc.SampleDesc.Count = 1; textureDesc.Usage = D3D11_USAGE_STAGING; textureDesc.BindFlags = 0; textureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; ID3D11Texture2D* pTexture = nullptr; hr = pDevice->CreateTexture2D(&textureDesc, NULL, &pTexture); CHECK_HR(hr, "Failed to create CPU-readable texture"); // Copy back buffer to CPU-readable texture pContext->CopyResource(pTexture, pBackBuffer); // Read texture data D3D11_MAPPED_SUBRESOURCE mappedResource; hr = pContext->Map(pTexture, 0, D3D11_MAP_READ, 0, &mappedResource); CHECK_HR(hr, "Failed to map texture"); QImage image(mappedResource.pData, textureDesc.Width, textureDesc.Height, QImage::Format_RGB32); pContext->Unmap(pTexture, 0); // Save image to file if (!image.save(filePath)) { qDebug() << "Failed to save screenshot to" << filePath; return false; } // Cleanup pContext->Release(); pDevice->Release(); pSwapChain->Release(); pAdapter->Release(); pFactory->Release(); pTexture->Release(); pBackBuffer->Release(); return true; } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); if (saveScreenshot("screenshot.png")) { qDebug() << "Screenshot saved to screenshot.png"; } return a.exec(); } ``` 该代码使用DXGI和D3D11 API创建设备和交换链,然后获取主适配器和后缓冲器纹理。然后,它创建一个CPU可读的纹理,将后缓冲器纹理复制到该纹理中,然后将其映射到内存中以获取图像数据。最后,它使用Qt的QImage类将图像数据保存文件中。请注意,此代码需要使用Windows SDK和DirectX SDK来构建。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Z小偉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值