Поделиться через


Применение преобразований в Direct2D

В разделе Рисование с помощью Direct2D мы увидели, что метод ID2D1RenderTarget::FillEllipse рисует эллипс, выравниваемый по осям X и Y. Но предположим, что вы хотите нарисовать эллипс с наклоном под углом?

изображение, показывающее наклонный эллипс.

С помощью преобразований можно изменить фигуру следующими способами.

  • Поворот вокруг точки.
  • Масштабирование.
  • Перевод (смещение в направлении X или Y).
  • Наклон (также известный как сдвига).

Преобразование — это математическая операция, которая сопоставляет набор точек с новым набором точек. Например, на следующей схеме показан треугольник, повернутый вокруг точки P3. После применения поворота точка P1 сопоставляется с P1', точка P2 сопоставляется с P2', а точка P3 сопоставляется с самой собой.

Схема, показывющая поворот вокруг точки.

Преобразования реализуются с помощью матриц. Тем не менее, вам не нужно понимать математику матриц, чтобы использовать их. Дополнительные сведения о математических вычислениях см. в разделе Приложение. Преобразования матриц.

Чтобы применить преобразование в Direct2D, вызовите метод ID2D1RenderTarget::SetTransform . Этот метод принимает D2D1_MATRIX_3X2_F структуру, которая определяет преобразование. Эту структуру можно инициализировать, вызвав методы в классе D2D1::Matrix3x2F . Этот класс содержит статические методы, возвращающие матрицу для каждого вида преобразования:

Например, следующий код применяет поворот на 20 градусов вокруг точки (100, 100).

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

Преобразование применяется ко всем последующим операциям рисования до повторного вызова SetTransform . Чтобы удалить текущее преобразование, вызовите SetTransform с матрицей удостоверений. Чтобы создать матрицу удостоверений, вызовите функцию Matrix3x2F::Identity .

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

Рисование часов стрелки

Давайте поместим преобразования для использования, преобразуя нашу программу Circle в аналоговые часы. Это можно сделать, добавив линии для рук.

снимок экрана с аналоговой программой часов.

Вместо вычисления координат для линий можно вычислить угол, а затем применить преобразование поворота. В следующем коде показана функция, которая рисует одну часовую стрелку. Параметр 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() );
}

Полный проект Visual Studio можно скачать в разделе Пример часов Direct2D. (Просто для удовольствия, скачиваемая версия добавляет радиальный gradiant к лицевой стороне часов.)

Объединение преобразований

Четыре основных преобразования можно объединить путем умножения двух или более матриц. Например, следующий код сочетает вращение с переводом.

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.

Следующая

Приложение. Преобразования матрицы