Utiliser HBitmap natif en C # tout en préservant le canal alpha / la transparence

Supposons que je reçois un object / descripteur HBITMAP à partir d’une fonction Windows native. Je peux le convertir en bitmap géré à l’aide de Bitmap.FromHbitmap (nativeHBitmap) , mais si l’image native contient des informations de transparence (canal alpha), elle est perdue par cette conversion.

Il y a quelques questions sur le dépassement de stack concernant ce problème. En utilisant les informations de la première réponse de cette question ( Comment dessiner un bitmap ARGB avec GDI +? ), J’ai écrit un morceau de code que j’ai essayé et qui marche.

Il obtient essentiellement la largeur, la hauteur et le pointeur natifs de HBitmap sur l’emplacement des données de pixel à l’aide de GetObject et de la structure BITMAP , puis appelle le constructeur Bitmap géré:

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

Si je comprends bien (corrigez-moi si je me trompe), cela ne copie pas les données de pixel réelles de la carte HBit native sur la bitmap gérée, il pointe simplement la carte gérée sur les données de pixel de la carte HBit native.

Et je ne dessine pas le bitmap ici sur un autre Graphics (DC) ou sur un autre bitmap, pour éviter une copie inutile en mémoire, en particulier pour les gros bitmaps.

Je peux simplement assigner ce bitmap à un contrôle PictureBox ou à la propriété Form BackgroundImage. Et cela fonctionne, le bitmap est affiché correctement, en utilisant la transparence.

Lorsque je n’utilise plus le bitmap, je m’assure que la propriété BackgroundImage ne pointe plus sur le bitmap et je dispose à la fois du bitmap géré et du HBitmap natif.

La question: Pouvez-vous me dire si ce raisonnement et ce code semblent corrects? J’espère ne pas avoir certains comportements ou erreurs inattendus. Et j’espère libérer toute la mémoire et les objects correctement.

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

    Oui, aucune copie n’est faite. C’est pourquoi la section Remarques de la bibliothèque MSDN indique:

    L’appelant est responsable de l’affectation et de la libération du bloc de mémoire spécifié par le paramètre scan0. Toutefois, la mémoire ne doit pas être libérée tant que le bitmap correspondant n’est pas libéré.

    Cela ne poserait pas de problème si les données de pixels étaient copiées. Incidemment, il s’agit normalement d’un problème difficile à traiter. Vous ne pouvez pas savoir quand le code client appelé Dispose (), il n’y a aucun moyen d’intercepter cet appel. Ce qui rend impossible de faire en sorte qu’un tel bitmap se comporte comme un remplaçant de Bitmap. Le code client doit être conscient que du travail supplémentaire est nécessaire.

    Le code suivant a fonctionné pour moi même si HBITMAP est une icône ou un bmp, il ne renverse pas l’image lorsqu’il s’agit d’une icône et fonctionne également avec les images bitmap qui ne contiennent pas de canal 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; } 

    Après avoir lu les points positifs soulevés par Hans Passant dans sa réponse, j’ai modifié la méthode pour copier immédiatement les données de pixels dans le bitmap géré et libérer le bitmap natif.

    Je crée deux objects bitmap gérés (mais un seul alloue de la mémoire pour les données de pixels réelles) et utilise graphics.DrawImage pour copier l’image. Y a-t-il une meilleure façon d’accomplir cela? Ou est-ce bien / assez rapide?

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