1

I want to get the average values of R, G and B from an image displayed inside an Image control. The source of this image is a frame captured from a webcam using this dll.

Even though I found some functions that should be able to do this, they are either for C# using pointers and unsafe code or for Windows Forms, and I couldn't get them to work with the Image control.

How should I proceed to calculate that, or at least convert the Image.Source to Bitmap in order to use the function from the WinForms link?

Community
  • 1
  • 1
Molx
  • 6,816
  • 2
  • 31
  • 47

2 Answers2

3

Here is a straightforward, pure WPF solution, which directly accesses the pixel buffer of a BitmapSource. It works for the Bgr24, Bgr32, Bgra32 and Pbgra32 formats. In case of Pbgra32 all alpha values should be 255, otherwise you may have to divide each pixel's (pre-multiplied) color values by alpha / 255.

public Color GetAverageColor(BitmapSource bitmap)
{
    var format = bitmap.Format;

    if (format != PixelFormats.Bgr24 &&
        format != PixelFormats.Bgr32 &&
        format != PixelFormats.Bgra32 &&
        format != PixelFormats.Pbgra32)
    {
        throw new InvalidOperationException("BitmapSource must have Bgr24, Bgr32, Bgra32 or Pbgra32 format");
    }

    var width = bitmap.PixelWidth;
    var height = bitmap.PixelHeight;
    var numPixels = width * height;
    var bytesPerPixel = format.BitsPerPixel / 8;
    var pixelBuffer = new byte[numPixels * bytesPerPixel];

    bitmap.CopyPixels(pixelBuffer, width * bytesPerPixel, 0);

    long blue = 0;
    long green = 0;
    long red = 0;

    for (int i = 0; i < pixelBuffer.Length; i += bytesPerPixel)
    {
        blue += pixelBuffer[i];
        green += pixelBuffer[i + 1];
        red += pixelBuffer[i + 2];
    }

    return Color.FromRgb((byte)(red / numPixels), (byte)(green / numPixels), (byte)(blue / numPixels));
}

As the Image control's Source property is of type ImageSource, you have to cast it to BitmapSource before passing it to the method:

var bitmap = (BitmapSource)image.Source; 
var color = GetAverageColor(bitmap);
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • This worked perfectly after I translated it to VB (not a problem with online tools), thank you! – Molx Apr 26 '15 at 13:55
1

EDITED to support WPF image control (still requires reference to System.Drawing).

Public Function GetWPFImageAverageRGB(wpfImage As System.Windows.Controls.Image) As System.Drawing.Color
    Using ms = New IO.MemoryStream()
        Dim encoder = New JpegBitmapEncoder()
        encoder.Frames.Add(BitmapFrame.Create(CType(wpfImage.Source, BitmapImage)))
        encoder.Save(ms)

        Using bmp = CType(System.Drawing.Bitmap.FromStream(ms), System.Drawing.Bitmap)
            Dim reds As Long
            Dim greens As Long
            Dim blues As Long

            For x = 0 To bmp.Width - 1
                For y = 0 To bmp.Height - 1
                    With bmp.GetPixel(x, y)
                        reds += .R
                        greens += .G
                        blues += .B
                    End With
                Next
            Next

            Dim count = bmp.Height * bmp.Width

            Return System.Drawing.Color.FromArgb(CInt(reds / count), CInt(greens / count), CInt(blues / count))
        End Using
    End Using
End Function

Usage:

With GetWPFImageAverageRGB(image) 'image is a System.Windows.Controls.image
   Console.WriteLine("Average: R={0}, G={1}, B={2}", .R, .G, .B)
   Console.ReadKey()
End With
Craig Johnson
  • 744
  • 4
  • 8
  • @OneFineDay, yes it would. – Craig Johnson Apr 24 '15 at 03:41
  • Thanks Craig. I was able to get the RGB averages from a file, but still can't find a way to use the Source of a Image control instead. Any tips on that? Also, like @OneFineDay mentioned, this double loop is kinda slow, would the conversion to `lockbits` bit too complicated? – Molx Apr 25 '15 at 00:58
  • One way or the other you will be traversing all the bytes, whether in a double loop or through a byte array. `.GetPixel` is where any adverse cost is incurred (it's still pretty zippy though). You have a WPF Image element? The function just accepts an Image object so it could be straight from your element instead of using `Drawing.Image.FromFile()` And yeah I will post a LockBits implementation as well. – Craig Johnson Apr 25 '15 at 01:17
  • Sorry for my insistence, but if I simply try `GetImageAverageRGB(imgCapture)` (`imgCapture` is the name of the Image object) I get `Value of type 'System.Windows.Controls.Image' cannot be converted to 'System.Drawing.Image'`. – Molx Apr 25 '15 at 01:23
  • @Molx sorry about that! See edits. I tried this from a WPF window with an image control and a loaded .PNG. If anyone has an easier, less roundabout way to do this, I'd love to know as well. – Craig Johnson Apr 25 '15 at 02:16
  • Still no go, unfortunately. I was able to compile and run, but I get an error once I try to get the Avg RGBs on the `Ctype` line: `Unable to cast object of type 'System.Windows.Interop.InteropBitmap' to type 'System.Windows.Media.Imaging.BitmapImage'.` I get a similar error from a clear project with a image (bmp and png) imported into an Image object. – Molx Apr 25 '15 at 03:24