Udostępnij przez


Stosowanie przekształceń w trybie Direct2D

W Rysowaniu z Direct2D widzieliśmy, że metoda ID2D1RenderTarget::FillEllipse rysuje elipsę wyrównaną do osi poziomej i pionowej. Załóżmy jednak, że chcesz narysować wielokropek przechylony pod kątem?

obraz przedstawiający pochyloną elipsę.

Za pomocą przekształceń można zmienić kształt w następujący sposób.

  • Obracanie wokół punktu.
  • Skalowanie.
  • Tłumaczenie (przemieszczenie w kierunku X lub Y).
  • Niesymetryczność (znana również jako ścinanie).

Przekształcenie to operacja matematyczna, która przekształca zestaw punktów w nowy zbiór punktów. Na przykład na poniższym diagramie przedstawiono trójkąt obrócony wokół punktu P3. Po zastosowaniu rotacji punkt P1 jest mapowany na P1', punkt P2 jest mapowany na P2', a punkt P3 pozostaje niezmieniony.

diagram przedstawiający obracanie się wokół punktu.

Przekształcenia są implementowane przy użyciu macierzy. Jednak nie musisz rozumieć matematyki macierzy, aby ich używać. Jeśli chcesz dowiedzieć się więcej o matematyce, zobacz Dodatek: Przekształcenia macierzy.

Aby zastosować przekształcenie w trybie Direct2D, wywołaj metodę ID2D1RenderTarget::SetTransform . Ta metoda przyjmuje D2D1_MATRIX_3X2_F strukturę definiującą transformację. Tę strukturę można zainicjować, wywołując metody w klasie D2D1::Matrix3x2F . Ta klasa zawiera metody statyczne zwracające macierz dla każdego rodzaju transformacji:

Na przykład poniższy kod stosuje obrót 20 stopni wokół punktu (100, 100).

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

Przekształcenie jest stosowane do wszystkich późniejszych operacji rysowania do momentu ponownego wywołania metody SetTransform . Aby usunąć bieżącą transformację, wywołaj metodę SetTransform za pomocą macierzy tożsamości. Aby utworzyć macierz tożsamości, wywołaj funkcję Matrix3x2F::Identity .

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

Rysowanie rąk zegara

Wykorzystajmy przekształcenia, przekształcając nasz program do rysowania koła na zegar analogowy. Możemy to zrobić, dodając linie dla rąk.

zrzut ekranu programu zegara analogowego.

Zamiast obliczać współrzędne linii, możemy obliczyć kąt, a następnie zastosować przekształcenie obrotu. Poniższy kod przedstawia funkcję, która rysuje jedną wskazówkę zegara. Parametr fAngle daje kąt dłoni w stopniach.

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);
}

Ten kod rysuje pionową linię, zaczynając od środka twarzy zegara i kończąc na punkcie końcowym. Linia jest obracana wokół środka elipsy przez zastosowanie przekształcenia obrotowego. Centralnym punktem obrotu jest środek wielokropka, który tworzy twarz zegara.

diagram przedstawiający obrót ręki zegara.

Poniższy kod pokazuje, jak jest rysowana cała twarz zegara.

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() );
}

Pełny projekt programu Visual Studio można pobrać z Direct2D Clock Sample. (Tylko dla zabawy, wersja do pobrania dodaje gradient promieniowy do tarczy zegara.)

Łączenie przekształceń

Cztery podstawowe przekształcenia można połączyć, mnożąc co najmniej dwie macierze. Na przykład poniższy kod łączy rotację z tłumaczeniem.

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

pRenderTarget->SetTransform(rot * trans);

Klasa Matrix3x2F udostępnia operator*() dla mnożenia macierzy. Kolejność mnożenia macierzy jest ważna. Ustawienie przekształcenia (M × N) oznacza "Najpierw zastosuj M, a następnie N". Na przykład poniżej przedstawiono rotację, po której następuje tłumaczenie:

diagram przedstawiający obrót, po którym następuje tłumaczenie.

Oto kod dla tej transformacji:

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

Teraz porównaj tę transformację z przekształceniem w odwrotnej kolejności, tłumaczenie, a następnie rotacja.

diagram przedstawiający tłumaczenie, po którym następuje rotacja.

Rotacja jest wykonywana wokół środka oryginalnego prostokąta. Oto kod dla tej transformacji.

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

Jak widać, macierze są takie same, ale kolejność operacji uległa zmianie. Dzieje się tak, ponieważ mnożenie macierzy nie jest przemienne: M × N ≠ N × M.

Dalej

dodatek : Przekształcenia macierzy