8

我正在尝试将两个不同的 gif 文件合并到一个文件中。

首先,我学到了很多关于 gif 格式的知识。而且我知道延迟时间值是在图形控制扩展中设置的,它是一个 gif 文件块。

我保存了第一个 gif 并设置了 FrameDelay 值,代码如下:

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

然后我尝试添加另一个图像作为第二帧。

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

最后,我应该终止这个图像。

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

而且我发现第二帧的延迟时间总是0。

我尝试了很多方法,但我不知道将其设为 0x96。

那么它有什么问题呢?

4

4 回答 4

19

任何.NET 图像编码器都不支持这一点。System.Drawing.Bitmap既不是由 GDI+ 也不是由 WIC,以及 和System.Windows.Media.Imaging.PngBitmapEncoder类的底层本机编解码器。

虽然这听起来像是一个非常奇怪的疏忽,但最可能的原因是 GIF 受到软件专利的阻碍。Unisys 拥有 LZW 压缩算法的权利,并开始积极争取获得它的许可费。从可以获得最多资金的最明显目标开始,微软永远位居榜首。他们也不谦虚,一个在网页上使用 GIF 的非商业或私人网站不得不在 1999 年咳出 5000美元

这杀死了图像格式。在此之前无处不在,几乎每个人都停止使用它们。也快得惊人,这只花了几个月的时间。顺便说一句,与每个人都完全拥有动画 GIF 的情况相吻合,这在以前被严重夸大了。您可能会在回程机器上找到一些当时的网页,在那里眼角的所有东西都在移动。这不是唯一幸运的巧合,它是开源 PNG 格式被开发的核心原因。感谢我们的幸运星 :)

该专利在 2004 年左右到期,具体取决于您居住的地方,因此您不必再担心 Unisys 的来信了。

长话短说,您必须四处寻找另一个库才能将此功能添加到您的程序中。这个现有的 SO question已经很好地涵盖了它,无需在此重复。

于 2013-08-03T13:47:49.837 回答
1

Microsoft 开发人员中心:您不能使用 SaveAdd 将帧添加到动画 gif 文件。

为了构建动画 gif,您需要逐步浏览动画中的每一帧。VCSKicks或Stackoverflow 上的相同代码显示了一个很好的例子。

于 2013-07-30T01:16:43.057 回答
1

更新:

我做了更多的研究,我认为这些 ffmpeg 和 mplayer 建议值得一试:
Create Animation gif from a set of jpeg images

更新 2:

Rick van den Bosch的这段代码也非常好,因为它可以让您访问延迟时间:

.Net(至少 1.1,他们可能会将其合并到 2.0 中)无法让您通过 GDI+ 创建动画 GIF。

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

正如 Hans 提到的那样,它不支持,所以第三个解决方案是 RenniePet 建议从两个 Gif 中提取帧,然后将所有帧组合在一起。

添加对 System.Drawing.DLL 的引用并使用此代码获取框架:

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
    }
}

我们可以像这样提取所有的帧:

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;
}

接下来我们使用NGif将所有帧组合成一个动画 gif

下载代码,打开解决方案并编译组件项目以获取 DLLGif.Components.dll并在您的解决方案中引用该 DLL。

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;
}
于 2013-08-02T02:30:00.010 回答
1

如果您愿意使用第三方库,您可以使用Magick.NET。这是ImageMagick的 C# 包装器。

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");
}
于 2013-08-03T08:41:53.023 回答