Handling Animations and Bitmaps Using GDI+ for Image Manipulation
Among all the formats of image files, GIF (Graphic Interchange Format) and BMP occupy a special place. GIF can support animation, while BMP's bitmapped nature sets it apart. GDI+ provides APIs to control the animation contained in a GIF as well as manipulate the bitmaps in a BMP.
Contributed by A.P.Rajshekhar Rating: / 20 March 20, 2007
In this discussion I will focus on controlling animated GIF and the basics of manipulating BMP images using GDI+. The first and section sections will concentrate on the animated GIF and the APIs for controlling it. The third section will cover working with bitmapped images. Finally, in the fourth section I will enhance the application developed in the previous part by adding the functionality to play animations when the selected image is an animated GIF. That's the agenda for this discussion.
By definition, "Animation is the rapid display of a sequence of 2-D artwork or model positions in order to create an illusion of movement." The articles I have written until now dealt with the manipulation of static images.
There are formats that can hold data for animation. These are AVI, MPEG, GIF and others. The images in these formats are known as animated images, as in the animated GIF. Animated images hold three kinds of data: images that have to be used, known as frames, and the order as well as the interval between the frames.
Though there are multiple formats that can handle animation, GDI+ currently supports multi-frames only in TIFF and GIF. Out of these I will concentrate solely on GIF. The following are the APIs provided by GDI+ for the animated GIF through the ImageAnimator class :
CanAnimate
Animate
StopAnimate
UpdateFrames
All of these methods are static. GDI+ doesn't provide any support for the creation of animated images. In this section and the next I will focus on each of these methods in turn.
CanAnimate is the first method that needs to be called to ensure that the image can be animated. It returns true if the image contains time-based frames. This method takes the reference of the image to be tested for time-based frames as an argument. The point to remember is that any animated image will contain time based frames. If the time-based frames are not there, it is a static image. When a static image is given as an argument, false is returned by the method.
For example, the following code checks for the animation capability of an image:
curImage = Image.FromFile(curFileName); if( ImageAnimator.CanAnimate(curImage) ) { //logic for animating the image } else MessageBox.Show("Image doesn't have frames");
Once it has been determined that an image contains time-based frames, then its animation can be controlled by controlling the frames. The Animate method is invoked to accomplish this goal. It takes two arguments, an image and an event handler. The image refers to the current image and the event handler is called when the frame changes. In other words, the Animate method controls the animation by triggering the event handler passed as an argument on the change of frames within the passed image parameter. In code it would be:
if (!currentlyAnimating) { //Begin the animation only once. ImageAnimator.Animate(animatedImage, new EventHandler(this.OnFrameChanged)); currentlyAnimating = true; } private void OnFrameChanged(object o, EventArgs e) { //Force a call to the Paint event handler. this.Invalidate(); }
In the above example, the Animate method is given two parameters. These are the animatedImage, which is the image to be animated, and an event handler referenced by this.OnFrameChanged. The handler invalidates the current rendered image to draw the new frame, thus providing an "animation."
Just as the name suggests, the StopAnimate method stops the animation; in effect, it's the reverse of StartAnimate(). The parameters accepted by this method are the same as those for StartAnimate, namely the image to animate and the handler to be called when the frame changes. The following code illustrates the point:
if(curImage != null) { ImageAnimator.StopAnimate(curImage, new EventHandler(this.OnFrameChanged)); }
where curImage is the reference of the image to be animated and OnFrameChanged is the handler to be called which is define as
private void OnFrameChanged(object o, EventArgs e) { //Force a call to the Paint event handler. this.Invalidate(); }
UpdateFrames is used to prepare the next frame for rendering. There are two versions of the method. One accepts an image reference as a parameter, while the other version doesn't accept any parameter. If the former is used, only the frame in the reference image is advanced i.e. prepared for rendering. When the latter is used, it advances the frame in all images currently being animated i.e. if there is more than one image being animated, frames in all the images are prepared for rendering. The actual rendering is done when the next Paint event occurs. The following code explains the point:
protected override void OnPaint(PaintEventArgs e) { //Begin the animation. //call the method to start animation //Get the next frame ready for rendering. ImageAnimator.UpdateFrames(); //Draw the next frame in the animation. e.Graphics.DrawImage(this.animatedImage, new Point(0, 0)); }
Once the Animate method is called, the UpdateFrames method is called to prepare the next frame for animation.
That brings us to the end of this section. In the next section the focus will be on the basics of bitmap manipulation.
By definition, a Bitmap is "a data file or structure representing a generally rectangular grid of pixels, or points of color, on a computer monitor, paper, or other display device." In other words, a bitmapped image corresponds bit by bit to the image displayed on the screen. So a BMP (bitmapped) file stores data in pixel format. GDI+ provides functionalities to manipulate BMP through the Bitmap class which is a derivation of the Image class. Hence, it provides all the functionalities provided by the Image class and adds its own functionalities to work with BMP images. The basic functionalities provided by the Bitmap class are:
Loading the BMP Image
Viewing the BMP Image
Since the BMP images differ in the way that data is stored (it's pixel based), the way the images are loaded and viewed is also different.
To load a BMP image, an object of the Bitmap class needs to be created. Such an object can be created using a constructor of Bitmap class in the following ways:
From File:
In this case, a new Bitmap object is created from an existing file. The existing file can be in any of the GDI+ supported formats. For example, the following code instantiates a Bitmap object from a GIF file:
Bitmap curBitmap = new Bitmap("myfile.gif");
From an object of Image class:
A Bitmap object can also be created from an existing image object. Just as with creating an object from a file, all the supported GDI+ formats based on the Image object can be used. The following code creates a Bitmap object from an Image object which in turn is created from a GIF file:
Image curImage = Image.FromFile("myfile.gif"); Bitmap curBitmap = new Bitmap(curImage);
From an object of the Image class with a specified size:
This is the specialized form of the previous method. In this case, the object being created can be assigned dimensions (width and height) at the time of instantiation. The following statement illustrates this point:
Bitmap curBitmap3 =new Bitmap(curImage, new Size(200, 100) );
An empty Bitmap object:
When no Image reference or file name is provided, an empty Bitmap object is created. The following statement does the same:
Bitmap curBitmap = new Bitmap(200, 100);
Apart from the constructor, the Bitmap class provides two static methods that can be used to create BMP from icons (.ico files) and resources (.res files). They are FromHicon and FromResource. Both use handles to icons or resources to create a Bitmap object from icons and resources. That completes the ways to load BMP. Next we'll see what we need to do to view the loaded BMP.
Viewing BMP Image using the Bitmap class is just like viewing any other image using the Image class. Once the Bitmap object is created, it has to be passed to the DrawImage method. The following code illustrates this point:
Graphics g = this.CreateGraphics(); Bitmap bitmap = new Bitmap("myfile.jpg"); g.DrawImage(bitmap, 20, 20); g.Dispose();
That completes this section. In the next section I will enhance the image viewer application to include a GIF animation control menu.
Now let's implement the animation control into the application that was being built during previous parts of this series. The enhancements will provide the following functionalities:
Start the animation if the GIF contains time based frames.
Stop the animation.
The following are the menus that correspond to the functionalities:
mnuAnimationStart
mnuAnimationStop
The handler for the mnuAnimationStart is as follows:
Here I am checking whether or not the image contained in the curImage object has frames, because the rendering of other images also happens in OnPaint.
And here is the event handler referenced by the Animate and StopAnimate methods:
private void OnFrameChanged(object o, EventArgs e) { this.Invalidate(); }
Whenever the animation has to be started or stopped (in other words, the next frame has to be rendered), OnFrameChanged is called internally by the Animate and StopAnimate methods, which in turn invalidate the current drawing region.
This brings us to the end of the discussion on animation control and the basics of handling BMP files. In the next part I will discuss advanced aspects of BMP images and other image manipulation techniques such as skewing. Till then...