使用Print Screen
+ Alt
键组合手动捕获窗口时,我得到以下信息:
但是如果我尝试使用Windows API以编程方式执行此操作,我会得到:
为什么会出现差异?我如何以编程方式获得第一个?
这是我的代码:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags);
public Bitmap PrintWindow()
{
Bitmap bmp = new Bitmap(windowRect.Width, windowRect.Height, PixelFormat.Format32bppArgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();
bool success = PrintWindow(windowHandle, hdcBitmap, 0);
gfxBmp.ReleaseHdc(hdcBitmap);
if (!success)
{
Console.WriteLine("Error copying image");
Console.WriteLine(getLastError());
}
gfxBmp.Dispose();
return bmp;
}
更新 使用BitBlt做同样的事情。
此处code from CodeProject仍会返回黑色遮罩图片:
public Image CaptureWindow(IntPtr handle)
{
// get te hDC of the target window
IntPtr hdcSrc = User32.GetWindowDC(handle);
// get the size
User32.RECT windowRect = new User32.RECT();
User32.GetWindowRect(handle,ref windowRect);
int width = windowRect.right - windowRect.left;
int height = windowRect.bottom - windowRect.top;
// create a device context we can copy to
IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
// create a bitmap we can copy it to,
// using GetDeviceCaps to get the width/height
IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc,width,height);
// select the bitmap object
IntPtr hOld = GDI32.SelectObject(hdcDest,hBitmap);
// bitblt over
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);
// restore selection
GDI32.SelectObject(hdcDest,hOld);
// clean up
GDI32.DeleteDC(hdcDest);
User32.ReleaseDC(handle,hdcSrc);
// get a .NET image object for it
Image img = Image.FromHbitmap(hBitmap);
// free up the Bitmap object
GDI32.DeleteObject(hBitmap);
img.Save("SampleImage.png");
return img;
}
我尝试了CopyPixelOperation
的许多组合,(131,000中约有15,000个),但它仍然不起作用。
使用Windows 8,AMD Radeon HD 6870。
更新2
似乎窗户是透明的,允许窗户的蓝色渗透。当我将窗口颜色更改为黑色(使用Windows个性化对话框)时,我得到的内容大致类似于第二个窗口。但边界仍然缺失。
我还没有找到解决方案,但它能够深入了解问题。
答案 0 :(得分:9)
PrintWindow不起作用的原因是它取决于应用程序正确处理WM_PRINT消息。许多应用程序都没有WM_PRINT,并且没有正确实现或者测试它。因此,依赖它是一个坏主意,除非你只在已知和测试的应用程序上使用它。
如果您想要抓住屏幕上显示的窗口,只需从桌面窗口句柄(GetDesktopWindow())进行blt,然后只显示包含窗口的矩形。
透明度是窗口抓取的问题。捕获现代Windows操作系统的窗口是不可能的,因为没有图像文件类型支持模糊底层图像的幻想。但是,可以将简单的透明度捕获到PNG文件中。
小心假设窗口看起来最好如屏幕上所示。根据下面的内容,这可能不是真的。复制下面的内容可能也是一个坏主意,因为它可能不会被制裁出现在图像中,就像在Excel的屏幕截图下出现在业务演示文稿中的显式背景图像一样:)。
如果你从桌面上看,你复制你在屏幕上看到的内容,包括任何重叠的窗口和底层窗口(透明的地方)。与PrintWindow一样,它通常被认为是“更清洁”,但是你可能想要在你选择的背景上合成它,比如白色或蓝色。如果你想从屏幕上blt,有一些方法可以暂时隐藏覆盖目标的窗口,但这是与EnumWindows等一系列工作。
正确地抓取Windows 在Windows中并不是一项容易的任务,并且有很多屏幕抓取应用程序因为它们如何处理这个问题而相互竞争。此外,在Windows中,有几种方法可以使窗口区域透明,并且它们也可以一起使用。
此外,在Vista +中,DWM thumbnail API允许您获取窗口上绘制的应用程序窗口的副本。有关演示和源代码,请参阅ShareX(以前称为zScreen)。
最后,您可以查看Open Broadcaster Software的来源,它使用Direct3D进行屏幕抓取。
答案 1 :(得分:4)
Alt+PrntScrn
图像看起来不同的原因是因为它实际上并没有拍摄所选窗口的快照 - 它正在拍摄类似桌面窗口的快照,但剪掉了相应的部分。
为了证明这一点,请像任务管理器一样使用一个永远在顶部的窗口并将其定位,使其覆盖您正在快照的窗口。您会看到它实际上包含快照中的两个窗口。而对PrintWindow
的调用只会返回指定的窗口。
因此,如果您想要完全模拟Alt+PrntScrn
,则需要从桌面进行BitBlt。像这样:
IntPtr hDesktop = User32.GetDesktopWindow();
IntPtr hdcSrc = User32.GetWindowDC(hDesktop);
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, CopyPixelOperation.SourceCopy);
我根据你的示例代码猜测这里的语法。原始Windows API调用如下所示:
HWND hDesktop = GetDesktopWindow();
HDC hdcSrc = GetDC(hDesktop);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, SRCCOPY);
至少这对我有用。如果你从这里搞清楚其他问题,请在评论中告诉我。
答案 2 :(得分:1)
“为什么会出现差异?如何以编程方式获得第一个?”
BitBlt vs PrintWindow
答案 3 :(得分:0)
不确定是否是这样,但 BitBlt
接受一个 rop
(光栅操作代码) 参数,该参数指示如何将数据组合到目标 >
默认为BLACKNESS
,在复制截图数据之前用全黑像素预填充目标内存
您可能想要将此参数设置为 CAPTUREBLT
,这似乎包括屏幕上的所有内容,而不仅仅是您要捕获的特定窗口
https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt
ofc 在桌面窗口句柄上运行 BitBlt