在C#中使用原生HBitmap,同时保留alpha通道/透明度

时间:2011-01-07 15:54:37

标签: c# bitmap gdi alpha-transparency hbitmap

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

Stack Overflow上有一些关于此问题的问题。使用来自这个问题的第一个答案(How to draw ARGB bitmap using GDI+?)的信息,我写了一段代码,我已经尝试过并且有效。

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

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

根据我的理解(如果我错了请纠正我),这不会将实际像素数据从原生HBitmap复制到托管位图,它只是将托管位图指向来自原生HBitmap的像素数据。 / p>

我不会在另一个图形(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);
}

3 个答案:

答案 0 :(得分:8)

即使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 :(得分:2)

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

  

来电者负责   分配和释放块   scan0指定的内存   然而,参数应该是内存   相关之前不得发布   位图已发布。

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

答案 2 :(得分:1)

在阅读了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;
    }