Direct2D教程VI——轉換(Transform)


目前博客園中成系列的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教程I——簡介及首個例子

Direct2D教程II——繪制基本圖形和線型(StrokeStyle)的設置詳解

Direct2D教程III——幾何(Geometry)對象

Direct2D教程IV——筆刷(Brush)對象

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矩陣如下所示

image

M矩陣中起到決定作用是第1、2列的6個數字

 

則轉換的基本計算公式是

P1=P×M

X1=X*m11+Y*m21+m31

Y1=X*m12+Y*m22+m32

 

 

平移轉換(TranslateTransform)

 

如下圖所示,平移轉換(TranslateTransform),把T1轉換到T2

image

 

很容易的推導出,x2=x1+dx,y2=y1+dy。由上面的基本計算公式,可以推導出轉換矩陣M

image

image

 

 

旋轉轉換(RotateTransform)

旋轉轉換(RotateTransform)如下圖所示,T1繞着原點旋轉Θ,轉換到T2

image

假設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

image

image

 

 

縮放轉換(ScaleTransform)

縮放轉換(ScaleTransform)如下圖所示,T1沿着X軸縮放dx倍、Y軸縮放dy倍,轉換到T2

image

 

很容易的推導出,x2=x1·dx,y2=y1·dy。由上面的基本計算公式,可以推導出轉換矩陣M

image

image

 

 

 

傾斜轉換(SkewTransform)

傾斜轉換(SkewTransform)如下圖所示,Y軸向右旋轉Θ1到Y',X軸向下旋轉Θ2到X',T1轉換到T2

image

在上圖中的紅色輔助線的幫助下,可以推導出,x2=x1+y1·TanΘ1,y2=y1+x1·TanΘ2。由上面的基本計算公式,可以推導出轉換矩陣M

image

image

 

 

 

在我們常見的四種轉換(平移轉換(TranslateTransform)、旋轉轉換(RotateTransform)、縮放轉換(ScaleTransform)、傾斜轉換(SkewTransform))都可以用轉換矩陣M來表達。

但轉換矩陣的優勢不僅僅是四種轉化。他還可以衍生出其他的轉化

 

復合轉換

上面介紹的四種轉化都是轉化基准點在原點。如果現在我有需求,繞着(2,1)這個點旋轉30度的這個轉換怎么辦?

實際上,可以把這個轉換分解成三個轉換

1、先是平移轉換,把(2,1)平移到(0,0)

2、再是旋轉轉換,繞着原點旋轉30度

3、再是平移轉換,把(0,0)平移到(2,1)

則這個轉換矩陣M可以用三個轉換的轉換矩陣的連乘來表示

image

image

 

要注意的是矩陣的運算不滿足交換率,即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)類似,都是通過旋轉坐標軸來轉換,具體的區別看下面的示意圖

image

區別在於仿射轉換(AffineTransform)在旋轉坐標軸的時候,坐標軸上的單位長度沒有發生變化(傾斜轉換(SkewTransform)坐標軸的單位長度是發生變化的)。

個人覺得,仿射轉換(AffineTransform)比傾斜轉換(SkewTransform)更加接近於真實的視覺轉換

可以推導出,x2=x1·CosΘ2+y1·SinΘ1,y2=x1·SinΘ2+y1·CosΘ1。由上面的基本計算公式,可以推導出轉換矩陣M

image

image

 

由於在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)上運用轉換的例子。先看看准備的文件

218

這個是之前的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

看看效果圖吧

image

 

效果還是不錯的吧。把原本正方形的圖案轉換成傾斜視角的圖案。這個效果像不像一些游戲的效果?這個就是仿射轉換(AffineTransform)的功勞。(傾斜轉換(SkewTransform)達不到這樣的效果)

 

下面再給這個畫面添加一個人物,人物如下:

219

 

由於已經把坐標系改成仿射坐標,所以在繪制時,先要進行逆轉換。於是用到之前的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

示例代碼的效果如下:

image

 

有點游戲畫面的雛形了吧。

不過,這個代碼僅僅是舉例,來說明轉換(Transform)的效果。實際上,背景和人物放在不同的圖層里來顯示,可能代碼會簡單一些。但是,繪制在一個圖層里,可以做到坐標的統一,不需要進行坐標的轉換。就看你的取舍了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM