在C#中,Image.SaveAdd可能存在一个bug,请问谁能帮我解决这个问题?

9
我希望将两个不同的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。

那么出了什么问题呢?


2
它在GDI+内置的gif编码器中根本不受支持。你无法让它工作。 - Hans Passant
好的,这是一个愚蠢的问题,但只是为了确认一下:你说的是动态GIF图,对吗? - RenniePet
假设我们谈论的是动态GIF,一个可行的解决方案是提取所有您想要的单个帧作为单独的图像,然后创建一个全新的动态GIF包含所需的帧/图像吗? - RenniePet
我没有尝试过这种方法。但我认为这对我不合适。我需要将这些图像合并在一起,而不是提取它们。 - roast_soul
4个回答

19

这根本不被.NET图像编码器所支持,无论是GDI+还是WIC,它们都是System.Drawing.BitmapSystem.Windows.Media.Imaging.PngBitmapEncoder类的底层本地编解码器。

虽然听起来像是非常奇怪的疏忽,但最有可能的原因是,GIF受软件专利的限制。Unisys拥有LZW压缩算法的权利,并开始积极追求获得许可费用。从最明显的目标开始,即可以获得大部分收益的地方,微软一直排在榜首。他们也不谦虚,一个使用GIF在其网页上的非商业或私人网站必须在1999年支付 5000美元

这导致图像格式的死亡。在此之前,它无处不在,几乎每个人都在使用它们。但惊人地快,只用了几个月时间,所有人都停止了使用。顺便说一句,在每个人完全满足于动态GIF之前,这也是个好巧合。过度使用已经相当严重了。你可能会在wayback machine上找到一些早期网页,里面一切都在眼角落里动。不是唯一的幸运巧合,这是开源PNG格式开发的核心原因。感谢我们的幸运之星:)

专利在2004年左右到期,取决于您所在的地区,因此您不必再担心收到Unisys的信件。

长话短说,您需要四处寻找另一个库,以添加此功能到您的程序中。这个existing SO question已经很详细地涵盖了它,这里不需要重复阐述。


3
多么有趣而深刻的回答,汉斯。感谢你的分享!(+1) - Jeremy Thompson

2

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

1

更新:

我进行了更多的研究,认为这些 ffmpeg 和 mplayer 的建议值得一试:
从一组jpeg图像创建动画gif

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

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接