[win8] 画像加工をDirectXに任せて、UIはC#にする技 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3454
なところで、metro アプリで DirectX を使おうとしていたのですが、もっと良い方法がありました。既に、SurfaceImageSource ということで DirectX を使えるように用意がされているのですね。
わんくま大阪#49の資料
http://jyurimaru.info/data/20120602wankuma_osaka49/20120602_sao.pdf
SurfaceImageSource
http://blogs.msdn.com/b/windowsappdev_ja/archive/2012/03/22/xaml-directx.aspx
DirectX and XAML interop (Metro style apps using C++ and DirectX) (Preliminary)
http://64.4.10.18/ja-jp/library/hh825871.aspx
なところに、SurfaceImageSource を使って、DirectX で描画できます。と書いてあるのです。
SurfaceImageSource^ surfaceImageSource =
ref new SurfaceImageSource(rectangle1->Width, rectangle1->Height, true);
ImageBrush^ brush = ref new ImageBrush();
brush->ImageSource = surfaceImageSource;
rectangle1->Fill = brush;
ImageBrush オブジェクトに設定して、XAML の UI コントロール(ここでは、Rectangle コントロール)の Fill プロパティに設定しています。簡単に言えば、背景で使う bitmap 型のブラシを作って rectangle コントロールに設定している訳です。bitmap オブジェクトへの描画を DirectX でやるということです。リアルタイムに描画、とは違うのですが、まぁ普通の画像を描画するだけならばこれで十分です。コントロール同士の重なりや、透明度も指定できるので相当汎用的に使えます。
が…問題はですね、この surfaceImageSource ってのにどうやって描画するんですか?というのが、なかなかぶち当たりません。つーか、「省略」してるだろッ!!!(意図的か無意識的かわかりませんが)ってな具合です。DirectX に詳しければ ok なんだけど、詳しくないと途端に分からんという(私を含む)有様 orz
■どうやって、SurfaceImageSource に描画するのか?
結論から言うと、
Direct2D Quickstart for Windows 8 Release Preview
http://msdn.microsoft.com/en-us/library/windows/desktop/hh780340(v=vs.85).aspx
あたりに書いてありました。直接ではないのですが、「Step 3: Create an ID2D1Device and an ID2D1DeviceContext」の「ID2D1DeviceContext::CreateBitmapFromDxgiSurface」の記述が肝です。
SurfaceImageSource オブジェクトを、描画コンテキスト(ID2D1DeviceContext)に結びつけるために、CreateBitmapFromDxgiSurface メソッドを使う訳ですね。なるほど。知らないと分かりませんがな orz
結びつける先は、bitmap でなくても良いらしくテクスチャを使っているサンプルもあるのですが、ComPtr<ID2D1Bitmap1> を使うのが一番楽っぽいので、こんな風に書きます。
Native の surface で、BeginDraw/EndDraw を使うのですが、その間にコンテキストの方の BeginDraw/EndDraw を突っ込めば ok … というか、このコンテキストを使った描画部分は別に、surface の描画の中にある必要はなくって、bitmap に書き込んであるので別のところで描画しても大丈夫です。そのあたりは後程。
ComPtr<IDXGISurface> surface;
RECT updateRect = { 0,0,400,300 };
POINT offset = { 0,0 };
HRESULT beginDrawHR = m_sisNative->BeginDraw(updateRect, &surface, &offset);
// draw to IDXGISurface (the surface paramater)
ComPtr<ID2D1Bitmap1> bitmap;
m_d2dContext->CreateBitmapFromDxgiSurface( surface.Get(), NULL, &bitmap );
m_d2dContext->SetTarget( bitmap.Get());
m_d2dContext->BeginDraw();
m_d2dContext->Clear(D2D1::ColorF(D2D1::ColorF::Green));
Platform::String^ text = "日本語";
IDWriteTextFormat *pTextFormat = NULL;
ID2D1SolidColorBrush *pBlackBrush = NULL;
// static const WCHAR sc_fontName[] = L"Calibri";
static const WCHAR sc_fontName[] = L"HGP明朝E";
static const FLOAT sc_fontSize = 32;
m_dwriteFactory->CreateTextFormat(
sc_fontName,
NULL,
DWRITE_FONT_WEIGHT_NORMAL,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
sc_fontSize,
L"", //locale
&pTextFormat
);
m_d2dContext->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&pBlackBrush
);
m_d2dContext->DrawText(
text->Data(),
text->Length(),
pTextFormat,
D2D1::RectF(0, 0, 400,300),
pBlackBrush);
m_d2dContext->EndDraw();
m_sisNative->EndDraw();
// The SurfaceImageSource object's underlying ISurfaceImageSourceNative object contains the completed bitmap.
ImageBrush^ brush = ref new ImageBrush();
brush->ImageSource = surfaceImageSource;
// fill;
rect1->Fill = brush;
これだと、再描画するたびに bitmap を作成するので処理の無駄ですよね。あらかじめ別のところで bitmap を作成しておくのがベターです。
実行すると、こんな風に扱えます。きちんと透明度(Opacity)も実行できているし、コントロールのz-index(描画順序)も有効なので、かなり使えます。
さて、グラフ描画のような静的な画像はこれでいいのですが、少しアニメーションをしようとするとどうなるのか?が問題ですよね。このあたりは、後で調べることにしましょう。
定期的に ImageBrush の中身を変えて、再描画をすれば変わるような気もするけど…どうなのかな?


良く考えたら、DirectX に慣れている人にとっては、context を使って bitmap に書き込むけど、これを xaml に適用する場合には SurfaceImageSource を使えば良いよね、という流れがあって bitmap の部分は省略しても ok なのかなと。
DirectX を知らない人は、xaml 側から入るので、xaml に書くには SurfaceImageSource を使えば良い、ってところで止まってしまうという罠なのかも。
あれ? surface に bitmap を描画させて、fill に設定するのであれば、context から bitmap に書き込んで、直接 fill に設定するので良くない?
と思ったんだけど、後で調べる。