在C#中,可能是Image.SaveAdd的一个bug,谁能帮我解决一下?

问题描述 投票:0回答:5

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

那么这有什么问题吗?

c# image gif
5个回答
20
投票

任何 .NET 图像编码器都不支持此功能。 既不是通过 GDI+,也不是通过 WIC,System.Drawing.Bitmap

System.Windows.Media.Imaging.PngBitmapEncoder
类的底层本机编解码器。

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

五千

美元。 这杀死了图像格式。 在此之前,它们无处不在,几乎每个人都停止使用它们。 速度也快得惊人,只花了几个月的时间。 幸运的是,恰逢每个人都完全沉浸在动画 GIF 中,顺便说一句,之前的做法确实有些过头了。 你可能会在回程机器上找到一些当时的网页,眼角的一切都在移动。 这并不是唯一的幸运巧合,这也是开源 PNG 格式被开发的核心原因。 感谢我们的幸运星:)

该专利在 2004 年左右到期,具体取决于您居住的地方,因此您不必再担心 Unisys 的来信。 长话短说,您必须四处寻找另一个库才能将此功能添加到您的程序中。 这个现有的SO问题很好地涵盖了它,无需在这里重复。

如果您愿意使用第三方库,您可以使用Magick.NET。这是

ImageMagick

2
投票

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"); } 来自

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

1
投票
VCSKicks

或 Stackoverflow 上的相同代码就是一个很好的示例。

更新:

我做了更多研究,我认为这些 ffmpeg 和 mplayer 推荐值得尝试:

1
投票
从一组 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

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

所有帧延迟值需要连接并设置在第一帧上。

using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; internal static class Program { public static void Main() { var redImage = new Bitmap(64, 64); using (var g = Graphics.FromImage(redImage)) g.Clear(Color.Red); var greenImage = new Bitmap(64, 64); using (var g = Graphics.FromImage(greenImage)) g.Clear(Color.Green); var blueImage = new Bitmap(64, 64); using (var g = Graphics.FromImage(blueImage)) g.Clear(Color.Blue); using (var stream = new FileStream("test.gif", FileMode.Create, FileAccess.Write)) WriteAnimatedGif(stream, new (Image Image, uint Delay)[] { (redImage, 20u), (greenImage, 60u), (blueImage, 180u), }); } private static void WriteAnimatedGif(Stream stream, (Image Image, uint Delay)[] frames) { var frameDelayProperty = (PropertyItem)Activator.CreateInstance(typeof(PropertyItem), nonPublic: true); frameDelayProperty.Id = (int)PropertyTag.FrameDelay; frameDelayProperty.Len = frames.Length * sizeof(uint); frameDelayProperty.Type = (int)PropertyTagType.Long; frameDelayProperty.Value = new byte[frameDelayProperty.Len]; byte[] frameDelayData = frameDelayProperty.Value; for (int i = 0; i < frames.Length; ++i) BitConverter.GetBytes(frames[i].Delay).CopyTo(frameDelayData, i * sizeof(uint)); var loopCountProperty = (PropertyItem)Activator.CreateInstance(typeof(PropertyItem), nonPublic: true); loopCountProperty.Id = (int)PropertyTag.LoopCount; loopCountProperty.Len = sizeof(ushort); loopCountProperty.Type = (short)PropertyTagType.Short; loopCountProperty.Value = BitConverter.GetBytes((ushort)0); var firstImage = frames[0].Image; firstImage.SetPropertyItem(frameDelayProperty); firstImage.SetPropertyItem(loopCountProperty); var encoder = FindGifEncoder(); using (var encoderParameters = new EncoderParameters(1)) { using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.MultiFrame)) { encoderParameters.Param[0] = saveFlagParameter; firstImage.Save(stream, encoder, encoderParameters); } for (int i = 1; i < frames.Length; ++i) { var frameBitmap = frames[i].Image; using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.FrameDimensionTime)) { encoderParameters.Param[0] = saveFlagParameter; firstImage.SaveAdd(frameBitmap, encoderParameters); } } using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.Flush)) { encoderParameters.Param[0] = saveFlagParameter; firstImage.SaveAdd(encoderParameters); } } } private static ImageCodecInfo FindGifEncoder() { foreach (var encoder in ImageCodecInfo.GetImageEncoders()) if (encoder.FormatID == ImageFormat.Gif.Guid) return encoder; throw new InvalidOperationException("Failed to find GIF encoder."); } private enum PropertyTagType { Short = 3, // ushort Long = 4, // uint } private enum PropertyTag { FrameDelay = 0x5100, LoopCount = 0x5101, } }
但是无法设置帧处理方法,因此如果您尝试创建具有透明度的动画 GIF,请放弃所有希望。

0
投票

© www.soinside.com 2019 - 2024. All rights reserved.