是否可以使用 DirectX 工具包中的屏幕抓图捕获桌面/屏幕截图?

Posted

技术标签:

【中文标题】是否可以使用 DirectX 工具包中的屏幕抓图捕获桌面/屏幕截图?【英文标题】:Is Desktop/screenshot capture with screengrab from DirectX Tool Kit possible? 【发布时间】:2017-07-07 12:40:33 【问题描述】:

所以,马上开始接触金属。我正在编写一个 c++ 程序,其目的是将屏幕作为输入图像并使用对象检测器(来自 dlib)对其进行处理,但我无法理解如何获得指向 swapChain 和上下文/内容的指针屏幕。

ScreenGrab (DirectX Tool Kit) 的以下代码似乎可以解决问题。问题是我不知道如何让它工作。我在 Visual Studio 2017 中使用 NuGet 来获取库(称为 directxtk_uwp,还有一个我没有使用的称为 directxtk_desktop_2015。)我收到以下六个错误:

错误 C2065:“swapChain”:未声明的标识符 错误 C2227:'->GetBuffer' 的左侧必须指向类/结构/联合/泛型类型 错误 C2065:“immContext”:未声明的标识符 错误 C2228:“.Get”左侧必须有类/结构/联合 错误 C2653:“DX”:不是类或命名空间名称 错误 C3861:“ThrowIfFailed”:找不到标识符

从 ScreenGrab wiki 页面运行插入的示例代码:

#include <ScreenGrab.h>
#include <wrl\client.h>
#include <Wincodec.h>


int main() 


    using namespace DirectX;
    using namespace Microsoft::WRL;

    ComPtr<ID3D11Texture2D> backBuffer;
    HRESULT hr = swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D),
        reinterpret_cast<LPVOID*>(backBuffer.GetAddressOf()));
    if (SUCCEEDED(hr))
    
        hr = SaveWICTextureToFile(immContext.Get(), backBuffer.Get(),
            GUID_ContainerFormatJpeg, L"SCREENSHOT.JPG");
    
    DX::ThrowIfFailed(hr);


        return 0;

我刚刚开始使用 c++ 进行编程,并且来自 Java 编程,我已经积极开发了一年。所以保持简单易懂将不胜感激^^

为了清楚起见,我想要一些相当快的东西。我已经通过以下代码获得了与 GDI 一起使用的屏幕截图:

#include <iostream>
#include <Windows.h>
#include <Wincodec.h>
#include <ctime>
#include <cstring>
#include <atlimage.h>
#include <d3d9.h>
using namespace std;


int main()

    try
    
        int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
        int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
        HWND hDesktopWnd = GetDesktopWindow();
        HDC hDesktopDC = GetDC(hDesktopWnd);
        HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);

        for (int i = 0; i < 10;i++) 
            clock_t begin = clock();
            HBITMAP hCaptureBitmap = CreateCompatibleBitmap(hDesktopDC,
                nScreenWidth, nScreenHeight);
            SelectObject(hCaptureDC, hCaptureBitmap);
            BitBlt(hCaptureDC, 0, 0, nScreenWidth, nScreenHeight,
                hDesktopDC, 0, 0, SRCCOPY | CAPTUREBLT);
            CImage image;
            image.Attach(hCaptureBitmap);
            image.Save("test.jpg");
            DeleteObject(hCaptureBitmap);
            cout << double(clock() - begin) / (clock_t)1000 << endl;
        
        ReleaseDC(hDesktopWnd, hDesktopDC);
        DeleteDC(hCaptureDC);
        IDirect3DSurface9 *surface;
        system("pause");
    
    catch (exception& e)
    
        cout << "\nexception thrown!" << endl;
        cout << e.what() << endl;
        system("pause");
    

结果如下:

0.091
0.05
0.052
0.06
0.047
0.05
0.057
0.06
0.06
0.051

如您所见,这实际上只能使我的速度达到每秒 1/0.47 ≈ 21 帧。我也看过this 的回答,但我还是不知道如何获取屏幕的上下文和交换链。 谢谢。

【问题讨论】:

您使用的示例代码避免假设您尝试截取自己的渲染目标纹理(直接创建或从您自己的交换链获得)。这适用于您自己的应用程序,但截取整个桌面的屏幕截图是另一回事,需要使用 DXGI API。您应该查看this article 的一些选项。 DirectX 工具包wiki 上的示例代码假设您至少简要地查看了tutorials,我在其中介绍了ThrowIfFailed。 谢谢,查克!正如您在变量名中可能看到的那样,我能够找到该页面,我只是不记得该站点。从来没有让 DirectX 工作的方式,但那是很早的,我想我现在有更好的理解来做它。但是,DirectX9 不是像您在有关 DirectX11 的链接问题中所说的那样已经过时了吗?无论如何,非常感谢您的帮助! 啊,对。我应该更仔细地观察,因为我认为他们正在做 DXGI 解决方案。这是更新的 blog、article 和 codebase。 太棒了!非常感谢你,查克!这应该这样做。有时有专家指出正确的方向真是太好了^^ 【参考方案1】:

因此,在伟大的 Chuck 的指导下多看几眼!我找到了一个解决方案,将屏幕截图保存到磁盘的速度大约是 3.5 倍(如果您删除绘图鼠标以及在 grabImage 函数开始时有 8 毫秒的开销)。你可以看到代码here。以下是将十张图像保存到磁盘的结果(以毫秒为单位):

0.015
0.017
0.012
0.021
0.012
0.019
0.012
0.022
0.018
0.015

将这些和 GDI 程序中的其他相加,我们得到 0.578/0.163≈3.546 倍的速度。请记住,我已经替换了这部分:

// Copy image into GDI drawing texture
        lImmediateContext->CopyResource(lGDIImage, lAcquiredDesktopImage);

        // Draw cursor image into GDI drawing texture
        CComPtrCustom<IDXGISurface1> lIDXGISurface1;

        hr = lGDIImage->QueryInterface(IID_PPV_ARGS(&lIDXGISurface1));

        if (FAILED(hr))
            return false;

        CURSORINFO lCursorInfo =  0 ;

        lCursorInfo.cbSize = sizeof(lCursorInfo);

        auto lBoolres = GetCursorInfo(&lCursorInfo);

        if (lBoolres == TRUE)
        
            if (lCursorInfo.flags == CURSOR_SHOWING)
            
                auto lCursorPosition = lCursorInfo.ptScreenPos;

                auto lCursorSize = lCursorInfo.cbSize;

                HDC  lHDC;

                lIDXGISurface1->GetDC(FALSE, &lHDC);

                DrawIconEx(
                    lHDC,
                    lCursorPosition.x,
                    lCursorPosition.y,
                    lCursorInfo.hCursor,
                    0,
                    0,
                    0,
                    0,
                    DI_NORMAL | DI_DEFAULTSIZE);

                lIDXGISurface1->ReleaseDC(nullptr);
            

        

        // Copy image into CPU access texture
        lImmediateContext->CopyResource(lDestImage, lGDIImage);

下面一行:

lImmediateContext->CopyResource(lDestImage, lAcquiredDesktopImage);

因为我不需要光标。事实上,考虑到我会在屏幕上检测到东西,拥有它可能会更糟。如前所述,我还从这里重新定位了Sleep(8); // not sure if required 行:

    hr = lDevice->CreateTexture2D(&desc, NULL, &lDestImage);

    if (FAILED(hr))
        return false;

    if (lDestImage == nullptr)
        return false;

    return true;


bool grabImage() 
    int lTryCount = 4;

    do
    
        Sleep(8); // not sure if required

到这里,在init函数中:

    hr = lDevice->CreateTexture2D(&desc, NULL, &lDestImage);

    if (FAILED(hr))
        return false;

    if (lDestImage == nullptr)
        return false;
    Sleep(8);
    return true;


bool grabImage() 
    int lTryCount = 4;

    do
    
            //Sleep(5); // not sure if required

您也可以将其完全删除,尽管这似乎会在开始时导致“空白”/黑色图像文件。对我来说,在它开始捕捉之前只有两个空白图像。随着整个初始化函数的延迟,我也得到了第一张图像而没有开销。谢谢。

【讨论】:

以上是关于是否可以使用 DirectX 工具包中的屏幕抓图捕获桌面/屏幕截图?的主要内容,如果未能解决你的问题,请参考以下文章

“printscreen”按键怎样使用?

Qt之使用GDI实现屏幕快速抓图与缩放

delphi不用getdc如何屏幕截图

旋转相机时 DirectX 基元不会在屏幕上移动

在directX诊断工具中显示的显卡内存是否指已分配给显卡的内存?(我家

软件新瓶装老酒 - MyCapture