詳解WPF如何使用WriteableBitmap提升Image性能
WriteableBitmap 背景
WriteableBitmap 繼承至 System.Windows.Media.Imaging.BitmapSource
“巨硬” 官方介紹: WriteableBitmap 類
WriteableBitmap使用 類可按幀更新和呈現(xiàn)位圖。 這對于生成算法內(nèi)容(如分形圖像)和數(shù)據(jù)可視化(如音樂可視化工具)非常有用。
類 WriteableBitmap 使用兩個緩沖區(qū)。 后臺緩沖區(qū) 在系統(tǒng)內(nèi)存中分配,并累積當前未顯示的內(nèi)容。 前端緩沖區(qū) 在系統(tǒng)內(nèi)存中分配,并包含當前顯示的內(nèi)容。 呈現(xiàn)系統(tǒng)將前緩沖區(qū)復制到視頻內(nèi)存中以供顯示。
兩個線程使用這些緩沖區(qū)。 用戶界面 (UI) 線程生成 UI,但不會將其呈現(xiàn)在屏幕上。 UI 線程響應用戶輸入、計時器和其他事件。 一個應用程序可以有多個 UI 線程。 呈現(xiàn)線程編寫和呈現(xiàn)來自 UI 線程的更改。 每個應用程序只有一個呈現(xiàn)線程。
UI 線程將內(nèi)容寫入后臺緩沖區(qū)。 呈現(xiàn)線程從前緩沖區(qū)讀取內(nèi)容并將其復制到視頻內(nèi)存。 使用更改的矩形區(qū)域跟蹤對后臺緩沖區(qū)所做的更改。
調用其中 WritePixels 一個重載以自動更新和顯示后臺緩沖區(qū)中的內(nèi)容。
為了更好地控制更新,并且要對后臺緩沖區(qū)進行多線程訪問,請使用以下工作流:
1.Lock 調用 方法以保留更新的后臺緩沖區(qū)。
2.通過訪問 屬性獲取指向后臺緩沖區(qū)的 BackBuffer 指針。
3.將更改寫入后臺緩沖區(qū)。 鎖定時 WriteableBitmap ,其他線程可能會將更改寫入后臺緩沖區(qū)。
4.AddDirtyRect 調用 方法以指示已更改的區(qū)域。
5.Unlock 調用 方法以釋放后臺緩沖區(qū)并允許在屏幕上演示。
6.將更新發(fā)送到呈現(xiàn)線程時,呈現(xiàn)線程會將更改后的矩形從后緩沖區(qū)復制到前緩沖區(qū)。 呈現(xiàn)系統(tǒng)控制此交換以避免死鎖和重繪項目。
WriteableBitmap 渲染原理
在調用 WriteableBitmap 的 AddDirtyRect 方法的時候,實際上是調用 MILSwDoubleBufferedBitmap.AddDirtyRect,這是 WPF 專門為 WriteableBitmap 而提供的非托管代碼的雙緩沖位圖的實現(xiàn)。
在 WriteableBitmap 內(nèi)部數(shù)組修改完畢之后,需要調用 Unlock 來解鎖內(nèi)部緩沖區(qū)的訪問,這時會提交所有的修改。
WriteableBitmap 使用技巧
1.WriteableBitmap 的性能瓶頸源于對臟區(qū)的重新渲染。
臟區(qū)為 0 或者不在可視化樹渲染,則不消耗性能。
只要有臟區(qū),渲染過程就會開始成為性能瓶頸。
- CPU 占用基礎值就很高了。
- 臟區(qū)越大,CPU 占用越高,但增幅不大。
2.內(nèi)存拷貝不是 WriteableBitmap 的性能瓶頸。
建議使用 Windows API 或者 .NET API 來拷貝內(nèi)存數(shù)據(jù)。
特殊的應用場景,可以適當調整下自己寫代碼的策略:
- 如果你希望有較大臟區(qū)的情況下降低 CPU 占用,可以考慮降低 WriteableBitmap 臟區(qū)的刷新率。
- 如果你希望 WriteableBitmap 有較低的渲染延遲,則考慮減小臟區(qū)。
案例
測試 Demo 使用 OpenCvSharp 將視頻幀讀取出來,將視頻幀圖像數(shù)據(jù)通過 WriteableBitmap 渲染到界面的 Image 控件。
核心源碼
核心代碼,利用雙緩存區(qū)更新位圖圖像信息
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(); } }
測試結果
測試結果,經(jīng)供參考,更精準的性能測試請使用專業(yè)工具。
- VS Debug模式下的性能監(jiān)測,以及Windows任務管理器中的資源占用,可以看出各項資源的使用是比較穩(wěn)定的。
- 發(fā)布之后獨立運行資源的占用應該會有5%的降低。
以上就是詳解WPF如何使用WriteableBitmap提升Image性能的詳細內(nèi)容,更多關于WPF WriteableBitmap的資料請關注腳本之家其它相關文章!