Question

Disons que j'obtiens un objet/handle HBITMAP à partir d'une fonction Windows native.Je peux le convertir en bitmap géré en utilisant 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 Stack Overflow concernant ce problème.En utilisant les informations de la première réponse à cette question (Comment dessiner un bitmap ARGB en utilisant GDI+ ?), j'ai écrit un morceau de code que j'ai essayé et qui fonctionne.

Il obtient essentiellement la largeur, la hauteur et le pointeur natif du HBitmap ainsi que l'emplacement des données de pixels en utilisant ObtenirObjet et le BITMAP structure, 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 (veuillez me corriger si je me trompe), cela ne copie pas les données de pixels réelles du HBitmap natif vers le bitmap géré, cela pointe simplement le bitmap géré vers les données de pixels du HBitmap natif.

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

Je peux simplement attribuer ce bitmap à un contrôle PictureBox ou à la propriété Form BackgroundImage.Et ça marche, le bitmap s'affiche correctement, en utilisant la transparence.

Lorsque je n'utilise plus le bitmap, je m'assure que la propriété BackgroundImage ne pointe plus vers 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 que je n'aurai pas de comportements ou d'erreurs inattendus.Et j'espère que je libère correctement toute la mémoire et les objets.

    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);
}
Était-ce utile?

La solution

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

L'appelant est responsable de l'allocation et de la libération du bloc de mémoire spécifié par le paramètre Scan0, mais la mémoire ne doit pas être libérée avant la sortie du bitmap associé.

Cela ne poserait pas de problème si les données de pixels étaient copiées.Il s’agit d’ailleurs généralement d’un problème difficile à résoudre.Vous ne pouvez pas savoir quand le code client a appelé Dispose(), il n'y a aucun moyen d'intercepter cet appel.Ce qui rend impossible qu'un tel bitmap se comporte comme un remplacement du Bitmap.Le code client doit être conscient que du travail supplémentaire est nécessaire.

Autres conseils

Le code suivant a fonctionné pour moi, même si l'HBITMAP est une icône ou bmp, il ne permute pas l'image quand il est une icône, et travaille également avec des bitmaps 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 bons points de Hans dans sa réponse Passant, j'ai changé la méthode de copier immédiatement les données de pixels dans le bitmap géré, et libérer le bitmap natif.

Je crée deux objets bitmap gérés (mais une seule alloue la mémoire de données de pixel réel), et l'utilisation Graphics.DrawImage pour copier l'image. Y at-il une meilleure façon d'y arriver? Ou est-ce bon / assez vite?

    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;
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top