using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Matrix_App { /// /// Creates a GIF using .Net GIF encoding and additional animation headers. /// public class GifWriter : IDisposable { #region Fields const long SourceGlobalColorInfoPosition = 10, SourceImageBlockPosition = 789; readonly BinaryWriter _writer; bool _firstFrame = true; readonly object _syncLock = new object(); #endregion /// /// Creates a new instance of GifWriter. /// /// The to output the Gif to. /// Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay. /// No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely. public GifWriter(Stream OutStream, int DefaultFrameDelay = 500, int Repeat = 0) { 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; } /// /// Creates a new instance of GifWriter. /// /// The path to the file to output the Gif to. /// Default Delay between consecutive frames... FrameRate = 1000 / DefaultFrameDelay. /// No of times the Gif should repeat... -1 not to repeat, 0 to repeat indefinitely. public GifWriter(string FileName, int DefaultFrameDelay = 500, int Repeat = -1) : this(new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read), DefaultFrameDelay, Repeat) { } #region Properties /// /// Gets or Sets the Default Width of a Frame. Used when unspecified. /// public int DefaultWidth { get; set; } /// /// Gets or Sets the Default Height of a Frame. Used when unspecified. /// public int DefaultHeight { get; set; } /// /// Gets or Sets the Default Delay in Milliseconds. /// public int DefaultFrameDelay { get; set; } /// /// The Number of Times the Animation must repeat. /// -1 indicates no repeat. 0 indicates repeat indefinitely /// public int Repeat { get; } #endregion /// /// Adds a frame to this animation. /// /// The image to add /// Delay in Milliseconds between this and last frame... 0 = 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 /// /// Frees all resources used by this object. /// public void Dispose() { // Complete File _writer.Write((byte)0x3b); // File Trailer _writer.BaseStream.Dispose(); _writer.Dispose(); } } }