Search code examples
c#winapiscreen-capturebitblt

Getting the screen pixels as byte array


I need to change my screen capture code to get a pixel array instead of a Bitmap.

I change the code to this:

BitBlt > Image.FromHbitmap(pointer) > LockBits > pixel array

But, I'm checking if it's possible to cut some middle man, and have something like this:

BitBlt > Marshal.Copy > pixel array

Or even:

WinApi method that gets the screen region as a pixel array

So far, I tried to use this code, without success:

public static byte[] CaptureAsArray(Size size, int positionX, int positionY)
{
    var hDesk = GetDesktopWindow();
    var hSrce = GetWindowDC(hDesk);
    var hDest = CreateCompatibleDC(hSrce);
    var hBmp = CreateCompatibleBitmap(hSrce, (int)size.Width, (int)size.Height);
    var hOldBmp = SelectObject(hDest, hBmp);

    try
    {
        new System.Security.Permissions.UIPermission(System.Security.Permissions.UIPermissionWindow.AllWindows).Demand();

        var b = BitBlt(hDest, 0, 0, (int)size.Width, (int)size.Height, hSrce, positionX, positionY, CopyPixelOperation.SourceCopy | CopyPixelOperation.CaptureBlt);

        var length = 4 * (int)size.Width * (int)size.Height;
        var bytes = new byte[length];

        Marshal.Copy(hBmp, bytes, 0, length);

        //return b ? Image.FromHbitmap(hBmp) : null;
        return bytes;
    }
    finally
    {
        SelectObject(hDest, hOldBmp);
        DeleteObject(hBmp);
        DeleteDC(hDest);
        ReleaseDC(hDesk, hSrce);
    }

    return null;
}

This code gives me an System.AccessViolationException while stepping on Marshal.Copy.

Is there any more efficient way of getting screen pixels as a byte array while using BitBlt or similar screen capture methods?


EDIT:

As found in here and as suggested by CodyGray, I should use

var b = Native.BitBlt(_compatibleDeviceContext, 0, 0, Width, Height, _windowDeviceContext, Left, Top, Native.CopyPixelOperation.SourceCopy | Native.CopyPixelOperation.CaptureBlt);

var bi = new Native.BITMAPINFOHEADER();
bi.biSize = (uint)Marshal.SizeOf(bi);
bi.biBitCount = 32; 
bi.biClrUsed = 0;
bi.biClrImportant = 0;
bi.biCompression = 0;
bi.biHeight = Height;
bi.biWidth = Width;
bi.biPlanes = 1;

var data = new byte[4 * Width * Height];

Native.GetDIBits(_windowDeviceContext, _compatibleBitmap, 0, (uint)Height, data, ref bi, Native.DIB_Color_Mode.DIB_RGB_COLORS);

My data array has all the pixels of the screenshot. Now, I'm going to test if there's any performance improvements or not.


Solution

  • Yeah, you can't just start accessing the raw bits of a BITMAP object through an HBITMAP (as returned by CreateCompatibleBitmap). HBITMAP is just a handle, as the name suggests. It's not a pointer in the classic "C" sense that it points to the beginning of the array. Handles are like indirect pointers.

    GetDIBits is the appropriate solution to get the raw, device-independent pixel array from a bitmap that you can iterate through. But you'll still need to use the code you have to get the screen bitmap in the first place. Essentially, you want something like this. Of course, you'll need to translate it into C#, but that shouldn't be difficult, since you already know how to call WinAPI functions.

    Note that you do not need to call GetDesktopWindow or GetWindowDC. Just pass NULL as the handle to GetDC; it has the same effect of returning a screen DC, which you can then use to create a compatible bitmap. In general, you should almost never call GetDesktopWindow.