-2

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.

Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • You use `hBmp` (essentially `Bitmap.GetHBitmap`) as if it was `BitmapData.Scan0`. These pointers are different. What was wrong with `BitmapData` anyway? If you want to use some managed wrapper for GDI+ that's your best choice. It ends up in the same extern calls you would use anyway. – György Kőszeg Oct 13 '19 at 19:51
  • Indeed, they are different pointers. I'm just trying to know if reduce the capture lag. – Nicke Manarin Oct 13 '19 at 19:54
  • You wouldn't gain much as `LockBits` is a simple call to `GdipBitmapLockBits` in gdiplus.dll, which fills up a `BitmapData`. You would just do the same. The most performance is lost at `Marshal.Copy`, imho. – György Kőszeg Oct 13 '19 at 20:02
  • 1
    You could study the code of a good screen capture application: https://github.com/ShareX/ShareX – aybe Oct 13 '19 at 20:02
  • @GyörgyKőszeg I found the solution that I wanted in another website. They basically use `GetDIBits` to get the array. I'm going to test it. – Nicke Manarin Oct 13 '19 at 22:04
  • I'm not sure why my comment got deleted, but your comment was not useful, @Aybe since they use the same code as I do. You didn't read the question. – Nicke Manarin Oct 14 '19 at 12:13

1 Answers1

1

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.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • Yes, I just found this: https://stackoverflow.com/a/16115730/1735672 a few minutes ago and converted to C#. It works ;) – Nicke Manarin Oct 13 '19 at 22:58