首页 > 解决方案 > 从辅助监视器捕获图像、裁剪和保存

问题描述

我正在尝试编写一个应用程序,在其中一个监视器上截取一个区域的屏幕截图并将其保存到图像中。屏幕截图应在给定宽度和高度的 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

标签: c++directx

解决方案


传入的坐标需要自行归一化到监视器,而不考虑其他监视器。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;
        }
    }

请注意,当显示器处于纵向模式时,这仍然不起作用,因为保存的图像不正确。但是看到我的应用程序不打算在肖像模式下使用它并没有多大关系,所以我没有花更多的精力来解决这个问题。


推荐阅读