Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PrintWindow bitmap differs from PrintScreen Key bitmap

When capturing a window manually with the Print Screen+Alt key combo, I get the following:

enter image description here

but if I try to do it programmatically using Windows API, I get this:

enter image description here

Why the discrepancy? How do I get the first programmatically?

Here is my code:

    [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;
    }

Update: Doing it with BitBlt does the same thing.

Here's code from CodeProject that still returns a black-masked image:

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

I have tried many combinations of CopyPixelOperation, (somewhere around 15,000 of the 131,000) but it still doesn't work.

Using Windows 8, AMD Radeon HD 6870.


Update 2

It seems that the window is transparent, allowing the blue color of the window to bleed through. When I change the window color to black (using the Windows personalization dialog), I get roughly something similar to the second window. The border is still missing though.

I haven't found a solution, but it's insight into the problem.

like image 504
Jason Avatar asked May 03 '13 03:05

Jason


2 Answers

The reason the Alt+PrntScrn image looks different is because it's not actually taking a snapshot of the selected window - it's taking a snapshot of something like the desktop window, but clipping out the appropriate part.

To prove this, take an always-on-top window like the Task Manager and position it so it's overlaying the window that you're snapshotting. You'll see it actually includes both windows in the snapshot. Whereas a call to PrintWindow will just return the specified window alone.

So, if you want to emulate the Alt+PrntScrn exactly, you need to BitBlt from the desktop. Something like this:

IntPtr hDesktop = User32.GetDesktopWindow();
IntPtr hdcSrc = User32.GetWindowDC(hDesktop);
GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, CopyPixelOperation.SourceCopy);

I'm kind of guessing at the syntax here based on your example code. The raw Windows API calls would look like this:

HWND hDesktop = GetDesktopWindow();
HDC hdcSrc = GetDC(hDesktop);
BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, SRCCOPY);

At least that works for me. If you have any problems figuring out the rest from here, let me know in the comments.

like image 126
James Holderness Avatar answered Sep 28 '22 02:09

James Holderness


The reason that PrintWindow does not work is because it depends on the correct handling of the WM_PRINT message by the app. A lot of apps are flaky with WM_PRINT and don't implement it right or ever test it. It's therefore a bad idea to rely on it unless you use it only on known and tested apps.

If you want to grab the window as it shows on screen, just blt it from the Desktop window handle (GetDesktopWindow()) and only blt the rect that includes the window.

Transparency is an issue with window grabbing. It is impossible to capture modern Windows OS' windows, because there is no image file type that supports fanciness like blurring the underlying imagery. However, it's possible to capture simple transparency into a PNG file.

Be careful with assuming a window looks best as seen on screen. Depending on what's underneath, this may not be true. It may also be a bad idea to copy whatever's underneath as it may not be sanctioned to appear in the image, like an explicit background image appearing in a business presentation under a screenshot of Excel :).

If you blt from the desktop, you copy what you see on screen, including any overlaying windows and underlying windows (where transparent). It is usually regarded as "cleaner" to grab the window only, as with PrintWindow, but you may want to composite this on a background of your choice, like white or blue. If you want to blt from screen, there are ways to temporarily hide windows that overlay your target, but it's a bunch of work with EnumWindows and such.

Grabbing windows correctly is not an easy task in Windows and there are many screen-grab apps that compete with each other by virtue of how well they deal with exactly this issue. Also, in Windows there are several ways to make make areas of windows transparent, and they can be used together as well.

Also, in Vista+, there's the DWM thumbnail API which allows you to get a copy of an app window drawn on your window. For a demo and source, see ShareX (formerly zScreen).

Finally, you can look into the source of Open Broadcaster Software, which uses Direct3D to do some screen grabbing.

like image 32
DDS Avatar answered Sep 28 '22 02:09

DDS