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?
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].
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With