Share via


Anwenden von Transformationen in Direct2D

In Zeichnen mit Direct2D haben wir gesehen, dass die ID2D1RenderTarget::FillEllipse-Methode eine Ellipse zeichnet, die an der x- und y-Achse ausgerichtet ist. Aber angenommen, Sie möchten eine in einem Winkel gekippte Ellipse zeichnen?

Ein Bild, das eine gekippte Ellipse zeigt.

Mithilfe von Transformationen können Sie ein Shape auf folgende Weise ändern.

  • Drehung um einen Punkt.
  • Skalieren.
  • Übersetzung (Verschiebung in X- oder Y-Richtung).
  • Skew (auch bekannt als Scheren).

Eine Transformation ist eine mathematische Operation, die einen Satz von Punkten einem neuen Satz von Punkten zuordnet. Das folgende Diagramm zeigt beispielsweise ein Dreieck, das um den Punkt P3 gedreht ist. Nachdem die Drehung angewendet wurde, wird der Punkt P1', der Punkt P2 'P2' zugeordnet, und der Punkt P3 wird sich selbst zugeordnet.

Ein Diagramm, das die Drehung um einen Punkt zeigt.

Transformationen werden mithilfe von Matrizen implementiert. Allerdings müssen Sie die Mathematik von Matrizen nicht verstehen, um sie verwenden zu können. Weitere Informationen zur Mathematik finden Sie im Anhang: Matrixtransformationen.

Um eine Transformation in Direct2D anzuwenden, rufen Sie die ID2D1RenderTarget::SetTransform-Methode auf. Diese Methode verwendet eine D2D1_MATRIX_3X2_F-Struktur , die die Transformation definiert. Sie können diese Struktur initialisieren, indem Sie Methoden für die D2D1::Matrix3x2F-Klasse aufrufen. Diese Klasse enthält statische Methoden, die eine Matrix für jede Art von Transformation zurückgeben:

Der folgende Code wendet beispielsweise eine Drehung um 20 Grad um den Punkt (100, 100) an.

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

Die Transformation wird auf alle späteren Zeichnungsvorgänge angewendet, bis Sie SetTransform erneut aufrufen. Um die aktuelle Transformation zu entfernen, rufen Sie SetTransform mit der Identitätsmatrix auf. Um die Identitätsmatrix zu erstellen, rufen Sie die Matrix3x2F::Identity-Funktion auf.

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

Zeichnen von Uhr-Zeigern

Lassen Sie uns Transformationen verwenden, indem wir unser Circle-Programm in eine analoge Uhr konvertieren. Wir können dies tun, indem wir Linien für die Hände hinzufügen.

Ein Screenshot des analogen Uhrprogramms.

Anstatt die Koordinaten für die Linien zu berechnen, können wir den Winkel berechnen und dann eine Drehungstransformation anwenden. Der folgende Code zeigt eine Funktion, die eine Uhr zeigt. Der fAngle-Parameter gibt den Winkel der Hand in Grad an.

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

Dieser Code zeichnet eine vertikale Linie, die von der Mitte der Uhrseite beginnt und am Punkt endPoint endet. Die Linie wird durch Anwenden einer Drehungstransformation um die Mitte der Ellipse gedreht. Der Mittelpunkt für die Drehung ist der Mittelpunkt der Ellipse, die das Zifferblatt bildet.

Ein Diagramm, das die Drehung des Uhrgebers zeigt.

Der folgende Code zeigt, wie das gesamte Uhrgesicht gezeichnet wird.

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

Sie können das gesamte Visual Studio-Projekt aus dem Direct2D Clock-Beispiel herunterladen. (Nur zum Spaß fügt die Downloadversion dem Zifferblatt ein radiales Gradiant hinzu.)

Kombinieren von Transformationen

Die vier grundlegenden Transformationen können kombiniert werden, indem zwei oder mehr Matrizen multipliziert werden. Der folgende Code kombiniert beispielsweise eine Drehung mit einer Übersetzung.

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

pRenderTarget->SetTransform(rot * trans);

Die Matrix3x2F-Klasse stellt operator*() für die Matrixmultiplikation bereit. Die Reihenfolge, in der Sie die Matrizen multiplizieren, ist wichtig. Das Festlegen einer Transformation (M × N) bedeutet "Zuerst M anwenden, gefolgt von N". Hier ist z. B. die Rotation gefolgt von der Übersetzung:

Ein Diagramm, das die Drehung gefolgt von der Übersetzung zeigt.

Hier ist der Code für diese Transformation:

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

Vergleichen Sie diese Transformation nun mit einer Transformation in umgekehrter Reihenfolge, der Übersetzung gefolgt von der Drehung.

Ein Diagramm, das die Übersetzung gefolgt von der Drehung zeigt.

Die Drehung wird um die Mitte des ursprünglichen Rechtecks ausgeführt. Hier ist der Code für diese Transformation.

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

Wie Sie sehen können, sind die Matrizen identisch, aber die Reihenfolge der Vorgänge hat sich geändert. Dies geschieht, weil die Matrixmultiplikation nicht kommutativ ist: M × N ≠ N × M.

Nächste

Anhang: Matrixtransformationen