Introduction
In spite of reach support of animation in WPF, I have not found a quick and easy way to insert animated GIFs in a WPF application. After some searching in the net and analyzing the gathered information, I have assumed that every public available solution has one ore more of the following disadvantages:
- Use of unsafe code (yes, back to good old BitBlt!)
- Thread unsafe.
- No or incomplete support of WPF layout, stretching, zoom, etc.
- No support for transparency.
- Not supported in XAML.
- Impossible to use “embedded” resources (only external GIF files supported).
- Too complicated and “heavy” design (for example, browser control with dynamically generated content, etc.)
This article presents my trial to solve the problem, taking into account the above mentioned issues. Hope that a future version of the framework brings us such functionality by default.
How it works and design of the code
The main idea is just simple: extend the standard WPF Image
control to accept animated GIF, split the GIF into frames, constructing BitmapSource
s from each frame and keeping them “inside”, then dynamically switching sources to simulate animation.
Let’s look a bit deeper in code: I have created a custom control AnimatedImage
, based on the WPF Image
control, with an additional Dependency Property AnimatedBitmap
of type Bitmap
. This saves the full functionality of the original WPF image like scaling, layout, events etc., and makes it easy to set everything directly from XAML. The custom routed event AnimatedBitmapChanged
was registered to provide some extra initialization/cleanup during setting the source GIF, if necessary. Actually, the main functionality is concentrated in the following function:
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();
}
}
Here, the frames are retrieved from the source GIF, then each frame is made transparent, converted to a BitmapSource
, and saved into an internal buffer. Finally, we call the StartAnimate()
helper to start the animation process. For controlling animation, an internal ImageAnimator
control is used, with the ChangeSource()
callback, doing nothing but just switching the active source of the Image
control and sending an update appearance request. The BeginInvoke
used here is for thread safety.
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();
}
As I mentioned, there are also two helpers StartAnimate()
and StopAnimate()
and a property IsAnimating
to give the basic control over the 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; }
}
Using the code
Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
Step 1a. Using this custom control with sources:
- Add the WpfAnimatedControl project to your workspace.
- Add the
XmlNamespace
attribute to the root element of the markup file where it is to be used:
xmlns:MyNamespace="clr-namespace:WpfAnimatedControl"
Step 1b. Using this custom control as DLL:
Add the XmlNamespace
attribute to the root element of the markup file where it is to be used:
xmlns:MyNamespace="clr-namespace:WpfAnimatedControl;assembly=WpfAnimatedControl"
You will also need to add a project reference from the project where the XAML file lives to this assembly, and Rebuild to avoid compilation errors.
Step 2. Go ahead and use your control in the XAML file.
To add AnimatedImage
to a window, insert the following line to your window XAML file, for example, as shown below:
< MyNamespace:AnimatedImage Name="aimg"
AnimatedBitmap="{x:Static code:Resources.wrong}"
Stretch="None" AnimatedBitmapChanged="aimg_AnimatedBitmapChanged"/>
Here we assume that you already insertedthe GIF in the window resources.
The provided test application (VS2008, Framework 3.5) demonstrates the common use of the control, and also setting the GIF from an external file.
Warning
Be careful to set the Source
property together with the AnimatedBitmap
property from XAML; generally, it was designed to use Source
only if no animation runs. I.e., if you need a static picture, better use the original WPF Image
, and do not set the Source
if you are using the AnimatedImage
control.
Conclusion
This is only a very basic implementation of an animated image control, but could save a lot of time if you need to “just show a transparent animated GIF”. Feel free to extend it with extra validation, functionality, and so on.
History
- 31.10.2008: Initial release.