目前博客園中成系列的Direct2D的教程有
1、萬一的 Direct2D 系列,用的是Delphi 2009
2、zdd的 Direct2D 系列,用的是VS中的C++
3、本文所在的 Direct2D教程 系列,用的是VS2010的Visual Basic語言(可以很方便的轉為C#),基於Windows API Code Pack 1.1。
還有官方的說明文檔 Direct2D ,用的是C++。
本系列的前幾篇文章:
Direct2D教程II——繪制基本圖形和線型(StrokeStyle)的設置詳解
Direct2D教程V——位圖(Bitmap)和位圖筆刷(BitmapBrush)
自GDI+開始,GDI+、WPF、Direct2D一路過來,轉換(Transform)始終是一個重要的內容,通過轉換(Transform)能實現一些特殊的效果。
轉換(Transform):通過一定的運算,把一個平面坐標系的點變換為另一個坐標系的點。
目前變換主要有平移轉換(TranslateTransform)、旋轉轉換(RotateTransform)、縮放轉換(ScaleTransform)、傾斜轉換(SkewTransform)
由於圖形是由點組成的,因此轉換也能把圖形轉換成另一個圖形
轉換(Transform)的矩陣知識
在.net中(包括GDI+、WPF、Direct2D),轉換(Transform)是通過定義轉換矩陣實現的。源點P(X,Y),通過轉換(Transform)后得到目標點P1(X1,Y1),是通過轉換矩陣M來實現的。把點的坐標擴成3個分量,前2個分量分別是X分量和Y分量,最后1個分量定義成1。則源點P(X,Y,1),目標點P1(X1,Y1,1)。
轉換矩陣M是個3*3的矩陣,最后1列的3個數字自上而下分別是0、0、1。則M矩陣如下所示
M矩陣中起到決定作用是第1、2列的6個數字
則轉換的基本計算公式是
P1=P×M
X1=X*m11+Y*m21+m31
Y1=X*m12+Y*m22+m32
平移轉換(TranslateTransform)
如下圖所示,平移轉換(TranslateTransform),把T1轉換到T2。
很容易的推導出,x2=x1+dx,y2=y1+dy。由上面的基本計算公式,可以推導出轉換矩陣M
旋轉轉換(RotateTransform)
旋轉轉換(RotateTransform)如下圖所示,T1繞着原點旋轉Θ,轉換到T2點
假設OT1的距離是R,T1和X軸的夾角是α。則x1和y1的公式為
x1=R·Cosα
y1=R·Sinα
同理的,x2和y2的公式為
x2=R·Cos(α+Θ)=R·Cosα·CosΘ-R·Sinα·SinΘ=x1·CosΘ-y1·SinΘ
y2=R·Sin(α+Θ)=R·Sinα·CosΘ+R·Cosα·SinΘ=y1·CosΘ+x1·SinΘ
由上面的基本計算公式,可以推導出轉換矩陣M
縮放轉換(ScaleTransform)
縮放轉換(ScaleTransform)如下圖所示,T1沿着X軸縮放dx倍、Y軸縮放dy倍,轉換到T2點
很容易的推導出,x2=x1·dx,y2=y1·dy。由上面的基本計算公式,可以推導出轉換矩陣M
傾斜轉換(SkewTransform)
傾斜轉換(SkewTransform)如下圖所示,Y軸向右旋轉Θ1到Y',X軸向下旋轉Θ2到X',T1轉換到T2點
在上圖中的紅色輔助線的幫助下,可以推導出,x2=x1+y1·TanΘ1,y2=y1+x1·TanΘ2。由上面的基本計算公式,可以推導出轉換矩陣M
在我們常見的四種轉換(平移轉換(TranslateTransform)、旋轉轉換(RotateTransform)、縮放轉換(ScaleTransform)、傾斜轉換(SkewTransform))都可以用轉換矩陣M來表達。
但轉換矩陣的優勢不僅僅是四種轉化。他還可以衍生出其他的轉化
復合轉換
上面介紹的四種轉化都是轉化基准點在原點。如果現在我有需求,繞着(2,1)這個點旋轉30度的這個轉換怎么辦?
實際上,可以把這個轉換分解成三個轉換
1、先是平移轉換,把(2,1)平移到(0,0)
2、再是旋轉轉換,繞着原點旋轉30度
3、再是平移轉換,把(0,0)平移到(2,1)
則這個轉換矩陣M可以用三個轉換的轉換矩陣的連乘來表示
要注意的是矩陣的運算不滿足交換率,即M1·M2和M2·M1不一定相等
Direct2D中的轉換矩陣
在Direct2D中,也是用矩陣來表示轉換。轉換矩陣的結構是Matrix3x2F
在上面的說明中,矩陣的最后1列是固定的三個數字(0,0,1)。因此結構Matrix3x2F在內部的實現是通過6個變量來實現的(m11、m12、m21、m22、m31、m32)。
來看看結構Matrix3x2F的原型定義
Direct2D1. Matrix3x2F(m11 As Single, m12 As Single, m21 As Single, m22 As Single, m31 As Single, m32 As Single)
Public Shared ReadOnly Property Identity() As Direct2D1. Matrix3x2F
Public Shared Function Translation(x As Single, y As Single) As Direct2D1. Matrix3x2F
Public Shared Function Translation(size As Direct2D1. SizeF) As Direct2D1. Matrix3x2F
Public Shared Function Scale(x As Single, y As Single) As Direct2D1. Matrix3x2F
Public Shared Function Scale(x As Single, y As Single, center As Direct2D1. Point2F) As Direct2D1. Matrix3x2F
Public Shared Function Scale(size As Direct2D1. SizeF) As Direct2D1. Matrix3x2F
Public Shared Function Scale(size As Direct2D1. SizeF, center As Direct2D1. Point2F) As Direct2D1. Matrix3x2F
Public Shared Function Rotation(angle As Single) As Direct2D1. Matrix3x2F
Public Shared Function Rotation(angle As Single, center As Direct2D1. Point2F) As Direct2D1. Matrix3x2F
Public Shared Function Skew(angleX As Single, angleY As Single) As Direct2D1. Matrix3x2F
Public Shared Function Skew(angleX As Single, angleY As Single, center As Direct2D1. Point2F) As Direct2D1. Matrix3x2F
從上面的原型定義可以看出,結構Matrix3x2F以共享方法的形式提供了四種基本的轉換。在旋轉轉換(RotateTransform)、縮放轉換(ScaleTransform)、傾斜轉換(SkewTransform)的共享方法中還提供了以不同基准點的轉換的方法。這樣,就免去了自己再額外計算的過程。
不過由於沒有提供矩陣乘法的函數,也就是在其他的復合轉換中,只能自己進行計算。這也是這個結構的局限性。(或者微軟認為,只需要這幾個轉換就足夠了。實際上在Direct2D的基本類庫中是提供了矩陣的乘法。在封裝成Windows API Code Pack 1.1后,反而是取消了矩陣乘法)
GDI+、WPF中的轉換矩陣
除了Direct2D中的結構Matrix3x2F外。在System.Drawing.Drawing2D空間下也提供了結構Matrix,用於GDI+和WPF中的轉換矩陣。這個結構提供了三個非常有用的方法
Public Sub Multiply(matrix As Drawing2D. Matrix)
Public ReadOnly Property IsInvertible() As Boolean
Public Sub Invert()
第一個方法將指定的矩陣和自身相乘(自身在后,可以用這個函數的另一個重載來實現矩陣的位置不同),提供了矩陣的乘法,實現了自定義的復合轉換
第二個屬性是判斷該矩陣是否可逆
第三個方法是對該矩陣求逆矩陣。求逆矩陣的意義在於獲得逆轉換。(我覺得這個方法很實用,可惜在Direct2D中沒有提供這個方法)
利用結構Matrix實現Direct2D中的Matrix3x2F的矩陣乘法和逆矩陣
我們可以利用結構Matrix來在Direct2D中實現矩陣乘法和逆矩陣,代碼如下:
Public Function Multiply(M1 As Direct2D1. Matrix3x2F, M2 As Direct2D1. Matrix3x2F) As Direct2D1. Matrix3x2F
Dim S1 As New Drawing2D. Matrix(M1.M11, M1.M12, M1.M21, M1.M22, M1.M31, M1.M32)
Dim S2 As New Drawing2D. Matrix(M2.M11, M2.M12, M2.M21, M2.M22, M2.M31, M2.M32)
S1.Multiply(S2, Drawing2D. MatrixOrder.Append)
Dim S() As Single = S1.Elements
Return New Direct2D1. Matrix3x2F(S(0), S(1), S(2), S(3), S(4), S(5))
End Function
Public Function Invert(M1 As Direct2D1. Matrix3x2F) As Direct2D1. Matrix3x2F
Dim S1 As New Drawing2D. Matrix(M1.M11, M1.M12, M1.M21, M1.M22, M1.M31, M1.M32)
If S1.IsInvertible = True Then S1.Invert()
Dim S() As Single = S1.Elements
Return New Direct2D1. Matrix3x2F(S(0), S(1), S(2), S(3), S(4), S(5))
End Function
自定義的仿射轉換(AffineTransform)
仿射轉換(AffineTransform)和傾斜轉換(SkewTransform)類似,都是通過旋轉坐標軸來轉換,具體的區別看下面的示意圖
區別在於仿射轉換(AffineTransform)在旋轉坐標軸的時候,坐標軸上的單位長度沒有發生變化(傾斜轉換(SkewTransform)坐標軸的單位長度是發生變化的)。
個人覺得,仿射轉換(AffineTransform)比傾斜轉換(SkewTransform)更加接近於真實的視覺轉換
可以推導出,x2=x1·CosΘ2+y1·SinΘ1,y2=x1·SinΘ2+y1·CosΘ1。由上面的基本計算公式,可以推導出轉換矩陣M
由於在Direct2D中沒有提供仿射轉換(AffineTransform)的函數,因此自己編寫一個仿射轉換(AffineTransform)的矩陣函數。代碼如下:
Public Function AffineMatrix(angelX As Single, angelY As Single) As Direct2D1. Matrix3x2F
Dim M As New Direct2D1. Matrix3x2F
M.M11 = Math.Cos(angelY * Math.PI / 180)
M.M12 = Math.Sin(angelY * Math.PI / 180)
M.M21 = Math.Sin(angelX * Math.PI / 180)
M.M22 = Math.Cos(angelX * Math.PI / 180)
M.M31 = 0
M.M32 = 0
Return M
End Function
轉換(Transform)在Direct2D中運用的范圍
在Direct2D中,什么對象能運用轉換(Transform)?答案是很多,可以是畫布(RenderTarget對象,相當於改變畫布的坐標系)、筆刷(Brush對象,主要是用於位圖筆刷(BitmapBrush),更改位圖筆刷(BitmapBrush)的起始位置等)、形狀(更改形狀的外形、位置等)。幾乎所有的對象都能運用轉換
下面這個例子,是在畫布(RenderTarget)上運用轉換的例子。先看看准備的文件
這個是之前的216.png,現在加上一圈紅邊后,保存為218.png。
Public Class clsDirect2DSample14
Inherits clsDirect2DSample11
Public Shadows Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
.Clear( New Direct2D1. ColorF( Color.Chocolate.ToArgb))
Dim B As Direct2D1. D2DBitmap = LoadBitmapFromFile( "218.png")
Dim BB As Direct2D1. BitmapBrush = _renderTarget.CreateBitmapBrush(B)
BB.Transform = Direct2D1. Matrix3x2F.Scale(0.25, 0.25)
BB.ExtendModeX = Direct2D1. ExtendMode.Wrap
BB.ExtendModeY = Direct2D1. ExtendMode.Wrap
.Transform = AffineMatrix(60, -30)
Dim R As New Direct2D1. RectF(-195, 195, 65, 455)
Dim SB As Direct2D1. SolidColorBrush = _renderTarget.CreateSolidColorBrush( New Direct2D1. ColorF(0, 0, 0))
.DrawRectangle(R, SB, 3)
.FillRectangle(R, BB)
.EndDraw()
End With
End If
End Sub
Public Function AffineMatrix(angelX As Single, angelY As Single) As Direct2D1. Matrix3x2F
Dim M As New Direct2D1. Matrix3x2F
M.M11 = Math.Cos(angelY * Math.PI / 180)
M.M12 = Math.Sin(angelY * Math.PI / 180)
M.M21 = Math.Sin(angelX * Math.PI / 180)
M.M22 = Math.Cos(angelX * Math.PI / 180)
M.M31 = 0
M.M32 = 0
Return M
End Function
Public Function Multiply(M1 As Direct2D1. Matrix3x2F, M2 As Direct2D1. Matrix3x2F) As Direct2D1. Matrix3x2F
Dim S1 As New Drawing2D. Matrix(M1.M11, M1.M12, M1.M21, M1.M22, M1.M31, M1.M32)
Dim S2 As New Drawing2D. Matrix(M2.M11, M2.M12, M2.M21, M2.M22, M2.M31, M2.M32)
S1.Multiply(S2, Drawing2D. MatrixOrder.Append)
Dim S() As Single = S1.Elements
Return New Direct2D1. Matrix3x2F(S(0), S(1), S(2), S(3), S(4), S(5))
End Function
Public Function Invert(M1 As Direct2D1. Matrix3x2F) As Direct2D1. Matrix3x2F
Dim S1 As New Drawing2D. Matrix(M1.M11, M1.M12, M1.M21, M1.M22, M1.M31, M1.M32)
If S1.IsInvertible = True Then S1.Invert()
Dim S() As Single = S1.Elements
Return New Direct2D1. Matrix3x2F(S(0), S(1), S(2), S(3), S(4), S(5))
End Function
End Class
看看效果圖吧
效果還是不錯的吧。把原本正方形的圖案轉換成傾斜視角的圖案。這個效果像不像一些游戲的效果?這個就是仿射轉換(AffineTransform)的功勞。(傾斜轉換(SkewTransform)達不到這樣的效果)
下面再給這個畫面添加一個人物,人物如下:
由於已經把坐標系改成仿射坐標,所以在繪制時,先要進行逆轉換。於是用到之前的Invert(逆轉換函數)和Multiply(矩陣乘法,實現復合轉換)。代碼如下:
Public Class clsDirect2DSample15
Inherits clsDirect2DSample14
Public Shadows Sub Render()
If Not _renderTarget Is Nothing Then
With _renderTarget
.BeginDraw()
.Clear( New Direct2D1. ColorF( Color.Chocolate.ToArgb))
Dim B As Direct2D1. D2DBitmap = LoadBitmapFromFile( "218.png")
Dim Man As Direct2D1. D2DBitmap = LoadBitmapFromFile( "219.png")
Dim BB As Direct2D1. BitmapBrush = _renderTarget.CreateBitmapBrush(B)
BB.Transform = Direct2D1. Matrix3x2F.Scale(0.25, 0.25)
BB.ExtendModeX = Direct2D1. ExtendMode.Wrap
BB.ExtendModeY = Direct2D1. ExtendMode.Wrap
.Transform = AffineMatrix(60, -30)
Dim R As New Direct2D1. RectF(-195, 195, 65, 455)
Dim SB As Direct2D1. SolidColorBrush = _renderTarget.CreateSolidColorBrush( New Direct2D1. ColorF(0, 0, 0))
.DrawRectangle(R, SB, 3)
.FillRectangle(R, BB)
PaintMan(Man, New Direct2D1. Point2F(40, 140), .Transform)
.EndDraw()
End With
End If
End Sub
Public Sub PaintMan(man As Direct2D1. D2DBitmap, point As Direct2D1. Point2F, T As Direct2D1. Matrix3x2F)
Dim F As New Direct2D1. RectF(point.X - 200, point.Y - 200, man.PixelSize.Width + point.X + 200, man.PixelSize.Height + point.Y + 200)
Dim B As Direct2D1. BitmapBrush = _renderTarget.CreateBitmapBrush(man)
B.Transform = Multiply(Invert(T), Direct2D1. Matrix3x2F.Translation(point.X, point.Y))
_renderTarget.FillRectangle(F, B)
End Sub
End Class
示例代碼的效果如下:
有點游戲畫面的雛形了吧。
不過,這個代碼僅僅是舉例,來說明轉換(Transform)的效果。實際上,背景和人物放在不同的圖層里來顯示,可能代碼會簡單一些。但是,繪制在一個圖層里,可以做到坐標的統一,不需要進行坐標的轉換。就看你的取舍了。