node.js - 无法通过winapi获取窗口截图
问题描述
使用Node.js ffi-napi 包,如果没有提供窗口句柄,我试图从给定窗口或桌面的屏幕截图中获取位图数据的缓冲区。为此,我正在尝试从microsoft 文档中移植 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>
解决方案
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());
}
您可以使用打包的库,或者,如果您愿意,只需参考其源代码并提取您需要的部分。屏幕截图的主要模块可以在这里看到。
推荐阅读
- erlang - 将 C++ 共享库作为 Erlang 端口驱动程序打开时出错
- javascript - How to splice several strings
- java - 对我的 ArrayList 进行排序的正确方法是什么?
- git - 如何通过“devtool finish”命令设置 git 项目以在 Yocto 中发布配方?
- java - 如何在垂直方向显示按钮?
- javascript - storing data from service.ts to use in the same service.ts
- r - I cannot add geom_point on top of an existing geom_map, "differing number of rows"?
- c# - Dynamically add rows to Google Charts using JQuery loop
- unit-testing - 测试 Observables——检查订阅的副作用
- sas - SAS将excel数字列作为文本导入