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

假设我从本机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); } 

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

调用者负责分配和释放scan0参数指定的内存块,但是,在释放相关的Bitmap之前,不应释放内存。

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

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

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