2012.12.18更新:修復下載鏈接
已知WPF的Image元素只能顯示GIF圖片的第一幀,而MediaElement不能加載作為資源或內嵌的資源的GIF圖片,所以網上有幾種實現方法。
我抄襲網上提供的方法,改頭換面后作為自己的GifImage實現。本文的前半部分介紹我的GifImage實現;后半部分做實驗,將我的GifImage和網上現存的幾種Gif支持方法做性能上的比較。
GifImage
我抄襲了這些地方提供的代碼:
- http://www.cnblogs.com/zhouyinhui/archive/2007/12/23/1011555.html 作者周銀輝
- http://www.codeproject.com/Articles/71891/WPF-GIF-Animation 作者asprodotru
GifImage繼承自FrameworkElement,添加了Source、Stretch、StretchDirection依賴項屬性,用法就跟標准Image元素差不多。從GIF里分解出各幀及其延續時間后,我在OnRender里自行繪制,並啟動DispatcherTimer計時,以便按時繪制下一幀。
解析GIF需要GifFormat類的幫助。GifFormat的構造函數需要Stream對象,構造函數認為從該Stream對象中可以讀到gif文件,然后按字節解析。
GIF圖片是由很多幀構成的,每一幀有延續時間、處置方法、左邊、上邊等屬性,當然還有最重要的圖像數據。GifFrame類就代表GIF圖片里的幀。
經GifFormat解析后的數據可由LogicalScreenWidth、LogicalScreenHeight和GetFrames方法獲得。
每當設置Source屬性,如果是gif圖片,就會重新創建一個新的GifFormat,然后啟動timer。
當然,Source URI的方案是多種多樣的,GifImage支持http、ftp、file、pack。
顯示GIF的兩個重點在MeasureOverride和OnRender方法,它們考慮了Stretch、StretchDirection、Width、Height等屬性。
比較
周銀輝的GifImageLib提供對GIF圖片的支持。他的GifImage的繼承鏈是FrameworkElement <-Control <-ContentControl <-UserControl<-GifImage。他的GifImage內含Canvas,Canvas內含N個Image,每個Image顯示GIF圖片的一幀。(N等於GIF圖片的幀數)設置GifImage.Source來顯示GIF圖片。
asprodotru的WpfAnimatedControl中的AnimatedImage是用來顯示gif圖片的。它繼承自Image元素,通過按時改變Image.Source以實現動畫效果。(GIF里的有些幀需要與前一幀疊加才能顯示出正確的影像,我不知道他只設置Image.Source為單一幀怎么保證正確顯示的,我還沒完全看懂。)設置AnimatedBitmap或調用LoadSmile方法來顯示圖片。
測試配置
測試用兩幅gif圖片,如下。

wrong.gif,19.3KB

oh.gif,51.2KB
代碼如下
- <Window xmlns:my="clr-namespace:Gqqnbig.Windows.Controls;assembly=GifImage" x:Class="WpfApplication2.MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="MainWindow" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="187" d:DesignWidth="349">
- <Viewbox>
- <UniformGrid Columns="10" Name="grid"/>
- </Viewbox>
- </Window>
- public partial class MainWindow : Window
- {
- public MainWindow()
- {
- InitializeComponent();
- for (int i = 0; i < 100; i++)
- {
- //Gqqnbig.Windows.Controls.GifImage image = new Gqqnbig.Windows.Controls.GifImage();
- //image.Source = "pack://Application:,,,/OH.gif";
- //grid.Children.Add(image);
- //GifImageLib.GifImage image = new GifImageLib.GifImage();
- //image.Source = "pack://Application:,,,/OH.gif";
- //grid.Children.Add(image);
- //WpfAnimatedControl.AnimatedImage image = new WpfAnimatedControl.AnimatedImage();
- //var im = System.Drawing.Bitmap.FromFile("OH.gif");
- //image.LoadSmile((System.Drawing.Bitmap)im);
- //grid.Children.Add(image);
- }
- }
- }
取樣方法
兩幅gif圖片都進行測試。
運行后,每分鍾記錄一次CPU占用率、private bytes[1]、working set[2]的大小。共記錄三次。
測試結果
| 我的GifImage | GifImageLib | AnimatedControl | |||||||
| CPU | Private bytes | Working set | CPU | Private bytes | Working set | CPU | Private bytes | Working set | |
| 第一次測量 | 9 | 95136 | 80768 | 11 | 94780 | 82096 | 3 | 100248 | 94428 |
| 第二次測量 | 2 | 94320 | 80767 | 2 | 93568 | 82000 | 3 | 100224 | 94420 |
| 第三次測量 | 6 | 93952 | 80492 | 3 | 94576 | 82036 | 0 | 100116 | 94416 |
| 平均 | 5.67 | 94469.33 | 80675.67 | 5.33 | 94308.00 | 82044.00 | 2.00 | 100196.00 | 94421.33 |
使用wrong.gif
| 我的GifImage | GifImageLib | AnimatedControl | |||||||
| CPU | Private bytes | Working set | CPU | Private bytes | Working set | CPU | Private bytes | Working set | |
| 第一次測量 | 6 | 110792 | 102612 | 0 | 110520 | 103032 | 3 | 244320 | 237968 |
| 第二次測量 | 3 | 109188 | 102100 | 8 | 109772 | 103328 | 3 | 245356 | 238148 |
| 第三次測量 | 4 | 109132 | 102088 | 7 | 109664 | 103280 | 3 | 247668 | 238524 |
| 平均 | 4.33 | 109704.00 | 102266.67 | 5.00 | 109985.33 | 103213.33 | 3.00 | 245781.33 | 238213.33 |
使用oh.gif
結論
從CPU占用率來看,我的實現和周銀輝的實現不分仲伯;而通過改變Image.Source的AnimatedControl效率最高,應該得益於WPF的內部優化。
從Working set(工作集)來看,我的實現略優於周銀輝的GifImageLib;AnimatedControl則大很多,在第二次實驗時竟然是另兩者的兩倍還多。
下載代碼
代碼包括測試代碼、我的Gifimage、GifImageLib、AnimatedControl。測試代碼版權沒有,GifImageLib版權參考http://www.cnblogs.com/zhouyinhui/archive/2007/12/23/1011555.html,AnimatedControl版權是CPOL。
地址:https://www.box.com/files#/files/0/f/66672665/1/f_5203962625
愛讓一切都對了
本文(不含程序代碼)以3.0協議發布
[1] Working Set看成一個進程可以用到(但不一定會使用)的物理內存。即不引起page fault異常就能夠訪問的內存。 Working Set包含了可能被其他程序共享的內存, 例如DLL就是一個典型的可能被其他程序共享的資源。所以所有進程的Working Set加起來有可能大於實際的物理內存。
[2] Private Bytes是只被本進程用占用的虛擬地址空間,不包括其他進程共享的內存。Private Bytes既包括不引起page fault異常就能夠訪問的內存也包括引起page fault異常才能夠訪問的內存。所以一般Private Bytes大於Working Set。但是如果一個進程和其他進程共享較多內存,也可能造成Working Set大於Private Bytes。(摘自http://blog.csdn.net/fw0124/article/details/6367360)
from:http://blog.csdn.net/gqqnb/article/details/7213449
