44

I have a WPF BitmapImage which I loaded from a .JPG file, as follows:

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

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?

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • For what purpose nStride ? And why you adding 7 and dividing by 8 in nStride calculation ? – Jviaches May 07 '12 at 20:16
  • 3
    @Jviaches Add 7 and divide by 8 to correctly round to enough bytes (f.i. 10 bits will need 2 bytes.) – erikH May 09 '12 at 08:32

9 Answers9

60

Here is how I would manipulate pixels in C# using multidimensional arrays:

[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:

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.

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:

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.

WritePixels accepts two-dimensional arrays, so no extension method is required.

Edit: As Jerry pointed out in the comments, because of the memory layout, the two-dimensional array has the vertical coordinate first, in other words it must be dimensioned as Pixels[Height,Width] not Pixels[Width,Height] and addressed as Pixels[y,x].

Ray Burns
  • 62,163
  • 12
  • 140
  • 141
  • 3
    When I try this I get "Input array is not a valid rank". Any ideas? – whitehawk Jul 19 '10 at 17:30
  • @whitehawk: see this post: http://stackoverflow.com/questions/3418494/input-array-is-not-a-valid-rank-error-message-when-using-copypixels-method/3418567#3418567 – Andrew Shepherd Aug 05 '10 at 21:54
  • @whitehawk: Thanks for letting me know about the error. I have updated my answer to show how to fix it. In my own code I was actually using a different CopyPixels overload that takes an IntPtr. – Ray Burns Aug 06 '10 at 18:00
  • The safe version doesn't work. It throws “Cannot match the type of this array to a pixel format.” According to Reflector, `pixels` has to be an array of `byte`, `short`, `ushort`, `int`, `uint`, `float` or `double`. – svick Nov 30 '10 at 13:55
  • I have modified the safe version to use a `byte[]` instead of a `PixelColor[]`, so it should work correctly now. – Ray Burns Jan 26 '11 at 23:29
  • if(source.PixelFormat!=PixelFormats.Bgra32) should be if(source.Format!=PixelFormats.Bgra32) – David Hollinshead Feb 21 '11 at 12:24
  • The unsafe code works great for me. The safe/managed version give me an ArgumentOutOfRangeException on the stride parameter when calling source.CopyPixels. You pass in a parameter called stride but don't use it. Finally is "this" meant to be in the CopyPixels function signature? Thanks for your code though - it has helped me. – David Hollinshead Feb 21 '11 at 12:32
  • 9
    Note to Microsoft devs: we shouldn't have to jump through this many hoops just to read the value of a pixel in a bitmap file in WPF... – dodgy_coder Mar 01 '12 at 07:49
  • I don't understand what is meaning of x and y in PutPixels Functions. I have an PixelColor array. I'd like to convert it into BitmapImage. What should I do? – takayoshi Nov 12 '12 at 11:48
  • I know this question is super old but I thought I would point out that when using the unsafe code you would need to declare your PixelColor as `PixelColor[Height, Width]` as the memory layout goes [0,0] [0,1] [0,2] [1,0] [1,1] etc. – Jerry Apr 25 '21 at 04:58
  • Thanks Jerry. Useful for people to know. I added your information to the answer. – Ray Burns May 18 '21 at 17:11
17

I'd like to add to Ray´s answer that you can also declare PixelColor struct as a union:

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

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
Habitante
  • 233
  • 3
  • 8
16

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 composed of four bytes in the byte array. The first pixel would be interpreted thusly:

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.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Michael A. McCloskey
  • 2,391
  • 16
  • 19
  • 6
    Thanks, but... I feel like the .NET framework should have some interface that abstracts away all of the coding complexity. Obviously the System.Windows.Media library knows how to decode a bitmap, because it renders it to a screen. Why should I have to know all of the implementation details? Can't I utilise the rendering logic that's already implemented somehow? – Andrew Shepherd Jul 24 '09 at 13:07
  • 2
    I feel your pain. In windows forms, the Bitmap class has GetPixel and SetPixel methods to retrieve and set the value of individual pixels, but these methods are notoriously slow and all but useless except for very simple cases. You can use RenderTargetBitmap and other higher level objects to create and composite bitmaps, but in my opinion, image processing is best left in the domain of C++, where direct access to the bits is a better fit. Sure, you CAN do image processing in C#/.NET, but in my experience, it always feels like trying to put a square peg through a round hole. – Michael A. McCloskey Jul 24 '09 at 13:48
  • 11
    I disagree. In my opinion, C# is a better choice than C++ for 90%of image processing needs. The C# language has many advantages over C++, and its "unsafe" and "fixed" keywords give you direct access to the bits a la C++ when you need them. In most cases a C# implementation of an image processing algorithm will be very similar in speed to that of C++, but occasionally it will be significantly slower. I would only use C++ in the few cases where C# will perform poorly due to optimizations missed by the JIT compiler. – Ray Burns Nov 16 '09 at 06:42
6

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)

I also compared this WPF tactic versus:

  • GDI by using Graphics (system.drawing)
  • Interop by directly invoking GetPixel from GDI32.Dll

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.

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;
    }
}
G.Y
  • 6,042
  • 2
  • 37
  • 54
5

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.

    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;
    }
Brent
  • 4,153
  • 4
  • 30
  • 63
3

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;
    }
CZahrobsky
  • 762
  • 7
  • 7
2

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:

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

and then call the CopyPixels method like this:

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.

I hope this helps you if you got the same problem.

Dänu
  • 5,791
  • 9
  • 43
  • 56
2

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

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.

Gábor
  • 9,466
  • 3
  • 65
  • 79
0

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

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