次の方法で共有


Direct2D での変換の適用

Direct2D を使用した描画では、ID2D1RenderTarget::FillEllipse メソッドが x 軸と y 軸に揃えた楕円を描画しているのを確認しました。 しかし、ある角度で傾いた楕円を描画するとします。

傾いた楕円を示す画像。

変換を使用すると、次の方法で図形を変更できます。

  • ポイントの周りの回転。
  • スケーリング。
  • 平行移動 (X または Y 方向の変位)。
  • スキュー ( せん断とも呼ばれます)。

変換は、一連のポイントを新しい点のセットにマップする数学的演算です。 たとえば、次の図は、点 P3 を中心に回転する三角形を示しています。 回転が適用されると、ポイント P1 は P1' にマップされ、ポイント P2 は P2' にマップされ、ポイント P3 はそれ自体にマップされます。

ポイントの周りの回転を示す図。

変換は、マトリックスを使用して実装されます。 ただし、使用するために行列の数学を理解する必要はありません。 数学の詳細については、「 付録: マトリックス変換」を参照してください。

Direct2D で変換を適用するには、 ID2D1RenderTarget::SetTransform メソッドを 呼び出します。 このメソッドは、変換を定義する D2D1_MATRIX_3X2_F 構造体を受け取ります。 D2D1::Matrix3x2F クラスのメソッドを呼び出すことで、この構造体を初期化できます。 このクラスには、変換の種類ごとに行列を返す静的メソッドが含まれています。

たとえば、次のコードは、点 (100、100) を中心に 20 度回転を適用します。

pRenderTarget->SetTransform(
    D2D1::Matrix3x2F::Rotation(20, D2D1::Point2F(100,100)));

変換は、 SetTransform を再度呼び出すまで、それ以降のすべての描画操作に適用されます。 現在の変換を削除するには、ID マトリックスを使用して SetTransform を呼び出します。 ID マトリックスを作成するには、 Matrix3x2F::Identity 関数を 呼び出します。

pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity());

時計の手の描画

Circle プログラムをアナログ クロックに変換して、変換を使用してみましょう。 これを行うには、手の線を追加します。

アナログ時計プログラムのスクリーン ショット。

線の座標を計算する代わりに、角度を計算し、回転変換を適用できます。 次のコードは、1 つの時計の手を描画する関数を示しています。 fAngle パラメーターは、手の角度を度単位で指定します。

void Scene::DrawClockHand(float fHandLength, float fAngle, float fStrokeWidth)
{
    m_pRenderTarget->SetTransform(
        D2D1::Matrix3x2F::Rotation(fAngle, m_ellipse.point)
            );

    // endPoint defines one end of the hand.
    D2D_POINT_2F endPoint = D2D1::Point2F(
        m_ellipse.point.x,
        m_ellipse.point.y - (m_ellipse.radiusY * fHandLength)
        );

    // Draw a line from the center of the ellipse to endPoint.
    m_pRenderTarget->DrawLine(
        m_ellipse.point, endPoint, m_pStroke, fStrokeWidth);
}

このコードは、時計の面の中心から始まり、ポイント endPoint で終わる垂直線を描画します。 線は、回転変換を適用して楕円の中心を中心に回転します。 回転の中心点は、時計の面を形成する楕円の中心です。

時計の針の回転を示す図。

次のコードは、時計全体の顔がどのように描画されるかを示しています。

void Scene::RenderScene()
{
    m_pRenderTarget->Clear(D2D1::ColorF(D2D1::ColorF::SkyBlue));

    m_pRenderTarget->FillEllipse(m_ellipse, m_pFill);
    m_pRenderTarget->DrawEllipse(m_ellipse, m_pStroke);

    // Draw hands
    SYSTEMTIME time;
    GetLocalTime(&time);

    // 60 minutes = 30 degrees, 1 minute = 0.5 degree
    const float fHourAngle = (360.0f / 12) * (time.wHour) + (time.wMinute * 0.5f);
    const float fMinuteAngle =(360.0f / 60) * (time.wMinute);

    DrawClockHand(0.6f,  fHourAngle,   6);
    DrawClockHand(0.85f, fMinuteAngle, 4);

    // Restore the identity transformation.
    m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Identity() );
}

Direct2D Clock サンプルから完全な Visual Studio プロジェクトをダウンロードできます。 (楽しみのために、ダウンロードバージョンは時計の顔に放射状の輝きを追加します。

変換の組み合わせ

4 つの基本的な変換は、2 つ以上の行列を乗算することで組み合わせることができます。 たとえば、次のコードでは、回転と変換を組み合わせます。

const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(20);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(40, 10);

pRenderTarget->SetTransform(rot * trans);

Matrix3x2F クラスは、行列乗算に operator*() を提供します。 行列を乗算する順序は重要です。 変換 (M × N) を設定すると、"Apply M first, followed by N" (最初に M を適用し、その後に N が続く) を意味します。たとえば、回転の後に平行移動が続く場合を次に示します。

回転と平行移動を示す図。

この変換のコードを次に示します。

const D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
const D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(rot * trans);

次に、その変換と逆の順序で変換を比較し、その後に回転を続けます。

平行移動とそれに続く回転を示す図。

回転は、元の四角形の中心を中心に実行されます。 この変換のコードを次に示します。

D2D1::Matrix3x2F rot = D2D1::Matrix3x2F::Rotation(45, center);
D2D1::Matrix3x2F trans = D2D1::Matrix3x2F::Translation(x, 0);
pRenderTarget->SetTransform(trans * rot);

ご覧のように、マトリックスは同じですが、操作の順序は変更されています。 これは、行列乗算が可換ではなく、M × N ≠ N × M であるために発生します。

次へ

付録: マトリックス変換