查找BitmapImage的特定像素颜色
image-processing
5
0

我有一个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值?

参考资料:
Stack Overflow
收藏
评论
共 6 个回答
高赞 时间 活跃

这是我将使用多维数组在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)
{
  ...
}

如果要更新像素,除了创建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);
}

因此:

var pixels = new PixelColor[4, 3];
pixels[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接受二维数组,因此不需要扩展方法。

收藏
评论

我想完善Ray的答案-不足以发表评论。 > :(此版本兼具安全性/可管理性以及不安全版本的效率。此外,我还放弃了跨步,因为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;
    }
收藏
评论

我以所有示例为例,并创建了一个更好的示例-也对其进行了测试
(唯一的缺点是魔术96作为DPI确实困扰了我)

我还比较了WPF策略与:

  • 通过使用GDI图形(system.drawing)
  • 通过直接从GDI32.Dll调用GetPixel进行互操作

令我惊讶的是
它的工作速度比GDI快10倍,比Interop快约15倍。
因此,如果您使用的是WPF,则最好使用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;
    }
}
收藏
评论

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

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

要处理图像中的每个像素,您可能需要创建嵌套循环以遍历行和列,并通过每个像素中的字节数增加索引变量。

某些位图类型将多个像素组合成一个字节。例如,单色图像将八个像素打包到每个字节中。如果您需要处理每像素24/32位以外的图像(简单的图像),那么我建议您找到一本涵盖位图基本二进制结构的好书。

收藏
评论

如果只需要一种像素颜色:

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的答案,您也可以将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(用于快速像素访问或复制)。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号