AffineTransform類描述了一種二維仿射變換的功能,它是一種二維坐標(biāo)到二維坐標(biāo)之間的線性變換,保持二維圖形的“平直性”(譯注:straightness,即變換后直線還是直線不會打彎,圓弧還是圓弧)和“平行性”(譯注:parallelness,其實是指保二維圖形間的相對位置關(guān)系不變,平行線還是平行線,相交直線的交角不變。大二學(xué)過的復(fù)變,“保形變換/保角變換”都還記得吧,數(shù)學(xué)就是王道啊!)。仿射變換可以通過一系列的原子變換的復(fù)合來實現(xiàn),包括:平移(Translation)、縮放(Scale)、翻轉(zhuǎn)(Flip)、旋轉(zhuǎn)(Rotation)和剪切(Shear)。
此類變換可以用一個3×3的矩陣來表示,其最后一行為(0, 0, 1)。該變換矩陣將原坐標(biāo)(x, y)變換為新坐標(biāo)(x', y'),這里原坐標(biāo)和新坐標(biāo)皆視為最末一行為(1)的三維列向量,原列向量左乘變換矩陣得到新的列向量:
1
2
3
|
[x'] [m00 m01 m02] [x] [m00*x+m01*y+m02] [y'] = [m10 m11 m12] [y] = [m10*x+m11*y+m12] [1 ] [ 0 0 1 ] [1] [ 1 ] |
幾種典型的仿射變換:
1
|
public static AffineTransform getTranslateInstance( double tx, double ty) |
平移變換,將每一點移動到(x+tx, y+ty),變換矩陣為:
1
2
3
|
[ 1 0 tx ] [ 0 1 ty ] [ 0 0 1 ] |
(譯注:平移變換是一種“剛體變換”,rigid-body transformation,中學(xué)學(xué)過的物理,都知道啥叫“剛體”吧,就是不會產(chǎn)生形變的理想物體,平移當(dāng)然不會改變二維圖形的形狀。同理,下面的“旋轉(zhuǎn)變換”也是剛體變換,而“縮放”、“錯切”都是會改變圖形形狀的。)
1
|
public static AffineTransform getScaleInstance( double sx, double sy) |
縮放變換,將每一點的橫坐標(biāo)放大(縮小)至sx倍,縱坐標(biāo)放大(縮小)至sy倍,變換矩陣為:
1
2
3
|
[ sx 0 0 ] [ 0 sy 0 ] [ 0 0 1 ] |
1
|
public static AffineTransform getShearInstance( double shx, double shy) |
剪切變換,變換矩陣為:
1
2
3
|
[ 1 shx 0 ] [ shy 1 0 ] [ 0 0 1 ] |
相當(dāng)于一個橫向剪切與一個縱向剪切的復(fù)合
1
2
3
|
[ 1 0 0 ][ 1 shx 0 ] [ shy 1 0 ][ 0 1 0 ] [ 0 0 1 ][ 0 0 1 ] |
(譯注:“剪切變換”又稱“錯切變換”,指的是類似于四邊形不穩(wěn)定性那種性質(zhì),街邊小商店那種鐵拉門都見過吧?想象一下上面鐵條構(gòu)成的菱形拉動的過程,那就是“錯切”的過程。)
1
|
public static AffineTransform getRotateInstance( double theta) |
旋轉(zhuǎn)變換,目標(biāo)圖形圍繞原點順時針旋轉(zhuǎn)theta弧度,變換矩陣為:
1
2
3
4
|
[ cos(theta) -sin(theta) 0 ] [ sin(theta) cos(theta) 0 ] [ 0 0 1 ] |
1
|
public static AffineTransform getRotateInstance( double theta, double x, double y) |
旋轉(zhuǎn)變換,目標(biāo)圖形以(x, y)為軸心順時針旋轉(zhuǎn)theta弧度,變換矩陣為:
1
2
3
|
[ cos(theta) -sin(theta) x-x*cos+y*sin] [ sin(theta) cos(theta) y-x*sin-y*cos ] [ 0 0 1 ] |
相當(dāng)于兩次平移變換與一次原點旋轉(zhuǎn)變換的復(fù)合:
1
2
3
|
[1 0 -x][cos(theta) -sin(theta) 0][1 0 x] [0 1 -y][sin(theta) cos(theta) 0][0 1 y] [0 0 1 ][ 0 0 1 ][0 0 1] |
幾何中,一個向量空間進行一次線性變換并接上一個平移,這么一個過程就稱為仿射變換或放射映射。
可以簡單地表示為:y = Ax + b ,其中有下標(biāo)的字母表示向量,而粗體的字母A表示一個矩陣。
如果暫時無法理解也沒有關(guān)系(我也沒理解 ^_^#),沒關(guān)系,我們這里僅使用了它的幾個特例:平移和旋轉(zhuǎn)變換。
按照慣例,下面先把整個代碼貼出來:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Checkbox; import java.awt.CheckboxGroup; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Panel; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.Random; public class AffineTest extends Applet implements ItemListener{ private Rectangle2D rect; private Checkbox rotateFirst; private Checkbox translateFirst; public void init() { setLayout( new BorderLayout()); CheckboxGroup cbg = new CheckboxGroup(); Panel p = new Panel(); rotateFirst = new Checkbox( "rotate, translate" , cbg, true ); rotateFirst.addItemListener( this ); p.add(rotateFirst); translateFirst = new Checkbox( "translate, rotate" , cbg, false ); translateFirst.addItemListener( this ); p.add(translateFirst); add(p, BorderLayout.SOUTH); rect = new Rectangle2D.Float(- 0 .5f, - 0 .5f, 1 .0f, 1 .0f); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; final AffineTransform identify = new AffineTransform(); boolean rotate = rotateFirst.getState(); Random r = new Random(); final double oneRadian = Math.toRadians( 1.0 ); for ( double radians = 0.0 ; radians < 2.0 *Math.PI ; radians += oneRadian) { g2d.setTransform(identify); if (rotate) { g2d.translate( 100 , 100 ); g2d.rotate(radians); } else { g2d.rotate(radians); g2d.translate( 100 , 100 ); } g2d.scale( 100 , 100 ); g2d.setColor( new Color(r.nextInt())); g2d.fill(rect); } } @Override public void itemStateChanged(ItemEvent arg0) { // TODO Auto-generated method stub repaint(); } } import java.applet.Applet; import java.awt.BorderLayout; import java.awt.Checkbox; import java.awt.CheckboxGroup; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Panel; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.Random; public class AffineTest extends Applet implements ItemListener{ private Rectangle2D rect; private Checkbox rotateFirst; private Checkbox translateFirst; public void init() { setLayout( new BorderLayout()); CheckboxGroup cbg = new CheckboxGroup(); Panel p = new Panel(); rotateFirst = new Checkbox( "rotate, translate" , cbg, true ); rotateFirst.addItemListener( this ); p.add(rotateFirst); translateFirst = new Checkbox( "translate, rotate" , cbg, false ); translateFirst.addItemListener( this ); p.add(translateFirst); add(p, BorderLayout.SOUTH); rect = new Rectangle2D.Float(- 0 .5f, - 0 .5f, 1 .0f, 1 .0f); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; final AffineTransform identify = new AffineTransform(); boolean rotate = rotateFirst.getState(); Random r = new Random(); final double oneRadian = Math.toRadians( 1.0 ); for ( double radians = 0.0 ; radians < 2.0 *Math.PI ; radians += oneRadian) { g2d.setTransform(identify); if (rotate) { g2d.translate( 100 , 100 ); g2d.rotate(radians); } else { g2d.rotate(radians); g2d.translate( 100 , 100 ); } g2d.scale( 100 , 100 ); g2d.setColor( new Color(r.nextInt())); g2d.fill(rect); } } @Override public void itemStateChanged(ItemEvent arg0) { // TODO Auto-generated method stub repaint(); } } |
對比可知,仿射變換的順序是不能隨便交換的。