Kyore
Kyore

Reputation: 438

PrintWindow Flickering

I'm trying to capture a window using the method I always used with 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");

The problem is that in this specific game process the window gets blank really fast when it calls the Printwindow function, so sometimes the image saved is totally white.

So I tried to use 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");

But using the code above the image saved is totally black.

There is any way to prevent PrintWindow to make the process window to flick that "white layer" ? If it's nto possible BitBtl should solve that issue for me right? But what is wrong with my code?

Thank you

Upvotes: 3

Views: 1609

Answers (2)

user1764583
user1764583

Reputation: 46

Francesco's answer almost worked, but there is a memory leak due to attempting to call ReleaseDC(IntPtr.Zero, hMemDc); it will not leak if DeleteDC(hMemDc); is called.

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

The application must call the ReleaseDC function for each call to the GetWindowDC function and for each call to the GetDC function that retrieves a common DC.

An application cannot use the ReleaseDC function to release a DC that was created by calling the CreateDC function; instead, it must use the DeleteDC function. ReleaseDC must be called from the same thread that called GetDC.

This was verified by the number of GDI Objects in taskmanager and also memory usage

Upvotes: 1

Francesco
Francesco

Reputation: 5173

You could try with this code, it's working for me. If you still see black image, probably you have to go for DWM solution.

UPDATE: fixed missing window argument

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

Upvotes: 3

Related Questions