Применение преобразований в Direct2D
В разделе Рисование с помощью Direct2D мы увидели, что метод ID2D1RenderTarget::FillEllipse рисует эллипс, выравниваемый по осям X и Y. Но предположим, что вы хотите нарисовать эллипс с наклоном под углом?
С помощью преобразований можно изменить фигуру следующими способами.
- Поворот вокруг точки.
- Масштабирование.
- Перевод (смещение в направлении X или Y).
- Наклон (также известный как сдвига).
Преобразование — это математическая операция, которая сопоставляет набор точек с новым набором точек. Например, на следующей схеме показан треугольник, повернутый вокруг точки P3. После применения поворота точка P1 сопоставляется с P1', точка P2 сопоставляется с P2', а точка P3 сопоставляется с самой собой.
Преобразования реализуются с помощью матриц. Тем не менее, вам не нужно понимать математику матриц, чтобы использовать их. Дополнительные сведения о математических вычислениях см. в разделе Приложение. Преобразования матриц.
Чтобы применить преобразование в Direct2D, вызовите метод ID2D1RenderTarget::SetTransform . Этот метод принимает D2D1_MATRIX_3X2_F структуру, которая определяет преобразование. Эту структуру можно инициализировать, вызвав методы в классе D2D1::Matrix3x2F . Этот класс содержит статические методы, возвращающие матрицу для каждого вида преобразования:
- Matrix3x2F::Rotation
- Matrix3x2F::Scale
- Matrix3x2F::Translation
- 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.