Search code examples
c#winapigetdibits

Strange behavior with DrawIconEx and GetDIBits, with alpha as zero


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;

Solution

  • 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?