Tillämpa transformationer i Direct2D

I Drawing with Direct2Dsåg vi att metoden ID2D1RenderTarget::FillEllipse-metoden ritar en ellips som är anpassad till x- och y-axlarna. Men anta att du vill rita en ellips lutad i vinkel?

en bild som visar en lutande ellips.

Genom att använda transformeringar kan du ändra en form på följande sätt.

  • Rotation runt en punkt.
  • Skalning.
  • Översättning (förskjutning i X- eller Y-riktningen).
  • Skew (kallas även skjuvning).

En transformering är en matematisk åtgärd som mappar en uppsättning punkter till en ny uppsättning punkter. I följande diagram visas till exempel en triangel roterad runt punkt P3. När rotationen har tillämpats mappas punkten P1 till P1, punkten P2 mappas till P2 och punkten P3 mappas till sig själv.

ett diagram som visar rotation runt en punkt.

Transformeringar implementeras med hjälp av matriser. Du behöver dock inte förstå matematiken i matriser för att kunna använda dem. Om du vill lära dig mer om matematiken kan du läsa Bilaga: Matristransformeringar.

Om du vill använda en transformering i Direct2D anropar du metoden ID2D1RenderTarget::SetTransform. Den här metoden tar en D2D1_MATRIX_3X2_F struktur som definierar omvandlingen. Du kan initiera den här strukturen genom att anropa metoder på klassen D2D1::Matrix3x2F. Den här klassen innehåller statiska metoder som returnerar en matris för varje typ av transformering:

Följande kod tillämpar till exempel en 20-graders rotation runt punkten (100, 100).

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

Transformen tillämpas på alla senare ritningsåtgärder tills du anropar SetTransform igen. Om du vill ta bort den aktuella transformeringen anropar du SetTransform- med identitetsmatrisen. Om du vill skapa identitetsmatrisen anropar du funktionen Matrix3x2F::Identity.

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

Rita klockvisarna

Nu ska vi använda transformeringar genom att konvertera vårt Circle-program till en analog klocka. Vi kan göra detta genom att lägga till rader för händerna.

en skärmdump av det analoga klockprogrammet.

I stället för att beräkna koordinaterna för linjerna kan vi beräkna vinkeln och sedan tillämpa en rotationstransformation. Följande kod visar en funktion som ritar en klockhand. Parametern fAngle ger handens vinkel i grader.

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

Den här koden ritar en lodrät linje från mitten av klocksidan och slutar vid punkten slutpunkt. Linjen roteras runt mitten av ellipsen genom att tillämpa en rotationstransformation. Mittpunkten för rotationen är mitten av ellipsen som bildar urtavlan.

ett diagram som visar klockhandens rotation.

Följande kod visar hur hela urtavlan ritas.

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

Du kan ladda ned hela Visual Studio-projektet från Direct2D Clock Sample. (Bara för skojs skull lägger nedladdningsversionen till en radiell gradient till urtavlan.)

Kombinera transformationer

De fyra grundläggande transformerna kan kombineras genom att multiplicera två eller flera matriser. Följande kod kombinerar till exempel en rotation med en översättning.

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

pRenderTarget->SetTransform(rot * trans);

Klassen Matrix3x2F tillhandahåller operator*() för matris multiplikation. Den ordning i vilken du multiplicerar matriserna är viktig. Om du anger en transformering (M × N) betyder det "Använd M först, följt av N". Här är till exempel rotation följt av översättning:

ett diagram som visar rotation följt av översättning.

Här är koden för den här transformeringen:

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

Jämför nu transformeringen med en transformering i omvänd ordning, översättning följt av rotation.

ett diagram som visar översättning följt av rotation.

Rotationen utförs runt mitten av den ursprungliga rektangeln. Här är koden för den här transformeringen.

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

Som du ser är matriserna desamma, men ordningen på åtgärderna har ändrats. Detta beror på att matrisens multiplikation inte är kommutativ: M × N ≠ N × M.

Nästa

bilaga: Matrisomvandlingar