PrintWindow 闪烁问题

3

我想使用我一直使用的PrintWindow方法来捕获一个窗口。

RECT rc;
GetClientRect(hwnd, out rc); //The process window handler

Bitmap bmp = new Bitmap(rc.right, rc.bottom, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr hdcBitmap = gfxBmp.GetHdc();

PrintWindow(hwnd, hdcBitmap, 1);

gfxBmp.ReleaseHdc(hdcBitmap);
gfxBmp.Dispose();
bmp.Save("test.png");

在这个特定的游戏流程中,当调用Printwindow函数时,窗口很快变空,因此有时保存的图像完全是白色的。
所以我尝试使用BitBlt:
Bitmap bmp = new Bitmap(rc.right, rc.bottom, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Graphics gfxBmp = Graphics.FromImage(bmp);
IntPtr dest = gfxBmp.GetHdc();
IntPtr source = GetWindowDC(hwnd);

BitBlt(dest, 0, 0, rc.width, rc.height, source, 0, 0, 13369376);
bmp.Save("test.png");

但是使用上述代码保存的图像完全是黑色的。

有没有办法防止PrintWindow使过程窗口闪烁那个"白层"?如果不可能,BitBtl应该为我解决这个问题,但我的代码有什么问题吗?

谢谢


这是针对WPF的WinForms吗?在WinForms中,您只需绘制到图像上,最后将最终图像重新绘制到屏幕上,从而缓冲图形。在WPF中,您也可以做同样的事情,但根据您尝试做什么,有不同的方法。例如,BitMapCahcedBrush允许您直接从硬件渲染。 - noone392
1
@noone392:这个问题是关于捕获其他应用程序窗口内容的。 - IInspectable
你能用其他工具(例如 Snipping Tool)成功地截屏吗? - andlabs
1
看起来是使用DirectX渲染的Windows上PrintWindow()存在问题,应该使用BitBlt进行修复。奇怪的是,当保存时我得到了黑色图像,不确定我的代码哪里出了问题。 - Kyore
2个回答

3
您可以尝试使用以下代码,它在我的电脑上起作用。如果您仍然看到黑色图片,则可能需要采用DWM解决方案。
更新:修复了缺少窗口参数的问题。
    public Bitmap CaptureWindowImage(IntPtr hWnd, System.Drawing.Rectangle wndRect)
    {
        IntPtr hWndDc = GetDC(hWnd);
        IntPtr hMemDc = CreateCompatibleDC(hWndDc);
        IntPtr hBitmap = CreateCompatibleBitmap(hWndDc, wndRect.Width, wndRect.Height);
        SelectObject(hMemDc, hBitmap);

        BitBlt(hMemDc, 0, 0, wndRect.Width, wndRect.Height, hWndDc, 0, 0, TernaryRasterOperations.SRCCOPY);
        Bitmap bitmap = Bitmap.FromHbitmap(hBitmap);

        DeleteObject(hBitmap);
        ReleaseDC(hWnd, hWndDc);
        ReleaseDC(IntPtr.Zero, hMemDc);

        return bitmap;
    }

    private enum TernaryRasterOperations : uint
    {
        /// <summary>dest = source</summary>
        SRCCOPY = 0x00CC0020,
        /// <summary>dest = source OR dest</summary>
        SRCPAINT = 0x00EE0086,
        /// <summary>dest = source AND dest</summary>
        SRCAND = 0x008800C6,
        /// <summary>dest = source XOR dest</summary>
        SRCINVERT = 0x00660046,
        /// <summary>dest = source AND (NOT dest)</summary>
        SRCERASE = 0x00440328,
        /// <summary>dest = (NOT source)</summary>
        NOTSRCCOPY = 0x00330008,
        /// <summary>dest = (NOT src) AND (NOT dest)</summary>
        NOTSRCERASE = 0x001100A6,
        /// <summary>dest = (source AND pattern)</summary>
        MERGECOPY = 0x00C000CA,
        /// <summary>dest = (NOT source) OR dest</summary>
        MERGEPAINT = 0x00BB0226,
        /// <summary>dest = pattern</summary>
        PATCOPY = 0x00F00021,
        /// <summary>dest = DPSnoo</summary>
        PATPAINT = 0x00FB0A09,
        /// <summary>dest = pattern XOR dest</summary>
        PATINVERT = 0x005A0049,
        /// <summary>dest = (NOT dest)</summary>
        DSTINVERT = 0x00550009,
        /// <summary>dest = BLACK</summary>
        BLACKNESS = 0x00000042,
        /// <summary>dest = WHITE</summary>
        WHITENESS = 0x00FF0062
    }

    [DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
    static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);

    [DllImport("gdi32.dll", SetLastError = true)]
    private static extern IntPtr CreateCompatibleDC(IntPtr hdc);

    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr hObject);

    [DllImport("gdi32.dll")]
    private static extern IntPtr CreateBitmap(int nWidth, int nHeight, uint cPlanes, uint cBitsPerPel, IntPtr lpvBits);

    [DllImport("user32.dll")]
    private static extern IntPtr GetDC(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

    [DllImport("gdi32.dll")]
    private static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);

1
你说得对@Franziee,我漏掉了窗口参数。代码已更新! - Francesco
我认为窗口参数仍然缺失。 - Luishg
1
@Luishg 现在应该没问题了,那是一个打字错误。让我知道。 - Francesco
@Francesco 谢谢,现在可以了!这是唯一一个真正对我有用的解决方案。其他的都给我一个空白/透明的屏幕。 - Luishg
显然出现了泄漏,调用该方法数千次(约10k),行"Bitmap bitmap = Bitmap.FromHbitmap(hBitmap);"将抛出GDI通用错误异常,只有重新打开应用程序才能再次运行,直到再次达到限制。我们是否遗漏了释放/删除某些东西? - Luishg
显示剩余3条评论

1

Francesco的答案几乎可以解决问题,但是由于尝试调用ReleaseDC(IntPtr.Zero, hMemDc);而导致内存泄漏,如果调用DeleteDC(hMemDc);则不会泄漏。

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc

应用程序必须对每个调用GetWindowDC函数和检索公共DC的每个GetDC函数调用一次ReleaseDC函数。 应用程序不能使用ReleaseDC函数释放通过调用CreateDC函数创建的DC;而是必须使用DeleteDC函数。ReleaseDC必须从调用GetDC的同一线程调用。 这是通过任务管理器中的GDI对象数量和内存使用情况验证的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接