Click here to Skip to main content
15,890,185 members
Articles / Mobile Apps
Article

NGif, Animated GIF Encoder for .NET

Rate me:
Please Sign up or sign in to vote.
4.02/5 (58 votes)
1 Sep 2005CPOL 1.5M   18K   117   123
Create animated GIF images using C#.

Sample Image - NGif.gif

Introduction

Because .NET Framework can't create animated GIF images, NGif provides a way to create GIF animations in the .NET framework. It can create an animated GIF from several images and extract images from an animated GIF.

Using the code

C#
/* create Gif */
//you should replace filepath
String [] imageFilePaths = new String[]{"c:\\01.png","c:\\02.png","c:\\03.png"}; 
String outputFilePath = "c:\\test.gif";
AnimatedGifEncoder e = new AnimatedGifEncoder();
e.Start( outputFilePath );
e.SetDelay(500);
//-1:no repeat,0:always repeat
e.SetRepeat(0);
for (int i = 0, count = imageFilePaths.Length; i < count; i++ ) 
{
 e.AddFrame( Image.FromFile( imageFilePaths[i] ) );
}
e.Finish();
/* extract Gif */
string outputPath = "c:\\";
GifDecoder gifDecoder = new GifDecoder();
gifDecoder.Read( "c:\\test.gif" );
for ( int i = 0, count = gifDecoder.GetFrameCount(); i < count; i++ ) 
{
 Image frame = gifDecoder.GetFrame( i ); // frame i
 frame.Save( outputPath + Guid.NewGuid().ToString() 
                       + ".png", ImageFormat.Png );
}

Points of Interest

Use Stream to replace BinaryWriter when you write a fixed-byte structured binary file.

History

  • 31 Aug 2005: Draft.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralRe: Added output to a MemoryStream Pin
ssh59110-Dec-06 20:21
ssh59110-Dec-06 20:21 
GeneralRe: Added output to a MemoryStream Pin
dB.19-Feb-07 11:33
dB.19-Feb-07 11:33 
GeneralSuggestions Pin
The_Mega_ZZTer1-Sep-05 17:46
The_Mega_ZZTer1-Sep-05 17:46 
QuestionRe: Suggestions Pin
ammarmujeeb30-Jan-06 2:10
ammarmujeeb30-Jan-06 2:10 
AnswerRe: Suggestions Pin
tianlupan214-Aug-06 5:45
tianlupan214-Aug-06 5:45 
AnswerRe: Suggestions Pin
pedro.pandre17-Aug-06 1:56
pedro.pandre17-Aug-06 1:56 
GeneralRe: Suggestions Pin
pedro.pandre17-Aug-06 2:03
pedro.pandre17-Aug-06 2:03 
AnswerRe: Suggestions [modified] Pin
Phoenixillusion2-Mar-07 23:08
Phoenixillusion2-Mar-07 23:08 
I have done a bit of optimizing since I downloaded the source today. So far I have run into one picture that would not process correctly (it rendered and was a valid GIF for other programs, but the picture was skewed), so it's not quite fine tuned so far.

The main drawbacks that are present in the code are that is designed to handle a lot of colors really well, but is hard coded to do so. Every frame includes an index of 256 colors(768 byte color table) and starts LZW compression hard coded at 9 bit code size (when GIF forces a reset of the compression at 12 bit, back to 9 bit in this case).

First things first, though this has nothing to do with file size I thought I would mention what I believe is the unsafe code:
<code>
byte[] pixels = new Byte[3 * image.Width * image.Height];
Bitmap tempBitmap = new Bitmap(image);
BitmapData bitmapData = tempBitmap.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
Marshal.Copy(bitmapData.Scan0, pixels, 0, pixels.Length);
tempBitmap.UnlockBits(bitmapData);
</code>
After this, you will be dealing with BGR pixels (bitmaps standard).
When referring to these in the code I usually used something to the manner of
<code>
private int getPixel(int index, byte[] pixels)
{
return pixels[index*3]<<16+pixels[index*3+1]<<8+pixels[index*3+2];
}
</code>
If you are interested in shrinking the table size, this will require a slight re-write of the color analysis and the palette writing. I decided to use an ArrayList to store my palettes and keep track of number of actual colors that existed in the image. I would probably suggest a Hash Table however, with the current count of the hash table stored as the value so that you can take advantage of the hashing... I do not know how well this would increase speed, and as of now most questions are related to size concern.

During the analyzing phase, you can loop through all the pixels in the array and keep track of how many actual colors are present in the image. In the following example the ArrayList is named smallTab(small table).
<code>
for(int i=0;i<nPix;i++)
{
int c = getPixel(i, pixels);
if (!smallTab.Contains(c))
smallTab.Add(c);
if (smallTab.Count == 256)
break;
}
...
for (int i = 0; i < nPix; i++)
g.indexedPixels[i] = getSmallIndex(g.getPixel(i),g);
</code>

To get the values used for the palSize and bitDepth, I sort of did some very crude coding. PalSize is BitDepth-1, and these two values are used for defining the length of the local color table and initializing the LZW compression algorithm respectively. The following code just tests for what the highest bit is and stores it for those algorithms.
<code>
colorTableSize = smallTab.Count-1;
int a = 0;
for (int i = 0; i < 8; i++)
if ((colorTableSize & (1 << i)) != 0) a = i;
colorDepth = a + 1;
palSize = a;
</code>

This will shrink the size of the LZW compression by allowing a lot more slots for it's dictionary before it has to reset. To have the color palette printed out correctly, you have to print out the ArrayList and then fill in all of the blanks that follow (this was another section that was hard coded). The reason that the bytes are printed out from least to most significant is because I preserved BGR color throughout the program instead of converting it around a lot.
<code>
foreach (int color in smallTab)
{
fs.WriteByte((byte)(color & 0xFF));//R
fs.WriteByte((byte)((color & 0xFF00) >> 8));//G
fs.WriteByte((byte)((color & 0xFF0000) >> 16));//B
}
int remain = (1 << (palSize + 1)) - colorTableSize - 1;
for (int i = 0; i < (remain); i++)
{
fs.WriteByte(0);
fs.WriteByte(0);
fs.WriteByte(0);
}
</code>

To do a lot of the other optimization methods that are used on gif, you have to have the ability to store either the current status of the image (a method I am going to attempt) that has the pixel data for what the image should appear like in that frame, or at the very least the previous frame along with the current frame. You compare the two images and zero out, or set to whatever transparent color you wish, all of the bytes that are the same. What you are left with is a much less detailed frame that that will have a lot of the same color with will shrink in LZW compression.

To do this, it helps to create objects for each frame, so that you can associate settings with individual bitmap data, and then write them all out at once. This allows you to also flag which ones you wish to set to the global color table, so that during the pixel analysis routine these use the colorTable of the parent GifAnimationEncoder instead of their own local table.

A final method that can reduce image size a little bit (not as much as the transparency comparison which can reduce color count did) is that once you have done the comparison, you can scan the rows and columns of the resulting pixel array to see how far left,right,top,and bottom the resulting picture actually is. You can copy this to a new array, reset the height and width values, and then change The Image Descriptor values:

<code>
protected void WriteImageDesc()
{
fs.WriteByte(0x2c); // image separator
WriteShort(left,fs); // image position x,y = 0,0
WriteShort(top, fs);
WriteShort(width, fs); // image size
WriteShort(height, fs);
// packed fields
byte b = (byte)palSize;
if (!useGlobalTable) b += 0x80;
fs.WriteByte(b);
}
</code>

Using this shrink wrap method, color palette reduction, and global/local color table control, I was able to create a 1000 frame, 300 x 100 px animation (black and white, so not really impressive) for 76.2k. That is 9.76k image descriptors, 7.8k Graphics Control data... so 58.64 k for the images about. Changing to 3 or 4 colors added only 14k, still averaging quite a small amount. Actually saving a 256 color image that has a change of over 200 x 200 px does add quite a bit of time to the process and did yield a 14.2 mb file... I have yet to see if transparency comparison will help at all. Using global color table only saved ~750k

As for time saving, it is a helpful feature to add to the code where you can cache an array of pixel data that has LZW compression if you will be using that frame again later at any X,Y coordinate, such as scrolling or repeated animation and movement over a fixed color background. I am unsure if Disposal Method 3 would allow it over a background image.



-- modified at 5:22 Saturday 3rd March, 2007
GeneralRe: Suggestions Pin
Jim Hunt15-Jun-07 8:22
Jim Hunt15-Jun-07 8:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.