我有一个WPF BitmapImage,它是从.JPG文件加载的,如下所示:
this.m_image1.Source = new BitmapImage(new Uri(path));
我想查询特定点的颜色。例如,像素(65,32)处的RGB值是什么?
我该如何解决这个问题?我采用这种方法:
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值?
答案 0 :(得分:55)
以下是我如何使用多维数组操作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)
...
如果你想更新像素,非常相似的代码可以正常工作,除非你要创建一个WritableBitmap
,并使用它:
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。这通常很快,但在某些情况下可能是性能瓶颈,在这种情况下,可以修改此技术以更密切地匹配基础输入格式。
<强>更新强>
由于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接受二维数组,因此不需要扩展方法。
答案 1 :(得分:14)
我想补充一下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(用于快速像素访问或复制)。
答案 2 :(得分:13)
结果字节数组的解释取决于源位图的像素格式,但在32位ARGB图像的最简单情况下,每个像素将由字节数组中的四个字节组成。因此将解释第一个像素:
alpha = pixelByteArray[0];
red = pixelByteArray[1];
green = pixelByteArray[2];
blue = pixelByteArray[3];
要处理图像中的每个像素,您可能希望创建嵌套循环来遍历行和列,将索引变量递增每个像素中的字节数。
某些位图类型将多个像素组合成一个字节。例如,单色图像将8个像素打包到每个字节中。如果你需要处理每像素24/32位以外的图像(简单的图像),那么我建议找一本涵盖底图二进制结构的好书。
答案 3 :(得分:4)
我想改进Ray的回答 - 没有足够的评论。 &gt; :(这个版本具有最好的安全/托管和不安全版本的效率。此外,我已经完成了传递,因为CopyPixels的.Net文档说它是位图的步幅,不是缓冲区。它有误导性,并且可以在函数内部进行计算。由于PixelColor数组必须与位图的步幅相同(能够将其作为单个复制调用执行),因此只需制作一个功能中的新数组也很简单。
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;
}
答案 4 :(得分:3)
我拿了所有的例子,创造了一个稍好的例子 - 测试过它 (唯一的缺陷就是魔法96作为DPI真的让我感到烦恼)
我还比较了这个WPF战术与:
对我来说,
这比GDI快x10,比Interop快约15倍。
因此,如果你正在使用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;
}
}
答案 5 :(得分:2)
如果您只想要一个像素颜色:
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;
}
答案 6 :(得分:1)
简单得多。无需复制数据,您可以直接获取。但这是有代价的:指针和unsafe
。在特定情况下,请决定对您而言,速度和便利性是否值得(但您可以将图像处理放入其自己的单独的不安全类中,而程序的其余部分不会受到影响)。
var bitmap = new WriteableBitmap(image);
data = (Pixel*)bitmap.BackBuffer;
stride = bitmap.BackBufferStride / 4;
bitmap.Lock();
// getting a pixel value
Pixel pixel = (*(data + y * stride + x));
bitmap.Unlock();
其中
[StructLayout(LayoutKind.Explicit)]
protected struct Pixel {
[FieldOffset(0)]
public byte B;
[FieldOffset(1)]
public byte G;
[FieldOffset(2)]
public byte R;
[FieldOffset(3)]
public byte A;
}
错误检查(格式是否确实为BGRA,如果不是,则进行处理)将留给读者。
答案 7 :(得分:0)
一点点评论:
如果您尝试使用此代码(编辑:由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
。
如果遇到同样的问题,我希望这可以帮到你。
答案 8 :(得分:0)
您可以在字节数组中获取颜色分量。首先将32位像素复制到一个数组,然后将其转换为大小为4倍的8位数组
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...]