[WPF疑难]在WPF中显示动态GIF
点击次数:24 次 发布日期:2008-11-26 10:36:07 作者:源代码网
|
源代码网推荐 在我们寻求帮助的时候,最不愿意听到的答复是:很抱歉,在当前版本的产品中还没有实现该功能... 在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控件了: 源代码网推荐 源代码网推荐 做人要厚道,请注明转自酷网动力(www.ASPCOOL.COM)。 源代码网推荐 源代码网供稿. |
