原文地址:https://www.cnblogs.com/younShieh/p/11279420.html
前文
項目中遇到一個難題,需要將上百個沒有顯示出來的Canvas存儲為圖片保存在本地。
操作步驟應該是將Canvas轉換成位圖,然后將位圖轉換成本地圖片。所以一步一步的來吧。
位圖轉本地文件
查閱資料后(百度一下)后得知把位圖轉換為本地圖片進行保存可以通過BitmapSource來進行轉換,通過PngBitmapEncoder() 來實現。具體代碼如下:
//path為保存路徑
using (FileStream outStream = new FileStream(path, FileMode.Create))
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(outStream);
}
Canvas轉位圖
如何將位圖轉換成圖片的方法知道了,接下來就是要把Canvas轉換成Bitmap了。RenderTargetBitmap() 方法就可以實現Visual對象到位圖的轉換。查看Canvas是繼承於Visual的,所以理論上應該沒問題。嘗試了一下,確實如此。代碼如下:
RenderTargetBitmap bitmap = new RenderTargetBitmap((int)canvas.ActualWidth, (int)canvas.ActualHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(canvas);
Canvas測量和定位
理論上到這里就已經完了,但是我的Canvas並沒有顯示出來,導致ActualWidth和ActualHeight兩個屬性值都為0,且不能通過設置大小改變其實際尺寸,這就有點摳腦殼了。那就繼續查資料吧。對於ActualWidth和ActualHeight這兩個屬性,MSDN如是說:
此屬性是基於其他寬度輸入和布局系統的計算的值。
值由布局系統本身基於實際呈現的傳遞,設置,因此可能稍微小於屬性的設置值如Width是作為輸入更改的基礎。
因為ActualWidth是計算后的值,應注意可能有多次或遞增的報告更改為它作為各種操作結果由布局系統。布局系統可能會計算子元素所需的測量空間、父元素的約束等。
MSDN的機翻雖然前言不搭后語,但是大致能明白是什么意思。也就是說ActualWidth和ActualHeight是不能設置的,是通過實際呈現來自動計算出來的。但是我的Canvas沒有呈現出來啊,就不能被動計算出來了,這可怎么辦呢。。。不可以被動計算,就只有主動去設置,主動計算這條路可以走了。。。又是查閱資料后,還是發現了有路可走的。
通過學習大佬們的經驗得知,沒有顯示的界面也是可以轉換成位圖的,主要是需要去測量Measure() 和定位Arrange() 來設置Canvas的位置和大小,通過這兩個方法可以形成遞歸布局更新。也就是說通過這兩個方法,設置了父元素為子元素計算的最終大小,也就是實際的尺寸。
canvas.Measure(new Size(300, 300));
canvas.Arrange(new Rect(new Size(300,300)));
內存的優化
1. 理論上到這里就已經完了,但是我的Cancas們還有一個先決條件,就是他們有很多。(問題確實有點多。。。)我需要保存的Canvas數量太多,所以不負眾望,我的內存爆了(要爆啦~)。但是我已經做了自動回收,沒理由啊。 我嘗試查找了很多地方的的可能會出現的問題,毫不吝嗇的使用 GC.Collect(); Dispose(); using(),異步,並行,多線程 等等等方法,但是卻沒什么卵用。。。扣破腦殼,一段代碼一段代碼的屏蔽,分布排查到底是哪里的問題。我以為是位圖轉換導致的內存泄露,但是分步調試后發現其實並不怎么消耗內存,而且都做了合理的回收。慢慢調試后我發現原來是我的測量Measure() 和定位Arrange() 這兩個方法占用的大量的內存,而且應該是沒有回收到。
2. 首先我想的是我能不能不用這兩個方法。因為我的界面不能顯示,所以我要把他放到內存里面去渲染,如果不渲染的話,直接設置RenderTargetBitmap() 的 尺寸得到的是空白的圖(試過了。。)。查了很久的資料后,確實是沒有找到合適的辦法,能做到對不顯示的圖片不用Arrange() 方法就能轉換成位圖。所以這兩個方法我必須要用,所以也不能投機取巧了,只能硬着頭皮上了。
3. 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();
由此得到的教訓是,代碼還是得自己敲。。。不然掉到坑你都不知道怎么爬出來。。。
打完收工。