アニメーションGIFを作成する

# はじめに
複数のgifファイルを元にアニメーションGIFを作成するソースコードを作成しました。

# ソースコード
```d:agif1.d
import std.stdio;
import std.utf;

ubyte[] loadGifFile(string ifile)
{
	auto file = File(ifile, "rb");
	scope(exit) file.close();
	uint fsize = cast(uint)(file.size());
	ubyte[] buf = file.rawRead(new ubyte[fsize]);
	return ( buf );
}

void saveGifFile(string ifile, string ofile)
{
	ubyte[] buf = loadGifFile(ifile);
	auto file = File(ofile, "wb");
	scope(exit) file.close();
	file.rawWrite(buf);
}

ubyte[] GetApplicationExtension(short loopCount)
{
	ubyte[] buf = [
		0x21,			// Extension Introducer
		0xFF,			// Application Extension Label
		0x0B,			// Block Size
		cast(byte)'N',	// Application Identifier
		cast(byte)'E',
		cast(byte)'T',
		cast(byte)'S',
		cast(byte)'C',
		cast(byte)'A',
		cast(byte)'P',
		cast(byte)'E',
		cast(byte)'2',	// Application Authentication Code
		cast(byte)'.',
		cast(byte)'0',
		0x03,			// Block Size
		0x01,			// Netscape Extension Code
		0x00,			// Loop Count
		0x00,
		0x00			// Block Terminator
	];
	buf[16] = cast(byte)(loopCount & 0xFF);	// Loop Count
	buf[17] = cast(byte)(loopCount >> 8);
	return ( buf );
}

ubyte[] GetGraphicControlExtension(short delayTime)
{
	ubyte[] buf = [
		0x21,		// Extension Introducer
		0xF9,		// Graphic Control Label
		0x04,		// Byte Size
		0x00,		// Transparent Color Flag
		0x00,		// Delay Time
		0x00,
		0x00,		// Transparent Color Index
		0x00		// Block Terminator
	];
	buf[4] = cast(byte)(delayTime & 0xFF);	// Delay Time
	buf[5] = cast(byte)(delayTime >> 8);
	return ( buf );
}

void saveAGifFile(string[] ifiles, string ofile, short loopCount, short delayTime)
{
	auto file = File(ofile, "wb");
	scope(exit) file.close();
	foreach ( i, ifile; ifiles ){
		ubyte[] buf = loadGifFile(ifile);
		int globalColorTableSize;
		if ( (buf[10] & 0x80) != 0 ){	// Global Color Table
			globalColorTableSize = buf[10] & 0x07;
			buf[10] &= 0x78;
		}
		if ( i == 0 ){
			file.rawWrite(buf[0 .. 13]);
			file.rawWrite(GetApplicationExtension(loopCount));
		}
		buf = buf[13 .. $];
		ubyte[] colorTable;
		if ( globalColorTableSize > 0 ){
			int size = 2 ^^ (globalColorTableSize + 1) * 3;
			colorTable = buf[0 .. size];
			buf = buf[size .. $];
		}
		while ( buf[0] == 0x21 ){
			if ( buf[1] == 0xF9 ){		// Graphics Control Extension
				buf = buf[8 .. $];
			} else if ( buf[1] == 0xFE ){	// Comment Extension
				int size = buf[2] + 4; 
				buf = buf[size .. $];
			} else if ( buf[1] == 0x01 ){	// Plain Text Extension
				int size = buf[15] + 17; 
				buf = buf[size .. $];
			} else if ( buf[1] == 0xFF ){	// Application Extension
				int size = buf[14] + 16; 
				buf = buf[size .. $];
			}
		}
		file.rawWrite(GetGraphicControlExtension(delayTime));
		// Image Block
		if ( globalColorTableSize == 0 ){
			int localColorTableSize = buf[9] & 0x07;
			int size = 2 ^^ (localColorTableSize + 1) * 3;
			buf[9] &= 0x78;
			buf[9] |= 0x80 | localColorTableSize;
			file.rawWrite(buf[0 .. 10]);
			buf = buf[10 .. $];
			colorTable = buf[0 .. size];
			buf = buf[size .. $];
		} else {
			buf[9] &= 0x78;
			buf[9] |= 0x80 | globalColorTableSize;
			file.rawWrite(buf[0 .. 10]);
			buf = buf[10 .. $];
		}
		file.rawWrite(colorTable);
		file.rawWrite(buf[0 .. $-1]);
	}
	file.rawWrite(cast(ubyte[])[0x3B]);		// Trailer
}

void main(string[] args)
{
	if ( args.length == 3 ){
		saveGifFile(args[1], args[2]);
	} else {
		short loopCount = 0;	// 0:infinite
		short delayTime = 50;	// delayTime * 0.01s
		saveAGifFile(args[1 .. $-1], args[$-1], loopCount, delayTime);
	}
}
```
# ソースコード補足
## loadGifFile
入力gifファイル`ifile`をメモリに読み込みます。

## saveGifFile
入力gifファイルが１つの場合、アニメーションGIFを作成せずそのまま出力gifファイル`ofile`を作成します。

## GetApplicationExtension
アニメーションGIFに必要な`Application Extension`を作成します。
ループ回数をセットしています。

## GetGraphicControlExtension
アニメーションGIFに必要な`Graphic Control Extension`を作成します。
アニメーションの表示間隔をセットしています。

## saveAGifFile
アニメーションGIF作成の本処理です。

* ヘッダやトレーラは、1つ目の入力gifファイルのものを使います。画像の幅、高さの情報はヘッダに保持しているため、すべての入力gifファイルは画像の幅、高さが同じであることが前提です。

* 各入力gifファイルが保持する`Graphic Control Extension`、`Comment Extension`、`Plain Text Extension`、`Application Extension`は除きます。

* 新たに`Application Extension`、`Graphic Control Extension`を作成します。

* gifファイルの場合、パレット情報を`Global Color Table`、または`Local Color Table`で保持していますが、それぞれのgifファイルのパレット情報を`Local Color Table`として、保持します。

# コンパイル、実行
```text:
D:\Dev\> dmd -m64 agif1.d

D:\Dev\> agif1 i1.gif i2.gif i3.gif i4.gif i5.gif ani.gif
```
# 実行結果
i1.gif
![i1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/4042a151-a56d-6544-23af-dacf0688c1bb.gif)
i2.gif
![i2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/23b12f1b-b1c9-884f-d36e-9b95525fd0b4.gif)
i3.gif
![i3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/aba87a4c-cf63-7249-ba1d-f72552ce580c.gif)
i4.gif
![i4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/ef8bac30-7ce7-3400-9142-803b76aafd44.gif)
i5.gif
![i5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/cc7b8799-7b32-f352-cfca-4dbbe583650e.gif)
ani.gif
![ani.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675901/024fd58a-5ea5-483e-0ce2-ff1dcce76ee9.gif)

# 参考情報
[GIFフォーマットの詳細](http://www.tohoho-web.com/wwwgif.htm)