Partilhar via


Aplicando transformações no Direct2D

Em Desenho com Direct2D, vimos que o método ID2D1RenderTarget::FillEllipse desenha uma elipse que está alinhada aos eixos x e y. Mas suponha que você queira desenhar uma elipse inclinada em um ângulo?

uma imagem que mostra uma elipse inclinada.

Usando transformações, você pode alterar uma forma das seguintes maneiras.

  • Rotação em torno de um ponto.
  • Dimensionamento.
  • Tradução (deslocamento na direção X ou Y).
  • Inclinação (também conhecida como cisalhamento).

Uma transformação é uma operação matemática que mapeia um conjunto de pontos para um novo conjunto de pontos. Por exemplo, o diagrama a seguir mostra um triângulo girado em torno do ponto P3. Após a rotação ser aplicada, o ponto P1 é mapeado para P1', o ponto P2 é mapeado para P2', e o ponto P3 é mapeado para si mesmo.

um diagrama que mostra a rotação em torno de um ponto.

As transformações são implementadas usando matrizes. No entanto, você não precisa entender a matemática das matrizes para usá-las. Se quiser saber mais sobre matemática, consulte Apêndice: Transformações de matriz.

Para aplicar uma transformação no Direct2D, chame o ID2D1RenderTarget::SetTransform método. Este método toma uma estrutura D2D1_MATRIX_3X2_F que define a transformação. Você pode inicializar essa estrutura chamando métodos na classeD2D1::Matrix3x2F. Esta classe contém métodos estáticos que retornam uma matriz para cada tipo de transformação:

Por exemplo, o código a seguir aplica uma rotação de 20 graus em torno do ponto (100, 100).

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

A transformação é aplicada a todas as operações de desenho posteriores até que você chame SetTransform novamente. Para remover a transformação atual, chame SetTransform com a matriz de identidade. Para criar a matriz de identidade, chame a função Matrix3x2F::Identity.

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

Desenhando ponteiros do relógio

Vamos colocar transformações para usar, convertendo nosso programa Circle em um relógio analógico. Podemos fazer isso adicionando linhas para as mãos.

uma captura de tela do programa de relógio analógico.

Em vez de calcular as coordenadas para as linhas, podemos calcular o ângulo e, em seguida, aplicar uma transformação de rotação. O código a seguir mostra uma função que desenha um ponteiro de relógio. O parâmetro fAngle dá o ângulo da mão, em graus.

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

Este código desenha uma linha vertical, começando no centro do mostrador do relógio e terminando no ponto de destino endPoint. A linha é girada em torno do centro da elipse aplicando uma transformação de rotação. O ponto central para a rotação é o centro da elipse que forma o mostrador do relógio.

um diagrama que mostra a rotação do ponteiro do relógio.

O código a seguir mostra como todo o mostrador do relógio é desenhado.

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

Você pode baixar o projeto completo do Visual Studio a partir do exemplo de relógio Direct2D . (Apenas por diversão, a versão de download adiciona um gradiente radial ao mostrador do relógio.)

Combinando transformações

As quatro transformações básicas podem ser combinadas multiplicando duas ou mais matrizes. Por exemplo, o código a seguir combina uma rotação com uma tradução.

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

pRenderTarget->SetTransform(rot * trans);

A classeMatrix3x2F fornece operador *() para multiplicação de matrizes. A ordem em que você multiplica as matrizes é importante. Definir uma transformação (M × N) significa "Aplicar M primeiro, seguido de N." Por exemplo, aqui está a rotação seguida de translação:

um diagrama que mostra a rotação seguida de translação.

Aqui está o código para esta transformação:

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

Agora compare essa transformação com uma transformada na ordem inversa, translação seguida de rotação.

um diagrama que mostra a translação seguida de rotação.

A rotação é realizada em torno do centro do retângulo original. Aqui está o código para essa transformação.

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

Como você pode ver, as matrizes são as mesmas, mas a ordem das operações mudou. Isso acontece porque a multiplicação matricial não é comutativa: M × N ≠ N × M.

Seguinte

Apêndice: Transformações matriciais