第 6 章: Windows Direct2D の使用

最新のコンピューターには先進的なグラフィックス カードが組み込まれています。こうしたグラフィックス カードの機能を開発者がフルに活用できるよう、Windows 7 には高パフォーマンスな 2D および 3D グラフィックスやマルチ メディア機能をアプリケーションに搭載するための API の集合である、DirectX ライブラリが組み込まれています。API の 1 つである Direct2D は、その名前が示すように、ハードウェア アクセラレータによる DirectX テクノロジを使用して x-y 平面を描画するためのクラスを提供します。また別の API である DirectWrite は、高品質のテキスト レンダリングをサポートします。この記事では、Hilo アプリケーションでの Direct2D および DirectWrite の使用について説明します。

Direct2D の使用

Direct2D は、最新のグラフィックス カードに搭載されたグラフィックス プロセッシング ユニット (GPU) の描画機能へのアクセスを提供する Direct3D アプリケーション プログラミング インターフェイス (API) を使用して作成されたオブジェクト指向型ライブラリです。Direct2D に対応する C++ 開発に必要なヘッダーとライブラリは、Windows Software Development Kit (SDK) for Windows 7 の一部分として提供されています。主要なヘッダーは d2d1.h と d2d1helper.h. の 2 つです。d2d1.h ヘッダー ファイルは、このライブラリに含まれる主な構造、列挙型、インターフェイスを定義します。Direct2D オブジェクトの作成は、他の Direct2D オブジェクト (通常、グローバル関数 D2D1CreateFactory の呼び出しによって作成される D2D1 ファクトリ オブジェクト) のインターフェイスに対する名前付きメソッドを使用して行います。

d2d1helper.h ヘッダー ファイルは、Direct2D を使いやすくするヘルパー クラスや関数を含む C++ 名前空間である D2D1 を定義します。たとえば、名前付きの色の値へのアクセスを提供する D2D1::ColorF というクラスや、D2D1_MATRIX_3X2_F 構造に対して実行可能な処理をカプセル化する D2D1::Matrix3x2F というクラスがあります。

Hilo では、dwrite.h ヘッダー ファイルを通じて DirectWrite も使用します。DirectWrite オブジェクトは Direct2D と同様、DWriteCreateFactory というグローバル メソッドを使用して作成されるファクトリ オブジェクトを使用して作成されます。

Direct2D ライブラリの初期化

Direct2D および DirectWrite ファクトリ オブジェクトの作成はプロセス内で 1 回だけ行います。Hilo の場合、静的メソッドのある Direct2DUtility というヘルパー クラスを使用してこれらのオブジェクトを作成しています。これらのメソッドでは、メソッドを初めて呼び出した時点で初期化され、返される静的変数を後続の呼び出しで使用します。これらのローカルな静的変数は ComPtr<> オブジェクトです。これらは静的であるため、エントリ ポイント関数 _tWinMain が終了すると、ComPtr<> のデストラクターが呼び出され、Direct2D オブジェクトの最後の参照が解放されます。

Direct2D メソッド D2D1CreateFactory では、Direct2D オブジェクトが呼び出されるのがマルチスレッド環境とシングルスレッド環境のどちらであるかを表すパラメーターを使用します。前者の場合、Direct2D ライブラリは、ファクトリ オブジェクトから作成されるすべてのオブジェクトで、あらゆるスレッドi依存の処理をマルチスレッド アクセスから保護します。Hilo はワーカー スレッドを使用して画像の非同期的な読み込み機能を提供します。したがって、Direct2D オブジェクトはマルチスレッド対応のオブジェクトとして作成されます。

レンダー ターゲットの作成

Hilo では、特定のウィンドウを初めて作成する時点で OnCreate メソッドを呼び出します。子ウィンドウへのメッセージを処理する Hilo オブジェクト (たとえば、CarouselPaneMessageHandler および MediaPaneMessageHandler) はそれぞれ、OnMethod を使用して CreateDeviceIndependentResources メソッドを呼び出し、それによって Direct2D および DirectWrite ファクトリ オブジェクトへのインターフェイス ポインターを取得します。

Hilo では子ウィンドウのメッセージ ハンドラー クラスにも、デバイス依存の Direct2D および DirectWrite オブジェクトを作成する CreateDeviceResources というメソッドがあります。デバイス依存のオブジェクトは、グラフィックス カードの GPU によって作成され、レンダー ターゲット、ブラシ、ビットマップ オブジェクトを含んでいます。レンダー ターゲット オブジェクトは描画が行われる場所であり、描画にはブラシとビットマップが使用されます。これらの GPU オブジェクトの寿命、つまりラッパー Direct2D オブジェクトの寿命は、GPU に左右されます。パフォーマンス上の理由から、これらのオブジェクトはできるだけ長く存在する必要がありますが、GPU によってこれらが再利用不可能で、再作成が必要であると示唆される場合もあります。

リスト 1 Hilo でウィンドウを描画するためのパターン

// レンダー ターゲットなどのリソースがまだ作成されていない場合、これらを作成する
HRESULT hr = CreateDeviceResources();
hr = m_renderTarget->BeginDraw();
// ここで描画を実行…
hr = m_renderTarget->EndDraw();

// GPU によってリソースの再作成が必要であると指示される場合、既存のリソースを削除する
if (hr == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources();
}

リスト 1 は、Hilo で使用されているコード パターンです。CreateDeviceResources メソッドは、リソースがまだ作成されていない場合にリソースを作成します。Direct2D リソースを使用する描画はすべて、ID2D1RenderTarget::BeginDraw メソッドの呼び出しと ID2D1RenderTarget::EndDraw メソッドの呼び出しの間に実行する必要があります。EndDraw メソッドは、Direct2D オブジェクトが再利用可能であるかどうかを示す値を返します。このメソッドにより、GPU がデバイス依存オブジェクトの削除を要求していることが判明した場合、DiscardDeviceResources が呼び出されます。この場合、リスト 1 のコードが呼び出されてウィンドウを描画する時点で、CreateDeviceResources メソッドの呼び出しによってオブジェクトが再作成されます。

座標系の使用

レンダー ターゲットで描画を実行する前に、描画する場所を決定しなければなりません。Direct2D では、描画の単位はデバイス独立ピクセル (DIP) で表します。1 DIP は 1/96 インチと定義されています。Direct2D は描画を実行する際に、この描画単位を実際のピクセルに拡大縮小します。この処理は、Windows 7 のドット/インチ (DPI) 設定を使用して行われます。DirectWrite を使用してテキストを描画する場合、フォントのサイズを表すポイント数ではなく、DIP 数を指定します。DIP は浮動小数点数で表記します。既定では、Direct2D 座標は左から右へ行くほど大きくなる x 値と、上から下へ行くほど大きくなる y 値で表されます。レンダー ターゲットの左上隅が x = 0.0f、y = 0.0f です (図 1)。

図 1 Direct2D 座標

Ff934857.a788921f-48b3-4ac9-85c1-2e9a3cba51de-thumb(ja-jp,MSDN.10).png

ウィンドウの DIP 単位でのサイズは、ID2D1RenderTarget::GetSize メソッドを呼び出すことによって取得できます。このメソッドを初めて呼び出した時点では、レンダー ターゲットが最初に作成されたときのウィンドウのクライアント領域のサイズから計算されたサイズが返されます。ウィンドウの大きさが変わると、Direct2D は新しいウィンドウ サイズに合わせて描画を拡大縮小します。これによって、テキストなどの項目がウィンドウ サイズの変化によって引き伸ばされるといった、望ましくない作用が起こる可能性があります。この問題を回避するには、ID2D1HwndRenderTarget::Resize メソッドを呼び出し、デバイス ピクセル単位でのウィンドウ サイズを渡します。

Hilo では多くの項目がアニメ化されているため、これらの項目の位置、サイズ、そして不透明度も時間と共に変化します。位置の変化は、Windows アニメーション マネージャーによって計算されます。この Windows 7 コンポーネントについては次章で詳しく説明します。この時点では、アニメーションに関係する Hilo クラス (たとえば、カルーセル上でのフォルダーの位置をアニメ化する目的で提供された CarouselThumbnailAnimation) には、アニメ化する項目の位置を返すためのメソッドがあるということだけを覚えておいてください。

Windows 7 の座標系の詳細については、MSDN ライブラリの C++ での Windows 対応プログラミングの学習を参照してください。

Direct2D による座標変換の使用

Direct2D 座標に関するここまでの説明は、座標変換が実行されないことを前提としていました。ID2D1RenderTarget::SetTransform メソッドを使用すると、行列を使って座標変換を記述するための D2D1_MATRIX_3X2_F 構造を提供できます。この座標変換はアフィン変換です。つまり、行列には平行移動のほか、回転と拡大縮小に関する情報が含まれます。これらの行列を簡単に使用できるようにするために、d2d1helper.h ヘッダー ファイルは、D2D1_MATRIX_3X2_F 構造から Matrix3x2F というクラスを派生します。Matrix3x2F クラスには、単位行列 (座標変換なし)、または回転、傾き、拡大縮小、平行移動を表す行列を返す静的メソッドがあります。このクラスには、2 つの行列を 1 つの行列に結合するための operator* もあります。

ID2D1RenderTarget::SetTransform メソッドは任意の時点で呼び出すことができ、その後でレンダー ターゲットを通じて実行する描画はすべて、その座標変換による影響を受けます。Hilo では、既定で単位行列が使用されますが、ある状況では回転行列が使用されます。その状況とは、リスト 2 に示すナビゲーション矢印の描画です。

リスト 2 座標変換の使用例 (mediapane.cpp から抜粋)

unsigned int currentPage = GetCurrentPageIndex();
unsigned int maxPages = GetMaxPagesCount();
if (currentPage > 0)
{
// 左矢印を表示する
m_renderTarget->DrawBitmap(
m_arrowBitmap, leftArrowRectangle, 
m_leftArrowSelected || m_leftArrowClicked ?1.0f : 0.5f);
}

if (maxPages > 0 && currentPage < maxPages - 1)
{
// ビットマップを 180 度回転し、右矢印を表示する
m_renderTarget->SetTransform(
D2D1::Matrix3x2F::Rotation(180.0f,
D2D1::Point2F(
rightArrowRectangle.left + (rightArrowRectangle.right - rightArrowRectangle.left) / 2.0f, 
rightArrowRectangle.top + (rightArrowRectangle.bottom - rightArrowRectangle.top) / 2.0f)));

m_renderTarget->DrawBitmap(
m_arrowBitmap, rightArrowRectangle, 
m_rightArrowSelected || m_rightArrowClicked ?1.0f : 0.25f);
}

マウス メッセージの処理

Hilo では、ユーザーはマウス クリックとタッチ スクリーンの両方でアプリケーションと対話できます。このような対話のメッセージでは、マウスまたは指のタッチ位置が、ウィンドウのクライアント領域の左上隅に相対するデバイス座標で渡されます。Hilo はこれらの位置を使用して、項目 (フォルダー、画像) が選択されたかどうかを判別します。そのためには、デバイス ピクセルと DIP 間の変換が可能でなければなりません。Direct2DUtility クラスは、リスト 3 に示すように、この目的で 2 つの静的メソッドを提供しています。これらのメソッドは、レンダー ターゲットに 96 DPI を使用し、ID2D1Factory::GetDesktopDpi メソッドを呼び出してウィンドウの解像度を取得します。

リスト 3 デバイス座標と DIP 間を変換する Direct2DUtility のメソッド

static POINT_2F GetMousePositionForCurrentDpi(LPARAM lParam)
{
static POINT_2F dpi = {96, 96}; // 既定の DPI

ComPtr<ID2D1Factory> factory;
if (SUCCEEDED(GetD2DFactory(&factory)))
   {
factory->GetDesktopDpi(&dpi.x, &dpi.y);
   }

return D2D1::Point2F(
static_cast<int>(static_cast<short>(LOWORD(lParam))) * 96 / dpi.x,
static_cast<int>(static_cast<short>(HIWORD(lParam))) * 96 / dpi.y);
}

static POINT_2F GetMousePositionForCurrentDpi(float x, float y)
{
static POINT_2F dpi = {96, 96}; // 既定の DPI
ComPtr<ID2D1Factory> factory;

f (SUCCEEDED(GetD2DFactory(&factory)))
   {
factory->GetDesktopDpi(&dpi.x, &dpi.y);
   }

return D2D1::Point2F(x * 96 / dpi.x, y * 96 / dpi.y);
}

グラフィックス項目の描画

Hilo で項目を描画するには、いくつかのリソースが必要です。線や塗りつぶしの項目、またはテキストを描画するには、ブラシが必要です。既にビットマップがある場合には、ビットマップ オブジェクトを使用してレンダー ターゲットにそのビットマップを描画できます。これらの項目はすべてデバイス依存リソースであり、レンダー ターゲット オブジェクトへのメソッド呼び出しによって作成されます。たとえば、テキストの描画にはフォント オブジェクトとブラシを使用します。Hilo のカルーセルでは、黒のブラシを使ってフォント名を描画しています。リスト 4 は、CarouselPaneMessageHandler::CreateDeviceResources で該当するコード部分を示しています。

リスト 4 単色のブラシを作成するコード

if (SUCCEEDED(hr))
{
hr = m_renderTarget->CreateSolidColorBrush(
D2D1::ColorF(D2D1::ColorF::Black),
&m_fontBrush
      );
}

D2D1::ColorF クラスには、名前付きの色に対応する静的メンバーが含まれています。色の値は 32 ビット整数なので、このクラスを使用する代わりに値を指定することができます。

線形グラデーション ブラシの作成と使用

Hilo ではグラデーション ブラシも使用しています。たとえば Hilo Browser のカルーセルでは、線形グラデーション ブラシを使用してカルーセル ペインの背景を塗りつぶし、放射状グラデーション ブラシを使用して軌道を描画しています。グラデーション ブラシを作成するには、グラデーション境界コレクションというオブジェクトを追加で使用する必要があります。名前からわかるように、このオブジェクトには、使用する色とその変化に関する情報が含まれています。

リスト 5 線形グラデーション ブラシの作成

// 背景用のグラデーション ブラシを作成する
ComPtr<ID2D1GradientStopCollection> gradientStopCollection;
D2D1_GRADIENT_STOP gradientStops[2];

if (SUCCEEDED(hr))
{
gradientStops[0].color = ColorF(BackgroundColor);
gradientStops[0].position = 0.0f;
gradientStops[1].color = ColorF(ColorF::White);
gradientStops[1].position = 0.25f;
}

if (SUCCEEDED(hr))
{
hr = m_renderTarget->CreateGradientStopCollection(
gradientStops,
      2,
D2D1_GAMMA_2_2,
D2D1_EXTEND_MODE_CLAMP,
&gradientStopCollection
      );
};

if (SUCCEEDED(hr))
{
hr = m_renderTarget->CreateLinearGradientBrush(
LinearGradientBrushProperties(
Point2F(m_renderTarget->GetSize().width, 0),
Point2F(m_renderTarget->GetSize().width, m_renderTarget->GetSize().height)),
gradientStopCollection,
&m_backgroundLinearGradientBrush
      );
}

gradientStopCollection = nullptr;

リスト 5 では、2 つのグラデーション境界構造体を定義しています。各構造体は色と位置についての詳細情報を指定しており、この色がグラデーション軸に沿って塗り広げられます。リスト 5 で重要な詳細情報は、グラデーションが薄紫 (BackgroundColor) で始まり、2 番目の色である白と混ぜることです。白のグラデーション境界はグラデーション軸上で 25% の位置です。したがって、一番上は薄紫の単色であり、それが徐々に薄れていって 25% の位置で完全に白くなり、25% から 100% までの部分は完全に白であるという意味になります。図 2 は、グラデーション境界が色の変化とどのように関係しているかを示しています。グラデーション軸についてはこの後で説明します。グラデーション境界は、この軸上での色の変化を決定するものです。

図 2 線形グラデーション ブラシの機能

Ff934857.f3f5913c-393b-4066-996c-1a458f6bd2fc-thumb(ja-jp,MSDN.10).png

境界は何個でも使用することができ、Direct2D は指定された色を混ぜようとします。そのためには、色の境界に関する情報を ID2D1RenderTarget::CreateGradientStopCollection メソッドに渡す、境界コレクション オブジェクトを作成します。さらに、ID2D1RenderTarget::CreateLinearGradientBrush メソッドを呼び出してブラシを作成します。このメソッドでは、グラデーション軸に関する情報が必要です。グラデーション軸は、どの方向に色を変化させるかを指定します。Hilo では D2D1 名前空間からヘルパー メソッドを使用し、D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES 構造体を初期化しています。この構造には、2 つの点 (それぞれグラデーション軸の始点と終点を表す) だけが含まれます。

グラデーション軸の最も明白な特徴は、軸の角度です。リスト 5 を見ると、軸は垂直線であることがわかります (始点の x 位置は終点と同じ)。コードによってこの直線がカルーセル ウィンドウの右端に定義されていますが、直線の x 位置がどこにあっても同じ効果が達成されます。図 2 は、グラデーション軸とグラデーション境界の対比を示しています。グラデーション境界は、グラデーション軸の長さ全体におけるパーセンテージの変化を定義します。軸の実際の長さは CreateLinearGradientBrush メソッドによって与えられます。

ただし、グラデーション軸にはいくつかの小さな特徴があります。CreateLinearGradientBrush メソッドはブラシを作成するだけで、描画は実行しません。リスト 6 は、グラデーション ブラシを使用する CarouselPaneMessageHandler::DrawClientArea から抜粋したコードです。このコードは、グラデーション軸とまったく同じ高さの領域をブラシを使ってペイントすることを示しています。ID2D1LinearGradientBrush インターフェイスには、グラデーション軸の端点を取得および設定するためのメソッドがあります。したがって、領域をペイントする時点で領域のサイズがブラシの作成時とは異なる場合、これらの点を変更することができます。Hilo Browser のコードではこの状況は起こらないので、ブラシは作成時と同じプロパティ値のままで使用しています。

リスト 6 線形グラデーション ブラシの使用

D2D1_SIZE_F size = m_renderTarget->GetSize();
m_renderTarget->BeginDraw();
m_renderTarget->SetTransform(Matrix3x2F::Identity());
m_renderTarget->FillRectangle(RectF(
0, 0, size.width, size.height), m_backgroundLinearGradientBrush);

// その他のコード

// Direct2D レンダリングの終了
hr = m_renderTarget->EndDraw(); 

テキストの描画

Direct2D でのレンダー ターゲットへのテキストの描画は、ID2D1RenderTarget::DrawText メソッドまたは DrawTextLayout メソッドを使用して行うことができます。テキストを描画する前に、使用するフォントや行間隔、テキストの配置などの情報を格納するオブジェクトを作成する必要があります。このオブジェクトをテキスト形式オブジェクトといい、DirectWrite を使用して作成する必要があります。DirectWrite は Direct2D と同様、オブジェクトが COM に似たインターフェイスを使用しますが、グローバル関数によって作成されたファクトリ オブジェクトからオブジェクトが作成されます。Hilo では、静的メソッド Direct2DUtility::GetDWriteFactory を使用して DirectWrite ファクトリ オブジェクトを作成しています。IDWriteFactory オブジェクトを作成した後、IDWriteFactory::CreateTextFormat メソッドを呼び出して、テキスト形式オブジェクトを作成できます。リスト 7 は、Hilo Browser プロジェクトのカルーセル ペインでテキスト形式オブジェクトを作成するために使用されているコードです。各パラメーターは一目瞭然ですが、サイズの単位はポイントではなく DIP である点に注意してください。

リスト 7 テキスト形式オブジェクトの作成

// メンバー変数: 
//   ComPtr<IDWriteFactory> m_dWriteFactory;
//   ComPtr<IDWriteTextFormat> m_textFormat;

hr = m_dWriteFactory->CreateTextFormat(
L"Arial",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
   12,
L"en-us",
&m_textFormat
   );

テキスト形式オブジェクトを作成した後、DrawText を呼び出してレンダー ターゲットにテキストを描画できます。このメソッドを呼び出す最も簡単な方法は、5 つのパラメーター (文字列とその長さ、テキスト形式オブジェクト、テキストを描画する四角形、およびブラシ) を渡すことです。DrawTextLayout メソッドも同様ですが、このメソッドには、文字列、テキスト形式オブジェクト、テキストを描画する領域のサイズをカプセル化したテキスト レイアウト オブジェクトを渡します。リスト 8 は、テキスト レイアウト オブジェクトを作成する、CarouselThumbnail::CreateTextLayout メソッドから抜粋したコードです。

リスト 8 テキスト レイアウト オブジェクトの作成

HRESULT hr = S_OK;

// テキストの配置を設定する
m_renderingParameters.textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER);

if (nullptr == m_textLayout)
{
hr = m_dWriteFactory->CreateTextLayout(
m_thumbnailInfo.title.c_str(),
static_cast<unsigned int>(m_thumbnailInfo.title.length()),
m_renderingParameters.textFormat,
m_rect.right - m_rect.left,
      16,
&m_textLayout
      );
}

このコードでは、カルーセル オブジェクトがレンダー ターゲット、テキスト形式オブジェクト、およびテキストの描画に使用するブラシを参照して、m_renderingParameters メンバー変数を設定しています。オブジェクトの実際のテキスト描画は、リスト 9 に示す CarouselThumbnail::Draw メソッドで行われます。最初のパラメーターはテキストを描画する位置です (リスト 8 で形式オブジェクトが中央揃えテキストに設定されているので、この場合、テキストの中心点になります)。2 番目のパラメーターはテキスト レイアウト オブジェクトであり、使用するテキストやフォントの詳細情報が含まれています。3 番目のパラメーターはブラシであり、最後にオプションのパラメーターがあります。

リスト 9 テキストの描画

m_renderingParameters.renderTarget->DrawTextLayout(
D2D1::Point2F(m_rect.left, m_rect.bottom),
m_textLayout,
m_renderingParameters.solidBrush,
D2D1_DRAW_TEXT_OPTIONS_CLIP);

前へ | 次へ | ホーム