アプリ置き場

アプリ置き場

http://www.moreread.net/

PixelFormat.Format8bppIndexedとGIFアニメ出力

これだとキャッチ―さが足りないのではないか。

 
せっかくペイントソフトも作った(http://www.moreread.net/)のでキャッチ―なドット絵を描きたい。描いてみよう。16x16サイズてきついな……。

描いたところでアニメーションさせてみたくなる。ということで簡易アニメーションビューワも作った。左上からZ字で指定の大きさでスキャンしてくだけのものだけど。

さらについでに画像の分割保存機能もつけた。よし、ついでにアニメーションGIFにしてまえ。DOBONさんのところにアニメーションGIFを吐き出すコードがあった。
 

!?
Bitmapクラス(32bpp)をそのままGIF指定で保存すると、デフォルトのパレットで誤差拡散されて無残な結果になってしまった。
どうも先に8bppIndexedカラー化しておかねばならないようだ。
ググっても見つからなかったのでとりあえずてきとうに書いた。
public static Bitmap GetIndexedBitmap(Bitmap orgBmp)
{
    if (orgBmp == null) return null;
 
    //処理時間計測
    Stopwatch sw = new Stopwatch();
    sw.Start();
 
    //元画像(32bpp)をバッファに読み出す
    BitmapData orgData = orgBmp.LockBits(new Rectangle(0, 0, orgBmp.Width, orgBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
    byte orgBuf = new byte[orgBmp.Width * orgBmp.Height * 4];
    Marshal.Copy(orgData.Scan0, orgBuf, 0, orgBuf.Length);
    orgBmp.UnlockBits(orgData);
 
    //出力先画像(8bppIndexed)の生成とバッファの取出し
    Bitmap indexedBmp = new Bitmap(orgBmp.Width, orgBmp.Height, PixelFormat.Format8bppIndexed);
    ColorPalette palette = indexedBmp.Palette;
    int boundaryWidth = ((indexedBmp.Width + 3) / 4) * 4;// 4Byte境界込みの幅
    BitmapData indexedData = indexedBmp.LockBits(new Rectangle(0, 0, indexedBmp.Width, indexedBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
    byte indexedBuf = new byte[boundaryWidth * indexedBmp.Height];
    Marshal.Copy(indexedData.Scan0, indexedBuf, 0, indexedBuf.Length);
 
    //元画像から色を抽出しつつパレットと画像の生成
    Dictionary<Color, int> dicPalette = new Dictionary<Color, int>();
    int count = 1;
    palette.Entries[0] = Color.FromArgb(1, 0, 0); //index 0 は透明色とする
    for (int i = 0; i < indexedBmp.Height; i++)
    {
        for (int j = 0; j < indexedBmp.Width; j++)
        {
            int pos_org = (i * indexedBmp.Width + j) * 4;
            int pos_indexed = i * boundaryWidth + j;
            Color c = Color.FromArgb(BitConverter.ToInt32(orgBuf, pos_org));
            if (c.A < 255)
            {
                indexedBuf[pos_indexed] = 0;
                continue;
            }
            if (!dicPalette.ContainsKey(c))
            {
                dicPalette.Add(c, count);
                palette.Entries[count] = c;
                count++;
                if (count > 255) count = 255;// 256色以上は非対応
            }
            int index = dicPalette[c];
            indexedBuf[pos_indexed] = (byte)index;
        }
    }
 
    //出力先画像(8bppIndexed)の後処理
    indexedBmp.Palette = palette;
    Marshal.Copy(indexedBuf, 0, indexedData.Scan0, indexedBuf.Length);
    indexedBmp.UnlockBits(indexedData);
 
    //計測結果出力
    sw.Stop();
    Console.WriteLine("[GetIndexedBitmap] time = " + sw.ElapsedMilliseconds + "ms");
 
    return indexedBmp;
}
 
ちなみにこちらを参考にしつつ透過情報を埋め込むにあたって、面倒なのでパレット0を透過色固定にしてある。
 
とりあえず上記のコードを通してから、DOBONさんところのGIFアニメを吐き出すコードを通したら、元のパレットを維持したままGIFアニメを出力できた。