详解WPF如何使用WriteableBitmap提升Image性能
WriteableBitmap 背景
WriteableBitmap 继承至 System.Windows.Media.Imaging.BitmapSource
“巨硬” 官方介绍: WriteableBitmap 类
WriteableBitmap使用 类可按帧更新和呈现位图。 这对于生成算法内容(如分形图像)和数据可视化(如音乐可视化工具)非常有用。
类 WriteableBitmap 使用两个缓冲区。 后台缓冲区 在系统内存中分配,并累积当前未显示的内容。 前端缓冲区 在系统内存中分配,并包含当前显示的内容。 呈现系统将前缓冲区复制到视频内存中以供显示。
两个线程使用这些缓冲区。 用户界面 (UI) 线程生成 UI,但不会将其呈现在屏幕上。 UI 线程响应用户输入、计时器和其他事件。 一个应用程序可以有多个 UI 线程。 呈现线程编写和呈现来自 UI 线程的更改。 每个应用程序只有一个呈现线程。
UI 线程将内容写入后台缓冲区。 呈现线程从前缓冲区读取内容并将其复制到视频内存。 使用更改的矩形区域跟踪对后台缓冲区所做的更改。
调用其中 WritePixels 一个重载以自动更新和显示后台缓冲区中的内容。
为了更好地控制更新,并且要对后台缓冲区进行多线程访问,请使用以下工作流:
1.Lock 调用 方法以保留更新的后台缓冲区。
2.通过访问 属性获取指向后台缓冲区的 BackBuffer 指针。
3.将更改写入后台缓冲区。 锁定时 WriteableBitmap ,其他线程可能会将更改写入后台缓冲区。
4.AddDirtyRect 调用 方法以指示已更改的区域。
5.Unlock 调用 方法以释放后台缓冲区并允许在屏幕上演示。
6.将更新发送到呈现线程时,呈现线程会将更改后的矩形从后缓冲区复制到前缓冲区。 呈现系统控制此交换以避免死锁和重绘项目。
WriteableBitmap 渲染原理
在调用 WriteableBitmap 的 AddDirtyRect 方法的时候,实际上是调用 MILSwDoubleBufferedBitmap.AddDirtyRect,这是 WPF 专门为 WriteableBitmap 而提供的非托管代码的双缓冲位图的实现。
在 WriteableBitmap 内部数组修改完毕之后,需要调用 Unlock 来解锁内部缓冲区的访问,这时会提交所有的修改。
WriteableBitmap 使用技巧
1.WriteableBitmap 的性能瓶颈源于对脏区的重新渲染。
脏区为 0 或者不在可视化树渲染,则不消耗性能。
只要有脏区,渲染过程就会开始成为性能瓶颈。
- CPU 占用基础值就很高了。
- 脏区越大,CPU 占用越高,但增幅不大。
2.内存拷贝不是 WriteableBitmap 的性能瓶颈。
建议使用 Windows API 或者 .NET API 来拷贝内存数据。
特殊的应用场景,可以适当调整下自己写代码的策略:
- 如果你希望有较大脏区的情况下降低 CPU 占用,可以考虑降低 WriteableBitmap 脏区的刷新率。
- 如果你希望 WriteableBitmap 有较低的渲染延迟,则考虑减小脏区。
案例
测试 Demo 使用 OpenCvSharp 将视频帧读取出来,将视频帧图像数据通过 WriteableBitmap 渲染到界面的 Image 控件。
核心源码
核心代码,利用双缓存区更新位图图像信息
private void ShowImage() { Bitmap.Lock(); bitmap = frame.ToBitmap(); bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0); bitmap.UnlockBits(bitmapData); bitmap.Dispose(); Bitmap.Unlock(); }
完整的 ViewModel 代码
public class MainWindowViewModel : Prism.Mvvm.BindableBase { #region 属性、变量、命令 private WriteableBitmap _bitmap; /// <summary> /// UI绑定的资源对象 /// </summary> public WriteableBitmap Bitmap { get => _bitmap; set => SetProperty(ref _bitmap, value); } /// <summary> /// OpenCvSharp 视频捕获对象 /// </summary> private static VideoCapture videoCapture; /// <summary> /// 视频帧 /// </summary> private static Mat frame = new Mat(); private static BitmapData bitmapData = new BitmapData(); private static Bitmap bitmap; Int32Rect rect; static int width = 0, height = 0; /// <summary> /// 打开文件 /// </summary> public DelegateCommand OpenFileCommand { get; set; } public DelegateCommand MNCommand { get; set; } #endregion public MainWindowViewModel() { videoCapture = new VideoCapture(); OpenFileCommand = new DelegateCommand(OpenFile); MNCommand = new DelegateCommand(MN); } #region 私有方法 private void OpenFile() { OpenFileDialog open = new OpenFileDialog() { Multiselect = false, Title = "请选择文件", Filter = "视频文件(*.mp4, *.wmv, *.mkv, *.flv)|*.mp4;*.wmv;*.mkv;*.flv|所有文件(*.*)|*.*" }; if (open.ShowDialog() is true) { ShowMove(open.FileName); } } /// <summary> /// 获取视频 /// </summary> /// <param name="fileName">文件路径</param> private void ShowMove(string fileName) { videoCapture.Open(fileName, VideoCaptureAPIs.ANY); if (videoCapture.IsOpened()) { var timer = (int)Math.Round(1000 / videoCapture.Fps) - 8; width = videoCapture.FrameWidth; height = videoCapture.FrameHeight; Bitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgra32, null); rect = new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight); while (true) { videoCapture.Read(frame); if (!frame.Empty()) { ShowImage(); Cv2.WaitKey(timer); } } } } private void ShowImage() { Bitmap.Lock(); bitmap = frame.ToBitmap(); bitmapData = bitmap.LockBits(new Rectangle(new System.Drawing.Point(0, 0), bitmap.Size), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb); Bitmap.WritePixels(rect, bitmapData.Scan0, bitmapData.Height * bitmapData.Stride, bitmapData.Stride, 0, 0); bitmap.UnlockBits(bitmapData); bitmap.Dispose(); Bitmap.Unlock(); } }
测试结果
测试结果,经供参考,更精准的性能测试请使用专业工具。
- VS Debug模式下的性能监测,以及Windows任务管理器中的资源占用,可以看出各项资源的使用是比较稳定的。
- 发布之后独立运行资源的占用应该会有5%的降低。
以上就是详解WPF如何使用WriteableBitmap提升Image性能的详细内容,更多关于WPF WriteableBitmap的资料请关注脚本之家其它相关文章!
相关文章
浅谈C#中HttpWebRequest与HttpWebResponse的使用方法
本篇文章主要介绍了浅谈C#中HttpWebRequest与HttpWebResponse的使用方法,具有一定的参考价值,有兴趣的可以了解一下。2017-01-01
最新评论