views:

2439

answers:

4

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?

+5  A: 

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:

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.

Michael McCloskey
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
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 McCloskey
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
+10  A: 

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.PixelFormat!=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 pixels;
}

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 WritableBitmap, and use this:

public void PutPixels(WritableBitmap bitmap, PixelColor[,] pixels, int x, int y)
{
  int width = colors.GetLength(0);
  int height = colors.GetLength(1);
  bitmap.WritePixels(new Int32Rect(0, 0, width, height), pixels, width*4, x, y);
}

thusly:

var pixels = new PixelColor[4, 3];
pixel[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 pixelVector = new PixelColor[height * width];
    source.CopyPixels(pixelVector, width, 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] = pixelVector[y*width + x];
  }
#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.

Ray Burns
When I try this I get "Input array is not a valid rank". Any ideas?
whitehawk
@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
@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
+2  A: 

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.

Habitante
A: 

[StructLayout(LayoutKind.Sequential)] what is the namespace where located this code?

thanks, i already get it.

and btw - PixelFormat - BitmapSource doesn't have that property?

in4man
System.Runtime.InteropServices.In Visual Studio you can type in the name and hit alt+shift+f10 to search for it.By the way, you'll want to post questions as comments rather than answers; you'll get flogged otherwise.
Rei Miyasaka
Input array is not a valid rank.Parameter name: pixels
in4man
that was error on - source.CopyPixels(result, width * 4, 0);
in4man
кто нибуть знать в чем причина?
in4man
hey maybe you know how to use CopyPixels method
in4man
bug remains does somebody help meInput array is not a valid rank
in4man