在C#中编辑GIF

7

我该如何获取GIF动画的播放时间?

这是我的代码:

GifTime = PoczatkowyGIF.GetFrameCount(FrameDimension.Time); 
//GifTime is a double; PoczatkowyGIF is my gif image.

GifTime不准确。我的动画是3.8秒,但GifTime是3.9秒。我尝试了另一个动画是0.88秒,但GifTime是0。那么我该如何获取动画时间?此外,我该如何编辑GIF的第一帧时间并向现有GIF添加指定时间的帧?


你为什么担心0.01秒的差异呢?你是如何计算动画长度的? - Security Hound
我用Easy Gif Animator制作了它。当我添加每个帧的时间(我是在Photoshop中制作动画)时,总时间为3.8秒。 但好吧,让我们跳过这个问题,回答第二个问题...如果程序能正常工作,那么我的计算持续时间的代码就是正确的。 - Jasiek Sudczak
说实话,我没有看到其他问题。 - Security Hound
1
我该如何编辑GIF的第一帧时间并向现有的GIF添加一个指定时间的帧? - Jasiek Sudczak
2个回答

5
在下面的代码片段中,您可以看到如何计算总持续时间,累加每个帧的持续时间:
for (int f = 0; f < frameCount; f++)
{
  this_delay = BitConverter.ToInt32(image.GetPropertyItem(20736).Value, index) * 10;
  // Minimum delay is 100 ms
  delay += (this_delay < 100 ? 100 : this_delay);  
  index += 4;
}

请注意,帧的持续时间不能短于100毫秒,这可能会导致您的计算差异。
您可以阅读有关检查 .GIF 的 此文章,其中包含其余代码。
此外,要编辑动画图像的最后一帧,您可以使用:
gifImage.SelectActiveFrame(dimension, first_frame_index);
SelectActiveFrame 可以让你编辑图片,修改指定的帧(在这个例子中是第一个)。
我不是完全确定,但这个框架似乎不支持添加帧,我使用了MagickNet重新编码一个新的GIF来实现此目的。你也可以考虑使用NGif作为替代方案。

2
下面的代码是用于创建、打开或编辑Gifs的类。
它无法从流中读取播放时间,但对于您问题的第二部分,它可以在每个帧上指定延迟时间来编写Gif动画。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

/// <summary>
/// Uses default .net GIF encoding and adds animation headers.
/// </summary>
public class Gif : IDisposable, IEnumerable<Image>
{
    #region Header Constants
    const byte FileTrailer = 0x3b,
        ApplicationBlockSize = 0x0b,
        GraphicControlExtensionBlockSize = 0x04;

    const int ApplicationExtensionBlockIdentifier = 0xff21,
        GraphicControlExtensionBlockIdentifier = 0xf921;

    const long SourceGlobalColorInfoPosition = 10,
        SourceGraphicControlExtensionPosition = 781,
        SourceGraphicControlExtensionLength = 8,
        SourceImageBlockPosition = 789,
        SourceImageBlockHeaderLength = 11,
        SourceColorBlockPosition = 13,
        SourceColorBlockLength = 768;

    const string ApplicationIdentification = "NETSCAPE2.0",
        FileType = "GIF",
        FileVersion = "89a";
    #endregion

    class GifFrame
    {
        public GifFrame(Image image, double delay, int xOffset, int yOffset)
        {
            Image = image;
            Delay = delay;
            XOffset = xOffset;
            YOffset = yOffset;
        }

        public Image Image;
        public double Delay;
        public int XOffset, YOffset;
    }

    List<GifFrame> Frames = new List<GifFrame>();

    public Gif() { DefaultFrameDelay = 500; }

    public Gif(Stream InStream, int Repeat = 0, int Delay = 500)
    {
        using (Image Animation = Bitmap.FromStream(InStream))
        {
            int Length = Animation.GetFrameCount(FrameDimension.Time);

            DefaultFrameDelay = Delay;
            this.Repeat = Repeat;

            for (int i = 0; i < Length; ++i)
            {
                Animation.SelectActiveFrame(FrameDimension.Time, i);

                var Frame = new Bitmap(Animation.Size.Width, Animation.Size.Height);

                Graphics.FromImage(Frame).DrawImage(Animation, new Point(0, 0));

                Frames.Add(new GifFrame(Frame, Delay, 0, 0));
            }
        }
    }

    #region Properties
    public int DefaultWidth { get; set; }

    public int DefaultHeight { get; set; }

    public int Count { get { return Frames.Count; } }

    /// <summary>
    /// Default Delay in Milliseconds
    /// </summary>
    public int DefaultFrameDelay { get; set; }

    public int Repeat { get; private set; }
    #endregion

    /// <summary>
    /// Adds a frame to this animation.
    /// </summary>
    /// <param name="Image">The image to add</param>
    /// <param name="XOffset">The positioning x offset this image should be displayed at.</param>
    /// <param name="YOffset">The positioning y offset this image should be displayed at.</param>
    public void AddFrame(Image Image, double? frameDelay = null, int XOffset = 0, int YOffset = 0)
    {
        Frames.Add(new GifFrame(Image, frameDelay ?? DefaultFrameDelay, XOffset, YOffset));
    }

    public void AddFrame(string FilePath, double? frameDelay = null, int XOffset = 0, int YOffset = 0)
    {
        AddFrame(new Bitmap(FilePath), frameDelay, XOffset, YOffset);
    }

    public void RemoveAt(int Index) { Frames.RemoveAt(Index); }

    public void Clear() { Frames.Clear(); }

    public void Save(Stream OutStream)
    {
        using (var Writer = new BinaryWriter(OutStream))
        {
            for (int i = 0; i < Count; ++i)
            {
                var Frame = Frames[i];

                using (var gifStream = new MemoryStream())
                {
                    Frame.Image.Save(gifStream, ImageFormat.Gif);

                    // Steal the global color table info
                    if (i == 0) InitHeader(gifStream, Writer, Frame.Image.Width, Frame.Image.Height);

                    WriteGraphicControlBlock(gifStream, Writer, Frame.Delay);
                    WriteImageBlock(gifStream, Writer, i != 0, Frame.XOffset, Frame.YOffset, Frame.Image.Width, Frame.Image.Height);
                }
            }

            // Complete File
            Writer.Write(FileTrailer);
        }
    }

    #region Write
    void InitHeader(Stream sourceGif, BinaryWriter Writer, int w, int h)
    {
        // File Header
        Writer.Write(FileType.ToCharArray());
        Writer.Write(FileVersion.ToCharArray());

        Writer.Write((short)(DefaultWidth == 0 ? w : DefaultWidth)); // Initial Logical Width
        Writer.Write((short)(DefaultHeight == 0 ? h : DefaultHeight)); // Initial Logical Height

        sourceGif.Position = SourceGlobalColorInfoPosition;
        Writer.Write((byte)sourceGif.ReadByte()); // Global Color Table Info
        Writer.Write((byte)0); // Background Color Index
        Writer.Write((byte)0); // Pixel aspect ratio
        WriteColorTable(sourceGif, Writer);

        // App Extension Header
        unchecked { Writer.Write((short)ApplicationExtensionBlockIdentifier); };
        Writer.Write((byte)ApplicationBlockSize);
        Writer.Write(ApplicationIdentification.ToCharArray());
        Writer.Write((byte)3); // Application block length
        Writer.Write((byte)1);
        Writer.Write((short)Repeat); // Repeat count for images.
        Writer.Write((byte)0); // terminator
    }

    void WriteColorTable(Stream sourceGif, BinaryWriter Writer)
    {
        sourceGif.Position = SourceColorBlockPosition; // Locating the image color table
        var colorTable = new byte[SourceColorBlockLength];
        sourceGif.Read(colorTable, 0, colorTable.Length);
        Writer.Write(colorTable, 0, colorTable.Length);
    }

    void WriteGraphicControlBlock(Stream sourceGif, BinaryWriter Writer, double frameDelay)
    {
        sourceGif.Position = SourceGraphicControlExtensionPosition; // Locating the source GCE
        var blockhead = new byte[SourceGraphicControlExtensionLength];
        sourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE

        unchecked { Writer.Write((short)GraphicControlExtensionBlockIdentifier); }; // Identifier
        Writer.Write((byte)GraphicControlExtensionBlockSize); // Block Size
        Writer.Write((byte)(blockhead[3] & 0xf7 | 0x08)); // Setting disposal flag
        Writer.Write((short)(frameDelay / 10)); // Setting frame delay
        Writer.Write((byte)blockhead[6]); // Transparent color index
        Writer.Write((byte)0); // Terminator
    }

    void WriteImageBlock(Stream sourceGif, BinaryWriter Writer, bool includeColorTable, int x, int y, int w, int h)
    {
        sourceGif.Position = SourceImageBlockPosition; // Locating the image block
        var header = new byte[SourceImageBlockHeaderLength];
        sourceGif.Read(header, 0, header.Length);
        Writer.Write((byte)header[0]); // Separator
        Writer.Write((short)x); // Position X
        Writer.Write((short)y); // Position Y
        Writer.Write((short)w); // Width
        Writer.Write((short)h); // Height

        if (includeColorTable) // If first frame, use global color table - else use local
        {
            sourceGif.Position = SourceGlobalColorInfoPosition;
            Writer.Write((byte)(sourceGif.ReadByte() & 0x3f | 0x80)); // Enabling local color table
            WriteColorTable(sourceGif, Writer);
        }
        else Writer.Write((byte)(header[9] & 0x07 | 0x07)); // Disabling local color table

        Writer.Write((byte)header[10]); // LZW Min Code Size

        // Read/Write image data
        sourceGif.Position = SourceImageBlockPosition + SourceImageBlockHeaderLength;

        var dataLength = sourceGif.ReadByte();
        while (dataLength > 0)
        {
            var imgData = new byte[dataLength];
            sourceGif.Read(imgData, 0, dataLength);

            Writer.Write((byte)dataLength);
            Writer.Write(imgData, 0, dataLength);
            dataLength = sourceGif.ReadByte();
        }

        Writer.Write((byte)0); // Terminator
    }
    #endregion

    public void Dispose()
    {
        Frames.Clear();
        Frames = null;
    }

    public Image this[int Index] { get { return Frames[Index].Image; } }

    public IEnumerator<Image> GetEnumerator() { foreach (var Frame in Frames) yield return Frame.Image; }

    IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}

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