次の方法で共有


Direct2D での変換の適用

Direct2D を使用した Drawing では、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);
}

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

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

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

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 クラスは、行列乗算に演算子 *() を提供します。 行列を乗算する順序は重要です。 変換 (M × 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 であるために発生します。

次へ

付録: マトリックス変換