【问题标题】:how to create an animated gif in .net如何在 .net 中创建动画 gif
【发布时间】:2010-11-14 20:14:10
【问题描述】:

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

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

【问题讨论】:

  • imagemagick 似乎仍然有最好的选择(抖动、色彩还原等)。建议的 lib 和其他创建方法质量很差
  • 您是否尝试过任何最终解决方案?未标记答案...

标签: c# .net dynamic animated-gif


【解决方案1】:

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

【讨论】:

  • 这个类怎么用?
【解决方案2】:

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

【讨论】:

  • 有没有办法使用此代码在每帧之间设置一定的延迟或设置帧速率?
  • 嗯。此代码大量失败。它所做的只是将一堆图像相互叠加并将其保存为单个图像。
  • 在上述解决方案中需要注意的一点是,您需要处理可能由 GDI 位图对象引起的内存泄漏 - 这是给定代码中的 bmpImage.GetHbitmap()。如MSDN notes, this has to be cleared manuallyHere 是一个代码示例,说明如何避免 GDI 位图对象占用过多内存。
  • 有趣,我为内存泄漏添加了额外的代码
  • 有人在创建和显示 gif 后得到一个黑色矩形吗?
【解决方案3】:

您也可以考虑使用 ImageMagick 库。

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

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

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

【讨论】:

    【解决方案4】:

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

    这些的优点是您不依赖第三方库,该库可能在​​执行您的代码的所有系统上都可用,也可能不可用。

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

    【讨论】:

      【解决方案5】:

      要使用 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

      然后

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

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

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

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

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

      【讨论】:

        【解决方案6】:

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

        【讨论】:

          【解决方案7】:

          我注意到答案中还没有列出 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。

          【讨论】:

          • 嘿,我正在寻找一种代码,可以使用 NReco 将多个图像转换为 MP4 (h.264) 格式。我在那里找不到任何工作示例。你能帮我解决这个问题吗?谢谢!
          • 嗨,您知道如何将图像与视频合并。合并视频工作正常。但是当我尝试在两个视频之间合并和图像时,它不适用于 nreco。
          • @Kanishka 如果您知道如何使用 ffmpeg.exe 从命令行执行此操作,那么 VideoConverter 包装器也可以这样做。
          • @VitaliyFedorchenko 关于这个问题的任何想法我已经发布了stackoverflow.com/questions/38613330/nreco-vedio-cut
          • 使用 FFMpeg 获得更多完整的真实世界样本?
          【解决方案8】:

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

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

          .NET - Creating a looping .gif using GifBitmapEncoder 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());
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-07-18
            • 2021-11-29
            • 1970-01-01
            • 2017-09-30
            • 1970-01-01
            • 2016-05-27
            相关资源
            最近更新 更多