25

I'm using this code to capture a process window in the background:

IntPtr = Process.GetProcessByName("memu")[0].MainWindowHandle;
RECT rc;
GetClientRect(hwnd, out rc);

IntPtr hdcFrom = GetDC(hwnd);
IntPtr hdcTo = CreateCompatibleDC(hdcFrom);

int Width = rc.right;
int Height = rc.bottom;

Bitmap bmp = null;

IntPtr hBitmap = CreateCompatibleBitmap(hdcFrom, Width, Height);
if (hBitmap != IntPtr.Zero) {
   IntPtr hLocalBitmap = SelectObject(hdcTo, hBitmap);

   BitBlt(hdcTo, 0, 0, Width, Height, hdcFrom, 0, 0, CopyPixelOperation.SourceCopy);
   SelectObject(hdcTo, hLocalBitmap);

   DeleteDC(hdcTo);
   ReleaseDC(hwnd, hdcFrom);

   bmp = Image.FromHbitmap(hBitmap);
   DeleteObject(hBitmap);
   return bmp;
}

This code is capture an Android emulator called MEmu, it is using DirectX to render the content. But this code stopped to work after Windows 10 updated to version 16299 (it was working normally before), it still working on Windows 7 with Aero mode enabled.

When I use this method in the Windows 10 Pro v16299.X it simply return a white image or it returns the emulator "loading screen", not the running content. On Windows 7, if I remove the Aero mode it will act the same, capturing the "loading screen", so looks like somehow the way the transparency works in the new windows 10 pro update changed.

I've tried everything, tried install some modules to force Aero Mode to work on Windows 10, tried PrintWindow to capture the screen in the background, but still the same.

Any ideas what could be happening? Or a possible solution? Or what changed in this last Windows 10 Pro version that could break that code?

Thank you!

Daniel A. White
  • 187,200
  • 47
  • 362
  • 445
Imac
  • 379
  • 5
  • 11
  • See this [post](https://stackoverflow.com/questions/2474538/win32-window-capture-with-bitblt-not-displaying-border) mentioning `CAPTUREBLT` flag, you might have the same issue. – Chris O Dec 13 '17 at 22:16
  • I've tried those flags, I get the same result. – Imac Dec 14 '17 at 00:31
  • [maybe related](https://social.msdn.microsoft.com/Forums/windows/en-US/9d570f8f-5242-400f-9824-882aea0d426b/bitblt-issue-after-win10-anniversary-update?forum=winforms) as per that msdn thread, "It looks like a bug in Windows10 Anniversary", no working solution yet. And [this](https://stackoverflow.com/q/44774169/1132334), about python but using the same API call and similar problem description. and [this](https://stackoverflow.com/q/43385376/1132334), for Windows 10 version 1703 (15063.138) 1703 (15063.138) also worked in previous versions. – Cee McSharpface Dec 17 '17 at 21:52
  • You could try to capture using DirectX: https://stackoverflow.com/questions/30021274/capture-screen-using-directx – Simon Mourier Dec 18 '17 at 04:58
  • by "in background" you mean minimized, or just covered with other windows? – MSDN.WhiteKnight Dec 18 '17 at 17:22
  • @MSDN.WhiteKnight: Just covered, not minimized. Simon Mourier: Looks like that method only works with "focused" windows, I need to capture windows on background. – Imac Dec 18 '17 at 21:28
  • No, it takes a shot of the whole screen. There is a modified version here: https://stackoverflow.com/questions/44004954/directx-partial-screen-capture that captures only a rectangle portion. Of course, windows not visible on the screen cannot be captured this way if it's what you mean. PS: don't forget the leading '@' when you send a comment, I wasn't notified of yours. – Simon Mourier Dec 19 '17 at 08:03
  • 1
    The 16299.64 changelog says "Security updates to Microsoft Graphics Component", probably these updates broken something in DWM as a side effect. There's no alternative API to capture hidden windows, so you can either hope MS fixes it, or use some hacks like DirectX hooks. – MSDN.WhiteKnight Dec 19 '17 at 16:56
  • I just talked to a guy using this: Windows 10 Pro 64 bit Version 1709 (Build 16299. 125), he said it's working for him, so it's crazier than I tought and I have no clue why it works for a few people and why it doesn't work to another. – Imac Dec 20 '17 at 22:16
  • 1
    FYI, it does **not** work in our Windows 10 Pro 64 bit, version 1607: get an all-black bitmap. – jsanalytics Dec 22 '17 at 11:47
  • Yeah, that's exactly what is happening. I'm starting to think it's a bug on windows, not sure where I should report it to them. – Imac Dec 22 '17 at 15:46
  • 4
    Right after calling `BitBlt`, do this: `var error = Marshal.GetLastWin32Error();` and in case you get anything different from 0(zero), verify the error code [HERE](https://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx). In order for this to work properly you need to `SetLastError = true` in your `DllImport` statement. This will give only the error for the last Win32 API call, so you may need to do this for each call. – jsanalytics Dec 22 '17 at 20:39
  • 2
    @IgorM - as for where to report error - Windows 10 is shipped with app called "Centrum Feedback" you can use to report both suggestions/bugs. – Ondrej Svejdar Dec 24 '17 at 06:25
  • 1
    After all those months the problem persists, after a few researchs and tests, me and some friends found some possible "solutions" for the problem [HERE](http://forums.clashroyalebot.com.br/showthread.php?tid=1252), but it still not 100% effective, also there is no error in the API call, anyone had luck on that problem? – Imac May 09 '18 at 22:07
  • If the window is not visible or hidden, it's possible that is not rendered... somehow you should force that window to be rendered, or to be shown off screen. – Laurent Lequenne Mar 05 '19 at 13:31
  • 1
    See here : https://stackoverflow.com/questions/53710135/printwindow-and-bitblt-of-hidden-windows (The solution worked for me) – KiwiJaune Apr 23 '19 at 15:08
  • Have you tried releasing the `dc` _after_ the call to `Image.FromHbitmap()?` – Paul Sumpner Oct 07 '19 at 12:10
  • @KiwiJaune Thank you so much, This worked for me, too: https://stackoverflow.com/questions/53710135/printwindow-and-bitblt-of-hidden-windows – tmighty Feb 22 '21 at 03:16

3 Answers3

2

For me this is working on Windows 10 11.02.2021:

static void hflipAndSwapRandB(unsigned char* data, int w, int h, int dim)
    {
        int y = 0;
        int x = 0;
        int d;
        unsigned char swap = 0;
        int hh = h / 2;
        for (y = 0; y < hh; y++)
        {
            for (x = 0; x < w; x++)
            {
                for (d = 0; d < dim; d++)
                {
                    swap = data[y * dim * w + dim * x + d];
                    data[y * dim * w + dim * x + d] = data[(h - 1 - y) * dim * w + dim * x + dim - 1 - d];
                    data[(h - 1 - y) * dim * w + dim * x + dim - 1 - d] = swap;
                }
            }
        }
    }

static void copyScreen(unsigned char* pixels_out, int x, int y, int width, int height)
{
    HDC hScreenDC = GetDC(GetDesktopWindow());
    HDC hMemoryDC = CreateCompatibleDC(hScreenDC);

    if (width == -1 || height == -1)
    {
        width = GetDeviceCaps(hScreenDC, HORZRES);
        height = GetDeviceCaps(hScreenDC, VERTRES);
    }

    HBITMAP hBitmap = CreateCompatibleBitmap(hScreenDC, width, height);
    HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemoryDC, hBitmap);

    BitBlt(hMemoryDC, x, y, width, height, hScreenDC, x, y, SRCCOPY);
    hBitmap = (HBITMAP)SelectObject(hMemoryDC, hOldBitmap);
    BITMAPINFOHEADER   bi;

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = width-x;
    bi.biHeight = height-y;
    bi.biPlanes = 1;
    bi.biBitCount = 24;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;
    GetDIBits(hMemoryDC, hBitmap, 0, height-y, pixels_out, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
    hflipAndSwapRandB(pixels_out, width, height, 3);

    DeleteDC(hMemoryDC);
    DeleteDC(hScreenDC);
    DeleteObject(hBitmap);
}
BeatEngine
  • 123
  • 7
1

Hopefully this will solve the problem. There is a method for capturing the screen that is built in to the .net framework that may work. Not sure if it will capture DirectX content, but it may be worth a try.

Please note that this solution captures the current screen, but you will probably be able to modify it to capture only the area you are interested in.

I found this solution here: https://www.c-sharpcorner.com/UploadFile/2d2d83/how-to-capture-a-screen-using-C-Sharp/

private void CaptureMyScreen()
{
      try
      {
           //Creating a new Bitmap object
          Bitmap captureBitmap = new Bitmap(1024, 768, PixelFormat.Format32bppArgb);

         //Bitmap captureBitmap = new Bitmap(int width, int height, PixelFormat);
         //Creating a Rectangle object which will  
         //capture our Current Screen
         Rectangle captureRectangle = Screen.AllScreens[0].Bounds;

         //Creating a New Graphics Object
         Graphics captureGraphics = Graphics.FromImage(captureBitmap);

        //Copying Image from The Screen
        captureGraphics.CopyFromScreen(captureRectangle.Left,captureRectangle.Top,0,0,captureRectangle.Size);

        //Saving the Image File (I am here Saving it in My E drive).
        captureBitmap.Save(@"E:\Capture.jpg",ImageFormat.Jpeg);

        //Displaying the Successfull Result

        MessageBox.Show("Screen Captured");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}
user3308241
  • 344
  • 3
  • 10
1

I've had the same white window problem when capturing another application's window. I could capture the whole desktop though. One way around this was to capture the desktop portion occupied by the app, another solution was to elevate my program to run as administrator. Running as administrator allowed me to capture a specific app window (in my case a .Net 4.8 framework app the only change needed was requireAdministrator in app manifest requestedExecutionLevel).

        public static System.Drawing.Image CaptureWindow(IntPtr handle, bool capturedesktop = false, bool capturemouse = true)
        {
            if (IsWindowVisible(handle))
            {
                IntPtr hdcSrc = GetWindowDC(handle);
                RECT windowRect;
                GetWindowRect(handle, out windowRect);
                int width = windowRect.right - windowRect.left;
                int height = windowRect.bottom - windowRect.top;
                if (capturedesktop)
                    hdcSrc = GetWindowDC(GetDesktopWindow());
                IntPtr hdcDest = GDI32.CreateCompatibleDC(hdcSrc);
                IntPtr hBitmap = GDI32.CreateCompatibleBitmap(hdcSrc, width, height);
                IntPtr hOld = GDI32.SelectObject(hdcDest, hBitmap);
                if (capturedesktop)
                    GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, windowRect.left, windowRect.top, GDI32.TernaryRasterOperations.SRCCOPY);
                else
                    GDI32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, GDI32.TernaryRasterOperations.SRCCOPY);
                if (capturemouse)
                {
                    try
                    {
                        CURSORINFO cursorInfo = new CURSORINFO();
                        cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);
                        IntPtr hicon;

                        if (GetCursorInfo(ref cursorInfo))
                        {
                            if (cursorInfo.flags == CURSOR_SHOWING)
                            {
                                if ((hicon = CopyIcon(cursorInfo.hCursor)) != IntPtr.Zero)
                                {
                                    ICONINFO iconInfo;
                                    if (GetIconInfo(hicon, out iconInfo))
                                    {
                                        using (Bitmap maskBitmap = System.Drawing.Image.FromHbitmap(iconInfo.hbmMask))
                                        {
                                            DrawIconEx(hdcDest, cursorInfo.ptScreenPos.X - iconInfo.xHotspot - windowRect.left, cursorInfo.ptScreenPos.Y - iconInfo.yHotspot - windowRect.top, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003);
                                            GDI32.DeleteObject(iconInfo.hbmMask);
                                            DestroyIcon(hicon);
                                        }
                                    }
                                    else
                                    {
                                        DestroyIcon(hicon);
                                    }
                                }
                            }
                        }
                    } catch
                    {

                    }
                }

                GDI32.SelectObject(hdcDest, hOld);
                GDI32.DeleteDC(hdcDest);
                ReleaseDC(handle, hdcSrc);
                System.Drawing.Image img = System.Drawing.Image.FromHbitmap(hBitmap);
                GDI32.DeleteObject(hBitmap);

                return img;
            } else
            {
                return null;
            }
        }
Vitch612
  • 31
  • 1
  • 3