1

I'm using this code to capture the screen + cursor:

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

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

if (!success)
    return FrameCount;

try
{
    var cursorInfo = new Native.CursorInfo();
    cursorInfo.cbSize = Marshal.SizeOf(cursorInfo);

    if (Native.GetCursorInfo(out cursorInfo))
    {
        if (cursorInfo.flags == Native.CursorShowing)
        {
            var hicon = Native.CopyIcon(cursorInfo.hCursor);

            if (hicon != IntPtr.Zero)
            {
                if (Native.GetIconInfo(hicon, out var iconInfo))
                {
                    frame.CursorX = cursorInfo.ptScreenPos.X - Left;
                    frame.CursorY = cursorInfo.ptScreenPos.Y - Top;

                    //if (frame.CursorX > 0 && frame.CursorY > 0)
                    Native.DrawIconEx(_compatibleDeviceContext, frame.CursorX - iconInfo.xHotspot, frame.CursorY - iconInfo.yHotspot, cursorInfo.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003);
                 }

                 Native.DeleteObject(iconInfo.hbmColor);
                 Native.DeleteObject(iconInfo.hbmMask);
             }

             Native.DestroyIcon(hicon);
        }

        Native.DeleteObject(cursorInfo.hCursor);
    }
}
catch (Exception)
{
    //LogWriter.Log(e, "Impossible to get the cursor");
}

frame.DataLength = _byteLength;
frame.Data = new byte[_byteLength];

//If saving to disk as bitmap, it works.
//System.Drawing.Image.FromHbitmap(_compatibleBitmap).Save(frame.Path);

//If getting the image as pixel array, the square where the cursor is located is all transparent.
Native.GetDIBits(_windowDeviceContext, _compatibleBitmap, 0, (uint)Height, frame.Data, ref _infoHeader, Native.DibColorMode.DibRgbColors);

For some reason, when the cursor is the I-beam (text cursor), the image region where it's supposed to be located is all transparent. The RGB info is there, but the alpha bit are 0. Wrong Right
(The right image is what the frame will look like after all pixel alpha bits are set to 255, manually).

If the cursor is the arrow, it works normally.

But if I get the Bitmap from the handle and then save to disk, the image has no transparent hole, as expected.

What's going on? Is it something with DrawIconEx or with FromHbitmap?

Maybe FromHbitmap always sets the alpha to 255 for all pixels? Maybe GetDiBits merges the two images (screenshot + cursor) differently?

Edit

As a quick fix, I understand that I can detect if the cursor is an I-Beam (masked monochrome type) and manually fix it while saving the pixel array.

//Set to fix all alpha bits back to 255.
frame.RemoveAnyTransparency = iconInfo.hbmMask != IntPtr.Zero; 

And:

if (frame.RemoveAnyTransparency)
    for (var i = 3; i < _byteLength; i += 4)
        frame.Data[i] = 255;
Nicke Manarin
  • 3,026
  • 4
  • 37
  • 79
  • That probably has something to do with this code using GDI functions. It is restricted to 24bpp. Replaced by GDI+, exposed in .NET through System.Drawing, you'd favor Graphics.CopyFromScreen() and Cursor.Draw(). – Hans Passant Nov 03 '19 at 03:05
  • `Graphics.CopyFromScreen()` uses the same `BitBlt()`, I believe. – Nicke Manarin Nov 03 '19 at 06:07
  • I hope you don't mind. I am not familiar with C# code, then I used [another piece of code](https://stackoverflow.com/a/48925443/11128312) to test, the functions called include `DrawIconEx` and `GetDIBits`, but I didn't find the white hole you said.Here is [snapshot](https://drive.google.com/file/d/1UWjm0-PAmPV1vYnkNbTh8O9mE7PVvsZ8/view?usp=sharing). If you can provide some details so that I can go further research, I will appreciate it. – Strive Sun Nov 06 '19 at 06:44
  • @StriveSun-MSFT Hi, the code that you referenced does not include a call to `GetDIBits` after the `DrawIconEx`, unless you added it later. – Nicke Manarin Nov 06 '19 at 13:35
  • @StriveSun-MSFT I can see that the code passes the size to the `DrawIconEx`, I passed 0. Try using 0 too. – Nicke Manarin Nov 06 '19 at 13:39
  • Sorry, I added `GetDIBits` later, if you are interested in seeing the complete code, please download [Source code](https://drive.google.com/file/d/1-TVtVcn1hhHS9XgfHX9eRCTjYTRtfKhD/view?usp=sharing). I had set `cxWidth = 0, cyWidth = 0`, but I still can't reproduce your issue.In addition, I used `writefile` to save the bitmap as a picture file to view. I don't know if it has an impact on the issue. – Strive Sun Nov 07 '19 at 01:57
  • Ahhh, I set 32 instead of 24 while creating the `BitmapInfoHeader` struct. That's it. Thanks. You can set as answer if you'd like to. But I don't understand why it only happens with the I-Beam cursor, not with the arrow. – Nicke Manarin Nov 07 '19 at 17:02

1 Answers1

1

Well, I suspect this is a special case. Beacuse I checked a lot of information and didn't find similar cases.

But there are two good ways to deal with this situation.

  • Avoid alpha channel by setting biBitCount = 24
  • Copy the cursor data into the DIBSection's bitmap data, using the alpha channel byte to blend(alpha = 255).

    Create a device context and select the DIBSection into it.

    Use BitBlt() to copy from the new device context to the paint device context.

    Refer: How to draw 32-bit alpha channel bitmaps?

Strive Sun
  • 5,988
  • 1
  • 9
  • 26