C# 如何在 .net 中创建动画 gif

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1196322/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 10:36:19  来源:igfitidea点击:

how to create an animated gif in .net

c#.netdynamicanimated-gif

提问by

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

有谁知道如何使用 C# 创建动画 gif?理想情况下,我会对使用的颜色减少进行一些控制。

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

使用 imagemagick(作为外部启动进程)是最佳选择吗?

回答by olle

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:

在不知道重要的质量参数的情况下,调用 imagemagick 是否是最佳选择有点难以判断。其他一些选择是:

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 articleat 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.

MS 支持的这篇文章解释了如何使用自定义颜色表保存 gif(这确实需要完全信任)。动画 gif 只是每个图像的一组 gif,在标题中带有一些附加信息。因此,将这两篇文章结合起来应该可以满足您的需求。

回答by fireydude

There is a built in .NET class which will encode GIF files. GifBitmapEncode MSDN

有一个内置的 .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);
}

回答by BalintN

To use the sample from a Windows Forms app, add references to these assemblies:

要使用 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 Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll

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 Files\Reference Assemblies\Microsoft\Framework.NETFramework\v4.0\WindowsBase.dll

Then

然后

  • Int32Rect is in the System.Windows namespace

  • BitmapSizeOptions is in the System.Windows.Media.Imaging namespace

  • BitmapFrame is in the System.Windows.Media.Imaging namespace

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

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

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

Also, don't forget to close the file stream (something like this):

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

using(FileStream targetFile = new FileStream(path, FileMode.Create))
{
   gEnc.Save(targetFile);
}

回答by Bryan Legend

You might also consider using the ImageMagick library.

您也可以考虑使用 ImageMagick 库。

There are two .net wrappers for the library listed at http://www.imagemagick.org/script/api.php

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

Here is an example on how to do it using the Magick.net wrapper:

这是一个关于如何使用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");
}

回答by Mathew Sachin

This Gif Animation Creater code from https://github.com/DataDink/Bumpkitcan set Delay foreach Frame:

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

Uses .Net standard Gif Encoding and adds Animation headers.

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

EDIT: Made the code similar to a typical file writer.

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

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();
    }
}

回答by Vitaliy Fedorchenko

I noticed that one more great alternative to ImageMagic and NGif is not listed in answers yet.

我注意到答案中尚未列出 ImageMagic 和 NGif 的另一种更好的替代方案。

FFMpegcan be used for creating animated GIFs from:

FFMpeg可用于从以下内容创建动画 GIF:

  • sequence of images (files)
  • existing video clip (say, mp4 or avi)
  • from C# bitmap objects by providing input data as "ravvideo" through stdin (without using any temp files)
  • 图像序列(文件)
  • 现有的视频剪辑(例如,mp4 或 avi)
  • 从 C# 位图对象通过 stdin 提供输入数据作为“ravvideo”(不使用任何临时文件)

You can start ffmpeg.exe directly from C# code (with System.Diagnostics.Process) or use one of the existing .NET ffmpeg wrappers:

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

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

(this code example uses free NReco VideoConverter- I'm an author of this component, feel free to ask any questions about its usage).

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

GIF size can be easily reduced by decreasing frame rate and/or frame size. Also it is possible to get fine-looking animated GIFs with 2-pass approach that generates optimal GIF palette.

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

回答by jaycer

The AnimatedGifpackage can make animated gifs.

AnimatedGif包可以使GIF动画。

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