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

发布于 2024-10-11 01:46:45 字数 2675 浏览 0 评论 0原文

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

Stack Overflow 上有几个关于这个问题的问题。使用此问题第一个答案中的信息(如何使用 GDI+ 绘制 ARGB 位图? ),我编写了一段代码,我已经尝试过并且它有效。

它基本上使用 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);
}

Let's say I get a HBITMAP object/handle from a native Windows function. I can convert it to a managed bitmap using Bitmap.FromHbitmap(nativeHBitmap), but if the native image has transparency information (alpha channel), it is lost by this conversion.

There are a few questions on Stack Overflow regarding this issue. Using information from the first answer of this question (How to draw ARGB bitmap using GDI+?), I wrote a piece of code that I've tried and it works.

It basically gets the native HBitmap width, height and the pointer to the location of the pixel data using GetObject and the BITMAP structure, and then calls the managed Bitmap constructor:

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

As I understand (please correct me if I'm wrong), this does not copy the actual pixel data from the native HBitmap to the managed bitmap, it simply points the managed bitmap to the pixel data from the native HBitmap.

And I don't draw the bitmap here on another Graphics (DC) or on another bitmap, to avoid unnecessary memory copying, especially for large bitmaps.

I can simply assign this bitmap to a PictureBox control or the the Form BackgroundImage property. And it works, the bitmap is displayed correctly, using transparency.

When I no longer use the bitmap, I make sure the BackgroundImage property is no longer pointing to the bitmap, and I dispose both the managed bitmap and the native HBitmap.

The Question: Can you tell me if this reasoning and code seems correct. I hope I will not get some unexpected behaviors or errors. And I hope I'm freeing all the memory and objects correctly.

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

倾`听者〃 2024-10-18 01:46:45

即使 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;
    }

The following code worked for me even if the HBITMAP is an icon or bmp, it doesn't flip the image when it's an icon, and also works with bitmaps that don't contain Alpha channel:

    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;
    }
霞映澄塘 2024-10-18 01:46:45

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

调用者负责
分配和释放块
scan0指定的内存
参数,但是,内存应该
待相关事宜完成后才可发布
位图已发布。

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

Right, no copy is made. Which is why the Remarks section of the MSDN Library says:

The caller is responsible for
allocating and freeing the block of
memory specified by the scan0
parameter, however, the memory should
not be released until the related
Bitmap is released.

This wouldn't be a problem if the pixel data was copied. Incidentally, this is normally a difficult problem to deal with. You can't tell when the client code called Dispose(), there's no way to intercept that call. Which makes it impossible to make such a bitmap behave like a replacement for Bitmap. The client code has to be aware that additional work is needed.

辞取 2024-10-18 01:46:45

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

After reading the good points made by Hans Passant in his answer, I changed the method to immediately copy the pixel data into the managed bitmap, and free the native bitmap.

I'm creating two managed bitmap objects (but only one allocates memory for the actual pixel data), and use graphics.DrawImage to copy the image. Is there a better way to accomplish this? Or is this good/fast enough?

    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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文