207 lines
8.2 KiB
C#
207 lines
8.2 KiB
C#
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
|
|
{
|
|
/// <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 = 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;
|
|
}
|
|
|
|
/// <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();
|
|
}
|
|
}
|
|
}
|