c++ - 从辅助监视器捕获图像、裁剪和保存
问题描述
我正在尝试编写一个应用程序,在其中一个监视器上截取一个区域的屏幕截图并将其保存到图像中。屏幕截图应在给定宽度和高度的 ax,y 坐标处拍摄。当图像保存时,它应该保存在 0,0 和相同的宽度高度。我在我的主显示器上工作,但是当我尝试拍摄我的第二或第三显示器的屏幕截图时它不起作用。我要么得到一个黑色图像,要么得到一个打开时显示其格式无效的文件。
我的代码是以下代码的修改版本,它截取了整个桌面的屏幕截图。 https://github.com/GERD0GDU/dxgi_desktop_capture
主显示器宽度/高度 2560 X 1440
主监视器顶角 x/y 坐标为 0,0 通过调用检索
POINT cursorPos;
GetCursorPos(&cursorPos);
辅助显示器宽度/高度 1080 x 1920
辅助显示器顶角 x/y 坐标为 2560,-238
作为在主监视器上工作的图像坐标的参考,我传入 x:801 y:227, width:1756, height:1115
对于我的第二台显示器,它位于我的主显示器的右侧并侧向翻转,使其高于宽度,我通过 x:2907 y:-11 width:737 height:1595
HRESULT CDXGICapture::CaptureToFile(_In_ LPCWSTR lpcwOutputFileName, int x, int y, int width, int height)
{
AUTOLOCK();
if (!m_bInitialized) {
return D2DERR_NOT_INITIALIZED;
}
CHECK_POINTER_EX(m_ipDxgiOutputDuplication, E_INVALIDARG);
CHECK_POINTER_EX(lpcwOutputFileName, E_INVALIDARG);
HRESULT hr = S_OK;
hr = DXGICaptureHelper::IsRendererInfoValid(&m_rendererInfo);
if (FAILED(hr)) {
return hr;
}
// is valid?
hr = DXGICaptureHelper::GetContainerFormatByFileName(lpcwOutputFileName);
if (FAILED(hr)) {
return hr;
}
DXGI_OUTDUPL_FRAME_INFO FrameInfo;
CComPtr<IDXGIResource> ipDesktopResource;
CComPtr<ID3D11Texture2D> ipAcquiredDesktopImage;
CComPtr<ID2D1Bitmap> ipD2D1SourceBitmap;
// Get new frame
int lTryCount = 4;
do
{
Sleep(1);
hr = m_ipDxgiOutputDuplication->AcquireNextFrame(250, &FrameInfo, &ipDesktopResource);
if (SUCCEEDED(hr))
break;
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
continue;
}
else if (FAILED(hr))
break;
} while (--lTryCount > 0);
if (hr == DXGI_ERROR_WAIT_TIMEOUT)
{
return S_FALSE;
}
else if (FAILED(hr))
{
return hr;
}
// QI for ID3D11Texture2D
hr = ipDesktopResource->QueryInterface(IID_PPV_ARGS(&ipAcquiredDesktopImage));
ipDesktopResource = nullptr;
CHECK_HR_RETURN(hr);
if (nullptr == ipAcquiredDesktopImage)
{
// release frame
m_ipDxgiOutputDuplication->ReleaseFrame();
return E_OUTOFMEMORY;
}
// Copy needed full part of desktop image
m_ipD3D11DeviceContext->CopyResource(m_ipCopyTexture2D, ipAcquiredDesktopImage);
// release frame
hr = m_ipDxgiOutputDuplication->ReleaseFrame();
CHECK_HR_RETURN(hr);
// create D2D1 source bitmap
hr = DXGICaptureHelper::CreateBitmap(m_ipD2D1RenderTarget, m_ipCopyTexture2D, &ipD2D1SourceBitmap);
CHECK_HR_RETURN(hr);
//try this
D2D1_RECT_F rcSource = D2D1::RectF(
(FLOAT)x,
(FLOAT)y,
(FLOAT)x + width,
(FLOAT)y+ height);
D2D1_RECT_F rcTarget = D2D1::RectF(
(FLOAT)0,
(FLOAT)0,
(FLOAT)width,
(FLOAT)height);
D2D1_POINT_2F ptTransformCenter = D2D1::Point2F(width / 2.0f, height / 2.0f);
// Apply the rotation transform to the render target.
D2D1::Matrix3x2F rotate = D2D1::Matrix3x2F::Rotation(
m_rendererInfo.RotationDegrees,
ptTransformCenter
);
D2D1::Matrix3x2F scale = D2D1::Matrix3x2F::Scale(
D2D1::SizeF(1, 1),
ptTransformCenter
);
// Priority: first rotate, after scale...
m_ipD2D1RenderTarget->SetTransform(rotate * scale);
m_ipD2D1RenderTarget->BeginDraw();
// clear background color
m_ipD2D1RenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::White, 1.0f));
m_ipD2D1RenderTarget->DrawBitmap(ipD2D1SourceBitmap, rcTarget, 1.0f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR, rcSource);
//m_ipD2D1RenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());
hr = m_ipD2D1RenderTarget->EndDraw();
if (FAILED(hr)) {
return hr;
}
hr = DXGICaptureHelper::SaveImageToFile(m_ipWICImageFactory, m_ipWICOutputBitmap, lpcwOutputFileName, width, height);
if (FAILED(hr)) {
return hr;
}
return S_OK;
} // CaptureToFile
我的保存到文件方法
static
COM_DECLSPEC_NOTHROW
inline
HRESULT
SaveImageToFile(
_In_ IWICImagingFactory* pWICImagingFactory,
_In_ IWICBitmapSource* pWICBitmapSource,
_In_ LPCWSTR lpcwFileName,
_In_ unsigned int uiWidth,
_In_ unsigned int uiHeight
)
{
CHECK_POINTER_EX(pWICImagingFactory, E_INVALIDARG);
CHECK_POINTER_EX(pWICBitmapSource, E_INVALIDARG);
HRESULT hr = S_OK;
GUID guidContainerFormat;
hr = GetContainerFormatByFileName(lpcwFileName, &guidContainerFormat);
if (FAILED(hr)) {
return hr;
}
WICPixelFormatGUID format = GUID_WICPixelFormatDontCare;
CComPtr<IWICImagingFactory> ipWICImagingFactory(pWICImagingFactory);
CComPtr<IWICBitmapSource> ipWICBitmapSource(pWICBitmapSource);
CComPtr<IWICStream> ipStream;
CComPtr<IWICBitmapEncoder> ipEncoder;
CComPtr<IWICBitmapFrameEncode> ipFrameEncode;
// unsigned int uiWidth = 1756;
// unsigned int uiHeight = 1115;
hr = ipWICImagingFactory->CreateStream(&ipStream);
if (SUCCEEDED(hr)) {
hr = ipStream->InitializeFromFilename(lpcwFileName, GENERIC_WRITE);
}
if (SUCCEEDED(hr)) {
hr = ipWICImagingFactory->CreateEncoder(guidContainerFormat, NULL, &ipEncoder);
}
if (SUCCEEDED(hr))
{
hr = ipEncoder->Initialize(ipStream, WICBitmapEncoderNoCache);
}
if (SUCCEEDED(hr))
{
hr = ipEncoder->CreateNewFrame(&ipFrameEncode, NULL);
}
if (SUCCEEDED(hr))
{
hr = ipFrameEncode->Initialize(NULL);
}
if (SUCCEEDED(hr))
{
hr = ipFrameEncode->SetSize(uiWidth, uiHeight);
}
if (SUCCEEDED(hr))
{
hr = ipFrameEncode->SetPixelFormat(&format);
}
if (SUCCEEDED(hr))
{
hr = ipFrameEncode->WriteSource(ipWICBitmapSource, NULL);
}
if (SUCCEEDED(hr))
{
hr = ipFrameEncode->Commit();
}
if (SUCCEEDED(hr))
{
hr = ipEncoder->Commit();
}
return hr;
} // SaveImageToFile
解决方案
传入的坐标需要自行归一化到监视器,而不考虑其他监视器。IE 所有的 x 和 y 值都需要从显示器左上角的 0,0 开始为正。
为了获得监视器的坐标,我使用了以下内容,其中 Control 是我试图在我的应用程序中捕获的视图。
Monitor[] monitors = Display.getDefault().getMonitors();
Control control = getScreenCaptureControlArea();
Point p = control.toDisplay(0, 0);
for (Monitor monitor : monitors) {
Rectangle bounds = monitor.getBounds();
if (monitor.getBounds().contains(p.x, p.y)) {
setxCord(Math.abs(Math.abs(bounds.x) - Math.abs(p.x)));
setyCord(Math.abs(Math.abs(bounds.y) - Math.abs(p.y)));
setWidth(control.getSize().x);
setHeight(control.getSize().y);
break;
}
}
请注意,当显示器处于纵向模式时,这仍然不起作用,因为保存的图像不正确。但是看到我的应用程序不打算在肖像模式下使用它并没有多大关系,所以我没有花更多的精力来解决这个问题。
推荐阅读
- prometheus - 我可以在 Grafana 的 Prometheus / Prometheus 中使用列表吗?
- rust - 宏是否可以采用常量表达式并“内联”它以生成有效的 LiteralPattern?
- python - python 3中len()函数是如何实现的,如何在python中找到内置函数的源代码?
- php - 将 POST 文本输出转换为 JSON
- swift - 在 SwiftUI 中使用 Combine 进行映射、捕获错误和分配
- c# - c#中如何根据表行生成新列表
- entity-framework-core - 更新可能彼此共享同一实体(类别)的嵌套实体(产品)列表
- node.js - 如何使用 fetch api 将 FormData 从前端发布到后端。并在后端访问此 FormData 以与 node.js 反应并表达
- java - JVM 在启动时占用整个 XMX 内存
- google-cloud-platform - GCP 负载均衡器的运行状况检查(来自 terraform)