Rick van den Bosch - Blog

... on software development, architecture and more

HOWTO: create an animated GIF using .Net (C#)

.Net (at least 1.1, they might incorporate it in 2.0) does not give you possibilities to create animated GIFs through GDI+. But there are ways to make them! This solution is one I used myself, and I'm very pleased!

//Variable declaration
StringCollection stringCollection;
MemoryStream memoryStream;
BinaryWriter binaryWriter;
Image image;
Byte[] buf1;
Byte[] buf2;
Byte[] buf3;
//Variable declaration

stringCollection = a_StringCollection_containing_images;

Response.ContentType = "Image/gif";
memoryStream = new MemoryStream();
buf2 = new Byte[19];
buf3 = new Byte[8];
buf2[0] = 33;  //extension introducer
buf2[1] = 255; //application extension
buf2[2] = 11;  //size of block
buf2[3] = 78;  //N
buf2[4] = 69;  //E
buf2[5] = 84;  //T
buf2[6] = 83;  //S
buf2[7] = 67;  //C
buf2[8] = 65;  //A
buf2[9] = 80;  //P
buf2[10] = 69; //E
buf2[11] = 50; //2
buf2[12] = 46; //.
buf2[13] = 48; //0
buf2[14] = 3;  //Size of block
buf2[15] = 1;  //
buf2[16] = 0;  //
buf2[17] = 0;  //
buf2[18] = 0;  //Block terminator
buf3[0] = 33;  //Extension introducer
buf3[1] = 249; //Graphic control extension
buf3[2] = 4;   //Size of block
buf3[3] = 9;   //Flags: reserved, disposal method, user input, transparent color
buf3[4] = 10;  //Delay time low byte
buf3[5] = 3;   //Delay time high byte
buf3[6] = 255; //Transparent color index
buf3[7] = 0;   //Block terminator
binaryWriter = new BinaryWriter(Response.OutputStream);
for (int picCount = 0; picCount < stringCollection.Count; picCount++)
{
  
image = Bitmap.FromFile(stringCollection[picCount]);
  
image.Save(memoryStream, ImageFormat.Gif);
  
buf1 = memoryStream.ToArray();

  
if (picCount == 0)
  
{
      //only write these the first time....
     
binaryWriter.Write(buf1, 0, 781); //Header & global color table
      binaryWriter.Write(buf2, 0, 19); //Application extension
  
}

   binaryWriter.Write(buf3, 0, 8); //Graphic extension
   binaryWriter.Write(buf1, 789, buf1.Length - 790); //Image data

   if (picCount == stringCollection.Count - 1)
   {
      //only write this one the last time....
     
binaryWriter.Write(";"); //Image terminator
   }

   memoryStream.SetLength(0);
}
binaryWriter.Close();
Response.End();



Edit
To illustratie te poor quality I'm talking about, I've added an animated gif created dynamically using the code above, based on a directory containing some source images. Look at the 'raster' in the darker images... (the source images were solid filled 200 x 200 GIF's, created using Paint.Net, where they look fine)





Edit2
Damn Paint.Net! I make a test-image: looks great. I save it as a GIF: still looks great. Use it in my anigif-code: no more greatness. I reopen the file in Paint.Net: then it's fubar! Creating the testfiles in oldskool Paint solves this (because you only have GIF-supported colors in your toolbar)...

When you are converting a file to a GIF, old Paint warns you because you might be losing color information, Paint.Net does not. Paint reloads the file the way you saved it so you see what remains. Paint.Net does not. These are two wannahaves on Paint.Net for me!



Edit3
Thanks to Dennis for pointing out a small glitch in my type-work.
It should have been 'picCount == stringCollection.Count - 1'.

Comments

Rick van den Bosch said:

Is the quality so bad, can you add an example?
# May 10, 2005 12:00 PM

Rick van den Bosch said:

I am glad to hear it is solved now. Give Paint.NET some feedback about it and hope it gets solved.
# May 11, 2005 10:23 AM

Rick van den Bosch said:

Looking forward to trying this code...
# May 17, 2005 2:29 PM

Rick van den Bosch said:

Victor, will you let me know how it turned out?
# May 17, 2005 2:45 PM

Rick van den Bosch said:

Rick, thank you! I've been searching for a way of doing this for a while, and if not for you (and Scott Swigart, who pointed me your way), I had given up.

This is yet another great example of how the people really using .NET know more than the product team (who told me it wasn't possible).

Thank you!
# May 27, 2005 2:12 AM

Rick van den Bosch said:

Hey Kent,

I'm glad my blog could be of service. I think the people of the product team mean it's not possible with GDI+ by saying: these are the GIF's, now make me an animated one!

But when you know the file format, why not make it yourself?? ;)
# May 27, 2005 7:39 AM

Rick van den Bosch said:

Our GIF saving is better looking in Paint.NET v2.1, but our GIF support will be more comprehensive in v2.2 with a preview showing what the saved image will look like. Maybe even quantization options. We'll see what we've got time for.
# June 5, 2005 12:54 AM

Rick van den Bosch said:

I tried the code, and it compiles, but does not make an animated GIF that IE can display. One issue is this line: if (stringCollection.Count == stringCollection.Count - 1)

I think it should be if (picCount == stringCollection.Count - 1)
since the Count will never = Count - 1

My test was with 7 images of about 20K each. Still trying to figure out why the image won't display.
# June 14, 2005 2:40 PM

Rick van den Bosch said:


I believe this line is wrong


//only write this one the last time....
binaryWriter.Write(";"); //Image terminator

it should be
binaryWriter.Write(0x3B); //Image terminator

otherwise it writes out a 2 byte unicode version of ";"

ie 003B
# June 28, 2005 1:28 PM

Rick van den Bosch said:


Sorry, it should be
binaryWriter.Write((Byte)0x3B); //Image terminator

otherwise it writes a 4 byte int out ;o)
# June 28, 2005 3:31 PM

Rick van den Bosch said:

Hi all!

Somebody knows how to get the frame delay of an existing gif?

I tried this :"PropertyItem prop= img.GetPropertyItem(0x5100);" but how to get the exact value? The Value property of this object returns me a bitArray and i don't know how to get the value.

Thanks
# June 29, 2005 12:20 PM

Rick van den Bosch said:

i couldn't get this to work either, though Firefox showed a missing image box with a little better information - "...because image contains errors."

so there was definitely something wrong in the basic structure of the file.

a quick change from this:

binaryWriter.Write(buf1, 789, buf1.Length - 790); //Image data

to

binaryWriter.Write(buf1, 781, buf1.Length - 782); //Image data

not sure if this is correct per the file format, but it does now work in IE and Firefox.

also, changing the delay time high byte from "3" to "0" makes it way more apparent if the animated part is working ;-)

and there was no visible difference in using the semicolon (";") or the 0x3B, though the byte method makes it more apparent what's being written.

cheers
g.
# July 7, 2005 11:13 PM

Rick van den Bosch said:

forget everything i just said... :-)

after working with things a bit more, it seems to be okay now... WTF, mate?
# July 8, 2005 12:12 AM

Rick van den Bosch said:

I don't know, I haven't seen your code ;)

It worked for me the first time, but there are some people who it hasn't worked for. Maybe I'll create a component for this ...
# July 8, 2005 8:04 AM

erie said:

Hi so could someone post teh correct completed code pls.
# August 1, 2005 1:59 AM

titch said:

Can you save your newly created animated gif to a file? If so how?
# October 18, 2005 6:57 AM

Syl said:

Hi,
nice code here :)
About the poor quality, I tried to use a quantizer (ImageManipulation.OctreeQuantizer) on each image. There's a better quality but the palette doesn't work very well. The problem with the quality is here, the palette. But I didn't resolve it (yet)
About a little optimisation, you can remove the latest "if", and binaryWriter.Write(";"); outside the "for", it does the same :)
# April 20, 2006 5:50 AM

Tyron said:

There is also an Article on Codeproject ( http://www.codeproject.com/dotnet/NGif.asp ) explaining how to open/save gif animations
# May 22, 2006 6:42 AM

Woo!!! said:

About the quick change from this:

binaryWriter.Write(buf1, 789, buf1.Length - 790); //Image data

to

binaryWriter.Write(buf1, 781, buf1.Length - 782); //Image data

The former works if you are making animated GIFs from pre-existing saved images.  

The latter works if you are making an animated from images you create on the fly with a Graphics class.  In other words, if you're doing something like this:

Image frameInGIF = new Bitmap(20, 20);

Graphics bnrGraphics =
            Graphics.FromImage(frameInGIF);

bnrGraphics.FillEllipse(
            new SolidBrush(Color.Blue), 0, 0,  20, 20);

Though the "781 case" seems to create a working animated GIF in any case, the delay between frames is set to 0 when you create an image with a Graphics class.
# August 1, 2006 12:34 PM

Damian Wood said:

Hi,

Does anyone know how to specify the delay time in milliseconds between frames. I am having trouble using:

buf3[4] = 10;  //Delay time low byte
buf3[5] = 3;   //Delay time high byte

How does that work?

Thanks.
# August 2, 2006 5:18 PM

Thiago Burgo said:

"Does anyone know how to specify the delay time in milliseconds between frames"
I wanna know also...
And anyone know how to set the number of times that the animation will be repeated??

Sorry for my bad english ( I'm Brazillian) :D

Thanks.
# August 8, 2006 11:10 PM

andyclap said:

There are a few minor problems with the code:

As mentioned above, you need to change the offset of the image data if there is a control block before the image.

Also the complete image array memory stream will be iniefficiently large as the gif encoder leaves a lot of null data at the end, more improtantly you need to trim down to before each individual image's end marker 3B, otherwise only the images up to the first frame with an endmarker will be shown.

If andybody's still interested, post a comment, and I can share my code.

I'm not using it however, as the default halftone palette used by the .net gif encoder is just not good enough. Rather than creating an optimized palette for each frame (which is annoyingly tricky and requires GDI bit twiddling), we've just ended up using multiple PNGs and animating them from javascript.

# May 3, 2007 5:04 AM

giancarlo said:

Yes I am still interested in this code and getting it to work.

I'm not really bery experienced with the type of programming going on here.. could anyone explain what to substitute the Response for? since it's giving me an error..

# May 24, 2007 5:20 PM

Arun said:

Below is sample code that "should" work, but the animation doesn't work. It stops after the first image. I don't know what andyclap was saying, sounds like it was what he was referring to with the 3B, so do you know how to fix that?

Below, the two GIF images, "test.gif" and "ok.gif" that are on the C: drive will get animated to a "result.gif".

using System;

using System.Collections.Generic;

using System.Text;

using System.IO;

using System.Collections;

using System.Collections.Specialized;

using System.Drawing;

using System.Drawing.Imaging;

namespace GIF

{

   class Program

   {

       static void Main(string[] args)

       {

           //Variable declaration

           StringCollection stringCollection = new StringCollection();

           MemoryStream memoryStream;

           BinaryWriter binaryWriter;

           Image image;

           Byte[] buf1;

           Byte[] buf2;

           Byte[] buf3;

           //Variable declaration

           //stringCollection = a_StringCollection_containing_images;

           stringCollection.Add(@"C:\test.gif");

           stringCollection.Add(@"C:\ok.gif");

           //Response.ContentType = "Image/gif";

           memoryStream = new MemoryStream();

           buf2 = new Byte[19];

           buf3 = new Byte[8];

           buf2[0] = 33;  //extension introducer

           buf2[1] = 255; //application extension

           buf2[2] = 11;  //size of block

           buf2[3] = 78;  //N

           buf2[4] = 69;  //E

           buf2[5] = 84;  //T

           buf2[6] = 83;  //S

           buf2[7] = 67;  //C

           buf2[8] = 65;  //A

           buf2[9] = 80;  //P

           buf2[10] = 69; //E

           buf2[11] = 50; //2

           buf2[12] = 46; //.

           buf2[13] = 48; //0

           buf2[14] = 3;  //Size of block

           buf2[15] = 1;  //

           buf2[16] = 0;  //

           buf2[17] = 0;  //

           buf2[18] = 0;  //Block terminator

           buf3[0] = 33;  //Extension introducer

           buf3[1] = 249; //Graphic control extension

           buf3[2] = 4;   //Size of block

           buf3[3] = 9;   //Flags: reserved, disposal method, user input, transparent color

           buf3[4] = 10;  //Delay time low byte

           buf3[5] = 0;   //Delay time high byte

           buf3[6] = 255; //Transparent color index

           buf3[7] = 0;   //Block terminator

           binaryWriter = new BinaryWriter(File.Open(@"C:\result.gif", FileMode.Create));

           for (int picCount = 0; picCount < stringCollection.Count; picCount++)

           {

               image = Bitmap.FromFile(stringCollection[picCount]);

               image.Save(memoryStream, ImageFormat.Gif);

               buf1 = memoryStream.ToArray();

               if (picCount == 0)

               {

                   //only write these the first time....

                   binaryWriter.Write(buf1, 0, 781); //Header & global color table

                   binaryWriter.Write(buf2, 0, 19); //Application extension

               }

               binaryWriter.Write(buf3, 0, 8); //Graphic extension

               binaryWriter.Write(buf1, 781, buf1.Length - 782); //Image data

               if (picCount == stringCollection.Count - 1)

               {

                   //only write this one the last time....

                   binaryWriter.Write(";"); //Image terminator

               }

               memoryStream.SetLength(0);

           }

           binaryWriter.Close();

       }

   }

}

# July 11, 2007 10:15 PM

Arun said:

Actually, what I posted does work! Just make sure the dimensions are exactly the same for all the images. Also to see the animation a little better, I changed the old values of high and low byte to these:

buf3[4] = 88;  //Delay time low byte

buf3[5] = 0;   //Delay time high byte

# July 11, 2007 10:23 PM

SAM said:

I ran the above code and it fails at the following line.

binaryWriter.Write(buf1, 0, 781); //Header & global color table

I found that buf1.Length is equal to 84.

Well then how can you expect buf1 to write 781 bytes when it has only 84 bytes.

Please explain.

# August 17, 2007 9:05 PM

SAM said:

Sorry, ignore that previous blog.

Another question: The code above works fine.

The only problem is trying to get the delay to work.

Any help on this please.

# August 20, 2007 3:32 PM

stuart said:

Hi andyclap,

I'd be interested in how you got this working. I'm struggling to understand the need for writing 781 bytes when my loaded image is only 129 in length.

Cheers,

Stuart.

# October 23, 2007 12:37 PM

Markus said:

Hi guys,

I tested the code above and drew the conclusion, that this code can work,

but does not have to.

(It will only work under certain conditions...)

Actually the code is not bad, but overlooks

several properties of the gif format:

Lets start with the creation of the buffers:

buf2:

This Buffer is the Application Extension for repeating the Animation,

and is completely correct as posted above.

Just additional:

buf2[16] = 0;  //Number of repetitions LowByte

buf2[17] = 0;  //Number of repetitions HighByte

The Value 0 indicates an endless loop.

buf3:

This Buffer is also absolutely correct

The only problem that I see with this is the way the pictures are merged:

memoryStream.ToArray() will give you the complete byte[] of the file, including

GIF-Signature, ScreenDescriptor, GlobalColorTable, Extensions, ImageDescriptor and ImageData.

You try to cut out the Start of each file until after the GlobalColorTable where the Picture specific Data beginns, and write one Table for the new gif-File:

//only write these the first time....

binaryWriter.Write(buf1, 0, 781); //Header & global color table

...

binaryWriter.Write(buf1, 781, buf1.Length - 782); //Image data

And therein lies the error:

This code would be perfectly correct for a gif with a GlobalColorTable of 256 Colors:

(3*256byte = 768byte + 6byte Signature + 7byte Screendescriptor = 781byte)

But you overlooked two possibilites:

First of all, a gif file does not necessarily have to contain a Global ColorTable,

but can have a LocalColorTable defined after each single picture

Another point is, that the ColorTables do not have to contain 256Colors.

A quite good help for this is:

www.matthewflickinger.com/.../bits_and_bytes.asp

So you have to Split your single pictures

into their components before you

merge the required components.

If you wish I will post the Code I wrote to

merge Gifs of the same size.

(i'm not posting it already now, because it will take quite a lot of space...)

Cheers,

Markus

# November 10, 2007 12:58 PM

Paulo said:

Markus

It would be great if you could post your working code!

# November 22, 2007 5:00 PM

john said:

Hi, Markus I'd really appreciate the code too! thanks!

# December 11, 2007 6:10 PM

Markus said:

OK, this could take some Space now...

I tested the Code with equalsized flags that are not animated from www.nationalflaggen.de

file GifClass.cs:

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Imaging;

using System.IO;

namespace GifCreator

{

   public class GifClass

   {

       public GIFVersion m_Version = GIFVersion.GIF87a;

       public Bitmap m_Image = null;

       public List<byte> m_GifSignature = new List<byte>();

       public List<byte> m_ScreenDescriptor = new List<byte>();

       public List<byte> m_ColorTable = new List<byte>();

       public List<byte> m_ImageDescriptor = new List<byte>();

       public List<byte> m_ImageData = new List<byte>();

       public GifClass()

       { }

       public void LoadGifPicture(string filename)

       { LoadGifPicture((Bitmap)Bitmap.FromFile(filename)); }

       public void LoadGifPicture(Bitmap gifPicture)

       {

           m_Image = gifPicture;

           MemoryStream stream = new MemoryStream();

           m_Image.Save(stream, ImageFormat.Gif);

           List<byte> dataList = new List<byte>(stream.ToArray());

           if (!AnalyzeGifSignature(dataList))

               throw (new Exception("File is not a gif!"));

           AnalyzeScreenDescriptor(dataList);

           GIFBlockType blockType = GetTypeOfNextBlock(dataList);

           while (blockType != GIFBlockType.Trailer)

           {

               switch (blockType)

               {

                   case GIFBlockType.ImageDescriptor:

                       AnalyzeImageDescriptor(dataList);

                       break;

                   case GIFBlockType.Extension:

                       break;

                   default:

                       break;

               }

               blockType = GetTypeOfNextBlock(dataList);

           }

       }

       private bool AnalyzeGifSignature(List<byte> gifData)

       {

           for (int i = 0; i < 6; i++)

               m_GifSignature.Add(gifData[i]);

           gifData.RemoveRange(0, 6);

           List<char> chars = m_GifSignature.ConvertAll<char>(new Converter<byte,char>(ByteToChar));

           string s = new string(chars.ToArray());

           if (s == GIFVersion.GIF89a.ToString())

               m_Version = GIFVersion.GIF89a;

           else if (s == GIFVersion.GIF87a.ToString())

               m_Version = GIFVersion.GIF87a;

           else

               return false;

           return true;

       }

       private char ByteToChar(byte b)

       { return (char)b; }

       private void AnalyzeScreenDescriptor(List<byte> gifData)

       {

           for (int i = 0; i < 7; i++)

               m_ScreenDescriptor.Add(gifData[i]);

           gifData.RemoveRange(0, 7);

           // if the first bit of the fifth byte is set the GlobelColorTable follows this block

           bool globalColorTableFollows = (m_ScreenDescriptor[4] & 0x80) != 0;

           if (globalColorTableFollows)

           {

               int pixel = m_ScreenDescriptor[4] & 0x07;

               int lengthOfColorTableInByte = 3 * ((int)Math.Pow(2, pixel + 1));

               for (int i = 0; i < lengthOfColorTableInByte; i++)

                   m_ColorTable.Add(gifData[i]);

               gifData.RemoveRange(0, lengthOfColorTableInByte);

           }

           m_ScreenDescriptor[4] = (byte)(m_ScreenDescriptor[4] & 0x7F);

       }

       private GIFBlockType GetTypeOfNextBlock(List<byte> gifData)

       {

           GIFBlockType blockType = (GIFBlockType)gifData[0];

           return blockType;

       }

       private void AnalyzeImageDescriptor(List<byte> gifData)

       {

           for (int i = 0; i < 10; i++)

               m_ImageDescriptor.Add(gifData[i]);

           gifData.RemoveRange(0, 10);

           // get ColorTable if exists

           bool localColorMapFollows = (m_ImageDescriptor[9] & 0x80) != 0;

           if (localColorMapFollows)

           {

               int pixel = m_ImageDescriptor[9] & 0x07;

               int lengthOfColorTableInByte = 3 * ((int)Math.Pow(2, pixel + 1));

               m_ColorTable.Clear();

               for (int i = 0; i < lengthOfColorTableInByte; i++)

                   m_ColorTable.Add(gifData[i]);

               gifData.RemoveRange(0, lengthOfColorTableInByte);

           }

           else

           {

               int lastThreeBitsOfGlobalTableDescription = m_ScreenDescriptor[4] & 0x07;

               m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] & 0xF8);

               m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] | lastThreeBitsOfGlobalTableDescription);

           }

           m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] | 0x80);

           GetImageData(gifData);

       }

       private void GetImageData(List<byte> gifData)

       {

           m_ImageData.Add(gifData[0]);

           gifData.RemoveAt(0);

           while (gifData[0] != 0x00)

           {

               int countOfFollowingDataBytes = gifData[0];

               for (int i = 0; i <= countOfFollowingDataBytes; i++)

               {

                   m_ImageData.Add(gifData[i]);

               }

               gifData.RemoveRange(0, countOfFollowingDataBytes + 1);

           }

           m_ImageData.Add(gifData[0]);

           gifData.RemoveAt(0);

       }

       private void ThrowAwayExtensionBlock(List<byte> gifData)

       {

           gifData.RemoveRange(0,2); // Delete ExtensionBlockIndicator and ExtensionDetermination

           while (gifData[0] != 0)

           {

               gifData.RemoveRange(0, gifData[0] + 1);

           }

           gifData.RemoveAt(0);

       }

   }

}

file GifCreator.cs:

using System;

using System.Collections.Generic;

using System.IO;

using System.Text;

namespace GifCreator

{

   public class GifCreator

   {

       public static void CreateAnimatedGif(List<string> gifFiles, int delay, string outputFile)

       {

           BinaryWriter writer = new BinaryWriter(new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite));

           byte[] gif_Signature = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };

           writer.Write(gif_Signature);

           for (int i = 0; i < gifFiles.Count; i++)

           {

               GifClass gif = new GifClass();

               gif.LoadGifPicture(gifFiles[i]);

               if(i==0)

                   writer.Write(gif.m_ScreenDescriptor.ToArray());

               writer.Write(GifCreator.CreateGraphicControlExtensionBlock(delay));

               writer.Write(gif.m_ImageDescriptor.ToArray());

               writer.Write(gif.m_ColorTable.ToArray());

               writer.Write(gif.m_ImageData.ToArray());

           }

           writer.Write(GifCreator.CreateLoopBlock());

           writer.Write((byte)0x3B); //End file

           writer.Close();

       }

       public static byte[] CreateGraphicControlExtensionBlock(int delay)

       {

           byte[] result = new byte[8];

           // Split the delay into high- and lowbyte

           byte d1 = (byte)(delay % 256);

           byte d2 = (byte)(delay / 256);

           result[0] = (byte)0x21; // Start ExtensionBlock

           result[1] = (byte)0xF9; // GraphicControlExtension

           result[2] = (byte)0x04; // Size of DataBlock (4)

           result[3] = d2;

           result[4] = d1;

           result[5] = (byte)0x00;

           result[6] = (byte)0x00;

           result[7] = (byte)0x00;

           return result;

       }

       public static byte[] CreateLoopBlock()

       { return CreateLoopBlock(0); }

       public static byte[] CreateLoopBlock(int numberOfRepeatings)

       {

           byte rep1 = (byte)(numberOfRepeatings % 256);

           byte rep2 = (byte)(numberOfRepeatings / 256);

           byte[] result = new byte[19];

           result[0] = (byte)0x21; // Start ExtensionBlock

           result[1] = (byte)0xFF; // ApplicationExtension

           result[2] = (byte)0x0B; // Size of DataBlock (11) for NETSCAPE2.0)

           result[3] = (byte)'N';

           result[4] = (byte)'E';

           result[5] = (byte)'T';

           result[6] = (byte)'S';

           result[7] = (byte)'C';

           result[8] = (byte)'A';

           result[9] = (byte)'P';

           result[10] = (byte)'E';

           result[11] = (byte)'2';

           result[12] = (byte)'.';

           result[13] = (byte)'0';

           result[14] = (byte)0x03; // Size of Loop Block

           result[15] = (byte)0x01; // Loop Indicator

           result[16] = (byte)rep1; // Number of repetitions

           result[17] = (byte)rep2; // 0 for endless loop

           result[18] = (byte)0x00;

           return result;

       }

   }

}

Example to create a sequence of three not animated Gif-Pictures of the same size:

List<string> files = new List<string>(new string[] { "D:\\f1.gif", "D:\\f2.gif", "D:\\f3.gif" });

           GifCreator.CreateAnimatedGif(files, 50, "D:\\result.gif");

I hope this helps...

# December 12, 2007 5:56 PM

Gary Gaughan-Smith said:

Can you publish the values in the GIFBlockType enum.

I think it's

enum GIFBlockType

{

ImageDescriptor = 0x2C,

Extension = 0x21,

Trailer = 0x3B

}

Is this correct?

Thanks

Gary

# December 14, 2007 4:34 PM

Markus said:

You're absolutely right!

Sorry I forgot to add the enum file...

Thanks for adding this!

# December 17, 2007 10:51 AM

Gary Gaughan-Smith said:

Seems the enum values were correct.

Also, there's a line missing in LoadGifPicture on the Extension case in the switch statement.

Add "ThrowAwayExtensionBlock(dataList);" before the break, and it will work.

Gary

# December 17, 2007 11:48 AM

Hissam said:

can any one tell me what is GIFVersion,GIFBlockType

I'm doing programming in c# using the GIF class

I'm getting an error saying directive or assembly reference missing

I'm new to the world of C#, a complete apprentice in this field plz help me out!!

# December 17, 2007 1:17 PM

Gary Gaughan-Smith said:

Include these two enums:

  public enum GIFVersion

  {

     GIF87a,

     GIF89a

  }

  public enum GIFBlockType

  {

     ImageDescriptor = 0x2C,

     Extension = 0x21,

     Trailer = 0x3B

  }

There's a line missing in LoadGifPicture(BitMap). Use this version:

      public void LoadGifPicture(Bitmap gifPicture)

      {

         MemoryStream stream = new MemoryStream();

         List<byte> dataList = new List<byte>(stream.ToArray());

         m_Image = gifPicture;

         m_Image.Save(stream, ImageFormat.Gif);

         if (!AnalyzeGifSignature(dataList))

         {

            throw (new Exception("File is not a gif!"));

         }

         AnalyzeScreenDescriptor(dataList);

         GIFBlockType blockType = GetTypeOfNextBlock(dataList);

         while (blockType != GIFBlockType.Trailer)

         {

           switch (blockType)

           {

              case GIFBlockType.ImageDescriptor:

                 AnalyzeImageDescriptor(dataList);

                 break;

              case GIFBlockType.Extension:

                 ThrowAwayExtensionBlock(dataList);    

                 break;

              default:

                 break;

           }

           blockType = GetTypeOfNextBlock(dataList);

        }

     }

Works a treat after that.

Gary

# December 17, 2007 2:41 PM

Sam said:

I used the latest version of LoadGifPicture method and I'm getting an error.

I created three 50*50 gif images and when i run this code it failed on

AnalyzeGifSignature(List<byte> gifData) method.

when it tries to add  m_GifSignature.Add(gifData[i]). The error is "Index was out of range. Must be non-negative and less than the size of the collection.Parameter name: index"

when i debug on the code LoadGifPicture is passing an empty gifData to AnalyzeGifSignature().

# December 18, 2007 3:47 AM

Sam said:

I created the datalist after it is saved in the memory stream and now works fine.

       public void LoadGifPicture(Bitmap gifPicture)

       {

           MemoryStream stream = new MemoryStream();

           m_Image = gifPicture;

           m_Image.Save(stream, ImageFormat.Gif);

           List<byte> dataList = new List<byte>(stream.ToArray());

           if (!AnalyzeGifSignature(dataList))

           {

               throw (new Exception("File is not a gif!"));

           }

Thanks for sharing this usefull info

# December 18, 2007 3:57 AM

Hissam said:

can anyone explain the purpose of these 2 enums??

public enum GIFVersion

 {

    GIF87a,

    GIF89a

 }

 public enum GIFBlockType

 {

    ImageDescriptor = 0x2C,

    Extension = 0x21,

    Trailer = 0x3B

 }

# December 26, 2007 12:13 PM

Gary said:

Regarding the CreateAnimatedGif method above, what units of time measurement is the delay parameter?  I have tried many values, but I could never get more than a couple seconds delay between frames.  Is there a way to put a delay of 5 or 10 seconds between frames?

Thanks in advance for your help.

# January 9, 2008 6:35 PM

Gary said:

Okay, I answered my own question.  I think there may be a slight problem with the CreateGraphicControlExtensionBlock method.  When I changed

          result[0] = (byte)0x21; // Start ExtensionBlock

          result[1] = (byte)0xF9; // GraphicControlExtension

          result[2] = (byte)0x04; // Size of DataBlock (4)

          result[3] = d2;

          result[4] = d1;

          result[5] = (byte)0x00;

          result[6] = (byte)0x00;

          result[7] = (byte)0x00;

to

          result[0] = (byte)0x21; // Start ExtensionBlock

          result[1] = (byte)0xF9; // GraphicControlExtension

          result[2] = (byte)0x04; // Size of DataBlock (4)

          result[3] = (byte)0x00;

          result[4] = d1;

          result[5] = d2;

          result[6] = (byte)0x00;

          result[7] = (byte)0x00;

Everything started working correctly and the delay time became 100ths of a second (delay of 100 = 1 second).

# January 9, 2008 10:03 PM

Rahman said:

Hi  Markus,

i dont know how to use your code? Both are class files or vb file? can you explain please?

# January 15, 2008 11:13 AM

ThinkEdge said:

Animated GIF Encoder for .NET Update

# May 15, 2008 6:40 PM

adam said:

wth r these codes for!

# November 15, 2008 7:34 PM

anzu said:

Hi all,

Please the code for VB .NET?

i translate the code...

Imports System

Imports System.Collections.Generic

Imports System.Drawing

Imports System.Drawing.Imaging

Imports System.IO

Namespace GifCreator

   Public Class GifClass

       Public Enum GIFVersion

           GIF87

           GIF89a

       End Enum

       Public Enum GIFBlockType

           ImageDescriptor = &H2C

           Extension = &H21

           Trailer = &H3B

       End Enum

       Public m_Version As GIFVersion = GIFVersion.GIF89a

       Public m_Image As Bitmap = Nothing

       Public m_GifSignature As New List(Of Byte)()

       Public m_ScreenDescriptor As New List(Of Byte)()

       Public m_ColorTable As New List(Of Byte)()

       Public m_ImageDescriptor As New List(Of Byte)()

       Public m_ImageData As New List(Of Byte)()

       Public Sub New()

       End Sub

       Public Sub LoadGifPicture(ByVal filename As String)

           LoadGifPicture(DirectCast(Bitmap.FromFile(filename), Bitmap))

       End Sub

       Public Sub LoadGifPicture(ByVal gifPicture As Bitmap)

           m_Image = gifPicture

           Dim stream As New MemoryStream()

           m_Image.Save(stream, ImageFormat.Gif)

           Dim dataList As New List(Of Byte)(stream.ToArray())

           If Not AnalyzeGifSignature(dataList) Then

               Throw (New Exception("File is not a gif!"))

           End If

           AnalyzeScreenDescriptor(dataList)

           Dim blockType As GIFBlockType = GetTypeOfNextBlock(dataList)

           While blockType <> GIFBlockType.Trailer

               Select Case blockType

                   Case GIFBlockType.ImageDescriptor

                       AnalyzeImageDescriptor(dataList)

                       Exit Select

                   Case GIFBlockType.Extension

                       Exit Select

                   Case Else

                       Exit Select

               End Select

               blockType = GetTypeOfNextBlock(dataList)

           End While

       End Sub

       Private Function AnalyzeGifSignature(ByVal gifData As List(Of Byte)) As Boolean

           For i As Integer = 0 To 5

               m_GifSignature.Add(gifData(i))

           Next

           gifData.RemoveRange(0, 6)

           Dim chars As List(Of Char) = m_GifSignature.ConvertAll(Of Char)(New Converter(Of Byte, Char)(ByteToChar))

           Dim s As New String(chars.ToArray())

           If s = GIFVersion.GIF89a.ToString() Then

               m_Version = GIFVersion.GIF89a

           ElseIf s = GIFVersion.GIF89a.ToString() Then

               m_Version = GIFVersion.GIF89a

           Else

               Return False

           End If

           Return True

       End Function

       Private Function ByteToChar(ByVal b As Byte) As Char

           Return CChar(ChrW(b))

       End Function

       Private Sub AnalyzeScreenDescriptor(ByVal gifData As List(Of Byte))

           For i As Integer = 0 To 6

               m_ScreenDescriptor.Add(gifData(i))

           Next

           gifData.RemoveRange(0, 7)

           ' if the first bit of the fifth byte is set the GlobelColorTable follows this block

           Dim globalColorTableFollows As Boolean = (m_ScreenDescriptor(4) And &H80) <> 0

           If globalColorTableFollows Then

               Dim pixel As Integer = m_ScreenDescriptor(4) And &H7

               Dim lengthOfColorTableInByte As Integer = 3 * CInt(Math.Pow(2, pixel + 1))

               For i As Integer = 0 To lengthOfColorTableInByte - 1

                   m_ColorTable.Add(gifData(i))

               Next

               gifData.RemoveRange(0, lengthOfColorTableInByte)

           End If

           m_ScreenDescriptor(4) = CByte((m_ScreenDescriptor(4) And &H7F))

       End Sub

       Private Function GetTypeOfNextBlock(ByVal gifData As List(Of Byte)) As GIFBlockType

           Dim blockType As GIFBlockType = DirectCast(gifData(0), GIFBlockType)

           Return blockType

       End Function

       Private Sub AnalyzeImageDescriptor(ByVal gifData As List(Of Byte))

           For i As Integer = 0 To 9

               m_ImageDescriptor.Add(gifData(i))

           Next

           gifData.RemoveRange(0, 10)

           ' get ColorTable if exists

           Dim localColorMapFollows As Boolean = (m_ImageDescriptor(9) And &H80) <> 0

           If localColorMapFollows Then

               Dim pixel As Integer = m_ImageDescriptor(9) And &H7

               Dim lengthOfColorTableInByte As Integer = 3 * CInt(Math.Pow(2, pixel + 1))

               m_ColorTable.Clear()

               For i As Integer = 0 To lengthOfColorTableInByte - 1

                   m_ColorTable.Add(gifData(i))

               Next

               gifData.RemoveRange(0, lengthOfColorTableInByte)

           Else

               Dim lastThreeBitsOfGlobalTableDescription As Integer = m_ScreenDescriptor(4) And &H7

               m_ImageDescriptor(9) = CByte((m_ImageDescriptor(9) And &HF8))

               m_ImageDescriptor(9) = CByte((m_ImageDescriptor(9) Or lastThreeBitsOfGlobalTableDescription))

           End If

           m_ImageDescriptor(9) = CByte((m_ImageDescriptor(9) Or &H80))

           GetImageData(gifData)

       End Sub

       Private Sub GetImageData(ByVal gifData As List(Of Byte))

           m_ImageData.Add(gifData(0))

           gifData.RemoveAt(0)

           While gifData(0) <> &H0

               Dim countOfFollowingDataBytes As Integer = gifData(0)

               For i As Integer = 0 To countOfFollowingDataBytes

                   m_ImageData.Add(gifData(i))

               Next

               gifData.RemoveRange(0, countOfFollowingDataBytes + 1)

           End While

           m_ImageData.Add(gifData(0))

           gifData.RemoveAt(0)

       End Sub

       Private Sub ThrowAwayExtensionBlock(ByVal gifData As List(Of Byte))

           gifData.RemoveRange(0, 2)

           ' Delete ExtensionBlockIndicator and ExtensionDetermination

           While gifData(0) <> 0

               gifData.RemoveRange(0, gifData(0) + 1)

           End While

           gifData.RemoveAt(0)

       End Sub

   End Class

End Namespace

Have a 2 error:

1)

ByteToChar: Delegate 'System.Converter(Of Byte, Char)' requires an 'AddressOf' expression or lambda expression as the only argument to its constructor.

2)

gifData(0): Value of type 'Byte' cannot be converted to 'WindowsApplication1.GifCreator.GifClass.GIFBlockType'.

thanks

sorry for my English :)

# December 13, 2008 11:00 AM

Thanks said:

Thanks !

One byte in "buff2" must control whether or not to loop the animation. It would be nice if you could add this information in the code as comment.

# January 21, 2009 1:42 PM

Martin said:

Hi,

how can i get the images to change every secound?

I tried

       buf3[4] = 1;    //Delay time low byte

       buf3[5] = 1;   //Delay time high byte

But this seems to be about 2.5 secounds.

When using 0 its much to fast.

Thanks for your reply!

# August 6, 2009 10:50 AM

Jason said:

CreateAnimatedGif Overload(s):

Array of Bitmaps and Delays (allows for various images and delays)

       public static void CreateAnimatedGif(System.Drawing.Bitmap[] gifFiles, int[] delays, string outputFile)

       {

           BinaryWriter writer = new BinaryWriter(new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite));

           byte[] gif_Signature = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };

           writer.Write(gif_Signature);

           for (int i = 0; i < gifFiles.Length-1; i++)

           {

               GifClass gif = new GifClass();

               gif.LoadGifPicture(gifFiles[i]);

               if (i == 0)

                   writer.Write(gif.m_ScreenDescriptor.ToArray());

               writer.Write(GifCreator.CreateGraphicControlExtensionBlock(delays[i]));

               writer.Write(gif.m_ImageDescriptor.ToArray());

               writer.Write(gif.m_ColorTable.ToArray());

               writer.Write(gif.m_ImageData.ToArray());

           }

           writer.Write(GifCreator.CreateLoopBlock());

           writer.Write((byte)0x3B); //End file

           writer.Close();

       }

# September 4, 2009 8:29 PM

Jason said:

Output to a stream?  Easypeasy:

       public static void CreateAnimatedGif(System.Drawing.Bitmap[] gifFiles, int[] delays, MemoryStream outputFile)

       {

           BinaryWriter writer = new BinaryWriter(outputFile);

# September 4, 2009 8:31 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Please add 6 and 6 and type the answer here: