This repository has been archived on 2023-12-10. You can view files and clone it, but cannot push or open issues or pull requests.
PainterlyUNO/Matrix App/GifWriter.cs

204 lines
8.2 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Matrix_App
{
/// <summary>
/// Creates a GIF using .Net GIF encoding and additional animation headers.
/// </summary>
public class GifWriter : IDisposable
{
#region Fields
private const long SourceGlobalColorInfoPosition = 10, SourceImageBlockPosition = 789;
private readonly object syncLock = new object();
private readonly BinaryWriter writer;
private bool firstFrame = true;
#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
private 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();
}
}
}