PrintWindow bitmap differs from PrintScreen Key bitmap
Asked Answered
C

4

9

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.

Clerihew answered 3/5, 2013 at 3:16 Comment(5)
When you called PrintWindow(), how did you manufacture the DC you sent to the API call?Connive
What do you get if you use PixelFormat.Format24bppRgb?Ruhr
I just tried and got the same image, even with PW_CLIENTONLY flagClerihew
Is this "blackening" of the border always happending or only in such office style windows that seem to use dwmextendframeintoclientarea?Nucleate
This happens for Office apps, Chrome, Windows Explorer windows, but not for Visual Studio.Clerihew
H
10

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.

Hyperventilation answered 12/5, 2013 at 0:44 Comment(3)
Have you worked with DWM? do you know if it's possible to draw to a buffer instead of a window?Clerihew
You can't do anything besides what the function lets you do, so that's a no. See the zScreen link for usage. A last way (Vista+) to grab the screen is to hook into D3D and grab it straight from the graphics system. If that sounds strange and dangerous, you should know that Fraps uses this system, and it can grab screens Quite Well (120fps!).Hyperventilation
DWM worked like a charm and was very simple to implement.Mccune
M
5

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.

Marek answered 11/5, 2013 at 22:2 Comment(0)
W
0

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

BitBlt vs PrintWindow

Winer answered 5/5, 2013 at 1:7 Comment(2)
I have tried BitBlt too but it's the same issue. See updated code in answerClerihew
Try BitBlt with GetDesktopWindow as target window, - don't know about win8, but win7 allows this way.Winer
L
0

not sure if this is it, but BitBlt takes in a rop (raster-operation code) parameter which dictates how the data is combined to the destination

this defaults to BLACKNESS, which pre-fills the destination memory with all-black pixels before copying over the screenshot data

what you probably want is to set this parameter to CAPTUREBLT, which seems to include everything on screen and not just the specific window you want to capture

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

ofc running BitBlt on the desktop window handle

Littlefield answered 23/2, 2021 at 0:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.