a perfect workaround support gif in Wpf

本文的思路是通过传统GDI+的方式显示gif图片,优点是简单方便易用,缺点是要引入System.Drawing,另外显示到UI上还是要经过GDI+的图片到WPF图片的一次转换,这一点同时带来了些微性能上的消耗。

Source Code

cs:

public class GifImage : System.Windows.Controls.Image
    {
        public string UriSource
        {
            get { return (string)GetValue(UriSourceProperty); }
            set { SetValue(UriSourceProperty, value); }
        }

        public static readonly DependencyProperty UriSourceProperty = DependencyProperty.Register("UriSource", typeof(string), typeof(GifImage), 
            new FrameworkPropertyMetadata("",  new PropertyChangedCallback(OnUriSourceChanged)));

        private static void OnUriSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GifImage obj = (GifImage)d;
            obj.CreateFromSourceString((string)e.NewValue);
        }

        public Bitmap GdiPlusBitmapSource
        {
            get { return (Bitmap)GetValue(GdiPlusBitmapSourceProperty); }
            set { SetValue(GdiPlusBitmapSourceProperty, value); }
        }

        public static readonly DependencyProperty GdiPlusBitmapSourceProperty = DependencyProperty.Register("GdiPlusBitmapSource", typeof(Bitmap), typeof(GifImage),
            new FrameworkPropertyMetadata(null,  new PropertyChangedCallback(OnGdiPlusBitmapSourceChanged)));

        private static void OnGdiPlusBitmapSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            GifImage obj = (GifImage)d;
            obj.CreateFromBitmap((Bitmap)e.OldValue, (Bitmap)e.NewValue);
        }

        private void CreateFromBitmap(Bitmap oldSource, Bitmap source)
        {
            if (null == source)
            {
                this.Source = null;
                return;
            }
            
            if (null != oldSource)
            {
                ImageAnimator.StopAnimate(oldSource, null);
            }

            Width = GdiPlusBitmapSource.Width;
            Height = GdiPlusBitmapSource.Height;

            if (!GdiPlusBitmapSource.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif))
            {
                GetBitmapSource();
                return;
            }

            ImageAnimator.Animate(GdiPlusBitmapSource, OnFrameChanged);
        }

        private void CreateFromSourceString(string source)
        {
            Uri uri;

            try
            {
                uri = new Uri(source, UriKind.RelativeOrAbsolute);
            }
            catch (Exception)
            {
                return;
            }

            if (!uri.IsAbsoluteUri) //如果是相对Uri
            {
                if (!CreateBitmapFromStreamResource(GetFromResource(uri)))  //不成功说明不在资源中
                {
                    CreateBitmapFromFile();
                }
                
                return;
            }

            if (uri.GetLeftPart(UriPartial.Scheme) == "pack://")
            {
                CreateBitmapFromStreamResource(GetFromResource(uri));
                return;
            }

            CreateBitmapFromFile();

        }

        private bool CreateBitmapFromFile()
        {
            try
            {
                GdiPlusBitmapSource = Image.FromFile(UriSource) as Bitmap;
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        private bool CreateBitmapFromStreamResource(StreamResourceInfo streamInfo)
        {
            if (null == streamInfo)
            {
                return false;
            }

            if (!streamInfo.ContentType.Contains("image"))
            {
                return false;
            }

            try
            {
                GdiPlusBitmapSource = Image.FromStream(streamInfo.Stream) as Bitmap;
            }
            catch (Exception)
            {
                return false;
            }
            return true;
        }

        private StreamResourceInfo GetFromResource(Uri uri)
        {
            StreamResourceInfo streamInfo = Application.GetContentStream(uri);
            if (null == streamInfo)
            {
                streamInfo = Application.GetResourceStream(uri);
            }
            return streamInfo;
        }

        private void OnFrameChanged(object sender, EventArgs e)
        {
            Dispatcher.BeginInvoke(new Action(() => {
                if (null == GdiPlusBitmapSource)
                {
                    return;
                }

                ImageAnimator.UpdateFrames(sender as Bitmap);
                GetBitmapSource();
            }));
        }

        private void GetBitmapSource()
        {
            IntPtr inptr = GdiPlusBitmapSource.GetHbitmap();    
            try
            {
                Source = Imaging.CreateBitmapSourceFromHBitmap(inptr, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            }
            finally
            {
                DeleteObject(inptr);
            }
        }

        [DllImport("gdi32")]
        static extern int DeleteObject(IntPtr o);

    }

  

代码是改良了网上现存的一个程序而来。

我做出的修改如下:

1. 支持显示非gif图片。

2. 加入了UriSource以及GdiPlusBitmapSource 两个DependencyProperty以便支持pack url 以及 xaml banding。

3. 可动态修改GdiPlusBitmapSource 或UriSource以切换显示图片

Usages

<gif:GifImage UriSource="c://2.gif"></gif:GifImage>

  or

<gif:GifImage UriSource="2.gif"></gif:GifImage>

  or

<gif:GifImage UriSource="pack://application:,,,/2.gif"></gif:GifImage>

  or

<gif:GifImage x:Name="gifImage"></gif:GifImage>
codebehind:
gifImage.GdiPlusBitmapSource = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile("c://2.gif");

note: 使用流或文件形式创建Bitmap对象时,请勿进行销毁流或删除文件、覆盖文件等操作,否则会出现GDI+错误

Performance

  <ListBox x:Name="lb">
            <ListBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid Columns="10">
                        
                    </UniformGrid>
                </ItemsPanelTemplate>
            </ListBox.ItemsPanel>
            <ListBox.ItemTemplate>
                <DataTemplate>

                    <gif:GifImage UriSource="pack://application:,,,/2.gif"></gif:GifImage>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

code behind

    List<int> list = new List<int>();
            for (int i = 0; i < 100; i++)
            {
                list.Add(i);
            }
            lb.ItemsSource = list;

  

CPU占用率从13%~16%不等

本机的配置

台式机,显卡集成。

转载请注明出处。

posted on 2011-08-26 16:34  陈儒  阅读(503)  评论(1编辑  收藏  举报