在 Direct2D 中应用转换

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。

下一个

附录:矩阵转换