How to create a BitmapImage from a pixel byte array (live video display)
bitmapimage, c#, video-capture, wpf
Solution
This should do the trick. it's super fast.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
using System.ComponentModel;
public class MakeBitmapSource
{
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, uint Length);
public static BitmapSource FromNativePointer(IntPtr pData, int w, int h, int ch)
{
PixelFormat format = PixelFormats.Default;
if (ch == 1) format = PixelFormats.Gray8; //grey scale image 0-255
if (ch == 3) format = PixelFormats.Bgr24; //RGB
if (ch == 4) format = PixelFormats.Bgr32; //RGB + alpha
WriteableBitmap wbm = new WriteableBitmap(w, h, 96, 96, format, null);
CopyMemory(wbm.BackBuffer, pData, (uint)(w * h * ch));
wbm.Lock();
wbm.AddDirtyRect(new Int32Rect(0, 0, wbm.PixelWidth, wbm.PixelHeight));
wbm.Unlock();
return wbm;
}
public static BitmapSource FromArray(byte[] data, int w, int h, int ch)
{
PixelFormat format = PixelFormats.Default;
if (ch == 1) format = PixelFormats.Gray8; //grey scale image 0-255
if (ch == 3) format = PixelFormats.Bgr24; //RGB
if (ch == 4) format = PixelFormats.Bgr32; //RGB + alpha
WriteableBitmap wbm = new WriteableBitmap(w, h, 96, 96, format, null);
wbm.WritePixels(new Int32Rect(0, 0, w, h), data, ch * w, 0);
return wbm;
}
}
Problem
I need to display live images on a WPF control. I'm looking for the fastest way to do this using WPF. I'm capturing images from a camera using its dll API (AVT). The image is writen by the dll and the camera rises a callback with an IntPtr to a Image struct called tFrame (described below). The pixel data is stored at the ImageBuffer propertie with is an InPtr to a byte array. I know how to create a Bitmap from the pixel byte array, but not a BitmapImage. So it is possible to create a Bitmap and then create a BitmapImagem from it. Here there is a way to create a BitmapImage from a Bitmap on the memory. But I want to create the BitmapImage directly from the data source (tFrame). How can I do that? I know that BitmapImage have a CopyPixels method, but it laks of a SetPixels. ``` public struct tFrame { public IntPtr AncillaryBuffer; public uint AncillaryBufferSize; public uint AncillarySize; public tBayerPattern BayerPattern; public uint BitDepth; public tFrameCtx Context; public tImageFormat Format; public uint FrameCount; public uint Height; public IntPtr ImageBuffer; public uint ImageBufferSize; public uint ImageSize; public uint RegionX; public uint RegionY; public tErr Status; public uint TimestampHi; public uint TimestampLo; public uint Width; } ``` Here is how I create a Bitmap from a pixel byte array. This was used at the WinForm version of the software. ``` private void CreateBitmap(tFrame frame) { //This sample is for a 8bpp captured image PixelFormat pxFormat = PixelFormat.Format8bppIndexed; //STRIDE //[https://stackoverflow.com/questions/1983781/why-does-bitmapsource-create-throw-an-argumentexception/1983886#1983886][3] //float bitsPerPixel = System.Drawing.Image.GetPixelFormatSize(format); int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF; //Number of bits used to store the image data per line (only the valid data) int validBitsPerLine = ((int)frame.Width) * bitsPerPixel; //4 bytes for every int32 (32 bits) int stride = ((validBitsPerLine + 31) / 32) * 4; Bitmap bmp = new Bitmap((int)frame.Width, (int)frame.Height, stride, pxFormat, frame.ImageBuffer); } ``` EDIT 1: Thanks to dr.mo, now I'm able to display 60 FPS 1024x1024 images with ~3% CPU usage! What I'm doing is: ``` //@ UI Thread public WriteableBitmap wbm = new WriteableBitmap(1024, 1024, (double)96, (double)96, System.Windows.Media.PixelFormats.Gray8, null); this.wbBackBuffer = this.wbm.BackBuffer; //This can be called by a timer in the UI thread or at the grab Thread for every image, the CPU usage is almost the same. void UpdateDisplayImage() { wbm.Lock(); wbm.AddDirtyRect(new Int32Rect(0, 0, wbm.PixelWidth, wbm.PixelHeight)); wbm.Unlock(); } //@ Grab Thread //Update the backbuffer with new camera image data. UpdateBackBuffer(...); /// <summary> /// [http://msdn.microsoft.com/en-us/library/system.windows.media.imaging.writeablebitmap.aspx] /// </summary> public void UpdateBackBuffer(IntPtr pData, int w, int h, int ch) { //Can not acess wbm from outside UI thread //CopyMemory(wbm.BackBuffer, pData, (uint)(w * h * ch)); //I dont know if it is safe to write to it buffer like this: CopyMemory(this.wbBackBuffer, pData, (uint)(w * h * ch)); } ```