【问题标题】:In C#,maybe it's a bug for Image.SaveAdd, who can help me to solve it?在 C# 中,可能是 Image.SaveAdd 的错误,谁能帮我解决?
【发布时间】:2013-07-27 13:32:27
【问题描述】:

我正在尝试将两个不同的 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。

那有什么问题呢?

【问题讨论】:

  • GDI+ 中内置的 gif 编码器根本不支持它。您无法完成这项工作。
  • 好吧,愚蠢的问题,但要确定的是:你说的是动画 GIF,对吧?
  • 假设我们正在谈论动画 GIF,一个可行的解决方案是将您想要的所有单个帧提取为单个图像,然后创建一个包含所需帧的全新动画 GIF/图片?
  • 我没有尝试这种方式。但我不认为这对我来说没问题。我需要合并图像,而不是提取它们。

标签: c# image gif


【解决方案1】:

任何 .NET 图像编码器根本不支持此功能。既不是 GDI+ 也不是 WIC,System.Drawing.BitmapSystem.Windows.Media.Imaging.PngBitmapEncoder 类的底层原生编解码器。

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

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

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

长话短说,您必须四处寻找另一个库才能将此功能添加到您的程序中。这个existing SO question已经很好地覆盖了,这里不再赘述。

【讨论】:

  • +1 汉斯的回答多么有趣且富有洞察力。感谢分享!
【解决方案2】:

来自Microsoft Developer Center:您不能使用 SaveAdd 将帧添加到动画 gif 文件中。

为了构建动画 gif,您需要逐步浏览动画中的每一帧。 VCSKickssame code here on Stackoverflow 显示了一个很好的例子。

【讨论】:

    【解决方案3】:

    更新:

    我做了更多的研究,我认为这些 ffmpeg 和 mplayer 建议值得一试:
    Create animated 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

    下载代码,打开解决方案并编译组件项目以获取 DLL Gif.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;
    }
    

    【讨论】:

      【解决方案4】:

      如果您愿意使用第三方库,您可以使用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");
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-20
        • 2020-10-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-16
        相关资源
        最近更新 更多