2010年11月9日火曜日

SlimDXでDirect2DのGIFアニメーション

今回は! GIFアニメーションを表示してみます!

GIFは256色しか使えません。でもその256色は1600万色から好きに選ぶことができます。パレットという考え方です。しかも透過もできちゃいます。
アニメーションGIFは一枚一枚の画像をフレームと呼んで、それを次々に切り替えて表示します。一つのフレームを表示している時間をフレームレートと呼びます。フレームレートは1/100秒単位で指定可能です。

残念ながら、Direct2Dは標準でGIFには対応していません。自分でGIFファイルからビットマップデータを作って、自分でフレームレートを調べてアニメーションを作らないといけません。
面倒ですが、なかなか面白いですよ。

そんな面倒なことは興味がない! とにかくGIFアニメを表示したい!
という場合には私が作ったサンプルをそのまま使ってください。
もし、ちょっと変わったことがしたくなったら、サンプルを参考にされてください。
WPFでアニメーションGIFを扱おうとしている方も参考になると思います。

AnimatedGifというクラスを作りました。使い方はとっても簡単です。
using System;
using System.Drawing;

using SlimDX;
using SlimDX.Direct2D;
using SlimDX.Windows;

using SupportSlimDX;

namespace Direct2DSample
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            var form = new RenderForm("SlimDX - Direct2D Sample");
            var factory = new Factory();

            var target = new WindowRenderTarget(factory,new WindowRenderTargetProperties(){
                Handle = form.Handle,
                PixelSize = form.ClientSize
            });

            var gifani = AnimatedGif.Create(target,"file.gif");
            gifani.Start();

            MessagePump.Run(form, () =>
            {
              target.BeginDraw();
              target.Clear();

              target.DrawBitmap(gifani.CurrentBitmap);

              target.EndDraw();
            });

            foreach (var item in ObjectTable.Objects)
                item.Dispose();
        }
    }
}
見よ! このシンプルさ! 汎用性を無茶苦茶下げているので無茶苦茶シンプルに扱えますがアニメーションGIFをひたすらループさせる以外何も出来ません。

Direct2Dの基本の説明はこの記事を参考にされてください。

using SupportSlimDX;
SupportSlimDXという名前空間でAnimatedGIFクラスを定義しているので参照を加えています。


            var gifani = AnimatedGif.Create(target,"file.gif");
            gifani.Start();
アニメーションGIFを作成し、アニメーションをスタートさせています。
Direct2Dのビットマップを作るときはレンダーターゲットが必要になります。それで、AnimatedGifクラスの中でビットマップの生成も行っているので、レンダーターゲットを指定してやらないといけません。


              target.DrawBitmap(gifani.CurrentBitmap);
ここでビットマップを描画しています。描画するときに、アニメーションGIFからその時間のビットマップを取得しています。


使い方はとっても簡単ですが、そのAnimatedGifクラスの中は大変です。
ご覧ください。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using GDI = System.Drawing;
using WIC = System.Windows.Media;
using System.Windows.Media.Imaging;

using SlimDX;
using SlimDX.Direct2D;
using D2D = SlimDX.Direct2D;

namespace SupportSlimDX
{
  public class AnimatedGif
  {
    #region Fields
    private GifBitmapDecoder decoder;
    private List<ushort> sumTimeList;
    private List<Bitmap> bitmapList;
    private Stopwatch stopwatch;
    #endregion

    #region properties
    public Bitmap CurrentBitmap
    {
      get{
        ushort time = (ushort)((this.stopwatch.ElapsedMilliseconds/10)%this.sumTimeList[this.sumTimeList.Count-1]);
        for(int i=0;i<this.sumTimeList.Count;i++){
          if(time<this.sumTimeList[i]){
            return this.bitmapList[i];
          }
        }
        return this.bitmapList[this.decoder.Frames.Count-1];
      }
    }
    #endregion

    #region constructors
    private AnimatedGif(){}
    #endregion

    #region Methods
    public void Start()
    {
      this.stopwatch.Start();
    }
    #endregion

    #region Static Methods
    public static AnimatedGif Create(RenderTarget target,string filename)
    {
      var gifani = new AnimatedGif();
      gifani.decoder = new GifBitmapDecoder(new Uri(filename,UriKind.RelativeOrAbsolute), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
      gifani.stopwatch = new Stopwatch();

      gifani.sumTimeList = new List<ushort>();
      gifani.bitmapList = new List<Bitmap>();
      ushort sum = 0;
      foreach(var frame in gifani.decoder.Frames){
        var metadata = frame.Metadata as BitmapMetadata;
        gifani.sumTimeList.Add(sum+=(ushort)metadata.GetQuery("/grctlext/Delay"));
        gifani.bitmapList.Add(CreateBitmapFromFrame(target,frame));
      }
      return gifani;
    }

    public static Bitmap CreateBitmapFromFrame(RenderTarget target,BitmapFrame frame){
      var source = new FormatConvertedBitmap(frame,WIC.PixelFormats.Bgra32,null,0.0) as BitmapSource;

      byte[] data = new byte[source.PixelWidth*source.PixelHeight*4];
      source.CopyPixels(data,source.PixelWidth*4,0);

      var bitmap = new Bitmap(target, new GDI.Size(source.PixelWidth,source.PixelHeight), new BitmapProperties(){
          PixelFormat = new D2D.PixelFormat(SlimDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied)
          });
      bitmap.FromMemory(data,source.PixelWidth*4);

      return bitmap;
    }
    #endregion
  }
}
これがAnimatedGifクラスです。中身がどうでもいい方はコピペして使ってください。WPFを用いているので、コンパイルにはWPFの参照が必要です。


それから、

この曲に今ハマってるから聴いてみてください。

コードの説明は大変なので、概要だけ説明しておきます。
重要なのはSystem.Windows.Media.Imaging名前空間です。この名前空間はWICという技術をラップしているだけなのですが、WICを使えば、画像ファイルを自由自在に扱うことができます。
WICのデコーダを使うことで、画像ファイルからデータを読み込むことができます。GIFファイルならばGifBitmapDecoderクラスを使います。GifBitmapDecoderクラスのFramesプロパティにフレームが全て入っているので、後はフレームレートを取得してフレームを次々と切り替えれば良いわけです。
フレームレートはフレームのメタデータに入っているので、それを取得すれば良いです。メタデータには他にこんなのがあるみたいです。

Create関数でAnimatedGIFクラスを作るときに、フレームのDirect2Dビットマップのリストと、そのフレームまでのフレームレートの和のリストを作っておきます。
Start関数でスタートしたときに、ストップウォッチをスタートさせます。
CurrentBitmapプロパティでビットマップを取得するときに、ストップウォッチの時間とフレームレートの和とを比較して、対応するフレームのビットマップを返しています。


何のこっちゃって感じですよねw
いやさ、どこまでどのくらい説明すればいいのか全く分からないものですから。コンストラクタを隠蔽してたり、ストップウォッチのカウントからGifアニメーションのループを作ったりとか、そんなのまで説明したほうがいいんですかね?
図を描いたりすればもっと分かりやすいと思うのですが、私もやってみた程度で、全てを把握しているわけではないですし、たまに間違っていたりするので、描き直しが面倒です。

めんどくさがりな性格を直すためにこんな講座を書いているのに面倒面倒言い過ぎだなコイツ。


0 件のコメント:

コメントを投稿