在WPF中显示动态GIF(转)
?
?
http://www.silverlightchina.net/html/study/WPF/2011/0824/9965.html
?
?
在我們尋求幫助的時候,最不愿意聽到的答復是:很抱歉,在當前版本的產品中還沒有實現該功能...
在WPF中顯示動態的GIF圖像時便遇到了這樣的問題,WPF中強大的Image控件卻不支持動態的GIF(其只能顯示第一幀).當然,我們可以說WPF強大的動畫能力,讓我們完全有理由拋棄傳統的GIF動畫,但如某種情況下如果你覺得使用動態的GIF更合適的話(比如QQ表情,因為GIF是利于保存和傳輸的),沒關系,本篇隨筆將幫助你解決這個問題.
1,曾有過的嘗試:
?
我們在實際開發過程中也遇到顯示動態GIF的問題.發現普通的Image控件不能正常顯示后,我們又發現網頁瀏覽器卻是可以的,以及windows
XP的"圖片和傳真查看器"也可以,但"Window
Live照片庫"卻不可以.所以我們最初打算使用通過包裝WebBrowseControl來實現,即是在WPF中host一個.net2.0中的瀏覽器控件,然后讓該瀏覽器來實現圖片,成功了,但麻煩的事情是鼠標右鍵可以點出網頁的上下文菜單.我們放棄了該方案,除了不愿意花時間來屏蔽上下文菜單和瀏覽器控件的多余功能外,同時我們的覺得瀏覽器控件過于"重量級",有點殺雞用牛刀的感覺.另外,你可能會想到使用WPF中的Frame控件,但也會得到上述結果.另外,有網友說可以使用MediaElement控件,但大都沒有成功,我也沒有(可能是RP不夠哈,呵呵...)
?
2,GifBitmapDecoder
?
我們發現WPF中有一個名為GifBitmapDecoder的類,其可以將動態GIF分解成很多幀并保存在一個列表中,每一幀為一個BitmapFrame類型的對象,其父類為BitmapSource,這也就意味著,我們可以將每一幀賦值給一個Image控件的Source屬性,這樣我們可以得到針對GIF各幀的Image系列:
?
GifBitmapDecoder decoder = new GifBitmapDecoder(?????????????????????????? new Uri("OH.gif", UriKind.Relative),
?????????????????????????? BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
??????????? foreach (BitmapFrame f in decoder.Frames)
??????????? {
??????????????? Image image = new Image();
??????????????? image.Source = f;
??????????????? this.panel1.Children.Add(image);
??????????? }
?
?
?
下圖為將一個GIF圖片的12幀分解出來的所得到的一個系列圖:
?
?
?
?
?
不過先別高興,這還不足以解決我們的問題,因為我們不知道每一幀顯示的時間(幀與幀之間切換的時間間隔),以及一幀顯示結束后它的處理方法(是顯示下一幀嗎?是顯示背景色嗎?等等...)所以我們還必須一個字節一個字節的解析GIF文件以便得到足夠多的信息.
?
3,解析GIF
?
要解析文件就必須知道文件的存儲結構,關于動態GIF的文件存儲結構,可以參考這里:http://blog.zhongmoo.cn/post/45.html
?
比如,得到幀的顯示時間的方法是這樣的:
?
private int ParseGraphicControlExtension(byte[] gifData, int offset)??????? {
??????????? int returnOffset = offset;
??????????? // Extension Block
??????????? int length = gifData[offset + 2];
??????????? returnOffset = offset + length + 2 + 1;
??????????? byte packedField = gifData[offset + 3];
??????????? currentParseGifFrame.disposalMethod = (packedField & 0x1C) >> 2;
??????????? // Get DelayTime
??????????? int delay = BitConverter.ToUInt16(gifData, offset + 4);
??????????? currentParseGifFrame.delayTime = delay;
??????????? while (gifData[returnOffset] != 0x00)
??????????? {
??????????????? returnOffset = returnOffset + gifData[returnOffset] + 1;
??????????? }
??????????? returnOffset++;
??????????? return returnOffset;
??????? }
?
?
?
關于如何解析就不多介紹了,你只有了解其文件結構然后不斷地移動讀取游標和讀取相應的字節就可以完成了.
?
4,包裝成控件
?
我們想要的最佳效果是,打造一個GifImage控件,就跟Image控件差不多,只要我們指定它的Source屬性,然后其就自動查找GIF文件并讀取或下載,然后解析并顯示.
?
所以,我們將該控件分成了兩個部分,一個部分負責將根據用戶指定的Source屬性查找并讀取或從網絡下載GIF到內存流,然后另外一部分負責將得到的內存流解析并顯示出來.
?
gif文件在哪里?這是一個必須考慮到的問題,控件用戶指定的是一個絕對路徑嗎,還是一個相對路徑,是本地文件還是內嵌的資源文件或者是網絡上的文件.還有該文件一定支持GIF動畫嗎,還是只是一個普通的靜態圖片,所以負責讀取文件到內存流的代碼中應該有類似于下面的代碼:
?
if (source.Trim().ToUpper().EndsWith(".GIF") || ForceGifAnim)??????????? {
??????????????? if (!uri.IsAbsoluteUri)
??????????????? {
????????????????????
??????????????????? GetGifStreamFromPack(uri);
??????????????? }
??????????????? else
??????????????? {
??????????????????? string leftPart = uri.GetLeftPart(UriPartial.Scheme);
??????????????????? if (leftPart == "http://" || leftPart == "ftp://" || leftPart == "file://")
??????????????????? {
????????????????????????
??????????????????????? GetGifStreamFromHttp(uri);
??????????????????? }
??????????????????? else if (leftPart == "pack://")
??????????????????? {
?????????????????????????
??????????????????????? GetGifStreamFromPack(uri);
??????????????????? }
??????????????????? else
??????????????????? {
??????????????????????? //創建無動畫的普通Image
??????????????????????? CreateNonGifAnimationImage();
??????????????????? }
??????????????? }
??????????? }
??????????? else
??????????? {
??????????????? //創建無動畫的普通Image
??????????????? CreateNonGifAnimationImage();
??????????? }
??????? }
?
?
?
當讀取文件成功后,一切都好辦了,通過解析內存流中的數據,我們可以得到足夠多的信息,比如幀的列表,每幀顯示的時間以及該幀顯示完成后如何處理,那么我們就可以用一個計時器(DispatcherTimer)來處理這一切而形成一個動畫了.
?
/**//// <summary>??????? /// 從內存流中創建圖片
??????? /// </summary>
??????? public void CreateGifAnimation(MemoryStream memoryStream)
??????? {
??????????? Reset();
??????????? byte[] gifData = memoryStream.GetBuffer();??
??????????? GifBitmapDecoder decoder = new GifBitmapDecoder(memoryStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
??????????? numberOfFrames = decoder.Frames.Count;
??????????? try
??????????? {
??????????????? ParseGif(gifData);
??????????? }
??????????? catch
??????????? {
??????????????? throw new FileFormatException("Unable to parse Gif file format.");
??????????? }
??????????? for (int i = 0; i < decoder.Frames.Count; i++)
??????????? {
??????????????? frameList[i].Source = decoder.Frames[i];
??????????????? frameList[i].Visibility = Visibility.Hidden;
??????????????? canvas.Children.Add(frameList[i]);
??????????????? Canvas.SetLeft(frameList[i], frameList[i].left);
??????????????? Canvas.SetTop(frameList[i], frameList[i].top);
??????????????? Canvas.SetZIndex(frameList[i], i);
??????????? }
??????????? canvas.Height = logicalHeight;
??????????? canvas.Width = logicalWidth;
??????????? frameList[0].Visibility = Visibility.Visible;
??????????? for (int i = 0; i < frameList.Count; i++)
??????????? {
??????????????? Console.WriteLine(frameList[i].disposalMethod.ToString() + " " + frameList[i].width.ToString() + " " + frameList[i].delayTime.ToString());
??????????? }
??????????? if (frameList.Count > 1)
??????????? {
??????????????? if (numberOfLoops == -1)
??????????????? {
??????????????????? numberOfLoops = 1;
??????????????? }
??????????????? frameTimer = new System.Windows.Threading.DispatcherTimer();
??????????????? frameTimer.Tick += NextFrame;
??????????????? frameTimer.Interval = new TimeSpan(0, 0, 0, 0, frameList[0].delayTime * 10);
??????????????? frameTimer.Start();
??????????? }
??????? }
?
?
?
OK,我們可以像使用Image控件一樣來使用我們的GifImage控件了:
?
?
?
?
?
?
?
| 源碼下載 |
?
?
本文來自周銀輝的博客,原文地址:http://www.cnblogs.com/zhouyinhui/archive/2007/12/23/1011555.html
總結
以上是生活随笔為你收集整理的在WPF中显示动态GIF(转)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 选择or不选择苹果的理由
- 下一篇: Erlang的散列数据结构