在我们寻求帮助的时候,最不愿意听到的答复是:很抱歉,在当前版本的产品中还没有实现该功能... 在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控件了:

from: http://www.cnblogs.com/zhouyinhui/archive/2007/12/23/1011555.html

在WPF中显示动态GIF的更多相关文章

  1. WPF 中,动态创建Button,并使Button得样式按照自定义的Resource样式显示

    第一步:自定义一个Button的样式 1.新建一个xaml文件,在其中自定义好自己的Resources 这个Resource 的根节点是 <ResourceDictionary xmlns=&q ...

  2. 在JSP页面中显示动态时间

    源地址:http://blog.csdn.net/aitcax/article/details/41285305 静态时间: 1.页面首部添加 <%@page import="java ...

  3. 在WPF中显示GIF图片并实现循环播放

    WPF中有一个MediaElement媒体控件,可以来播放媒体,同时也可以显示GIF图片.但看到网上有些人说用MediaElement不能加载作为资源或内嵌的资源的GIF图片,我猜他们一定是在前台用X ...

  4. Wpf中显示Unicode字符

    1. 引言 今天在写一个小工具,里面有些字符用Unicode字符表示更合适.但是一时之间却不知道怎么写了.经过一番查找,终于找到了办法.记到这里,一是加深印象,二则以备查询. 2. C#中使用Unic ...

  5. C# 在winform或者wpf中显示控制台窗口

    这儿需要使用两个系统函数: BOOL WINAPI FreeConsole(void); //// 关闭控制台窗口,参考:http://msdn.microsoft.com/en-us/library ...

  6. WPF 图片显示中的保留字符问题

    在WPF中显示一张图片,本是一件再简单不过的事情.一张图片,一行XAML代码即可. 但是前段时间遇到了一件奇怪的事: 开发机上运行正常的程序,在某些客户机器上却显示不了图片,而且除了这个问题,其它运行 ...

  7. WPF中利用后台代码实现窗口分栏动态改变

    在WPF中实现窗口分栏并能够通过鼠标改变大小已经非常容易,例如将一个GRID分成竖排三栏显示,就可以将GRID先分成5列,其中两个固定列放GridSplitter. <Grid Backgrou ...

  8. WPF中动态更新TextBlock文字中的超链接,文本

    1.------------------------------------------------------------------------- 修改超链接的文本文字: <TextBloc ...

  9. WPF中TreeView控件数据绑定和后台动态添加数据(一)

    数据绑定: 更新内容:补充在MVVM模式上的TreeView控件数据绑定的代码. xaml代码: <TreeView Name="syntaxTree" ItemsSourc ...

随机推荐

  1. js写一个插件

    //;分号开头,用于防止代码压缩合并时与其它代码混在一起造成语法错误 //而事实证明,uglify压缩工具会将无意义的前置分号去掉,我只是习惯了这么写 //(function(){})();立即执行函 ...

  2. Zookeeper(二)Zookeeper原理与API应用

    一 Zookeeper概述 1.1 概述 Zookeeper是Google的Chubby一个开源的实现.它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务. 分布式同步.组服 ...

  3. 【58沈剑架构系列】主从DB与cache一致性

    本文主要讨论这么几个问题: (1)数据库主从延时为何会导致缓存数据不一致 (2)优化思路与方案 一.需求缘起 上一篇<缓存架构设计细节二三事>中有一个小优化点,在只有主库时,通过“串行化” ...

  4. awk练习总结

    >>> >>>awk是个优秀文本处理工具,可以说是一门程序设计语言.下面是awk内置变量. 一.内置变量表 属性 说明 $0 当前记录(作为单个变量) $1~$n ...

  5. Python全栈开发之7、模块和几种常见模块以及format知识补充

    一.模块的分类 Python流行的一个原因就是因为它的第三方模块数量巨大,我们编写代码不必从零开始重新造轮子,许多要用的功能都已经写好封装成库了,我们只要直接调用即可,模块分为内建模块.自定义的模块. ...

  6. SQL Server 执行计划分析

    当一个查询到达数据库引擎时,SQL Server执行两个主要的步骤来产生期望的查询结果: 第一步:查询编译,生成查询计划. 第二步:执行这个查询计划. 1. 用于演示分析执行计划的查询语句 /* 查询 ...

  7. 初识GeneXus产品

    本人由于工作原因接触了GeneXus产品,从使用到现在也有些年头了.从刚开始的不熟悉到现在使用GeneXus开发了很多项目,慢慢也总结了一些经验,当然中间也走了很多的弯路.对于在国内同样使用GeneX ...

  8. scrapy抓取拉勾网职位信息(八)——使用scrapyd对爬虫进行部署

    上篇我们实现了分布式爬取,本篇来说下爬虫的部署. 分析:我们上节实现的分布式爬虫,需要把爬虫打包,上传到每个远程主机,然后解压后执行爬虫程序.这样做运行爬虫也可以,只不过如果以后爬虫有修改,需要重新修 ...

  9. Hibernate 单项多对一的关联映射

    在日常开发中会出现很对多对一的情况,本文介绍hibernate中多对一的关联映射. 1.设计表结构 2.创建student对象 3.创建Grade对象 4.写hbm.xml文件 5.生成数据库表 生成 ...

  10. New Year Tree 【DFS序+线段树区间查询修改+二进制保存状态】

    题目链接[http://codeforces.com/problemset/problem/620/E] 题意:给出n个数,每个数有一个初始的颜色.由这n个数组成一颗树.有两种操作1.将以节点u为根的 ...