57

Does anyone know how to create an animated gif using c#? Ideally I would have some control over the color reduction used.

Is using imagemagick (as an external started process) the best choice?

4

8 回答 8

28

这个来自https://github.com/DataDink/Bumpkit的 Gif Animation Creater 代码可以为每个帧设置延迟:

使用 .Net 标准 Gif 编码并添加动画标题。

编辑:使代码类似于典型的文件编写器。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading.Tasks;

/// <summary>
/// Creates a GIF using .Net GIF encoding and additional animation headers.
/// </summary>
public class GifWriter : IDisposable
{
    #region Fields
    const long SourceGlobalColorInfoPosition = 10,
        SourceImageBlockPosition = 789;

    readonly BinaryWriter _writer;
    bool _firstFrame = true;
    readonly object _syncLock = new object();
    #endregion

    /// <summary>
    /// Creates a new instance of GifWriter.
    /// </summary>
    /// <param name="OutStream">The <see cref="Stream"/> to output the Gif to.</param>
    /// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
    /// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
    public GifWriter(Stream OutStream, int DefaultFrameDelay = 500, int Repeat = -1)
    {
        if (OutStream == null)
            throw new ArgumentNullException(nameof(OutStream));

        if (DefaultFrameDelay <= 0)
            throw new ArgumentOutOfRangeException(nameof(DefaultFrameDelay));

        if (Repeat < -1)
            throw new ArgumentOutOfRangeException(nameof(Repeat));

        _writer = new BinaryWriter(OutStream);
        this.DefaultFrameDelay = DefaultFrameDelay;
        this.Repeat = Repeat;
    }

    /// <summary>
    /// Creates a new instance of GifWriter.
    /// </summary>
    /// <param name="FileName">The path to the file to output the Gif to.</param>
    /// <param name="DefaultFrameDelay">Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay.</param>
    /// <param name="Repeat">No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely.</param>
    public GifWriter(string FileName, int DefaultFrameDelay = 500, int Repeat = -1)
        : this(new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read), DefaultFrameDelay, Repeat) { }

    #region Properties
    /// <summary>
    /// Gets or Sets the Default Width of a Frame. Used when unspecified.
    /// </summary>
    public int DefaultWidth { get; set; }

    /// <summary>
    /// Gets or Sets the Default Height of a Frame. Used when unspecified.
    /// </summary>
    public int DefaultHeight { get; set; }

    /// <summary>
    /// Gets or Sets the Default Delay in Milliseconds.
    /// </summary>
    public int DefaultFrameDelay { get; set; }

    /// <summary>
    /// The Number of Times the Animation must repeat.
    /// -1 indicates no repeat. 0 indicates repeat indefinitely
    /// </summary>
    public int Repeat { get; }
    #endregion

    /// <summary>
    /// Adds a frame to this animation.
    /// </summary>
    /// <param name="Image">The image to add</param>
    /// <param name="Delay">Delay in Milliseconds between this and last frame... 0 = <see cref="DefaultFrameDelay"/></param>
    public void WriteFrame(Image Image, int Delay = 0)
    {
        lock (_syncLock)
            using (var gifStream = new MemoryStream())
            {
                Image.Save(gifStream, ImageFormat.Gif);

                // Steal the global color table info
                if (_firstFrame)
                    InitHeader(gifStream, _writer, Image.Width, Image.Height);

                WriteGraphicControlBlock(gifStream, _writer, Delay == 0 ? DefaultFrameDelay : Delay);
                WriteImageBlock(gifStream, _writer, !_firstFrame, 0, 0, Image.Width, Image.Height);
            }

        if (_firstFrame)
            _firstFrame = false;
    }

    #region Write
    void InitHeader(Stream SourceGif, BinaryWriter Writer, int Width, int Height)
    {
        // File Header
        Writer.Write("GIF".ToCharArray()); // File type
        Writer.Write("89a".ToCharArray()); // File Version

        Writer.Write((short)(DefaultWidth == 0 ? Width : DefaultWidth)); // Initial Logical Width
        Writer.Write((short)(DefaultHeight == 0 ? Height : 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 for Repeating
        if (Repeat == -1)
            return;

        Writer.Write(unchecked((short)0xff21)); // Application Extension Block Identifier
        Writer.Write((byte)0x0b); // Application Block Size
        Writer.Write("NETSCAPE2.0".ToCharArray()); // Application Identifier
        Writer.Write((byte)3); // Application block length
        Writer.Write((byte)1);
        Writer.Write((short)Repeat); // Repeat count for images.
        Writer.Write((byte)0); // terminator
    }

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

    static void WriteGraphicControlBlock(Stream SourceGif, BinaryWriter Writer, int FrameDelay)
    {
        SourceGif.Position = 781; // Locating the source GCE
        var blockhead = new byte[8];
        SourceGif.Read(blockhead, 0, blockhead.Length); // Reading source GCE

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

    static void WriteImageBlock(Stream SourceGif, BinaryWriter Writer, bool IncludeColorTable, int X, int Y, int Width, int Height)
    {
        SourceGif.Position = SourceImageBlockPosition; // Locating the image block
        var header = new byte[11];
        SourceGif.Read(header, 0, header.Length);
        Writer.Write(header[0]); // Separator
        Writer.Write((short)X); // Position X
        Writer.Write((short)Y); // Position Y
        Writer.Write((short)Width); // Width
        Writer.Write((short)Height); // 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(header[10]); // LZW Min Code Size

        // Read/Write image data
        SourceGif.Position = SourceImageBlockPosition + header.Length;

        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

    /// <summary>
    /// Frees all resources used by this object.
    /// </summary>
    public void Dispose()
    {
        // Complete File
        _writer.Write((byte)0x3b); // File Trailer

        _writer.BaseStream.Dispose();
        _writer.Dispose();
    }
}
于 2015-09-27T16:15:20.260 回答
26

有一个内置的 .NET 类可以对 GIF 文件进行编码。 GifBitmapEncode MSDN

System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();

foreach (System.Drawing.Bitmap bmpImage in images)
{
    var bmp = bmpImage.GetHbitmap();
    var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        bmp,
        IntPtr.Zero,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
    gEnc.Frames.Add(BitmapFrame.Create(src));
    DeleteObject(bmp); // recommended, handle memory leak
}
using(FileStream fs = new FileStream(path, FileMode.Create))
{
    gEnc.Save(fs);
}
于 2013-05-16T22:07:55.113 回答
24

您也可以考虑使用 ImageMagick 库。

http://www.imagemagick.org/script/api.php中列出的库有两个 .net 包装器

这是一个关于如何使用Magick.net 包装器的示例:

using (MagickImageCollection collection = new MagickImageCollection())
{
  // Add first image and set the animation delay to 100ms
  collection.Add("Snakeware.png");
  collection[0].AnimationDelay = 100;

  // Add second image, set the animation delay to 100ms and flip the image
  collection.Add("Snakeware.png");
  collection[1].AnimationDelay = 100;
  collection[1].Flip();

  // Optionally reduce colors
  QuantizeSettings settings = new QuantizeSettings();
  settings.Colors = 256;
  collection.Quantize(settings);

  // Optionally optimize the images (images should have the same size).
  collection.Optimize();

  // Save gif
  collection.Write("Snakeware.Animated.gif");
}
于 2014-09-30T23:11:16.987 回答
17

Whether or not calling imagemagick is the best choice is kind of hard to awnser without knowing the quality parameters that are important. Some other options would be:

these have the advantage that you don't have a dependency on a third partly library which might or might not be available on all systems executing your code.

This article at MS Support explains how to save a gif with a custom color table (this does require full trust). A animated gif is just a set of gifs for each image with some additional information in the header. So combining these two articles should get you what you need.

于 2009-07-28T19:54:44.883 回答
1

要使用 Windows 窗体应用程序中的示例,请添加对这些程序集的引用:

C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\PresentationCore.dll C:\Program Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\System.Xaml.dll C:\Program文件\参考程序集\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll

然后

  • Int32Rect 位于 System.Windows 命名空间中

  • BitmapSizeOptions 位于 System.Windows.Media.Imaging 命名空间中

  • BitmapFrame 位于 System.Windows.Media.Imaging 命名空间中

另外,不要忘记关闭文件流(像这样):

using(FileStream targetFile = new FileStream(path, FileMode.Create))
{
   gEnc.Save(targetFile);
}
于 2014-06-02T20:37:45.663 回答
1

AnimatedGif包可以制作动画 gif。

using (var gif = AnimatedGif.AnimatedGif.Create(Path.Combine(outputPath, "output.gif"), 20))
{
    for (var i = 0; i < 10; i++)
    {
        Image img = Image.FromFile(Path.Combine(outputPath, $"{i.ToString().PadLeft(3, '0')}.png"));
        gif.AddFrame(img, delay: -1, quality: GifQuality.Bit8);
    }
}
于 2020-04-12T03:52:25.980 回答
0

引用 fireydude 的回答: https ://stackoverflow.com/a/16598294/8917485

此方法不完整,导致.gif 无法重复。我在其他问题上发现了一些额外的代码,让 .gif 重复。

.NET - 使用 GifBitmapEncoder 创建循环 .gif http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp

完整的代码应如下所示:

System.Windows.Media.Imaging.GifBitmapEncoder gEnc = new GifBitmapEncoder();
foreach (System.Drawing.Bitmap bmpImage in bitMaps)
{
    var bmp = bmpImage.GetHbitmap();
    var src = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
        bmp,
        IntPtr.Zero,
        Int32Rect.Empty,
        BitmapSizeOptions.FromEmptyOptions());
    gEnc.Frames.Add(BitmapFrame.Create(src));
    DeleteObject(bmp); // recommended, handle memory leak
    bmpImage.Dispose(); // recommended, handle memory leak
}

// After adding all frames to gifEncoder (the GifBitmapEncoder)...
using (var ms_ = new MemoryStream())
{
    gEnc.Save(ms_);
    var fileBytes = ms_.ToArray();
    // This is the NETSCAPE2.0 Application Extension.
    var applicationExtension = new byte[] { 33, 255, 11, 78, 69, 84, 83, 67, 65, 80, 69, 50, 46, 48, 3, 1, 0, 0, 0 };
    var newBytes = new List<byte>();
    newBytes.AddRange(fileBytes.Take(13));
    newBytes.AddRange(applicationExtension);
    newBytes.AddRange(fileBytes.Skip(13));
    File.WriteAllBytes("abc.gif", newBytes.ToArray());
}
于 2021-09-09T01:28:21.583 回答
0

我注意到答案中还没有列出 ImageMagic 和 NGif 的另一种很好的选择。

FFMpeg可用于从以下位置创建动画 GIF:

  • 图像序列(文件)
  • 现有的视频剪辑(例如 mp4 或 avi)
  • 来自 C# 位图对象,通过标准输入将输入数据作为“ravvideo”提供(不使用任何临时文件)

您可以直接从 C# 代码(使用 System.Diagnostics.Process)启动 ffmpeg.exe,也可以使用现有的 .NET ffmpeg 包装器之一:

var ffmpeg = new NReco.VideoConverter.FFMpegConverter();
ffmpeg.ConvertMedia("your_clip.mp4", null, "result.gif", null, new ConvertSettings() );

(此代码示例使用免费的NReco VideoConverter - 我是此组件的作者,请随时询问有关其使用的任何问题)。

可以通过降低帧速率和/或帧大小来轻松减小 GIF 大小。此外,还可以通过生成最佳 GIF 调色板的 2-pass 方法获得精美的动画 GIF。

于 2015-11-27T08:59:11.613 回答