Looped GIF Animations in GDI+
Animated image display in GDI+ is fairly simple. They give you a special ImageAnimator class that internally stores a 50 ms timer to figure out when frames in an image should be updated, throws an event to control the update process in case you need to do something, and then gives you control to advance the frame. So let's take a basic animated gif and see how easy it would be to animate.
Image animatedImage = Image.FromFile("MyAnimation.gif");
if ( ImageAnimator.CanAnimate(animatedImage) ) {
ImageAnimator.Animate(animatedImage, new EventHandler(this.Image_FrameChanged));
}
public void Image_FrameChanged(object sender, EventArgs e) {
// Try to call the image overload if possible
ImageAnimator.UpdateFrames();
}
Now depending on how you are drawing the image (usually in a Paint handler for some sort of control) whenever UpdateFrames is called the next portion of the image will be made ready to display. This'll give you basic animation. One of the problems I've found when doing this is that all animations loop forever. ImageAnimator has no concept of a loop count, nor does it allow access to any properties that define how many loops have occured, what the current frame is, or how many frames are available in the animation. All of this has to be added by the programmer (total ballz if you ask me). So what can we do to help with this? Well, we have to define our own AnimationInfo structure to store the information needed to count loops. This is already being stored privately within the GDI+ code, they just don't give you access. So here goes, a class to hold loop counts, frame counts, get the total number of frames based on an Image, and a frame advancement method.
private class AnimationInfo {
private int currentLoop = 0;
private int currentFrame = 0;
private int totalFrames = 0;
public AnimationInfo(Image image) {
this.currentLoop = 0;
this.currentFrame = 0;
this.totalFrames = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
}
public AnimationInfo(int currentLoop, int currentFrame, int totalFrames) {
this.currentLoop = currentLoop;
this.currentFrame = currentFrame;
this.totalFrames = totalFrames;
}
public void UpdateFrames() {
currentFrame++;
if ( currentFrame >= totalFrames ) {
currentFrame = 0;
currentLoop++;
}
}
public int CurrentLoop { get { return this.currentLoop; } }
public int CurrentFrame { get { return this.currentFrame; } }
public int TotalFrames { get { return this.totalFrames; } }
}
Now we have the ability to control how many loops we do. But we still need to jump through some hoops to figure out how MANY loops to do. Thankfully the Image class has the ability to get extended image properties like loop count for animated gifs, it just isn't very accessible. The following code will investigate a file in order to determine if an animation should be infinite or finite based on the artists original intention.
public bool SetAnimationLoopsFromImage() {
if ( this.internalImage.RawFormat.Equals(ImageFormat.Gif) ) {
int loops = 0; // 0 will be infinite
PropertyItem propLoop = this.internalImage.GetPropertyItem(20737);
if ( propLoop == null ) {
loops = 1; // Some packages will remove the prop when loop should be only 1
} else {
loops = propLoop.Value[0];
loops += propLoop.Value[1] << 8;
if ( loops != 0 ) {
loops++; // Don't ask me, I guess it is part of the spec
}
}
this.animationMode = AnimationMode.ImageAnimation;
this.animationLoops = loops;
return true;
}
return false;
}
So tying all this together, we can now loop an animation based on data within a file and control how many times the animation loops before finally settling down. The remaining code looks as follows and would be part of the code used to derive a new animated picture box based on the System.Windows.Forms.Control class. You could use a PictureBox, but it does some work when you attach an image to initialize the ImageAnimator and you don't want the PictureBox code stepping on you.
private void Image_FrameChanged(object sender, EventArgs e) {
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs pe) {
if ( animationState == AnimationState.Animating ) {
if ( animationLoops == 0 || this.animationInfo.CurrentLoop < animationLoops ) {
this.animationInfo.UpdateFrames();
Console.WriteLine(this.animationInfo.CurrentFrame);
Console.WriteLine(this.animationInfo.CurrentLoop);
ImageAnimator.UpdateFrames(this.internalImage);
} else {
Stop(true);
}
}
GraphicsUnit gu = GraphicsUnit.Pixel;
pe.Graphics.DrawImage(
this.internalImage,
this.internalImage.GetBounds(ref gu));
base.OnPaint(pe);
}
Hopefully this is somewhat enlightening for users out there that want basic animated buttons without having to resort to sprite animation and/or DirectX. Animated gifs already have the ability to be extremly compressed, so much so that only very small regions of the image might be updated during a frame change. Using this to your advantage can result in some very professional looking hover effects or status indicators. If I get the chance I'll more fully define a control that provides all of the functionality define in the article. I currently have one, but I'm still not satisfied with the feature set, the API interface, or the documentation to let it loose on the world.