Crea bitmap da una serie di byte di dati pixel

Questa domanda riguarda come leggere / scrivere, allocare e gestire i dati dei pixel di una bitmap.

Ecco un esempio di come allocare un array di byte (memoria gestita) per i dati dei pixel e creare un Bitmap che lo usi:

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 

// Ma non è sempre vero. Maggiori informazioni su 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; } 

Ho pensato che Bitmap avrebbe fatto una copia dei dati dell’array, ma in realtà punta agli stessi dati. Era ansible vedere:

 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] 

Domande:

  1. È sicuro creare una bitmap da un array [byte] (memoria gestita) e da free () il GCHandle? Se non è sicuro, è necessario tenere un array aggiunto, quanto è grave questo a GC / Performance?

  2. È sicuro modificare i dati (es: data [0] = 255;)?

  3. L’indirizzo di Scan0 può essere modificato dal GC? Voglio dire, ottengo Scan0 da una bitmap bloccata, quindi lo sblocco e dopo un po ‘di tempo lo blocco di nuovo, Scan0 può essere diverso?

  4. Qual è lo scopo di ImageLockMode.UserInputBuffer nel metodo LockBits? È molto difficile trovare informazioni al riguardo! MSDN non lo spiega chiaramente!

EDIT 1: alcuni followup

  1. Devi tenerlo bloccato. Rallenterà il GC? L’ho chiesto qui . Dipende dal numero di immagini e dalle sue dimensioni. Nessuno mi ha dato una risposta quantitativa. Sembra che sia difficile da determinare. È anche ansible allocare la memoria utilizzando Marshal o utilizzare la memoria non gestita allocata da Bitmap.

  2. Ho fatto un sacco di test usando due thread. Finché Bitmap è bloccato, è ok. Se la Bitmap è sbloccata, allora non è sicura! Il mio post correlato su lettura / scrittura direttamente su Scan0 . Risposta di Boing “Ho già spiegato in precedenza perché sei fortunato ad usare scan0 al di fuori del blocco, perché usi il bmp originale PixelFormat e in questo caso GDI è ottimizzato per darti il ​​puntatore e non una copia. fino a quando il sistema operativo non deciderà di liberarlo, l’unica volta che c’è una garanzia tra LockBits e UnLockBits.

  3. Sì, può succedere, ma ampie regioni di memoria sono trattate in modo diverso dal GC, sposta / libera questo object di grandi dimensioni meno frequentemente. Quindi può richiedere un po ‘di tempo prima che GC sposti questo array. Da MSDN : “Qualsiasi allocazione maggiore o uguale a 85,000 bytes va large object heap (LOH) ” … “LOH viene raccolto solo durante una raccolta di generazione 2”. .NET 4.5 ha miglioramenti in LOH.

  4. A questa domanda è stata data risposta da @Boing. Ma lo ammetterò. Non l’ho capito completamente. Quindi, se Boing o qualcun altro potessero, per please clarify it , sarei felice. A proposito, perché non posso solo leggere / scrivere direttamente su Sca0 senza bloccare ? => Non si dovrebbe scrivere direttamente su Scan0 perché Scan0 punta a una copia dei dati Bitmap creati dalla memoria non gestita (all’interno di GDI). Dopo lo sblocco, questa memoria può essere riallocata ad altre cose, non è più certo che Scan0 punterà ai dati Bitmap effettivi. Questo può essere riprodotto ottenendo Scan0 in un lucchetto, sbloccando e facendo qualche rotazione a rotazione nella bitmap sbloccata. Dopo un po ‘di tempo, Scan0 punterà su una regione non valida e si otterrà un’eccezione quando si tenta di leggere / scrivere nella sua posizione di memoria.

  1. È sicuro se si esegue il marshall.copy piuttosto che l’impostazione scan0 (direttamente o tramite quel sovraccarico di BitMap ()). Non si desidera mantenere bloccati gli oggetti gestiti, questo vincolerà il garbage collector.
  2. Se copi, perfettamente al sicuro.
  3. L’array di input è gestito e può essere spostato dal GC, scan0 è un puntatore non gestito che verrebbe fuori data se l’array viene spostato. L’object Bitmap è gestito ma imposta il puntatore scan0 in Windows tramite un handle.
  4. ImageLockMode.UserInputBuffer è? Apparentemente può essere passato a LockBits, forse dice a Bitmap () di copiare i dati dell’array di input.

Esempio di codice per creare una bitmap in scala di grigi dall’array:

  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, eg with a for loop over an input array Marshal.Copy(rgbValues, 0, ptr, bytes); b.UnlockBits(bmpData); return b; 

Riguardo alla tua domanda 4: ImageLockMode.UserInputBuffer può darti il ​​controllo del processo di allocazione di quell’enorme quantità di memoria che potrebbe essere referenziata in un object BitmapData .

Se scegli di creare te stesso l’object BitmapData puoi evitare un Marshall.Copy . Dovrai quindi utilizzare questo flag in combinazione con un altro ImageLockMode .

Fai attenzione che è un business complicato, specialmente per quanto riguarda Stride e PixelFormat.

Ecco un esempio che otterrebbe in un colpo il contenuto del buffer 24bbp su una BitMap e poi l’uno nell’altro lo rileggere in un altro buffer in 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... 

Se usi ILSpy vedrai che questo metodo si collega a GDI + e questi metodi sono più completi.

È ansible aumentare le prestazioni utilizzando il proprio schema di memoria, ma attenzione che Stride potrebbe aver bisogno di un certo allineamento per ottenere le migliori prestazioni.

Sarai quindi in grado di scatenare, ad esempio, l’allocazione di scan0 mappate alla memoria virtuale e bliterarle in modo abbastanza efficiente. Si noti che il fissaggio di array giganteschi (e in particolare alcuni) non sarà un onere per il GC e consentirà di manipolare il byte / short in un modo totalmente sicuro (o non sicuro se si cerca la velocità)

Non sono sicuro che ci sia un motivo per cui lo stai facendo come sei. Forse c’è. Sembra che tu sia fuori dai sentieri battuti abbastanza da poter provare a fare qualcosa di più avanzato di quello che il titolo della tua domanda implica …

Tuttavia, il modo tradizionale di creare un Bitmap da un array Byte è:

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

Ecco un codice di esempio che ho scritto per convertire l’array di byte di pixel in un’immagine in scala di grigi a 8 bit (bmp) questo metodo accetta l’array di pixel, la larghezza dell’immagine e l’altezza come argomenti //

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