Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C#,maybe it's a bug for Image.SaveAdd, who can help me to solve it?

Tags:

c#

image

gif

I'm trying to combine two different gif file into one file.

First, I learned a lot about the gif format. And I know the delay time value is set in Graphics Control Extension which is a block of gif file.

I saved the first gif and set the FrameDelay value, code as below:

    ImageCodecInfo codeInfo = GetEncoder(ImageFormat.Gif);
    System.Drawing.Imaging.Encoder saveEncoder = System.Drawing.Imaging.Encoder.SaveFlag;
    EncoderParameters parameters = new EncoderParameters(1);

    parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.MultiFrame);
    PropertyItem PropertyTagFrameDelay = img1.GetPropertyItem(0x5100);
    PropertyTagFrameDelay.Value = new byte[] { 0x96, 0x00 };// this is the delay value 0x0096, means 1.5 second
    img1.SetPropertyItem(PropertyTagFrameDelay);

    PropertyItem LoopCount = img1.GetPropertyItem(0x5101);
    LoopCount.Value = new byte[] { 0x00, 0x00 };// this means the gif loops endlessly
    img1.SetPropertyItem(LoopCount);

    img1.Save(@"c:\ddd.gif", codeInfo, parameters);

Then I tried to add another image as second frame.

    parameters = new EncoderParameters(1);
    parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.FrameDimensionTime);
    PropertyTagFrameDelay = img2.GetPropertyItem(0x5100);
    PropertyTagFrameDelay.Value = new byte[] { 0x96, 0x00 };// this is the delay value 0x0096, means 1.5 second
    img2.SetPropertyItem(PropertyTagFrameDelay);

Last, I should terminate this image.

parameters = new EncoderParameters(1);
  parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.Flush);
  img1.SaveAdd(parameters);

And I found that the second frame's delay time is always 0.

I tried a lot of method, but i have no idea to make it as 0x96.

So what's wrong with it?

like image 510
roast_soul Avatar asked Jul 27 '13 07:07

roast_soul


4 Answers

This simply isn't supported by any of the .NET image encoders. Neither by GDI+ nor by WIC, the underlying native codecs for the System.Drawing.Bitmap and System.Windows.Media.Imaging.PngBitmapEncoder classes.

While that sounds like a very strange oversight, the most probably reason was that GIF was encumbered by a software patent. Unisys owned the rights to the LZW compression algorithm and started aggressively pursuing obtaining the license fees for it. Starting at the most obvious targets where most money can be had, Microsoft is forever on the top of the list. They were not modest either, a non-commercial or private web site that used GIFs on their web pages had to cough up five thousand dollars in 1999.

This killed the image format. Ubiquitous before this, just about everybody stopped using them. Stunningly quickly too, this only took a few months. Happily coinciding with everybody completely having their fill of animated gifs btw, it was grossly overdone before. You may find some web pages from back-then on the wayback machine where everything in the corner of eye was moving. Not the only lucky coincidence, it was the core reason for the open source PNG format being developed. Thank our lucky stars :)

The patent expired around 2004, depending on where you live, so you won't have to fear a letter from Unisys anymore.

Long story short, you'll have to shop around for another library to add this feature to your program. It is well-covered by this existing SO question, no need to repeat it here.

like image 197
Hans Passant Avatar answered Nov 15 '22 15:11

Hans Passant


From Microsoft Developer Center: You cannot use SaveAdd to add frames to an animated gif file.

In order to construct an animated gif, you will need to step through each frame in the animation. A good example of this is shown on VCSKicks or same code here on Stackoverflow.

like image 42
Claies Avatar answered Nov 15 '22 15:11

Claies


Update:

I did more research and I think these ffmpeg and mplayer recommendations are worth trying:
Create animated gif from a set of jpeg images

Update 2:

This code from Rick van den Bosch is very good too, as it gives you access to the delay times:

.Net (at least 1.1, they might incorporate it in 2.0) does not give you possibilities to create animated GIFs through GDI+.

//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();

As Hans mentioned its not supported, so this third solution is RenniePet's suggestion to extract the frames from both Gif's and then combine all the frames together.

Add a reference to System.Drawing.DLL and use this code to get the frames:

using System.Drawing;
using System.Drawing.Imaging;

public class GifImage
{
    private Image gifImage;
    private FrameDimension dimension;
    private int frameCount;
    private int currentFrame = -1;
    private bool reverse;
    private int step = 1;

    public GifImage(string path)
    {
        gifImage = Image.FromFile(path);
        //initialize
        dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
        //gets the GUID
        //total frames in the animation
        frameCount = gifImage.GetFrameCount(dimension);
    }

    public int GetFrameCount()
    {
        return frameCount;
    }

    public bool ReverseAtEnd
    {
        //whether the gif should play backwards when it reaches the end
        get { return reverse; }
        set { reverse = value; }
    }

    public Image GetNextFrame()
    {

        currentFrame += step;

        //if the animation reaches a boundary...
        if (currentFrame >= frameCount || currentFrame < 1)
        {
            if (reverse)
            {
                step *= -1;
                //...reverse the count
                //apply it
                currentFrame += step;
            }
            else
            {
                currentFrame = 0;
                //...or start over
            }
        }
        return GetFrame(currentFrame);
    }

    public Image GetFrame(int index)
    {
        gifImage.SelectActiveFrame(dimension, index);
        //find the frame
        return (Image)gifImage.Clone();
        //return a copy of it
    }
}

We can extract all the Frames like this:

private static readonly string tempFolder = @"C:\temp\";

static void Main(string[] args)
{
    CombineGifs(@"c:\temp\a.gif", @"c:\temp\b.gif");
}

public static void CombineGifs(string firstImageFilePath, string secondImageFilePath)
{
    int frameCounter = ExtractGifFramesAndGetCount(firstImageFilePath, 0);
    int secondframeCounter = ExtractGifFramesAndGetCount(secondImageFilePath, frameCounter);

    string filePathOfCombinedGif = CombineFramesIntoGif(0, secondframeCounter);
}

private static int ExtractGifFramesAndGetCount(string filePath, int imageNameStartNumber)
{
    ////NGif had an error when I tried it
    //GifDecoder gifDecoder = new GifDecoder();
    //gifDecoder.Read(filePath);

    //int frameCounter = imageNameStartNumber + gifDecoder.GetFrameCount();
    //for (int i = imageNameStartNumber; i < frameCounter; i++)
    //{
    //    Image frame = gifDecoder.GetFrame(i);  // frame i
    //    frame.Save(tempFolder + i.ToString() + ".png", ImageFormat.Png);
    //}

    //So we'll use the Gifimage implementation
    GifImage gifImage = new GifImage(filePath);
    gifImage.ReverseAtEnd = false;
    int frameCounter = imageNameStartNumber + gifImage.GetFrameCount();
    for (int i = imageNameStartNumber; i < frameCounter; i++)
    {
        Image img = gifImage.GetNextFrame();
        img.Save(tempFolder + i.ToString() + ".png");
    }
    return frameCounter;
}

Next we combine all the frames into a single animated gif using NGif

Download the code, open the Solution and compile the Components project to get the DLL Gif.Components.dll and reference that DLL in your solution.

private static string CombineFramesIntoGif(int startFrameCount, int endFrameCount)
{
    List<string> imageFilePaths = new List<string>();
    for (int i = startFrameCount; i < endFrameCount; i++)
    {
        imageFilePaths.Add(tempFolder + i.ToString() + ".png");
    }

    string outputFilePath = tempFolder + "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; i < imageFilePaths.Count; i++)
    {
        e.AddFrame(Image.FromFile(imageFilePaths[i]));
    }
    e.Finish();
    return outputFilePath;
}
like image 43
Jeremy Thompson Avatar answered Nov 15 '22 15:11

Jeremy Thompson


If you are willing to use a third party library you could use Magick.NET. This is a C# wrapper for ImageMagick.

using (MagickImageCollection images = new MagickImageCollection())
{
  MagickImage firstFrame = new MagickImage("first.gif");
  firstFrame.AnimationDelay = 1500;
  images.Add(firstFrame);

  MagickImage secondFrame = new MagickImage("second.gif");
  secondFrame.AnimationDelay = 200;
  images.Add(secondFrame);

  // This method will try to make your output image smaller.
  images.OptimizePlus();

  images.Write(@"c:\ddd.gif");
}
like image 20
dlemstra Avatar answered Nov 15 '22 14:11

dlemstra