【问题标题】:PrintWindow bitmap differs from PrintScreen Key bitmapPrintWindow 位图与 PrintScreen Key 位图不同
【发布时间】:2013-05-03 03:16:08
【问题描述】:

使用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 个性化对话框)时,我得到的内容与第二个窗口大致相似。但是边界仍然缺失。

我还没有找到解决方案,但它是对问题的洞察力。

【问题讨论】:

  • 当您致电PrintWindow() 时,您是如何制造发送给 API 调用的 DC?
  • 如果你使用PixelFormat.Format24bppRgb会得到什么?
  • 我刚刚尝试得到相同的图像,即使使用 PW_CLIENTONLY 标志
  • 这种边框“变黑”是总是发生还是只发生在似乎使用 dwmextendframeintoclientarea 的办公风格窗口中?
  • Office 应用程序、Chrome、Windows 资源管理器窗口会发生这种情况,但 Visual Studio 不会发生这种情况。

标签: windows winapi printscreen


【解决方案1】:

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);

至少这对我有用。如果您在从这里弄清楚其余部分有任何问题,请在 cmets 中告诉我。

【讨论】:

    【解决方案2】:

    PrintWindow 不起作用的原因是它取决于应用程序对WM_PRINT 消息的正确处理。许多应用程序在使用 WM_PRINT 时都很不稳定,并且没有正确实现它或从未对其进行测试。因此,除非您仅在已知和经过测试的应用上使用它,否则依赖它是一个坏主意。

    如果您想抓取屏幕上显示的窗口,只需从桌面窗口句柄 (GetDesktopWindow()) 中删除它,然后只删除包含该窗口的矩形。

    透明度是窗口抓取的一个问题。捕捉现代 Windows 操作系统的窗口是不可能的,因为没有图像文件类型支持像模糊底层图像这样的幻想。但是,可以将简单的透明度捕获到 PNG 文件中。

    小心假设窗口在屏幕上看起来最好。根据下面的内容,这可能不是真的。复制下面的任何内容也可能是个坏主意,因为它可能不被批准出现在图像中,例如在 Excel 屏幕截图下的业务演示文稿中出现的明确背景图像:)。

    如果您从桌面进行 blt,则复制您在屏幕上看到的内容,包括任何覆盖窗口和底层窗口(透明的)。与 PrintWindow 一样,仅抓取窗口通常被视为“更干净”,但您可能希望将其合成在您选择的背景上,如白色或蓝色。如果你想从屏幕上 blt,有一些方法可以暂时隐藏覆盖你的目标的窗口,但它是 EnumWindows 等的一堆工作。

    正确地抓取窗口在 Windows 中并不是一件容易的事,并且有许多屏幕抓取应用程序之间相互竞争,因为它们处理这个问题的能力。此外,在 Windows 中,有几种方法可以使窗口区域透明,它们也可以一起使用。

    此外,在 Vista+ 中,DWM thumbnail API 允许您获取在您的窗口上绘制的应用程序窗口的副本。有关演示和源代码,请参阅 ShareX(以前称为 zScreen)。

    最后你可以看看Open Broadcaster Software的源码,它使用Direct3D做一些抓屏。

    【讨论】:

    • 您使用过 DWM 吗?你知道是否可以绘制到缓冲区而不是窗口?
    • 除了函数允许你做的事情之外,你什么也做不了,所以这不是。有关用法,请参阅 zScreen 链接。抓取屏幕的最后一种方法(Vista+)是连接到 D3D 并直接从图形系统抓取它。如果这听起来很奇怪和危险,你应该知道Fraps 使用这个系统,它可以很好地抓取屏幕(120fps!)。
    • DWM 的工作原理非常棒,实施起来非常简单。
    【解决方案3】:

    “为什么会出现差异?我如何以编程方式获得第一个?”

    BitBlt 与 PrintWindow

    【讨论】:

    • 我也尝试过 BitBlt,但这是同样的问题。在答案中查看更新的代码
    • 用GetDesktopWindow作为目标窗口试试BitBlt,——不知道win8,但是win7允许这种方式。
    【解决方案4】:

    不确定是不是这样,但BitBlt 接受了一个rop (光栅操作代码) 参数,该参数指示数据如何组合到目标

    默认为BLACKNESS,在复制截屏数据之前用全黑像素预填充目标内存

    您可能想要将此参数设置为CAPTUREBLT,这似乎包括屏幕上的所有内容,而不仅仅是您要捕获的特定窗口

    https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt

    ofc 在桌面窗口句柄上运行BitBlt

    【讨论】:

      猜你喜欢
      • 2012-01-15
      • 2014-09-13
      • 1970-01-01
      • 2012-10-10
      • 2012-09-12
      • 1970-01-01
      • 2016-07-02
      • 2010-10-31
      • 1970-01-01
      相关资源
      最近更新 更多