Encontrar colores de píxeles específicos de una BitmapImage

Tengo un WPF BitmapImage que cargué desde un archivo .JPG, de la siguiente manera:

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

Quiero consultar sobre qué color es en puntos específicos. Por ejemplo, ¿cuál es el valor RGB en píxeles (65,32)?

¿Cómo hago esto? Estaba tomando este enfoque:

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

Aunque confesaré que hay un poco de mono, el mono sigue con este código. De todos modos, ¿hay una manera directa de procesar esta matriz de bytes para convertir a valores RGB?

La interpretación del conjunto de bytes resultante depende del formato de píxeles del bitmap de origen, pero en el caso más simple de una imagen ARGB de 32 bits, cada píxel estará compuesto por cuatro bytes en el conjunto de bytes. El primer pixel se interpretaría así:

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

Para procesar cada píxel en la imagen, es probable que desee crear bucles nesteds para recorrer las filas y las columnas, incrementando una variable de índice por el número de bytes en cada píxel.

Algunos tipos de bitmap combinan varios píxeles en un solo byte. Por ejemplo, una imagen monocromática empaqueta ocho píxeles en cada byte. Si necesita tratar con imágenes que no sean de 24/32 bits por píxel (las más simples), le sugiero que busque un buen libro que cubra la estructura binaria subyacente de los mapas de bits.

Así es como manipularía píxeles en C # usando matrices multidimensionales:

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

uso:

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

Si desea actualizar píxeles, funciona un código muy similar, excepto que creará un WritableBitmap y lo usará:

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

así:

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

Tenga en cuenta que este código convierte mapas de bits a Bgra32 si llegan en un formato diferente. En general, esto es rápido, pero en algunos casos puede ser un cuello de botella de rendimiento, en cuyo caso esta técnica se modificaría para que coincida más estrechamente con el formato de entrada subyacente.

Actualizar

Como BitmapSource.CopyPixels no acepta una matriz bidimensional, es necesario convertir la matriz entre una dimensión y dos dimensiones. El siguiente método de extensión debería hacer el truco:

 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 

Aquí hay dos implementaciones: la primera es rápida pero usa código inseguro para obtener un IntPtr en una matriz (debe comstackr con la opción / inseguro). El segundo es más lento pero no requiere código inseguro. Uso la versión insegura en mi código.

WritePixels acepta matrices bidimensionales, por lo que no se requiere ningún método de extensión.

Me gustaría agregar a la respuesta de Ray que también puedes declarar PixelColor struct como una unión:

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

Y de esa manera también tendrá acceso a UInit32 BGRA (para acceso rápido de píxeles o copia), además de los componentes de bytes individuales.

Me gustaría mejorar la respuesta de Ray: no hay suficiente representante para comentar. > 🙁 Esta versión tiene lo mejor de ambos, segura / administrada, y la eficiencia de la versión insegura. Además, he terminado con pasar el paso ya que la documentación de .Net para CopyPixels dice que es el paso del bitmap, no del búfer. Es engañoso, y se puede calcular dentro de la función de todos modos. Dado que la matriz PixelColor debe ser la misma zancada que el bitmap (para poder hacerlo como una sola copia), tiene sentido hacer una nueva array en la función también. Fácil como un pastel.

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

Tomé todos los ejemplos y creé uno ligeramente mejor, lo probé también
(El único defecto fue esa magia 96 como DPI que realmente me molestó)

También comparé esta táctica WPF versus:

  • GDI mediante el uso de Graphics (system.drawing)
  • Interop invocando directamente a GetPixel desde GDI32.Dll

Para mi sorpresa,
Esto funciona x10 más rápido que GDI, y alrededor de x15 veces más rápido que Interop.
Entonces, si está usando WPF, es mucho mejor trabajar con esto para obtener su color de píxel.

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

Si solo quieres un color de píxel:

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

Un pequeño comentario:

Si está intentando usar este código (Edición: proporcionado por Ray Burns), pero obtenga el error sobre el rango de la matriz, intente editar los métodos de extensión de la siguiente manera:

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

y luego llama al método CopyPixels así:

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

El problema es que cuando el método de extensión no difiere del original, se llama al original. Supongo que esto se debe a que PixelColor[,] coincide con Array .

Espero que esto te ayude si tienes el mismo problema.

Puede obtener componentes de color en una matriz de bytes. Primero copie los píxeles en 32 bits a una matriz y conviértelos a una matriz de 8 bits con un tamaño 4 veces mayor

 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...]