2011年3月10日木曜日

SlimDXでDirect2Dの画像ファイル読み込み 【書き直しver】

前に書いた記事で色々と問題が出てきたので書き直します。

色々図形を描いたり、文字を描いたりしても全然実践に役立ちませんね。

重要なのは絵を表示すること!

これは凄い難関でした。
というのも、Direct2Dではファイルから直接画像を読み込む方法が無いのです。そういうことは他の優れたライブラリに任せてしまおうということです。

Direct2Dは画像読み込みをWICというやつに任せる気だったようです。Direct2DにはWICのビットマップを読み込むCreateBitmapFromWicBitmap関数が用意されています。それを使うと簡単です。しかし! その関数はSlimDXが対応していないのです。最低。

でも、対応していなくても使えないことはないはずだと考え、色々頑張ってできるようにしました。


Direct2Dでの画像描画は、Bitmapクラスを作り、それを色々して描画するという手順です。このときのBitmapクラスはSlimDXのクラスです。System.Drawing名前空間のものとは別です。

さて、このBitmapクラスを作りたいのですが、Direct2Dには画像ファイルから直接Bitmapクラスを作る方法がありません。何かしら別の道具を使わないといけません。

Direct2Dで画像ファイルを読み込む方法に考えられるのは、GDI+を使う方法、WICを使う方法、他の非標準ライブラリを使う方法、Direct3D10を介する方法の4つです。
非標準ライブラリは知らないし、Direct3Dを使うには私の知識が足らなすぎるので、GDI+とWICを利用する方法を採ることにします。

GDI+とWICはC#から本格的にプログラミングを始めた私には馴染みのない言葉でした。C++とかでは出てくる低レベルな言葉です。C#ではそれらをラップしてあります。ではC#では何なのかと言いますと、

GDI+はC#におけるフォームの描画とかのことです。

そして

WICはC#においてWPFで利用されている画像処理のことです。


それでは2通りの方法を示しましょう。今回は細かく説明したら埒があかないし、誰の得にもならないので説明しません。概要として、GDI+やWICで画像ファイルを読み込んで32ビットのピクセルデータに変換してDirect2Dに渡してやるという感じです。ちなみにDirect2DはPremultipliedフォーマットにしか対応していないので、どちらの方法もPがついているフォーマットを使わないといけません。詳しくはこことかこことか。


<GDI+を使った方法>

これについては、先駆者がいらっしゃいます。このおじさんです。このページでSlimDXにおける方法も書かれています。C++屋さんのようですが、軽々とC#のコードを書いてしまわれるとは恐ろしい限りです。
ちなみにいくつか海外のコードも発見したのですが、同じ方法でした。
そのままも何ですので、C#3.0風にちょっとだけ書き換えました。

using System;
using System.Drawing;
using System.Windows.Forms;
using SlimDX;
using SlimDX.Windows;
using SlimDX.Direct2D;

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 bitmap = CreateBitmap(target,"png.png");

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

              target.DrawBitmap(bitmap);

              target.EndDraw();
            });

            foreach (var item in ObjectTable.Objects)
                item.Dispose();
        }

        public static SlimDX.Direct2D.Bitmap CreateBitmap(RenderTarget target,string filename){
          var srcBitmap = new System.Drawing.Bitmap(filename);

          var bitmapData = srcBitmap.LockBits(
              new Rectangle(0,0,srcBitmap.Width,srcBitmap.Height),
              System.Drawing.Imaging.ImageLockMode.ReadOnly,
              System.Drawing.Imaging.PixelFormat.Format32bppPArgb);

          var stream = new DataStream(bitmapData.Scan0, bitmapData.Stride*bitmapData.Height, true, false);

          var properties = new BitmapProperties(){
            PixelFormat = new PixelFormat(SlimDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied)
          };
          var bitmap = new SlimDX.Direct2D.Bitmap(target, srcBitmap.Size, stream, bitmapData.Stride, properties);

          srcBitmap.UnlockBits(bitmapData);

          return bitmap;
        }
    }
}

CreateBitmap関数を作ってファイルから直接Bitmapクラスを作れるようにしました。CreateBitmap関数の中身は頑張って解読してください。
あ、関数以外はちゃんと説明します。


            var bitmap = CreateBitmap(target,"png.png");
ここでファイルからビットマップを作っています。
このとき使っているCreateBitmap関数の中身を今回作ったわけですが、中身がどうなっているのかは、ぶっちゃけ気にする必要はありません。
ビットマップを作るときはレンダーターゲットが必要になります。


              target.DrawBitmap(bitmap);
ここで作ったビットマップを描いています。
座標を指定していないので(0,0)の位置に描画されます。座標を指定するには、第二引数にRectangleを与えてやります。
ちなみに、ビットマップを描画する方法は、このDrawBitmap関数を使う方法よりも、ビットマップブラシというのを使って描画する方法のほうが汎用性が高いので、たぶんそっちを使うのが普通になるでしょう。


<WICを使った方法>


using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using SlimDX;
using SlimDX.Windows;
using SlimDX.Direct2D;

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 bitmap = CreateBitmap(target,"png.png");

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

              target.DrawBitmap(bitmap);

              target.EndDraw();
            });

            foreach (var item in ObjectTable.Objects)
                item.Dispose();
        }

        public static SlimDX.Direct2D.Bitmap CreateBitmap(RenderTarget target,string filename){
          var uri = new Uri(filename, UriKind.RelativeOrAbsolute);
          var decoder = BitmapDecoder.Create(uri, BitmapCreateOptions.None, BitmapCacheOption.Default);

          var source = new FormatConvertedBitmap(decoder.Frames[0],System.Windows.Media.PixelFormats.Pbgra32,null,0.0);
          
          var size = new Size(source.PixelWidth, source.PixelHeight);
          int stride = 4 * size.Width;

          byte[] data = new byte[stride*size.Height];
          source.CopyPixels(data, stride, 0);
          var stream = new SlimDX.DataStream(data, true, false);

          var properties = new BitmapProperties(){
            PixelFormat = new PixelFormat(SlimDX.DXGI.Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied),
          };
          return new SlimDX.Direct2D.Bitmap(target, size, stream, stride, properties);
        }
    }
}

System.Windows.Media.Imaging名前空間を参照しています。この名前空間がWICをラップしたものになります。
さっきと違うのは、CreateBitmape関数の中身だけです。
使い方は一緒です。ただし、WPFを利用しているのでコンパイルするには長い参照を書かないといけません。私はdoskeyで
doskey wpf=csc $* /r:PresentationFramework.dll /r:PresentationCore.dll /r:WindowsBase.dll ^
    /r:System.Xaml.dll /lib:C:\Windows\Microsoft.NET\Framework\v4.0.30319\WPF
と書いておいて、
wpf test.cs /r:SlimDX.dll
とコンパイルしています。
VisualStudioでは参照を追加するだけなので簡単だと思います。



GDI+を使う方法と、WICを使う方法はどちらが良いのかと気になると思います。
速度の比較をしてみたところ、こんな結果になりました。用いた画像は150×50の透過PNG素材。単位は秒です。環境は私のPC。
1回繰り返し
GDI 合計:0.0102824  一回:0.0102824
WIC 合計:0.0212334  一回:0.0212334

10回繰り返し
GDI 合計:0.0441933  一回:0.00441933
WIC 合計:0.0020931  一回:0.00020931

100回繰り返し
GDI 合計:0.4503374  一回:0.004503374
WIC 合計:0.0220108  一回:0.000220108

1000回繰り返し
GDI 合計:4.8677718  一回:0.0048677718
WIC 合計:0.2646394  一回:0.0002646394

どちらも初回は時間がかかっていて、WICのほうが遅いです。

しかしそれ以降は!

圧倒的ですね。

WICはアニメーションGIFや独自フォーマットも扱えるらしいのでもうWICに決定ですね。ただしコンパイルは面倒です。

0 件のコメント:

コメントを投稿