DrawIconEx 和 GetDIBits 的奇怪行为,alpha 为零

Strange behavior with DrawIconEx and GetDIBits, with alpha as zero

我正在使用此代码捕获屏幕 + 光标:

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);

由于某些原因,当光标是工字梁(文本光标)时,它应该位于的图像区域是全透明的。 RGB 信息在那里,但 alpha 位是 0
(右图是所有像素 alpha 位手动设置为 255 后帧的样子)。

如果光标是箭头,则正常。

但是如果我从句柄中获取 Bitmap 然后保存到磁盘,图像没有透明孔,正如预期的那样。

这是怎么回事? 是 DrawIconEx 还是 FromHbitmap?

也许 FromHbitmap 总是将所有像素的 alpha 设置为 255? 也许 GetDiBits 以不同方式合并两个图像(屏幕截图 + 光标)?

编辑

作为快速修复,我知道我可以检测光标是否为 I-Beam(蒙版单色类型)并在保存像素阵列时手动修复它。

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

并且:

if (frame.RemoveAnyTransparency)
    for (var i = 3; i < _byteLength; i += 4)
        frame.Data[i] = 255;

嗯,我怀疑这是一个特例。因为查了很多资料都没有找到类似的案例。

但是有两种很好的方法来处理这种情况。

  • 通过设置 biBitCount = 24
  • 避免 alpha 通道
  • 将游标数据复制到 DIBSection 的位图数据中,使用 要混合的 alpha 通道字节(alpha = 255)。

    创建一个设备上下文并将 select DIBSection 放入其中。

    使用BitBlt()从新设备上下文复制到绘制设备上下文。

    参考:How to draw 32-bit alpha channel bitmaps?