什么是几何变换(Transform)
在图形学中,主要有三种几何变换,分别是平移(Translate),旋转(Rotation)和缩放(Scaling)。在D2D中,这三种变换都有实现,而且还有一种不太常见的变换,倾斜(Skewing)。
Transform是指将一个点从一个坐标系映射到另一个坐标系,或者将一个点从同一个坐标系的一个位置映射到另外一个位置。在实际应用中,通常是将几何图形从一个位置变换到另外一个位置,而图形是由多个顶点组成的,只要将图形中的所有顶点都变换一下,那么整个图形就完成了变换,所以变换的本质是对顶点的变换。在D2D中,用如下三维矩阵M来表示这种变换。
矩阵M的默认值是单位矩阵。
由于仿射变换矩阵的第三列一定是0.0, 0.0, 1.0,而D2D只支持仿射变换,所以我们可以将第三列省略。则上面的矩阵就变成了一个3X2的矩阵,在D2D中用D2D1_MATRIX_3X2来表示这样一个矩阵。如下
D2D坐标系
Direct2D使用左手坐标系,X轴向右为正,Y轴向下为正。如下图。
变换的对象
在Direct2D中有三种方式来实现几何变换,分别是
- Geometry Transform
- Render target Transform
- Brush Transform
其中前两者比较常见,后者比较少见。Geometry Transform很好理解,与D3D中的变换类似,就是直接变换图形本身。Render target Transform变换的是Render target,可以将Render target想象成一块大画布,如果我们想把图形(相当于画布上的画)从一个位置移动到另外一个位置,有两种方法,一种是在新位置重新画一个模型,这相当于Geometry Transform,另一种是奖画布移动一定的偏移量(新旧位置之差),然后在原来的位置上画图,这就相当于Render target变换了。至于画刷变换,则是这三者中最难理解的,画刷变换常常用于图片的绘制,稍候详述。
Geometry Transform
几何图形变换的本质是根据原来的几何图形生成变换后的图形,函数ID2D1Factory::CreateTransformedGeometry就是用来干这件事的。该函数的定义如下:第一参数是变换前的几何图形,第二个参数是变换矩阵,第三个参数是个输出参数,用来接收变换后的几何图形。
virtual HRESULT CreateTransformedGeometry( [in] ID2D1Geometry *sourceGeometry, [in, optional] const D2D1_MATRIX_3X2_F *transform, [out] ID2D1TransformedGeometry **transformedGeometry ) = 0;
下面代码演示了如何使用CreateTransformedGeometry变换一个几何图形。
// 创建新的变换矩形 // m_pRectangleGeometry 是变换前的矩形 // m_pTransformedGeometry 是变换后的矩形 // D2D1::Matrix3x2F::Scale 用来生成变换矩阵,以(175, 175)为中心,放大3倍 hr = m_pD2DFactory->CreateTransformedGeometry( m_pRectangleGeometry, D2D1::Matrix3x2F::Scale( D2D1::SizeF(3.f, 3.f), D2D1::Point2F(175.f, 175.f)), &m_pTransformedGeometry ); // 清除render target上原有的变换。 m_pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); // 绘制变换后的矩形。 m_pRenderTarget->DrawGeometry(m_pTransformedGeometry, m_pBlackBrush, 1);
Render target Transform
Render target是从接口ID2D1RenderTarget继承下来的一种资源类型,它负责创建绘图所需的资源并执行实际的绘制操作。当然它也提供了坐标变换的方法,可以调用函数ID2D1RenderTarget::SetTransform对render target进行变换。变换之后,所有后续的绘制操作都会在变换后的render target中进行。对render target进行变换会影响render target上的图形,如果想变换某个图形而不影响其它的,可以先保存当前的世界矩阵,待变换完毕后再应用一次这个矩阵即可。函数ID2D1RenderTarget::SetTransform()的定义如下:只有一个参数,就是变换用的矩阵。
virtual void SetTransform( [in] const D2D1_MATRIX_3X2_F *transform ) = 0;
下面的代码演示了如何使用render target变换
// 创建矩形 D2D1_RECT_F rectangle = D2D1::Rect(438.0f, 301.5f, 498.0f, 361.5f); // 绘制矩形 m_pRenderTarget->DrawRectangle( rectangle, m_pOriginalShapeBrush, 1.0f, m_pStrokeStyleDash ); // 对render target进行旋转变换 m_pRenderTarget->SetTransform( D2D1::Matrix3x2F::Rotation( 45.0f, D2D1::Point2F(468.0f, 331.5f)) ); // 填充矩形 m_pRenderTarget->FillRectangle(rectangle, m_pFillBrush); // 绘制矩形 m_pRenderTarget->DrawRectangle(rectangle, m_pTransformedShapeBrush);
效果如下图
Brush Transform
使用画刷绘制时,采用的是render target的坐标空间,画刷不会自动与绘制区域对齐,而是从render target的原点(0, 0)开始绘制。对于ID2D1BitmapBrush类型的画刷,可以使用SetTransform方法将位图移动到指定区域然后进行绘制,这一变换只影响画刷本身,不影响render target上的其他对象。
下图是一个画刷变换的例子,这里使用ID2D1BitmapBrush画刷来填充一个矩形,矩形的起始位置是(100, 100),长宽都是100。左图显示的是画刷变换前的绘制效果,可以看到位图是从render target的原点开始绘制的,矩形只有一部分被填充。而右图是画刷经过变换(x,y方向各平移50像素)后的绘制效果,可以看到位图从位置(50, 50)开始绘制,这样整个矩形都被填充了,而不是像左图那样,未填充区域由边界像素拉伸而成。所以画刷变换可以将画刷想象成一张大纸,而奖待填充矩形想象成纸上的模板,进行变换时,模板不动,下面的纸可以来回拉动,进而产生不同的效果。
下面的代码演示了如何使用画刷变换。
// Demonstrate the effect of transforming a bitmap brush. m_pBitmapBrush->SetTransform( D2D1::Matrix3x2F::Translation(D2D1::SizeF(50,50)) ); // To see the content of the rcTransformedBrushRect, comment // out this statement. m_pRenderTarget->FillRectangle( &rcTransformedBrushRect, m_pBitmapBrush ); m_pRenderTarget->DrawRectangle(rcTransformedBrushRect, m_pBlackBrush, 1, NULL);
对于ID2D1LinearGradientBrush类型的画刷,可以通过改变gradient中的start point和endpoint来进行变换。
对于ID2D1RadialGradientBrush类型的画刷,可以通过改变其中心和半径来实现变换。
关于以上两种画刷的详细介绍,请看我之前的一篇博文。
几何图形变换与render target变换的区别
- 几何图形变换只影响当前变换的图形,而render target变换则影响所有图形。
- 几何图形变换只影响图形的fill(填充),不影响图形的stroke(边线),因为变换是在stroke之前进行的。但render target变换则两者都影响。
解释一下上面第二点,比如对一个矩形进行缩放变换,如果使用Geometry Transform,那么变换前后的效果图如下:
如果使用Render target Transform,那么变换前后的效果图如下:
Render target变换对剪裁(Clip)的影响
Render target变换会影响剪裁区域(clipRect)包围矩形的计算,在D2D中,clipRect是一个矩形,它的包围矩形(Bounding box)是一个与坐标轴对齐的矩形。如果函数PushAxisAlignedClip被调用了,那么对render target的变换都会按相同效果施加于剪裁区域(clipRect)。在变换完成后,将重新计算clipRect的包围矩形。为了提高性能,render target所绘制的图形都由这个包围矩形进行剪裁,而不是由原始的clipRect进行剪裁。下图说明了如何计算新的bounding box。
1 下面这个矩形表示一个render target
2 下图表示对render target进行一次旋转变换,红色虚线矩形表示变换后的render target
3 当PushAxisAlignedClip被调用后,旋转变换也将作用于剪裁区域(clipRect),下图中蓝色矩形表示变换后的clipRect。
4 根据变换后的心cipRect,重新计算其Bounding box,下图中绿色虚线矩形即新的包围矩形,render target上绘制的内容将被这个包围矩形所剪裁。
== Happy Coding!!! ==