39 votes

Créer un bitmap à partir d'un tableau d'octets de données de pixels

Cette question est au sujet de comment lire/écrire, de répartir et de gérer les données de pixels d'une image Bitmap.

Voici un exemple de la façon d'allouer un tableau d'octets (gestion de la mémoire) pour les données de pixel et la création d'un Bitmap de l'utiliser:

Size size = new Size(800, 600);
PixelFormat pxFormat = PixelFormat.Format8bppIndexed;
//Get the stride, in this case it will have the same length of the width.
//Because the image Pixel format is 1 Byte/pixel.
//Usually stride = "ByterPerPixel"*Width

//Mais il n'est pas toujours vrai. Plus d'info à bobpowell.

int stride = GetStride(size.Width, pxFormat);
byte[] data = new byte[stride * size.Height];
GCHandle handle = GCHandle.Alloc(data, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(size.Width, size.Height, stride,
             pxFormat, handle.AddrOfPinnedObject());

//After doing your stuff, free the Bitmap and unpin the array.
bmp.Dispose();
handle.Free();

public static int GetStride(int width, PixelFormat pxFormat)
{
    //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format);
    int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
    //Number of bits used to store the image data per line (only the valid data)
    int validBitsPerLine = width * bitsPerPixel;
    //4 bytes for every int32 (32 bits)
    int stride = ((validBitsPerLine + 31) / 32) * 4;
    return stride;
}

Je pensais que l'image serait de faire une copie du tableau de données, mais il en fait des points pour les mêmes données. A été vous pouvez le voir:

Color c;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color before: " + c.ToString());
//Prints: Color before: Color [A=255, R=0, G=0, B=0]
data[0] = 255;
c = bmp.GetPixel(0, 0);
Console.WriteLine("Color after: " + c.ToString());
//Prints: Color after: Color [A=255, R=255, G=255, B=255]

Questions:

  1. Est-il sécuritaire de faire créer une image à partir d'un byte[] array (gestion de la mémoire) et free() le GCHandle? Si il n'est pas sûr, les Malades ont besoin de garder un épinglé tableau, comment le mauvais est que pour GC/Performance?

  2. Est-il sécuritaire de modifier les données (ex: data[0] = 255;)?

  3. L'adresse d'un Scan0 peut être modifié par le GC? Je veux dire, j'ai l'Scan0 d'un blocage bitmap, puis déverrouiller et après un certain temps de le verrouiller à nouveau, le Scan0 peut être différent?

  4. Quel est le but de ImageLockMode.UserInputBuffer dans la méthode LockBits? Il est très difficile de trouver des infos à ce sujet! MSDN ne pas expliquer clairement!

EDIT 1: Certains de suivi

  1. Vous avez besoin de garder épinglé. Il va ralentir la GC? Je lui ai demandé ici. Il dépend du nombre d'images et de ses dimensions. Personne ne l'ont m'a donné une réponse quantitative. Il paraît que c'est difficile à déterminer. Vous pouvez également affecter la mémoire à l'aide de Maréchal ou de l'utilisation de la mémoire non managée alloués par l'image Bitmap.

  2. J'ai fait beaucoup de test à l'aide de deux fils. Tant que le Bitmap est verrouillé, il est ok. Si l'image est débloquer, qu'il n'est pas sûr! Mon poste sur lire/écrire directement à Scan0. Boing la réponse "je l'ai déjà expliqué plus haut pourquoi vous avez de la chance d'être en mesure d'utiliser scan0 à l'extérieur de la serrure. Parce que vous utilisez l'original bmp PixelFormat et que le RIB est optimisé dans ce cas, pour vous donner le pointeur et non une copie. Ce pointeur est valide jusqu'à l'OS va décider de le libérer. Le seul moment où il y a une garantie est entre LockBits et UnLockBits. Période."

  3. Oui, ça peut arriver, mais les grandes régions de la mémoire sont traités différemment par le GC, il se déplace/libère de ce grand objet, moins fréquemment. Ainsi, il peut prendre un certain temps pour GC déplacer ce tableau. À partir de MSDN: "une allocation égale ou supérieure à 85,000 bytes va sur l' large object heap (LOH)" ... "LOH ne sont collectées pendant une collection de génération 2". .NET 4.5 a des Améliorations à LOH.

  4. Cette question ont été répondues par @Boing. Mais je vais l'admettre. Je n'ai pas la comprendre pleinement. Donc, si Boing ou quelqu'un d'autre pourrait - please clarify it, je serais heureux. Par ailleurs, Pourquoi je ne peux pas directement en lecture/écriture à Sca0 sans verrouillage? Non, vous ne pouvez pas. Scan0 points à une copie des données de l'image faite par la mémoire non managée (à l'intérieur de GDI). Après le déblocage, cette mémoire peut être de réaffecter à d'autres trucs, ses garantis pas plus que Scan0 le réel des données Bitmap. Ce peut être reproduite à l'obtention de la Scan0 dans un de verrouiller, déverrouiller, et faire un peu tourner-flit dans la déverrouillé bitmap. Après un certain temps, Scan0 sera point à une défaillance de la région, et vous aurez une exception lorsque vous tentez de lire/écrire à son emplacement de mémoire.

26voto

Peter Wishart Points 2307
  1. Son coffre-fort si vous le maréchal.copie de données plutôt que de fixer des scan0 (directement ou par l'intermédiaire de la surcharge de BitMap()). Vous ne voulez pas garder les objets gérés épinglé, cela va limiter le garbage collector.
  2. Si vous copiez, parfaitement sûr.
  3. Le tableau d'entrée est géré et peut être déplacé par le GC, scan0 est un pointeur non géré qui permettrait de sortir de la date si le tableau de déplacés. L'objet Bitmap lui-même est géré mais définit l'scan0 pointeur dans Windows via une poignée.
  4. ImageLockMode.UserInputBuffer est? Apparemment, il peut être transmis à LockBits, peut-être dit Bitmap() pour copier le tableau d'entrée de données.

Exemple de code pour créer un bitmap en niveaux de gris de la matrice:

    var b = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);

    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;

    var BoundsRect = new Rectangle(0, 0, Width, Height);
    BitmapData bmpData = b.LockBits(BoundsRect,
                                    ImageLockMode.WriteOnly,
                                    b.PixelFormat);

    IntPtr ptr = bmpData.Scan0;

    int bytes = bmpData.Stride*b.Height;
    var rgbValues = new byte[bytes];

    // fill in rgbValues, e.g. with a for loop over an input array

    Marshal.Copy(rgbValues, 0, ptr, bytes);
    b.UnlockBits(bmpData);
    return b;

12voto

Boing Points 92

Concernant votre question 4: L' ImageLockMode.UserInputBuffer peut vous donner le contrôle de l'allocation des processus de ces énormes quantité de mémoire qui peut être référencé en BitmapData objet. Si vous choisissez de créer vous-même l' BitmapData objet que vous pouvez éviter une Marshall.Copy. Vous devrez ensuite utiliser cette option en combinaison avec un autre ImageLockMode. Méfiez-vous que c'est un domaine compliqué, surtout concernant la Foulée et PixelFormat. Voici un exemple qui serait d'un seul coup le contenu de 24bbp tampon sur une image BitMap et puis dans un autre tir de le lire dans un autre buffer dans 48bbp.

Size size = Image.Size;
Bitmap bitmap = Image;
// myPrewrittenBuff is allocated just like myReadingBuffer below (skipped for space sake)
// But with two differences: the buff would be byte [] (not ushort[]) and the Stride == 3 * size.Width (not 6 * ...) because we build a 24bpp not 48bpp
BitmapData writerBuff= bm.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb, myPrewrittenBuff);
// note here writerBuff and myPrewrittenBuff are the same reference
bitmap.UnlockBits(writerBuff);
// done. bitmap updated , no marshal needed to copy myPrewrittenBuff 

// Now lets read back the bitmap into another format...
BitmapData myReadingBuffer = new BitmapData();
ushort[] buff = new ushort[(3 * size.Width) * size.Height]; // ;Marshal.AllocHGlobal() if you want
GCHandle handle= GCHandle.Alloc(buff, GCHandleType.Pinned);
myReadingBuffer.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(buff, 0);
myReadingBuffer.Height = size.Height;
myReadingBuffer.Width = size.Width;
myReadingBuffer.PixelFormat = PixelFormat.Format48bppRgb;
myReadingBuffer.Stride = 6 * size.Width;
// now read into that buff
BitmapData result = bitmap.LockBits(new Rectangle(0, 0, size.Width, size.Height), ImageLockMode.UserInputBuffer | ImageLockMode.ReadOnly, PixelFormat.Format48bppRgb, myReadingBuffer);
if (object.ReferenceEquals(result, myReadingBuffer)) {
    // Note: we pass here
    // and buff is filled
}
bitmap.UnlockBits(result);
handle.Free();
// use buff at will...

Si vous utilisez ILSpy vous verrez que cette méthode lien pour GDI+ et les méthodes d'aide sont plus complets. Vous pouvez augmenter les performances en utilisant votre propre mémoire, mais méfiez-vous que la Foulée peut-être besoin d'un certain alignement pour obtenir les meilleures performances. Ensuite, vous serez en mesure d'aller sauvage par exemple l'allocation énorme mappé en mémoire virtuelle scan0 et blit assez efficacement. Notez que l'épinglage tableau énorme (et surtout un peu) de ne pas être un fardeau pour le GC et vous permettra de manipuler l'octet/court de façon totalement sécuritaire (ou dangereux si vous chercher de la vitesse)

5voto

Steve Points 18193

Je ne sais pas s'il y a une raison pour laquelle vous le faites comme vous êtes. Peut-être qu'il y en a. Il semble que vous soyez suffisamment hors des sentiers battus pour que vous puissiez essayer de faire quelque chose de plus avancé que ce que le titre de votre question implique ...

Cependant, la manière traditionnelle de créer un bitmap à partir d'un tableau d'octets est la suivante:

 using (MemoryStream stream = new MemoryStream(byteArray))
{
     Bitmap bmp = new Bitmap(stream);
     // use bmp here....
}
 

-2voto

user3276415 Points 1

Voici un exemple de code que j'ai écrit pour convertir un tableau d'octets de pixels en une image à échelle de gris de 8 bits (bmp). Cette méthode accepte le tableau de pixels, la largeur et la hauteur de l'image comme arguments //

 public Bitmap Convert2Bitmap(byte[] DATA, int width, int height)
{
    Bitmap Bm = new Bitmap(width,height,PixelFormat.Format24bppRgb);
    var b = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
    ColorPalette ncp = b.Palette;
    for (int i = 0; i < 256; i++)
        ncp.Entries[i] = Color.FromArgb(255, i, i, i);
    b.Palette = ncp;
    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            int Value = DATA[x + (y * width)];
            Color C = ncp.Entries[Value];
            Bm.SetPixel(x,y,C);
        }
    }
   return Bm;
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X