【WPF】大量Canvas轉換為本地圖片遇到的問題

從南到北ss發表於2019-07-31

原文地址:https://www.cnblogs.com/younShieh/p/11279420.html

  專案中遇到一個難題,需要將上百個沒有顯示出來的Canvas儲存為圖片儲存在本地。
1 . 查閱資料後(百度一下)後得知儲存為本地圖片可以通過BitmapSource的轉換,通過PngBitmapEncoder() 來實現。具體程式碼如下:

//path為儲存路徑
using (FileStream outStream = new FileStream(path, FileMode.Create))
{
    PngBitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
    encoder.Save(outStream);
}

2 . 如何將點陣圖轉換成圖片的方法知道了,接下來就是要把Canvas轉換成Bitmap了。RenderTargetBitmap() 方法就可以實現Visual物件到點陣圖的轉換。檢視Canvas是繼承於Canvas的,所以理論上應該沒問題。嘗試了一下,確實如此。程式碼如下:

 RenderTargetBitmap bitmap = new RenderTargetBitmap((int)canvas.ActualWidth, (int)gd.ActualHeight, 96, 96, PixelFormats.Pbgra32);
 bitmap.Render(canvas);

3 . 理論上到這裡就已經完了,但是我的Canvas並沒有顯示出來,導致ActualWidth和ActualHeight兩個屬性值都為0,且不能通過設定大小改變其實際尺寸,這就有點摳腦殼了。那就繼續查資料吧。對於ActualWidth和ActualHeight這兩個屬性,CSDN如是說:

此屬性是基於其他寬度輸入和佈局系統的計算的值。
值由佈局系統本身基於實際呈現的傳遞,設定,因此可能稍微小於屬性的設定值如Width是作為輸入更改的基礎。

因為ActualWidth是計算後的值,應注意可能有多次或遞增的報告更改為它作為各種操作結果由佈局系統。佈局系統可能會計運算元元素所需的測量空間、父元素的約束等。

儘管您不能設定此屬性從XAML,您可以基於Trigger樣式中其值。

  也就是說ActualWidth和ActualHeight是不能設定的,是通過實際呈現來自動計算出來的。但是我的Canvas沒有呈現出來啊,就不能被動計算出來了,這可怎麼辦呢。。。不可以被動計算,就只有主動去設定,主動計算這條路可以走了。。。又是查閱資料後,還是發現了有路可走的。

https://stackoverflow.com/questions/5189139/how-to-render-a-wpf-usercontrol-to-a-bitmap-without-creating-a-window

  通過學習大佬們的經驗得知,沒有顯示的介面也是可以轉換成點陣圖的,主要是需要去測量Measure() 和定位Arrange() 來設定Canvas的位置和大小,通過這兩個方法可以形成遞迴佈局更新。也就是說通過這兩個方法,設定了父元素為子元素計算的最終大小,也就是實際的尺寸。

canvas.Measure(new Size(300, 300));
canvas.Arrange(new Rect(new Size(300,300)));

4 . 理論上到這裡就已經完了,但是我的Cancas們還有一個先決條件,就是他們有很多。(問題確實有點多。。。)我需要儲存的Canvas數量太多,所以不負眾望,我的記憶體爆了(要爆啦~)。但是我已經做了自動回收,沒理由啊。 我嘗試查詢了很多地方的的可能會出現的問題,毫不吝嗇的使用 GC.Collect(); Dispose(); using() 等等等方法,但是卻沒什麼卵用。。。扣破腦殼,一段程式碼一段程式碼的遮蔽,分佈排查到底是哪裡的問題。我以為是點陣圖轉換導致的記憶體洩露,但是分步除錯後發現其實並不怎麼消耗記憶體,而且都做了合理的回收。慢慢除錯後我發現原來是我的測量Measure() 和定位Arrange() 這兩個方法佔用的大量的記憶體,而且應該是沒有回收到。

5 . 首先我想的是我能不能不用這兩個方法。因為我的介面不能顯示,所以我要把他放到記憶體裡面去渲染,如果不渲染的話,直接設定RenderTargetBitmap() 的 尺寸得到的是空白的圖(試過了。。)。查了很久的資料後,確實是沒有找到合適的辦法,能做到對不顯示的圖片不用Arrange() 方法就能轉換成點陣圖。所以這兩個方法我必須要用,所以也不能投機取巧了,只能硬著頭皮上了。

6 . baidu、google輪番上陣,MSDN、stackoverflow各路齊飛,,,都沒有找到合適的辦法解決。或許沒有遇到我這麼特殊情況的人吧,也有可能是我的搜尋方式有問題。不過,正在我扣破腦殼之際,我想到會不會Canvas 也有對應的Dispose() 方法呢?(原諒我已經暈了,“Dispose()只能用於繼承於IDisposable類”的知識點早已飛到九霄雲外)不過,Canvas確實沒有的Dispose() 方法,,,傷心,絕望。那會不會有Arrange() 對應的 Dispose() 方法呢?嘗試在Canvas後輸入Arrage,得到了一個 InvalidateArrange() 方法:

使元素排列狀態(佈局)無效。 排列狀態失效後,該元素將更新其佈局,更新將以非同步方式發生,除非隨後由 System.Windows.UIElement.UpdateLayout強制執行。

  使元素排列狀態(佈局)無效,不就是釋放佈局佔用的記憶體資源了?嘗試了一下,果然如此。記憶體佔用情況再也不是“一行白鷺上青天”了。。。唉,記憶體爆了的問題就此得以解決,媽媽再也不用擔心我的軟體崩潰啦~~~

附程式碼如下:

canvas.Measure(new System.Windows.Size(1920, 1080));
canvas.Arrange(new Rect(0, 0, 1920, 1080));
renderBitmap.Render(canvas);
canvas.InvalidateArrange();
canvas.InvalidateMeasure();
canvas.UpdateLayout();

由此得到的教訓是,程式碼還是得自己敲。。。不然掉到坑你都不知道怎麼爬出來。。。

打完收工。

相關文章