在 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() );
}
可以从 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。