Ricerca di colors di pixel specifici di un BitmapImage

Ho un BitmapImage WPF che ho caricato da un file .JPG, come segue:

this.m_image1.Source = new BitmapImage(new Uri(path)); 

Voglio interrogare su quale sia il colore in punti specifici. Ad esempio, qual è il valore RGB in pixel (65,32)?

Come faccio a fare questo? Stavo prendendo questo approccio:

 ImageSource ims = m_image1.Source; BitmapImage bitmapImage = (BitmapImage)ims; int height = bitmapImage.PixelHeight; int width = bitmapImage.PixelWidth; int nStride = (bitmapImage.PixelWidth * bitmapImage.Format.BitsPerPixel + 7) / 8; byte[] pixelByteArray = new byte[bitmapImage.PixelHeight * nStride]; bitmapImage.CopyPixels(pixelByteArray, nStride, 0); 

Anche se confesserò che c’è un po ‘di scimmia, vedi, scimmia continua con questo codice. Ad ogni modo, esiste un modo semplice per elaborare questa matrice di byte da convertire in valori RGB?

L’interpretazione dell’array di byte risultante dipende dal formato pixel della bitmap di origine, ma nel caso più semplice di un’immagine ARGB a 32 bit, ciascun pixel sarà composto da quattro byte nell’array di byte. Il primo pixel verrebbe interpretato in questo modo:

 alpha = pixelByteArray[0]; red = pixelByteArray[1]; green = pixelByteArray[2]; blue = pixelByteArray[3]; 

Per elaborare ogni pixel nell’immagine, probabilmente vorrai creare cicli annidati per percorrere le righe e le colonne, incrementando una variabile di indice in base al numero di byte in ciascun pixel.

Alcuni tipi di bitmap combinano più pixel in un singolo byte. Ad esempio, un’immagine monocromatica racchiude otto pixel in ciascun byte. Se devi gestire immagini diverse da 24/32 bit per pixel (quelle semplici), ti suggerisco di trovare un buon libro che copra la struttura binaria sottostante delle bitmap.

Ecco come modificherei i pixel in C # usando array multidimensionali:

 [StructLayout(LayoutKind.Sequential)] public struct PixelColor { public byte Blue; public byte Green; public byte Red; public byte Alpha; } public PixelColor[,] GetPixels(BitmapSource source) { if(source.Format!=PixelFormats.Bgra32) source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0); int width = source.PixelWidth; int height = source.PixelHeight; PixelColor[,] result = new PixelColor[width, height]; source.CopyPixels(result, width * 4, 0); return result; } 

utilizzo:

 var pixels = GetPixels(image); if(pixels[7, 3].Red > 4) ... 

Se vuoi aggiornare i pixel, funziona un codice molto simile ad eccezione del fatto che creerai un WritableBitmap e userai questo:

 public void PutPixels(WritableBitmap bitmap, PixelColor[,] pixels, int x, int y) { int width = pixels.GetLength(0); int height = pixels.GetLength(1); bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width*4, x, y); } 

nel seguente modo:

 var pixels = new PixelColor[4, 3]; pixel[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 }; PutPixels(bitmap, pixels, 7, 7); 

Si noti che questo codice converte bitmap in Bgra32 se arrivano in un formato diverso. Questo è generalmente veloce, ma in alcuni casi può essere un collo di bottiglia delle prestazioni, nel qual caso questa tecnica verrebbe modificata per adattarsi più strettamente al formato di input sottostante.

Aggiornare

Poiché BitmapSource.CopyPixels non accetta un array bidimensionale, è necessario convertire l’array tra unidimensionale e bidimensionale. Il seguente metodo di estensione dovrebbe fare il trucco:

 public static class BitmapSourceHelper { #if UNSAFE public unsafe static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) { fixed(PixelColor* buffer = &pixels[0, 0]) source.CopyPixels( new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight), (IntPtr)(buffer + offset), pixels.GetLength(0) * pixels.GetLength(1) * sizeof(PixelColor), stride); } #else public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset) { var height = source.PixelHeight; var width = source.PixelWidth; var pixelBytes = new byte[height * width * 4]; source.CopyPixels(pixelBytes, stride, 0); int y0 = offset / width; int x0 = offset - width * y0; for(int y=0; y 

Ci sono due implementazioni qui: Il primo è veloce ma usa codice non sicuro per ottenere un IntPtr in un array (deve compilare con / opzione non sicura). Il secondo è più lento ma non richiede un codice non sicuro. Io uso la versione non sicura nel mio codice.

WritePixels accetta matrici bidimensionali, quindi non è richiesto alcun metodo di estensione.

Vorrei aggiungere alla risposta di Ray che puoi anche dichiarare la struttura di PixelColor come unione:

 [StructLayout(LayoutKind.Explicit)] public struct PixelColor { // 32 bit BGRA [FieldOffset(0)] public UInt32 ColorBGRA; // 8 bit components [FieldOffset(0)] public byte Blue; [FieldOffset(1)] public byte Green; [FieldOffset(2)] public byte Red; [FieldOffset(3)] public byte Alpha; } 

In questo modo avrai anche accesso a UInit32 BGRA (per l’accesso o la copia veloce dei pixel), oltre ai singoli componenti di byte.

Mi piacerebbe migliorare la risposta di Ray – non abbastanza rappresentante per commentare. > 🙁 Questa versione ha il meglio sia in sicurezza che in gestione, e l’efficienza della versione non sicura. Inoltre, ho eliminato il passo mentre la documentazione .Net per CopyPixels dice che è il passo della bitmap, non del buffer, è fuorviante e può essere calcolato all’interno della funzione in ogni caso, poiché l’array PixelColor deve essere lo stesso passo della bitmap (per poterlo fare come una singola copia), ha senso semplicemente creare una nuova array nella funzione pure. Facile come una torta.

  public static PixelColor[,] CopyPixels(this BitmapSource source) { if (source.Format != PixelFormats.Bgra32) source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0); PixelColor[,] pixels = new PixelColor[source.PixelWidth, source.PixelHeight]; int stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7) / 8); GCHandle pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned); source.CopyPixels( new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight), pinnedPixels.AddrOfPinnedObject(), pixels.GetLength(0) * pixels.GetLength(1) * 4, stride); pinnedPixels.Free(); return pixels; } 

Ho preso tutti gli esempi e ne ho creato uno leggermente migliore – lo ho testato anche io
(l’unico difetto era la magia 96 come DPI che mi infastidiva davvero)

Ho anche confrontato questa tattica WPF versus:

  • GDI utilizzando Graphics (system.drawing)
  • Interop invocando direttamente GetPixel da GDI32.Dll

Per il mio supprise,
Funziona x10 più veloce di GDI e circa x15 volte più veloce di Interop.
Quindi, se stai usando WPF, è molto meglio lavorare con questo per ottenere il colore dei tuoi pixel.

 public static class GraphicsHelpers { public static readonly float DpiX; public static readonly float DpiY; static GraphicsHelpers() { using (var g = Graphics.FromHwnd(IntPtr.Zero)) { DpiX = g.DpiX; DpiY = g.DpiY; } } public static Color WpfGetPixel(double x, double y, FrameworkElement AssociatedObject) { var renderTargetBitmap = new RenderTargetBitmap( (int)AssociatedObject.ActualWidth, (int)AssociatedObject.ActualHeight, DpiX, DpiY, PixelFormats.Default); renderTargetBitmap.Render(AssociatedObject); if (x <= renderTargetBitmap.PixelWidth && y <= renderTargetBitmap.PixelHeight) { var croppedBitmap = new CroppedBitmap( renderTargetBitmap, new Int32Rect((int)x, (int)y, 1, 1)); var pixels = new byte[4]; croppedBitmap.CopyPixels(pixels, 4, 0); return Color.FromArgb(pixels[3], pixels[2], pixels[1], pixels[0]); } return Colors.Transparent; } } 

Se desideri un solo colore Pixel:

 using System.Windows.Media; using System.Windows.Media.Imaging; ... public static Color GetPixelColor(BitmapSource source, int x, int y) { Color c = Colors.White; if (source != null) { try { CroppedBitmap cb = new CroppedBitmap(source, new Int32Rect(x, y, 1, 1)); var pixels = new byte[4]; cb.CopyPixels(pixels, 4, 0); c = Color.FromRgb(pixels[2], pixels[1], pixels[0]); } catch (Exception) { } } return c; } 

Una piccola osservazione:

Se stai cercando di utilizzare questo codice (Modifica: fornito da Ray Burns), ma ottieni l’errore relativo al posizionamento dell’array, prova a modificare i metodi di estensione come segue:

 public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset, bool dummy) 

e quindi chiama il metodo CopyPixels questo modo:

 source.CopyPixels(result, width * 4, 0, false); 

Il problema è che quando il metodo di estensione non differisce dall’originale, viene chiamato quello originale. Immagino che questo sia dovuto PixelColor[,] fatto che PixelColor[,] corrisponde anche a Array .

Spero che questo ti aiuti se hai lo stesso problema.

È ansible ottenere componenti del colore in un array di byte. Prima copia i pixel a 32 bit in un array e convertilo in array a 8 bit con dimensioni 4 volte maggiori

 int[] pixelArray = new int[stride * source.PixelHeight]; source.CopyPixels(pixelArray, stride, 0); // byte[] colorArray = new byte[pixelArray.Length]; // EDIT: byte[] colorArray = new byte[pixelArray.Length * 4]; for (int i = 0; i < colorArray.Length; i += 4) { int pixel = pixelArray[i / 4]; colorArray[i] = (byte)(pixel >> 24); // alpha colorArray[i + 1] = (byte)(pixel >> 16); // red colorArray[i + 2] = (byte)(pixel >> 8); // green colorArray[i + 3] = (byte)(pixel); // blue } // colorArray is an array of length 4 times more than the actual number of pixels // in the order of [(ALPHA, RED, GREEN, BLUE), (ALPHA, RED...]