複数の画像からGIFアニメーションを作る

juntk2010-03-10

キャプチャした映像からGIFアニメーションを生成した時のもの。

ソース

ほぼコピペ。ちょっと修正
http://bloggingabout.net/blogs/rick/archive/2005/05/10/3830.aspx
3MF Project: What's In A GIF - Bit by Byte

GifClass.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace GifCreator
{
   public class GifClass
   {
       public GIFVersion m_Version = GIFVersion.GIF87a;
       public Bitmap m_Image = null;
       public List<byte> m_GifSignature = new List<byte>();
       public List<byte> m_ScreenDescriptor = new List<byte>();
       public List<byte> m_ColorTable = new List<byte>();
       public List<byte> m_ImageDescriptor = new List<byte>();
       public List<byte> m_ImageData = new List<byte>();
       public GifClass()
       { }
       public void LoadGifPicture(string filename)
       { LoadGifPicture((Bitmap)Bitmap.FromFile(filename)); }
       public void LoadGifPicture(Bitmap gifPicture)
       {
           MemoryStream stream = new MemoryStream();
           m_Image = gifPicture;
           m_Image.Save(stream, ImageFormat.Gif);
           List<byte> dataList = new List<byte>(stream.ToArray());
           if (!AnalyzeGifSignature(dataList))
           {
               throw (new Exception("File is not a gif!"));
           }
           AnalyzeScreenDescriptor(dataList);
           GIFBlockType blockType = GetTypeOfNextBlock(dataList);
           while (blockType != GIFBlockType.Trailer)
           {
               switch (blockType)
               {
                   case GIFBlockType.ImageDescriptor:
                       AnalyzeImageDescriptor(dataList);
                       break;
                   case GIFBlockType.Extension:
                       ThrowAwayExtensionBlock(dataList);
                       break;
                   default:
                       break;
               }
               blockType = GetTypeOfNextBlock(dataList);
           }
       }
       private bool AnalyzeGifSignature(List<byte> gifData)
       {
           for (int i = 0; i < 6; i++)
               m_GifSignature.Add(gifData[i]);
           gifData.RemoveRange(0, 6);
           List<char> chars = m_GifSignature.ConvertAll<char>(new Converter<byte,char>(ByteToChar));
           string s = new string(chars.ToArray());
           if (s == GIFVersion.GIF89a.ToString())
               m_Version = GIFVersion.GIF89a;
           else if (s == GIFVersion.GIF87a.ToString())
               m_Version = GIFVersion.GIF87a;
           else
               return false;
           return true;
       }
       private char ByteToChar(byte b)
       { return (char)b; }
       private void AnalyzeScreenDescriptor(List<byte> gifData)
       {
           for (int i = 0; i < 7; i++)
               m_ScreenDescriptor.Add(gifData[i]);
           gifData.RemoveRange(0, 7);
           // if the first bit of the fifth byte is set the GlobelColorTable follows this block
           bool globalColorTableFollows = (m_ScreenDescriptor[4] & 0x80) != 0;
           if (globalColorTableFollows)
           {
               int pixel = m_ScreenDescriptor[4] & 0x07;
               int lengthOfColorTableInByte = 3 * ((int)Math.Pow(2, pixel + 1));
               for (int i = 0; i < lengthOfColorTableInByte; i++)
                   m_ColorTable.Add(gifData[i]);
               gifData.RemoveRange(0, lengthOfColorTableInByte);
           }
           m_ScreenDescriptor[4] = (byte)(m_ScreenDescriptor[4] & 0x7F);
       }
       private GIFBlockType GetTypeOfNextBlock(List<byte> gifData)
       {
           GIFBlockType blockType = (GIFBlockType)gifData[0];
           return blockType;
       }
       private void AnalyzeImageDescriptor(List<byte> gifData)
       {
           for (int i = 0; i < 10; i++)
               m_ImageDescriptor.Add(gifData[i]);
           gifData.RemoveRange(0, 10);
           // get ColorTable if exists
           bool localColorMapFollows = (m_ImageDescriptor[9] & 0x80) != 0;
           if (localColorMapFollows)
           {
               int pixel = m_ImageDescriptor[9] & 0x07;
               int lengthOfColorTableInByte = 3 * ((int)Math.Pow(2, pixel + 1));
               m_ColorTable.Clear();
               for (int i = 0; i < lengthOfColorTableInByte; i++)
                   m_ColorTable.Add(gifData[i]);
               gifData.RemoveRange(0, lengthOfColorTableInByte);
           }
           else
           {
               int lastThreeBitsOfGlobalTableDescription = m_ScreenDescriptor[4] & 0x07;
               m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] & 0xF8);
               m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] | lastThreeBitsOfGlobalTableDescription);
           }
           m_ImageDescriptor[9] = (byte)(m_ImageDescriptor[9] | 0x80);
           GetImageData(gifData);
       }
       private void GetImageData(List<byte> gifData)
       {
           m_ImageData.Add(gifData[0]);
           gifData.RemoveAt(0);
           while (gifData[0] != 0x00)
           {
               int countOfFollowingDataBytes = gifData[0];
               for (int i = 0; i <= countOfFollowingDataBytes; i++)
               {
                   m_ImageData.Add(gifData[i]);
               }
               gifData.RemoveRange(0, countOfFollowingDataBytes + 1);
           }
           m_ImageData.Add(gifData[0]);
           gifData.RemoveAt(0);
       }
       private void ThrowAwayExtensionBlock(List<byte> gifData)
       {
           gifData.RemoveRange(0,2); // Delete ExtensionBlockIndicator and ExtensionDetermination
           while (gifData[0] != 0)
           {
               gifData.RemoveRange(0, gifData[0] + 1);
           }
           gifData.RemoveAt(0);
       }
       public enum GIFVersion
       {
           GIF87a,
           GIF89a
       }
       enum GIFBlockType
       {
           ImageDescriptor = 0x2C,
           Extension = 0x21,
           Trailer = 0x3B
       }
   }
}
GifCreator.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace GifCreator
{
    public class GifCreator
    {
        public static void CreateAnimatedGif(List<string> gifFiles, int delay, string outputFile)
        {
            BinaryWriter writer = new BinaryWriter(new FileStream(outputFile, FileMode.Create, FileAccess.ReadWrite));
            byte[] gif_Signature = new byte[] { (byte)'G', (byte)'I', (byte)'F', (byte)'8', (byte)'9', (byte)'a' };
            writer.Write(gif_Signature);
            for (int i = 0; i < gifFiles.Count; i++)
            {
                GifClass gif = new GifClass();
                gif.LoadGifPicture(gifFiles[i]);
                if (i == 0)
                    writer.Write(gif.m_ScreenDescriptor.ToArray());
                writer.Write(GifCreator.CreateGraphicControlExtensionBlock(delay));
                writer.Write(gif.m_ImageDescriptor.ToArray());
                writer.Write(gif.m_ColorTable.ToArray());
                writer.Write(gif.m_ImageData.ToArray());
            }
            writer.Write(GifCreator.CreateLoopBlock());
            writer.Write((byte)0x3B); //End file
            writer.Close();
        }
        public static byte[] CreateGraphicControlExtensionBlock(int delay)
        {
            byte[] result = new byte[8];
            // Split the delay into high- and lowbyte
            byte d1 = (byte)(delay % 256);
            byte d2 = (byte)(delay / 256);
            result[0] = (byte)0x21; // Start ExtensionBlock
            result[1] = (byte)0xF9; // GraphicControlExtension
            result[2] = (byte)0x04; // Size of DataBlock (4)
            result[3] = (byte)0x00;
            result[4] = d1;
            result[5] = d2;
            result[6] = (byte)0x00;
            result[7] = (byte)0x00;
            return result;
        }
        public static byte[] CreateLoopBlock()
        { return CreateLoopBlock(0); }
        public static byte[] CreateLoopBlock(int numberOfRepeatings)
        {
            byte rep1 = (byte)(numberOfRepeatings % 256);
            byte rep2 = (byte)(numberOfRepeatings / 256);
            byte[] result = new byte[19];
            result[0] = (byte)0x21; // Start ExtensionBlock
            result[1] = (byte)0xFF; // ApplicationExtension
            result[2] = (byte)0x0B; // Size of DataBlock (11) for NETSCAPE2.0)
            result[3] = (byte)'N';
            result[4] = (byte)'E';
            result[5] = (byte)'T';
            result[6] = (byte)'S';
            result[7] = (byte)'C';
            result[8] = (byte)'A';
            result[9] = (byte)'P';
            result[10] = (byte)'E';
            result[11] = (byte)'2';
            result[12] = (byte)'.';
            result[13] = (byte)'0';
            result[14] = (byte)0x03; // Size of Loop Block
            result[15] = (byte)0x01; // Loop Indicator
            result[16] = (byte)rep1; // Number of repetitions
            result[17] = (byte)rep2; // 0 for endless loop
            result[18] = (byte)0x00;
            return result;
        }
        public enum GIFVersion
        {
            GIF87a,
            GIF89a
        }
        enum GIFBlockType
        {
            ImageDescriptor = 0x2C,
            Extension = 0x21,
            Trailer = 0x3B
        }
    }
}
Program.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using GifCreator;
using System.Drawing;
namespace gifmaker
{
    class Program
    {
        static void Main(string[] args)
        {
            List<string> files = new List<string>(new string[] { "1.jpg", "2.jpg" });
            GifCreator.GifCreator.CreateAnimatedGif(files, 5, "out.gif");
        }
    }
}