如何在.net中创build一个animationgif

有谁知道如何使用C#创build一个GIFanimation? 理想情况下,我会有一些控制使用的颜色减less。

正在使用imagemagick(作为外部启动过程)的最佳select?

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

您也可以考虑使用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"); } 

不pipe调用imagemagick是否是最好的select,在不知道重要的质量参数的情况下是很难调整的。 其他一些select是:

  • Rick van den Bosch的代码 archive.org镜像
  • 关于codeplex的NGif文章

这些优点是你不必依赖于所有执行你的代码的系统上可能或不可用的第三部分库。

MS Support上的这篇文章解释了如何使用自定义颜色表保存gif(这确实需要完全信任)。 一个GIFanimation只是一个GIF的每个图像与标题中的一些额外的信息。 所以把这两篇文章结合起来应该可以得到你所需要的。

来自https://github.com/DataDink/Bumpkit的这个Gifanimation创build者代码可以设置Delay foreach Frame:

使用.Net标准的Gif编码并添加animation标题。

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

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

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

然后

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

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

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

另外,不要忘记closures文件stream(如下所示):

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

我注意到ImageMagic和NGif的另一个更好的替代方法还没有在答案中列出。

FFMpeg可用于创buildanimationGIF:

  • 图像序列(文件)
  • 现有的video剪辑(例如,MP4或AVI)
  • 从C#位图对象通过标准input提供input数据为“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大小可以通过降低帧频和/或帧大小来轻松降低。 也可以使用2-pass方法获得精美的animationGIF,从而生成最佳的GIF调色板。