65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF 中处理带透明度的动画 GIF 的另一种方法

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.09/5 (8投票s)

2008年10月31日

CPOL

3分钟阅读

viewsIcon

117041

downloadIcon

7546

演示了在 WPF 应用程序中使用动画 GIF。

WpfAnimatedControlScreenshot.jpg

引言

尽管 WPF 对动画的支持很丰富,但我还没有找到一种快速简便的方法在 WPF 应用程序中插入动画 GIF。在网上搜索并分析收集到的信息后,我发现每个公开可用的解决方案都存在以下一个或多个缺点:

  1. 使用不安全的代码(是的,又回到了老式的 BitBlt!)
  2. 线程不安全。
  3. 对 WPF 布局、拉伸、缩放等的支持不完善或不完整。
  4. 不支持透明度。
  5. 不支持在 XAML 中使用。
  6. 无法使用“嵌入式”资源(只支持外部 GIF 文件)。
  7. 设计过于复杂和“沉重”(例如,使用动态生成内容的浏览器控件等)。

本文介绍了我尝试解决这个问题的方法,并考虑了上述问题。希望框架的未来版本能默认提供此功能。

工作原理及代码设计

主要思想很简单:扩展标准的 WPF Image 控件以接受动画 GIF,将 GIF 分割成帧,从每一帧构建 BitmapSource 并将其“保存在内部”,然后动态切换源以模拟动画。

让我们更深入地了解代码:我创建了一个自定义控件 AnimatedImage,它基于 WPF Image 控件,并添加了一个类型为 Bitmap 的附加依赖属性 AnimatedBitmap。这保留了原始 WPF 图像的所有功能,如缩放、布局、事件等,并且可以轻松地直接从 XAML 进行设置。自定义路由事件 AnimatedBitmapChanged 被注册,以便在设置源 GIF 时提供一些额外的初始化/清理操作(如果需要)。实际上,主要功能集中在以下函数中:

private void UpdateAnimatedBitmap()
{
  int nTimeFrames = AnimatedBitmap.GetFrameCount
        (System.Drawing.Imaging.FrameDimension.Time);
  _nCurrentFrame = 0;
  if (nTimeFrames > 0)
  {
    _BitmapSources = new BitmapSource[nTimeFrames];
    for (int i = 0; i < nTimeFrames; i++)
    {
      AnimatedBitmap.SelectActiveFrame(
           System.Drawing.Imaging.FrameDimension.Time, i);
      Bitmap bitmap = new Bitmap(AnimatedBitmap);
      bitmap.MakeTransparent();
      _BitmapSources[i] = 
        System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
         bitmap.GetHbitmap(),
         IntPtr.Zero,
         Int32Rect.Empty,
         System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
     }
     StartAnimate();
   }
}

在这里,帧从源 GIF 中检索出来,然后每一帧都被设置为透明,转换为 BitmapSource,并保存在内部缓冲区中。最后,我们调用 StartAnimate() 辅助函数来启动动画过程。为了控制动画,使用了一个内部的 ImageAnimator 控件,并通过 ChangeSource() 回调,该回调只做一件事:切换 Image 控件的活动源并发送一个更新外观的请求。这里使用的 BeginInvoke 是为了线程安全。

private delegate void VoidDelegate();

private void OnFrameChanged(object o, EventArgs e)
{
  Dispatcher.BeginInvoke(DispatcherPriority.Render, 
        new VoidDelegate(delegate { ChangeSource(); }));
}

void ChangeSource()
{
  Source = _BitmapSources[_nCurrentFrame++];
  _nCurrentFrame = _nCurrentFrame % _BitmapSources.Length;
  ImageAnimator.UpdateFrames();
}

如前所述,还有两个辅助函数 StartAnimate()StopAnimate() 以及一个属性 IsAnimating,用于对 AnimatedImage 进行基本控制。

public void StopAnimate()
{
  if (_bIsAnimating)
  {
    ImageAnimator.StopAnimate(AnimatedBitmap, 
         new EventHandler(this.OnFrameChanged));
    _bIsAnimating = false;
  }
}

public void StartAnimate()
{
  if (!_bIsAnimating)
  {
    ImageAnimator.Animate(AnimatedBitmap, 
                  new EventHandler(this.OnFrameChanged));
    _bIsAnimating = true;
  }
}

private bool _bIsAnimating;
public bool IsAnimating
{
   get { return _bIsAnimating; }
}

使用代码

按照步骤 1a 或 1b,然后是步骤 2,在 XAML 文件中使用此自定义控件。

步骤 1a。使用此自定义控件作为源

  • WpfAnimatedControl 项目添加到您的工作区。
  • 在将要使用此控件的标记文件的根元素中添加 XmlNamespace 属性。
  • xmlns:MyNamespace="clr-namespace:WpfAnimatedControl"

步骤 1b。将此自定义控件用作 DLL

在将要使用此控件的标记文件的根元素中添加 XmlNamespace 属性。

xmlns:MyNamespace="clr-namespace:WpfAnimatedControl;assembly=WpfAnimatedControl"

您还需要从包含 XAML 文件的项目添加一个项目引用到此程序集,并重新生成以避免编译错误。

步骤 2。继续在 XAML 文件中使用您的控件。

要将 AnimatedImage 添加到窗口,请将以下行插入您的窗口 XAML 文件中,例如,如下所示:

< MyNamespace:AnimatedImage Name="aimg" 
    AnimatedBitmap="{x:Static code:Resources.wrong}" 
    Stretch="None" AnimatedBitmapChanged="aimg_AnimatedBitmapChanged"/>

这里我们假设您已经将 GIF 插入到窗口资源中。

提供的测试应用程序(VS2008,Framework 3.5)演示了该控件的常见用法,以及从外部文件设置 GIF。

警告

请注意,从 XAML 设置 Source 属性和 AnimatedBitmap 属性时要小心;通常,它被设计为仅在没有动画运行时使用 Source。也就是说,如果您需要静态图片,最好使用原始的 WPF Image,并且在使用 AnimatedImage 控件时不要设置 Source

结论

这只是一个非常基础的动画图像控件实现,但如果您需要“仅仅显示一个透明的动画 GIF”,它可能会节省大量时间。随时可以根据需要扩展它,添加额外的验证、功能等。

历史

  • 2008年10月31日:初始发布。
© . All rights reserved.