wpf 查找 BitmapImage 的特定像素颜色
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1176910/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Finding specific pixel colors of a BitmapImage
提问by Andrew Shepherd
I have a WPF BitmapImage which I loaded from a .JPG file, as follows:
我有一个从 .JPG 文件加载的 WPF BitmapImage,如下所示:
this.m_image1.Source = new BitmapImage(new Uri(path));
I want to query as to what the colour is at specific points. For example, what is the RGB value at pixel (65,32)?
我想查询特定点的颜色。例如,像素 (65,32) 处的 RGB 值是多少?
How do I go about this? I was taking this approach:
我该怎么做?我正在采取这种方法:
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);
Though I will confess there's a bit of monkey-see, monkey do going on with this code. Anyway, is there a straightforward way to process this array of bytes to convert to RGB values?
虽然我承认有一点猴子看,猴子确实继续使用这段代码。无论如何,有没有一种直接的方法来处理这个字节数组以转换为 RGB 值?
采纳答案by Michael A. McCloskey
The interpretation of the resulting byte array is dependent upon the pixel format of the source bitmap, but in the simplest case of a 32 bit, ARGB image, each pixel will be comprised of four bytes in the byte array. The first pixel would be interpreted thusly:
结果字节数组的解释取决于源位图的像素格式,但在 32 位 ARGB 图像的最简单情况下,每个像素将由字节数组中的四个字节组成。第一个像素将被解释为:
alpha = pixelByteArray[0];
red = pixelByteArray[1];
green = pixelByteArray[2];
blue = pixelByteArray[3];
To process each pixel in the image, you would probably want to create nested loops to walk the rows and the columns, incrementing an index variable by the number of bytes in each pixel.
要处理图像中的每个像素,您可能希望创建嵌套循环来遍历行和列,通过每个像素中的字节数增加索引变量。
Some bitmap types combine multiple pixels into a single byte. For instance, a monochrome image packs eight pixels into each byte. If you need to deal with images other than 24/32 bit per pixels (the simple ones), then I would suggest finding a good book that covers the underlying binary structure of bitmaps.
某些位图类型将多个像素组合成一个字节。例如,单色图像每个字节包含八个像素。如果您需要处理每像素 24/32 位以外的图像(简单的图像),那么我建议您找一本涵盖位图底层二进制结构的好书。
回答by Ray Burns
Here is how I would manipulate pixels in C# using multidimensional arrays:
下面是我在 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;
}
usage:
用法:
var pixels = GetPixels(image);
if(pixels[7, 3].Red > 4)
{
...
}
If you want to update pixels, very similar code works except you will create a WriteableBitmap
, and use this:
如果你想更新像素,非常相似的代码可以工作,除了你会创建一个WriteableBitmap
,并使用这个:
public void PutPixels(WriteableBitmap 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);
}
thusly:
因此:
var pixels = new PixelColor[4, 3];
pixels[2,2] = new PixelColor { Red=128, Blue=0, Green=255, Alpha=255 };
PutPixels(bitmap, pixels, 7, 7);
Note that this code converts bitmaps to Bgra32 if they arrive in a different format. This is generally fast, but in some cases may be a performance bottleneck, in which case this technique would be modified to match the underlying input format more closely.
请注意,如果位图以不同的格式到达,此代码会将位图转换为 Bgra32。这通常很快,但在某些情况下可能是性能瓶颈,在这种情况下,将修改此技术以更紧密地匹配底层输入格式。
Update
更新
Since BitmapSource.CopyPixels
doesn't accept a two-dimensional array it is necessary to convert the array between one-dimensional and two-dimensional. The following extension method should do the trick:
由于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
}
There are two implementations here: The first one is fast but uses unsafe code to get an IntPtr to an array (must compile with /unsafe option). The second one is slower but does not require unsafe code. I use the unsafe version in my code.
这里有两个实现:第一个是快速但使用不安全代码将 IntPtr 获取到数组(必须使用 /unsafe 选项编译)。第二个较慢但不需要不安全的代码。我在我的代码中使用了不安全的版本。
WritePixels accepts two-dimensional arrays, so no extension method is required.
WritePixels 接受二维数组,因此不需要扩展方法。
回答by Habitante
I'd like to add to Ray′s answer that you can also declare PixelColor struct as a union:
我想补充一下 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;
}
And that way you'll also have access to the UInit32 BGRA (for fast pixel access or copy), besides the individual byte components.
这样,除了单个字节组件之外,您还可以访问 UInit32 BGRA(用于快速像素访问或复制)。
回答by G.Y
I took all examples and created a slightly better one - tested it too
(the only flaw was that magic 96 as DPI which really bugged me)
我采用了所有示例并创建了一个稍微好一点的示例 - 也对其进行了测试
(唯一的缺陷是神奇的 96 作为 DPI,这真的让我感到烦恼)
I also compared this WPF tactic versus:
我还比较了这种 WPF 策略与:
- GDI by using Graphics (system.drawing)
- Interop by directly invoking GetPixel from GDI32.Dll
- 使用图形的 GDI (system.drawing)
- 通过直接从 GDI32.Dll 调用 GetPixel 进行互操作
To my supprise,
This works x10 faster than GDI, and around x15 times faster then Interop.
So if you're using WPF - much better to work with this to get your pixel color.
令我惊讶的是,
这比 GDI 快 10 倍,比 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;
}
}
回答by Brent
I'd like to improve upon Ray's answer - not enough rep to comment. >:( This version has the best of both safe/managed, and the efficiency of the unsafe version. Also, I've done away with passing in the stride as the .Net documentation for CopyPixels says it's the stride of the bitmap, not of the buffer. It's misleading, and can be computed inside the function anyway. Since the PixelColor array must be the same stride as the bitmap (to be able to do it as a single copy call), it makes sense to just make a new array in the function as well. Easy as pie.
我想改进雷的回答 - 没有足够的代表发表评论。>:( 这个版本具有最好的安全/托管和不安全版本的效率。另外,我已经取消了跨步传递,因为 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;
}
回答by CZahrobsky
If you want just one Pixel color:
如果您只想要一种像素颜色:
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;
}
回答by Gábor
Much simpler. There's no need to copy the data around, you can get it directly. But this comes at a price: pointers and unsafe
. In a specific situation, decide whether it's worth the speed and ease for you (but you can simply put the image manipulation into its own separate unsafe class and the rest of the program won't be affected).
简单多了。无需复制数据,直接获取即可。但这是有代价的:指针和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();
where
在哪里
[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;
}
The error checking (whether the format is indeed BGRA and handling the case if not) will be left to the reader.
错误检查(格式是否确实是 BGRA 并处理这种情况)将留给读者。
回答by D?nu
A little remark:
一点说明:
If you are trying to use this code (Edit: provided by Ray Burns), but get the error about the array's rank, try to edit the extension methods as follows:
如果您尝试使用此代码(编辑:由 Ray Burns 提供),但得到有关数组排名的错误,请尝试按如下方式编辑扩展方法:
public static void CopyPixels(this BitmapSource source, PixelColor[,] pixels, int stride, int offset, bool dummy)
and then call the CopyPixels
method like this:
然后CopyPixels
像这样调用方法:
source.CopyPixels(result, width * 4, 0, false);
The problem is, that when the extension method doesn't differ from the original, the original one is called. I guess this is because PixelColor[,]
matches Array
as well.
问题是,当扩展方法与原始方法没有区别时,会调用原始方法。我想这也是因为PixelColor[,]
匹配Array
。
I hope this helps you if you got the same problem.
如果您遇到同样的问题,我希望这对您有所帮助。
回答by Iqaan Ullah
You can get color components in a byte array. First copy the pixels in 32 bit to an array and convert that to 8-bit array with 4 times larger size
您可以在字节数组中获取颜色分量。首先将 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...]