Search code examples
c#windowsbitmapdirect3dbitblt

BitBlt and Direct3D for Screenshots C#


I'm working on a program to capture screenshots of windows from a window handle. I start by taking the screen shot using BitBlt, and if that fails when the window is using accelerated graphics, I switch to using Direct3D.

For reference, here's the BitBlt code:

private Bitmap BitBltWindow(Rectangle rect, IntPtr window)
{
    const uint srcCopy = 0x00CC0020;

    IntPtr hWndDc = GetDC(window);
    IntPtr hMemDc = CreateCompatibleDC(hWndDc);
    IntPtr hBitmap = CreateCompatibleBitmap(hWndDc, rect.Width, rect.Height);
    SelectObject(hMemDc, hBitmap);

    BitBlt(hMemDc, 0, 0, rect.Width, rect.Height, hWndDc, 0, 0, srcCopy);
    Bitmap bitmap = Bitmap.FromHbitmap(hBitmap);

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

    return bitmap;
}

And here is my method to detect if it is empty:

private bool IsBitmapBlack(Bitmap bmp)
{
    Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
    BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);

    IntPtr ptr = bmpData.Scan0;
    int rowSize = bmpData.Stride < 0 ? -bmpData.Stride : bmpData.Stride;
    
    // Scanning for non-zero bytes
    bool allBlack = true;
    for (int y = 0; y < bmp.Height; y++)
    {
        byte[] pixels = new byte[rowSize];
        Marshal.Copy(IntPtr.Add(ptr, y * bmpData.Stride), pixels, 0, rowSize);
        
        if(pixels.Max() > 0)
        {
            allBlack = false;
            break;
        }
    }
    bmp.UnlockBits(bmpData);

    return allBlack;
}

My question is around how I'm detecting whether BitBlt failed or not. In windows like Chrome, the call to BitBlt just returns a blank bitmap, this is easy to detect and I switch to Direct3D. But in windows like Visual Studio 2022, it seems to use a mixture of graphics, so the bitmap returned is non-blank but has bits missing like so:

enter image description here

Whereas the same recording from Direct3D surface looks like this: enter image description here

Does anyone know of a Windows API call I can use to detect if the window uses any amount of accelerated graphics?


Solution

  • After posting the question I decided to pursue looking at the process to see if I could detect if the loaded modules included any known as Direct3D. I wrote this off as a bad experiment, getting the list of modules was easy enough and it worked on some windows, but it didn't work on all windows I tried this on. e.g. Chrome.

    I've settled on adjusting my IsBitmapBlack method into a method which detects if the window is part black. This gets around the issue of Visual Studio which has bits missing.

    The new method now looks like this:

    private bool IsBitmapPartBlack(Bitmap bmp)
    {
        int height = bmp.Height;
        int width = bmp.Width;
        Rectangle rect = new Rectangle(0, 0, bmp.Width, height);
        BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
    
        IntPtr ptr = bmpData.Scan0;
        int rowSize = bmpData.Stride < 0 ? -bmpData.Stride : bmpData.Stride;
            
        // Scanning for non-zero bytes
        bool partBlack = false;
        int blackRowCount = 0;
        for (int y = 0; y < height; y++)
        {
            byte[] pixels = new byte[rowSize];
            Marshal.Copy(IntPtr.Add(ptr, y * bmpData.Stride), pixels, 0, rowSize);
    
            if (pixels.FirstOrDefault(pixel => pixel > 0) == 0)
            { 
                blackRowCount++;
                if (blackRowCount >= height * .01)  // If 1% of the bitmap rows are black then treat the image as part black
                {
                    partBlack = true;
                    break;
                }
            }
        }
        bmp.UnlockBits(bmpData);
    
        return partBlack;
    }