WPF中显示图片的控件为Image控件。以下分别介绍显示图片文件和内存位图的使用方法,主要介绍如何赋予Image对象的Source属性值。
方式一:
方式二:
方式三:
Image image = new Image();
image .Width = 200;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit(); //给BitmapImage对象赋予数据的时候,需要用BeginInit()开始,用EndInit()结束
bitmapImage.UriSource = new Uri(@"D:\xxx.jpg");
bitmapImage.DecodePixelWidth = 320; //对大图片,可以节省内存。尽可能不要同时设置DecodePixelWidth和DecodePixelHeight,否则宽高比可能改变
bitmapImage.EndInit();
image .Source = bitmapImage;
有时常常不存在位图文件,而仅仅需要存在于内存中的位图,比如摄像头采集数据的每一帧,或者直接截取的屏幕位图(截屏)等等,那就不能采用以上集中方式了。WPF的Image控件的Source属性的类型为ImageSource,只要是继承自ImageSource的对象都可以赋值给Source属性。以上第一节所讲的采用的是BitmapImage,该类直接继承自BitmapSouce,间接继承自ImageSource.
为了能够将内存中的Bitmap位图显示在Image控件中,需要将Bitmap转换为ImageSource类型。通过查询与实践,总结了以下三种方式,分别为:利用不安全代码拷贝复制转换为WriteableBitmap,直接使用Imaging.CreateBitmapSourceFromHBitmap转换为BitmapSource,利用MemoryStream来创建BitmapImage。(注:个人主要比较前两种方式,第三种方式没有测试。经过测试,利用不安全代码拷贝复制的方式效率更高,对于640*480的位图,采用不安全代码拷贝的方式的时间消耗是另一种方法的25%左右,当然波动也较大)。对于确实性能要求比较高的,可以用不安全代码拷贝的方式,否则不建议用不安全代码拷贝,一方面是代码量比较多,另一方面必须要.net4.6或更高版本才可以使用Memory.Copy函数。
(1)利用不安全代码拷贝复制
Bitmap bmp=...
int bW=bmp.Width,bH=bmp.Height;
WriteableBitmap writableBmp = new WriteableBitmap(640, 480, 96, 96, PixelFormats.Bgra32, null);
//需要注意WriteableBitmap的长和宽不要小于需要拷贝的数据大小
int rPixelBytes = rWidth * rHeight * 4; //字节数
BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bW, bH), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
writableBmp.Lock();
unsafe
{
Buffer.MemoryCopy(data.Scan0.ToPointer(), writableBmp.BackBuffer.ToPointer(), rPixelBytes, rPixelBytes);
//Buffer.MemoryCopy需要在.net 4.6版本或更高版本上才可以使用,.net4.5不存在该方法。
//此外,如果不用Buffer.MemoryCopy,是否可以考虑使用Marshal中的其它方法---如Marshal.Copy,不知是否可行
}
writableBmp.AddDirtyRect(new Int32Rect(0, 0, bW, bH));
writableBmp.Unlock();
bmp.UnlockBits(data);
(2)直接使用Imaging.CreateBitmapSourceFromHBitmap()方法创建BitmapSource
Bitmap bitmap=...
IntPtr myImagePtr =bitmap.GetHbitmap(); //创建GDI对象,返回指针
BitmapSouce imgsource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(myImagePtr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions()); //创建imgSource
DeleteObject(myImagePtr);
//一定要卸载IntPtr,否则内存泄漏很快就没有内存了。网上有人说这个方法有时不能卸载掉,我短时间测试是能够及时卸载掉内存的。需要说明的是:DeleteObject()方法不会影响imgSource和bmp中的数据,也就是DeleteObject()执行过后,imgSource和bmp中的图像数据依然完整地存在。为了能使用DeleteObject方法,需要声明来自于gdi32的外部函数 [DllImport("gdi32")]static extern int DeleteObject(IntPtr o),参考MSDN说明https://msdn.microsoft.com/zh-tw/library/1dz311e4(v=vs.80).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-4
Image.Source=imgsource.
(3)利用MemoryStream来创建BitmapImage
此种方式主要参考jumtre转载的文章,先将Bitmap数据Save到Memory Stream中,然后再用MemoryStream来赋值给BitmapImage的StreamSource属性,从而间接将Bitmap对象转换为BitmapImage对象,具体参见https://blog.csdn.net/jumtre/article/details/16800273