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:
Whereas the same recording from Direct3D surface looks like this:
Does anyone know of a Windows API call I can use to detect if the window uses any amount of accelerated graphics?
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;
}