在 C# 中使用本机 HBitmap,同时保留 Alpha 通道/透明度

Posted

技术标签:

【中文标题】在 C# 中使用本机 HBitmap,同时保留 Alpha 通道/透明度【英文标题】:Use native HBitmap in C# while preserving alpha channel/transparency 【发布时间】:2011-06-05 08:53:45 【问题描述】:

假设我从本机 Windows 函数中获取了 HBITMAP 对象/句柄。我可以使用 Bitmap.FromHbitmap(nativeHBitmap) 将其转换为托管位图,但如果本机图像具有透明度信息(alpha 通道),则此转换会丢失它。

关于这个问题,Stack Overflow 上有几个问题。使用这个问题的第一个答案 (How to draw ARGB bitmap using GDI+?) 中的信息,我编写了一段我尝试过的代码,它可以工作。

它基本上是通过GetObjectBITMAP结构获取原生的HBitmap宽高和指向像素数据位置的指针,然后调用托管的Bitmap构造函数:

Bitmap managedBitmap = new Bitmap(bitmapStruct.bmWidth, bitmapStruct.bmHeight,
    bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);

据我了解(如果我错了,请纠正我),这不会将实际像素数据从本机 HBitmap 复制到托管位图,它只是将托管位图指向本机 HBitmap 的像素数据。

而且我不会在另一个图形 (DC) 或另一个位图上绘制这里的位图,以避免不必要的内存复制,尤其是对于大型位图。

我可以简单地将这个位图分配给 PictureBox 控件或 Form BackgroundImage 属性。它工作正常,位图正确显示,使用透明度。

当我不再使用位图时,我确保 BackgroundImage 属性不再指向该位图,并且我同时处置托管位图和本机 HBitmap。

问题:你能告诉我这个推理和代码是否正确。我希望我不会得到一些意想不到的行为或错误。我希望我能正确释放所有内存和对象。

    private void Example()
    
        IntPtr nativeHBitmap = IntPtr.Zero;

        /* Get the native HBitmap object from a Windows function here */

        // Create the BITMAP structure and get info from our nativeHBitmap
        NativeMethods.BITMAP bitmapStruct = new NativeMethods.BITMAP();
        NativeMethods.GetObjectBitmap(nativeHBitmap, Marshal.SizeOf(bitmapStruct), ref bitmapStruct);

        // Create the managed bitmap using the pointer to the pixel data of the native HBitmap
        Bitmap managedBitmap = new Bitmap(
            bitmapStruct.bmWidth, bitmapStruct.bmHeight, bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);

        // Show the bitmap
        this.BackgroundImage = managedBitmap;

        /* Run the program, use the image */
        MessageBox.Show("running...");

        // When the image is no longer needed, dispose both the managed Bitmap object and the native HBitmap
        this.BackgroundImage = null;
        managedBitmap.Dispose();
        NativeMethods.DeleteObject(nativeHBitmap);
    

internal static class NativeMethods

    [StructLayout(LayoutKind.Sequential)]
    public struct BITMAP
    
        public int bmType;
        public int bmWidth;
        public int bmHeight;
        public int bmWidthBytes;
        public ushort bmPlanes;
        public ushort bmBitsPixel;
        public IntPtr bmBits;
    

    [DllImport("gdi32", CharSet = CharSet.Auto, EntryPoint = "GetObject")]
    public static extern int GetObjectBitmap(IntPtr hObject, int nCount, ref BITMAP lpObject);

    [DllImport("gdi32.dll")]
    internal static extern bool DeleteObject(IntPtr hObject);

【问题讨论】:

诸如“请检查此代码,它可以在我的计算机上运行……”之类的内容确实不属于问题或主题标题。 你说得对,我改了标题。这是一个问题,但其中也有代码。 【参考方案1】:

即使HBITMAP 是图标或bmp,以下代码也对我有用,当它是图标时它不会翻转图像,并且也适用于不包含Alpha 通道的位图:

    private static Bitmap GetBitmapFromHBitmap(IntPtr nativeHBitmap)
    
        Bitmap bmp = Bitmap.FromHbitmap(nativeHBitmap);

        if (Bitmap.GetPixelFormatSize(bmp.PixelFormat) < 32)
            return bmp;

        BitmapData bmpData;

        if (IsAlphaBitmap(bmp, out bmpData))
            return GetlAlphaBitmapFromBitmapData(bmpData);

        return bmp;
    

    private static Bitmap GetlAlphaBitmapFromBitmapData(BitmapData bmpData)
    
        return new Bitmap(
                bmpData.Width,
                bmpData.Height,
                bmpData.Stride,
                PixelFormat.Format32bppArgb,
                bmpData.Scan0);
    

    private static bool IsAlphaBitmap(Bitmap bmp, out BitmapData bmpData)
    
        Rectangle bmBounds = new Rectangle(0, 0, bmp.Width, bmp.Height);

        bmpData = bmp.LockBits(bmBounds, ImageLockMode.ReadOnly, bmp.PixelFormat);

        try
        
            for (int y = 0; y <= bmpData.Height - 1; y++)
            
                for (int x = 0; x <= bmpData.Width - 1; x++)
                
                    Color pixelColor = Color.FromArgb(
                        Marshal.ReadInt32(bmpData.Scan0, (bmpData.Stride * y) + (4 * x)));

                    if (pixelColor.A > 0 & pixelColor.A < 255)
                    
                        return true;
                    
                
            
        
        finally
        
            bmp.UnlockBits(bmpData);
        

        return false;
    

【讨论】:

+1 以获得很棒的答案!这个解决方案非常适合,谢谢。 @DanielPeñalba 你能告诉我你会如何调用GetBitmapFromHBitmap吗?我假设我不能这样做 GetBitmapFromHBitmap(New Bitmap("fileName").GetHbitmap()) 因为那会破坏整个目的,对吗?那么从资源管理器中的文件中,我会得到一个保留了 alpha 通道的托管 Bitmap 对象吗? @test:此代码旨在调用具有(本机)图像句柄。不要将它用于托管位图。 用有用的可重用方法的优秀答案。它为我提供了具有适当透明度的文件夹图标(调用 IShellItemImageFactory.GetImage() )。在此之前尝试过“Bitmap.MakeTransparent()”,但不是一个好主意。【参考方案2】:

是的,没有复制。这就是为什么 MSDN 库的备注部分说:

调用者负责 分配和释放块 scan0 指定的内存 参数,但是,内存应该 直到相关的才被释放 位图已发布。

如果复制了像素数据,这将不是问题。顺便说一句,这通常是一个难以处理的问题。您无法判断客户端代码何时调用 Dispose(),无法拦截该调用。这使得不可能使这样的位图表现得像位图的替代品。客户端代码必须意识到需要额外的工作。

【讨论】:

感谢您的好评。我制作了下面列出的方法的另一个版本。你能看看吗?谢谢 嗯,没关系。我以为你不想复制。 一个问题是,如果本机 HBitmap 是一个图标(例如由 IShellItemImageFactory::GetImage 和 SIGBF_ICONONLY 返回),则位图会反转(上下颠倒)。所以一个简单的 graphics.DrawImage 在这种情况下是行不通的。 听起来像一个错误。你可以使用 Bitmap.RotateFlip() 来修复它。 是的,我确实使用了 Bitmap.RotateFlip() 并且它适用于图标,但 IShellItemImageFactory::GetImage 可以正确获取缩略图。那么如何检查返回的 HBITMAP 是否反转(是否为图标)?【参考方案3】:

在阅读了 Hans Passant 在他的回答中提出的优点后,我改变了方法,立即将像素数据复制到托管位图中,并释放原生位图。

我正在创建两个托管位图对象(但只有一个为实际像素数据分配内存),并使用 graphics.DrawImage 来复制图像。有没有更好的方法来实现这一点?或者这是否足够好/足够快?

    public static Bitmap CopyHBitmapToBitmap(IntPtr nativeHBitmap)
    
        // Get width, height and the address of the pixel data for the native HBitmap
        NativeMethods.BITMAP bitmapStruct = new NativeMethods.BITMAP();
        NativeMethods.GetObjectBitmap(nativeHBitmap, Marshal.SizeOf(bitmapStruct), ref bitmapStruct);

        // Create a managed bitmap that has its pixel data pointing to the pixel data of the native HBitmap
        // No memory is allocated for its pixel data
        Bitmap managedBitmapPointer = new Bitmap(
            bitmapStruct.bmWidth, bitmapStruct.bmHeight, bitmapStruct.bmWidth * 4, PixelFormat.Format32bppArgb, bitmapStruct.bmBits);

        // Create a managed bitmap and allocate memory for pixel data
        Bitmap managedBitmapReal = new Bitmap(bitmapStruct.bmWidth, bitmapStruct.bmHeight, PixelFormat.Format32bppArgb);

        // Copy the pixels of the native HBitmap into the canvas of the managed bitmap
        Graphics graphics = Graphics.FromImage(managedBitmapReal);
        graphics.DrawImage(managedBitmapPointer, 0, 0);

        // Delete the native HBitmap object and free memory
        NativeMethods.DeleteObject(nativeHBitmap);

        // Return the managed bitmap, clone of the native HBitmap, with correct transparency
        return managedBitmapReal;
    

【讨论】:

很好的答案,我已经使用了您在此答案中提供的代码并且它可以工作,但有时图像会翻转 180º 并旋转 180º。你知道为什么吗?提前致谢。 您应该在处理完 Graphics 对象后对其进行处理。最好将其包装在 using 语句中。

以上是关于在 C# 中使用本机 HBitmap,同时保留 Alpha 通道/透明度的主要内容,如果未能解决你的问题,请参考以下文章

将图像结构从本机代码导出到托管

从 hIcon/hBitmap 获取 bytes/char*

从 Win32 项目中的 *.bmp 文件加载 HBITMAP

将 HBITMAP 句柄从非托管代码传递到托管代码以创建 System.Drawing.Bitmap 的安全性

如何在 c# 中分配后保留有关对象的信息?

用C++在Win32中用LoadImage()绘制HBITMAP的二维数组