WPF 中处理带透明度的动画 GIF 的另一种方法
演示了在 WPF 应用程序中使用动画 GIF。
引言
尽管 WPF 对动画的支持很丰富,但我还没有找到一种快速简便的方法在 WPF 应用程序中插入动画 GIF。在网上搜索并分析收集到的信息后,我发现每个公开可用的解决方案都存在以下一个或多个缺点:
- 使用不安全的代码(是的,又回到了老式的 BitBlt!)
- 线程不安全。
- 对 WPF 布局、拉伸、缩放等的支持不完善或不完整。
- 不支持透明度。
- 不支持在 XAML 中使用。
- 无法使用“嵌入式”资源(只支持外部 GIF 文件)。
- 设计过于复杂和“沉重”(例如,使用动态生成内容的浏览器控件等)。
本文介绍了我尝试解决这个问题的方法,并考虑了上述问题。希望框架的未来版本能默认提供此功能。
工作原理及代码设计
主要思想很简单:扩展标准的 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日:初始发布。