什么是幾何變換(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!!! ==