查找BitmapImage的特定像素颜色

我有一个WPF的BitmapImage,我从一个.JPG文件加载,如下所示:

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

我想查询什么颜色是在特定的点。 例如,像素(65,32)处的RGB值是多less?

我如何去做这件事? 我正在采取这种做法:

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

虽然我会承认有一点猴子看,猴子会继续这个代码。 无论如何,是否有一个简单的方法来处理这个字节数组转换为RGB值?

结果字节数组的解释取决于源位图的像素格式,但在32位ARGB图像的最简单情况下,每个像素将由字节数组中的四个字节组成。 第一个像素将被如此解释:

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

要处理图像中的每个像素,您可能需要创build嵌套循环来遍历行和列,并通过每个像素中的字节数递增索引variables。

一些位图types将多个像素组合成单个字节。 例如,单色图像将八个像素打包成每个字节。 如果你需要处理每像素24/32比特以外的图像(简单图像),那么我build议找一本好的书,其中介绍了位图的基本二进制结构。

这里是我将如何处理使用multidimensional array的C#中的像素:

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

用法:

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

如果要更新像素,除非要创buildWritableBitmap ,否则非常类似的代码可用,并使用以下代码:

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

正是如此:

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

请注意,如果代码以不同的格式到达,则此代码将位图转换为Bgra32。 这通常是快速的,但在某些情况下可能是性能瓶颈,在这种情况下,这种技术将被修改以更紧密地匹配底层input格式。

更新

由于BitmapSource.CopyPixels不接受二维数组,所以需要在一维和二维之间转换数组。 下面的扩展方法应该做的伎俩:

 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<height; y++) for(int x=0; x<width; x++) pixels[x+x0, y+y0] = new PixelColor { Blue = pixelBytes[(y*width + x) * 4 + 0], Green = pixelBytes[(y*width + x) * 4 + 1], Red = pixelBytes[(y*width + x) * 4 + 2], Alpha = pixelBytes[(y*width + x) * 4 + 3], }; } #endif } 

这里有两个实现:第一个是快速的,但使用不安全的代码来获取IntPtr到数组(必须使用/ unsafe选项编译)。 第二个比较慢,但不需要不安全的代码。 我在我的代码中使用不安全的版本。

WritePixels接受二维数组,所以不需要扩展方法。

我想添加Ray的答案,你也可以声明PixelColor结构为一个联合:

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

这样,除了单个字节组件外,您还可以访问UInit32 BGRA(用于快速像素访问或复制)。

我想提高雷的答案 – 没有足够的代表评论。 > :(此版本具有最好的安全/pipe理和不安全版本的效率。此外,我已经取消了跨步,因为CopyPixels的.Net文档说这是位图的跨步,而不是这是误导的,可以在函数内部进行计算,因为PixelColor数组必须和位图一样(可以作为单个拷贝调用),所以只需要创build一个新的在函数中也是一样的。

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

我拿了所有的例子,创造了一个更好的 – 也testing了一下
(唯一的缺陷是那个神奇的96作为DPI真的搞砸了我)

我也比较了这个WPF策略与:

  • GDI通过使用Graphics(system.drawing)
  • 通过直接调用GDI32.Dll中的GetPixel进行互操作

对我来说,
这比GDI快x10,比Interop快x15倍。
所以,如果你使用的是WPF,要更好地处理像素的颜色。

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

如果你只想要一个像素的颜色:

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

有一点评论:

如果您正在尝试使用此代码(编辑:由Ray Burns提供),但获取有关数组排名的错误,请尝试编辑扩展方法,如下所示:

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

然后像这样调用CopyPixels方法:

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

问题是,当扩展方法与原始方法没有区别时,原来的方法被调用。 我想这是因为PixelColor[,]Array相匹配。

我希望这可以帮助你,如果你有同样的问题。

你可以得到一个字节数组中的颜色组件。 首先将32位像素复制到一个数组,并将其转换为8位数组,并将其大小扩大4倍

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