博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Direct2D教程VI——转换(Transform)
阅读量:6648 次
发布时间:2019-06-25

本文共 9054 字,大约阅读时间需要 30 分钟。

目前博客园中成系列的Direct2D的教程有

1、万一的 系列,用的是Delphi 2009

2、zdd的 系列,用的是VS中的C++

3、本文所在的 系列,用的是VS2010的Visual Basic语言(可以很方便的转为C#),基于Windows API Code Pack 1.1。

 

还有官方的说明文档 ,用的是C++。

 

本系列的前几篇文章:

 

自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)的效果。实际上,背景和人物放在不同的图层里来显示,可能代码会简单一些。但是,绘制在一个图层里,可以做到坐标的统一,不需要进行坐标的转换。就看你的取舍了。

转载地址:http://pcuto.baihongyu.com/

你可能感兴趣的文章
Centos安装各种数据分析库,numpy,pandas,matplotlib,seaborn,scipy
查看>>
C#基础知识整理:C#类和结构(3)
查看>>
SharePoint Server 2010 初始化
查看>>
【我眼中的戴尔转型】(四)惠普之道,月亮的脸悄悄地在改变
查看>>
***S 2012 聚合函数 -- 指定分页示例
查看>>
直播疑难杂症排查(3)— 首开慢
查看>>
某公司机房成功搭建openssh server跳板服务器
查看>>
ADT在線互動教學
查看>>
PowerShell 添加 自定义的ScriptProperty 属性
查看>>
Shell一些例子
查看>>
MySQL 可优化的一些参数详解
查看>>
zabbix监控web页面,以及告警配置
查看>>
C#中传值调用和传引用调用的理解
查看>>
硬盘整数分区最精确地方法(转载)
查看>>
Oracle-压缩数据
查看>>
Exchange Server2010系列之十六:客户端访问方式
查看>>
crawler4j 爬爬知多少
查看>>
记录:Protocol Buffers(protobuf)在Java开发中使用
查看>>
关于Diablo3的历史和现状思考
查看>>
一个tomcat配置多个端口
查看>>