【问题标题】:Unable to get screenshot of window through winapi无法通过winapi获取窗口截图
【发布时间】:2021-09-05 21:51:39
【问题描述】:

使用Node.js ffi-napi package,如果没有提供窗口句柄,我会尝试从给定窗口或桌面的屏幕截图中获取位图数据缓冲区。为此,我正在尝试从microsoft documentation 移植 c++ 示例并通过 ffi-napi 进行 api 调用。

即使所有的 api 调用都没有错误地返回,我最终只能得到一个填充为 0 的缓冲区。我已将其追溯到一些可能导致它的潜在部分,但我不知道哪个部分不正确以及如何修复它。

  • 尽管我对 BitBlt 的调用返回 true,但我在源窗口 dc 上调用 GetPixel 得到的像素值返回正确的值,但是当我在内存 dc 上调用 GetPixel 时,我得到 0。
  • 我对 GetObjectA 的调用似乎正在填充我创建的 BITMAP 结构(我的结构只是缓冲区的扩展),除了应该保存指向位图数据的指针的最后 8 个字节全为 0。
  • 我对 GetDIBits 的调用返回 1080,这是它应该从位图数据中读取的正确行数,但我返回的缓冲区全为 0。

我的猜测是 BitBlt 实际上并没有将桌面 dc 复制到内存 dc,即使它返回 true,我也不知道为什么会这样。我还尝试运行计算器应用程序并将 dc 传递给屏幕截图函数,即使它返回适当的项目,我返回的缓冲区仍然全为 0。

任何帮助将不胜感激!

这是我的 node.js 代码

function screenshot(hWnd = null) {
    let hdcWindow = null;
    let hdcMemDC = null;
    let hbmScreen = null;
    let hDIB = null;

    try {
        if (!hWnd) hWnd = user32.GetDesktopWindow();
        console.log('hWnd', hWnd);

        // Retrieve the handle to a display device context for the client area of the window.
        hdcWindow = user32.GetDC(hWnd);
        console.log('hdcWindow', hdcWindow);
        const rcClient = new win32_structs.RECT();
        user32.GetClientRect(hWnd, rcClient);
        console.log('rcClient', rcClient);

        // Create a compatible DC and bitmap
        hdcMemDC = gdi32.CreateCompatibleDC(hdcWindow);
        console.log('hdcMemDC', hdcMemDC);
        hbmScreen = gdi32.CreateCompatibleBitmap(hdcWindow, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
        console.log('hbmScreen', hbmScreen);
        const hPrevDC = gdi32.SelectObject(hdcMemDC, hbmScreen);
        console.log('hPrevDC', hPrevDC);

        // Bit block transfer into our compatible memory DC.
        const bitBltRes = gdi32.BitBlt(hdcMemDC, 0, 0, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, hdcWindow, 0, 0, apiConstants.SRCCOPY);
        const pixelWnd = gdi32.GetPixel(hdcWindow, 0, 0);
        const pixelMem = gdi32.GetPixel(hdcMemDC, 0, 0);
        console.log('pixelWnd', pixelWnd);
        console.log('pixelMem', pixelMem);
        console.log('bitBltRes', bitBltRes);

        // Get the BITMAP from the HBITMAP
        const bmpScreen = new win32_structs.BITMAP();
        const getObjectRes = gdi32.GetObjectA(hbmScreen, bmpScreen.length, bmpScreen);
        console.log('getObjectRes', getObjectRes);
        console.log('bmpScreen.length', bmpScreen.length);
        console.log('bmpScreen', bmpScreen);

        const bi = new win32_structs.BITMAPINFOHEADER();
        bi.biSize = bi.length;
        bi.biWidth = bmpScreen.bmWidth;
        bi.biHeight = bmpScreen.bmHeight;
        bi.biPlanes = 1;
        bi.biBitCount = 32;
        bi.biCompression = apiConstants.BI_RGB;
        bi.biSizeImage = 0;
        bi.biXPelsPerMeter = 0;
        bi.biYPelsPerMeter = 0;
        bi.biClrUsed = 0;
        bi.biClrImportant = 0;
        console.log('bi', bi);

        const dwBmpSize = ((bmpScreen.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmpScreen.bmHeight;
        console.log('dwBmpSize', dwBmpSize);

        // Starting with 32-bit Windows, GlobalAlloc and LocalAlloc are implemented as wrapper functions that
        // call HeapAlloc using a handle to the process's default heap. Therefore, GlobalAlloc and LocalAlloc
        // have greater overhead than HeapAlloc.
        // hDIB = kernel32.GlobalAlloc(apiConstants.GHND, dwBmpSize);
        // const lpBitmap = kernel32.GlobalLock(hDIB);
        const lpBitmap = new Buffer.alloc(dwBmpSize);

        // Gets the "bits" from the bitmap and copies them into buffer lpbitmap
        const getDIBitsRes = gdi32.GetDIBits(hdcWindow, hbmScreen, 0, bmpScreen.bmHeight, lpBitmap, bi, apiConstants.DIB_RGB_COLORS);
        console.log('getDIBitsRes', getDIBitsRes);
        console.log('lpBitmap', lpBitmap);

        for (const c of lpBitmap) {
            if (c > 0) {
                console.log(c);
                break;
            }
        }

        // clean up
        if (hDIB != null) {
            kernel32.GlobalUnlock(hDIB);
            kernel32.GlobalFree(hDIB);
        }

        if (hbmScreen != null) gdi32.DeleteObject(hbmScreen);
        if (hdcMemDC != null) gdi32.DeleteObject(hdcMemDC);
        if (hdcWindow != null) user32.ReleaseDC(hWnd, hdcWindow);


        return lpBitmap;

    } catch (err) {
        // clean up memory on errors
        if (hDIB != null) {
            kernel32.GlobalUnlock(hDIB);
            kernel32.GlobalFree(hDIB);
        }

        if (hbmScreen != null) gdi32.DeleteObject(hbmScreen);
        if (hdcMemDC != null) gdi32.DeleteObject(hdcMemDC);
        if (hdcWindow != null) user32.ReleaseDC(hWnd, hdcWindow);

        throw err;
    }
}

这是我的控制台日志:

hWnd 65552
hdcWindow 83954845

rcClient <Buffer@0x000001BE188E8670 00 00 00 00 00 00 00 00 80 07 00 00 38 04 00 00, _structProps: { left: { offset: 0, dataType: 'long' }, top: { offset: 4, dataType: 'long' }, right: { offset: 8, dataType: 'long' 
    }, bottom: { offset: 12, dataType: 'long' } }, left: 0, top: 0, right: 1920, bottom: 1080>
    
hdcMemDC 1375804638
hbmScreen 990189696
hPrevDC 8716303
pixelWnd { r: 231, g: 234, b: 237 }
pixelMem { r: 0, g: 0, b: 0 }
bitBltRes true
getObjectRes 32
bmpScreen.length 32

bmpScreen <Buffer@0x000001BE188C5CC0 00 00 00 00 80 07 00 00 38 04 00 00 00 1e 00 00 01 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00, 
    _structProps: { bmType: { offset: 0, dataType: 'long' }, bmWidth: { offset: 4, dataType: 'long' }, bmHeight: { offset: 8, dataType: 'long' }, bmWidthBytes: { offset: 12, dataType: 'long' }, bmPlanes: { offset: 16, dataType: 'uint' }, bmBitsPixel: { offset: 18, dataType: 'uint' }, pointerPadding: { offset: 20, dataType: 'long' }, bmBits: { offset: 24, dataType: 'ulonglong' } }, 
     bmType: 0, bmWidth: 1920, bmHeight: 1080, bmWidthBytes: 7680, bmPlanes: 1, bmBitsPixel: 32, pointerPadding: 0, bmBits: 0n>
     
bi <Buffer@0x000001BE188C5540 28 00 00 00 80 07 00 00 38 04 00 00 01 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00, 
    _structProps: { biSize: { offset: 0, dataType: 'ulong' }, biWidth: { offset: 4, dataType: 'long' }, biHeight: { offset: 8, dataType: 'long' }, biPlanes: { offset: 12, dataType: 'uint' }, biBitCount: { offset: 14, dataType: 'uint' }, biCompression: { offset: 16, dataType: 'ulong' }, biSizeImage: { offset: 20, dataType: 'ulong' }, biXPelsPerMeter: { offset: 24, dataType: 'long' }, biYPelsPerMeter: { offset: 28, dataType: 'long' }, biClrUsed: { offset: 32, dataType: 'ulong' }, biClrImportant: { offset: 36, dataType: 'ulong' } }, 
    biSize: 40, biWidth: 1920, biHeight: 1080, biPlanes: 1, biBitCount: 32, biCompression: 0, biSizeImage: 0, 
    biXPelsPerMeter: 0, biYPelsPerMeter: 0, biClrUsed: 0, biClrImportant: 0>
    
dwBmpSize 8298585
getDIBitsRes 1080

lpBitmap <Buffer@0x000001BE18AED040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ... 8298535 more bytes>      

【问题讨论】:

  • 我已经用你的示例步骤重新创建了一个 C++ 示例,它对我有用,你能展示你如何在 user32 和 gdi32 和 RECT 结构中声明 winapi 吗?
  • 我能够让它工作,这是我的 SRCCOPY 枚举,当我用谷歌搜索它时,我点击微软的第一个链接说它是 0xCC,但是在走了很多错误的路径之后,我发现另一个来源说 0xCC0020 并且有效。
  • 很高兴你解决了这个问题,我猜你误用了this document 中的值。它实际上是一个操作索引。
  • 是的,这就是我最初找到的文件。我花了很长时间才重新确认。
  • 你好,你能提供一个完整的代码示例吗?我正在学习使用 node-ffi 但我不知道如何定义正确的 gdi32 类型,非常感谢!

标签: node.js winapi


【解决方案1】:

OP 提到他们最终能够让他们的代码正常工作,只需对其中一个常量进行简单修复。但是,这对其他读者并没有太大帮助,因为他们没有完整的代码;所以我想我会尝试重现他们拥有的工作代码。 (感谢@vincitego 的起点!)

经过大量的反复试验,我能够让我的复制工作正常进行,并将解决方案作为一个组件发布在我的 Windows FFI 库中:https://github.com/Venryx/windows-ffi

用法:

import {VRect, CaptureScreenshot, GetForegroundWindowHandle} from "windows-ffi";

// First capture a screenshot of a section of the screen.
const screenshot = CaptureScreenshot({
    windowHandle: GetForegroundWindowHandle(), // comment to screenshot all windows
    rectToCapture: new VRect(0, 0, 800, 600),
});

// The image-data is now stored in the `screenshot.buffer` Buffer object.
// Access it directly (and cheaply) using the helper functions on `screenshot`.
for (let x = 0; x < 800; x++) {
    console.log(`Pixel color at [${x}, 0] is:`, screenshot.GetPixel(x, 0).ToHex_RGB());
}

您可以使用打包的库,或者,如果您愿意,只需引用其源代码并提取您需要的部分。截屏的主模块可以是seen here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-03-25
    • 2021-05-11
    • 2021-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多