在 Direct2D 绘图中,我们看到 ID2D1RenderTarget::FillEllipse 方法绘制与 x 轴和 y 轴对齐的椭圆。 假设你想要绘制一个倾斜一定角度的椭圆,该怎么办呢?
通过使用转换,可以通过以下方式更改形状。
- 围绕点旋转。
- 缩放。
- 翻译(X 或 Y 方向的偏移量)。
- 倾斜(也称为 剪切)。
转换是一个数学运算,用于将一组点映射到一组新的点。 例如,下图显示了围绕点 P3 旋转的三角形。 应用旋转后,点 P1 映射到 P1',点 P2 映射到 P2',点 P3 映射到自身。
转换是使用矩阵实现的。 但是,你不必了解矩阵的数学,才能使用它们。 若要了解有关数学的详细信息,请参阅 附录:矩阵转换。
若要在 Direct2D 中应用转换,请调用 ID2D1RenderTarget::SetTransform 方法。 此方法采用 D2D1_MATRIX_3X2_F 结构,该结构定义了转换。 可以通过在 D2D1::Matrix3x2F 类上调用方法来初始化此结构。 此类包含返回每种转换的矩阵的静态方法:
例如,以下代码在点(100,100)周围应用 20 度旋转。
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() );
}
可以从 Direct2D 时钟示例下载完整的 Visual Studio 项目。 (只是为了有趣,下载版本在时钟面上添加了径向渐变。)
组合转换
可以通过乘以两个或多个矩阵来组合四个基本转换。 例如,以下代码将旋转与翻译组合在一起。
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。