简单的仿射变换

什么是仿射变换

一组设备无关的坐标被用来将所有的坐标信息传递给Graphics2D对象。AffineTransform对象作为Graphics2D对象状态的一部分。该对象定义了如何将用户空间的坐标转化为设备空间的设备相关的坐标点。

AffineTransform类代表一个2D的仿射变化,将一组2D的坐标进行线性映射到另一组保留了平行关系和竖直关系的2D坐标中。该转化包括平移,缩放,翻转,旋转和扭曲。

根据AffineTransform定义的变化有两个非常重要的属性:
直线依然是直线
平行的线依然保持平行

AffineTransform是限次那个转化,所以可以通过矩阵的形式表示转化,然以一个AffineTransform可以通过数学的形式转化为一个包含六个数字的矩阵。
sx shx tx
shy sy ty

这里省略了矩阵的大括号。

不要方!##

不要担心看不懂矩阵。你不需要了解矩阵运算的一切。你只需要有些许的基础知识即可。

事实上,你甚至不用担心需要理解一般的矩阵运算来完成接下来我将说明的简单转换,但是这些可以帮助你更好的理解它是如何运作的。

矩阵里的符号都代表啥?

我将说明如何使用AffineTransform使图片在展示于外设之前进行下列转换:

  • 缩放
  • 平移
  • 扭曲
  • 旋转

在上面的矩阵中,sxsy代表缩放的比例,txty是实现平移的参数。shxshy是实现扭曲的参数。

旋转实际上是缩放和扭曲的组合,所以它可以由上面的参数组合完成操作。

如何使用这些参数?

1
2
3
// ‘*’ 代表乘号
newX = sx*x + shx*y + tx
newY = shy*x + sy*y + ty

假设在用户空间中存在坐标(x, y),这些参数将通过上面的公式计算出新的坐标,从而实现缩放,扭曲和平移。

我们后面会看到,如果表示剪切和平移的因子的值为零,那么将不执行该类型的变换。如果缩放的值为1(默认值),那么图片不会进行缩放。其它的任何值都会导致缩放,平移或扭曲的发生。

对于所有三种类型的变换,用于变换x坐标的值与用于变换y坐标的值无关。因此,例如,可以对x进行大树枝的平移,并在y中用进行小数值平移。

在详细描述执行这些转换之前,对每个转换提供些许解释可能会很有用。

缩放

缩放可能是四种转化中最好理解的一种。它是指如果一个点在用户空间中的坐标为(x,y),那么它在设备空间中的坐标为(sx * x, sy * y )。乘数sxsy可以为正数或是负数。

平移

平移的目的是在设备空间中移动坐标系的原点。

比如,默认的原点是组件左上角的点。假设组件是一个边长四英寸的Frame,你可能希望原点位于框架的中心,而不是左上角。你可以通过将原点坐标分别从水平和垂直方向平移两英寸实现。

另一个使用平移的场景(与缩放同时使用)是翻转垂直轴的默认正方向,从而使y轴值的增加使得点向上移动而不是默认的向下移动。

扭曲

这是网上给的一个shearing的图例,即将原图以平行四边形拉伸。

旋转

旋转从视觉上很好理解。为了可视化旋转,在一张纸上画一张照片。 使用一个钉子将其粘到公告板上。 然后围绕大头钉旋转纸张。

在Java 2D中,旋转是使用radians而不是degree的形式传入参数。如果您不熟悉使用弧度度量来指定角度,请记住以下标识:

PI radians = 180 degrees

PI是在几何类中学到的数值。它的值为3.14159………..。但是,你无须记住PI的数值,因为MATH类中包含了它的静态值。你可以通过Math.PI获得该值。

还有一个需要关注的是正角度的旋转代表顺时针旋转。

简单的程序

我们来看看一些代码,并且学习使用Java 2D实现缩放,平移,扭曲和旋转。

可叠加但不可交换

你会发现执行两个或是多个变化带来的结果是累加的,但是不是可交换的。比如,执行平移和旋转并不一定等于先旋转再平移得到的结果。

6个参数

执行相应操作时对应的矩阵参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
默认
1.0 0.0 0.0
0.0 1.0 0.0

添加缩放转换
1.6666666666666667 0.0 0.0
0.0 1.6666666666666667 0.0

添加平移转换
1.6666666666666667 0.0 30.0
0.0 1.6666666666666667 30.0

添加扭曲转换
1.6666666666666667 0.08333333333333334 30.0
0.16666666666666669 1.666666666666666730.0

添加旋转转换
1.6508996608400615 -0.2434184299932779 79.32270275806317
0.4886147500940855 1.6021270803360292 -7.066823581936546

完整代码

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
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.*;

class Affine01{
publicstaticvoid main(String[] args){
GUI guiObj = new GUI();
}//end main
}//end controlling class Affine01

class GUI extends Frame{
int res;//store screen resolution here
staticfinalint ds = 72;//default scale, 72 units/inch

GUI(){//constructor
//Get screen resolution
res = Toolkit.getDefaultToolkit().
getScreenResolution();

//Set Frame size to four-inches by four-inches
this.setSize(4*res,4*res);
this.setVisible(true);
this.setTitle("Copyright 1999, R.G.Baldwin");

//Window listener to terminate program.
this.addWindowListener(new WindowAdapter(){
publicvoid windowClosing(WindowEvent e){
System.exit(0);}});
}//end constructor

//Override the paint() method to draw and manipulate a
// square.
publicvoid paint(Graphics g){
//Downcast the Graphics object to a Graphics2D object
Graphics2D g2 = (Graphics2D)g;

//Display contents of default AffineTransform object
System.out.println("Default Transform");
displayMatrix(g2.getTransform());

//Update transform to include a scale component,
// and display the values.
System.out.println("Add Scale Transform");
g2.scale((double)res/72,(double)res/72);
displayMatrix(g2.getTransform());

//Update transform to include a translate component,
// and display the values.
System.out.println("Add Translate Transform");
g2.translate(0.25*ds,0.25*ds);
displayMatrix(g2.getTransform());

//Update transform to include a shear component,
// and display the values.
System.out.println("Add Shear Transform");
g2.shear(0.05,0.1);
displayMatrix(g2.getTransform());

//Update transform to provide rotation and display,
// the transform values.
System.out.println("Add Rotate Transform");
//11.25 degrees about center
g2.rotate(Math.PI/16,2.0*ds, 2.0*ds);
displayMatrix(g2.getTransform());

//Draw five concentric squares and apply the transform
// that results from the above transform updates. If
// the above scale transform is applied, the outer
// square is one inch on each side, and the squares
// are separated by 0.1 inch
double delta = 0.1;
for(int cnt = 0; cnt < 5; cnt++){
g2.draw(new Rectangle2D.Double(
(1.5+cnt*delta)*ds, (1.5+cnt*delta)*ds,
(1.0-cnt*2*delta)*ds, (1.0-cnt*2*delta)*ds));
}//end for loop

//Superimpose some text on the squares with the
// lower left corner of the first character at the
// center of the squares.
g2.drawString("Exit ->",2.0f*ds,2.0f*ds);
}//end overridden paint()

//This method receives a reference to an AffineTransform
// and displays the six controllable values in the
// transform matrix
void displayMatrix(AffineTransform theTransform){
double[] theMatrix = newdouble[6];
theTransform.getMatrix(theMatrix);

//Display first row of values by displaying every
// other element in the array starting with element
// zero.
for(int cnt = 0; cnt < 6; cnt+=2){
System.out.print(theMatrix[cnt] + " ");
}//end for loop

//Display second row of values displaying every
// other element in the array starting with element
// number one.
System.out.println();//new line
for(int cnt = 1; cnt < 6; cnt+=2){
System.out.print(theMatrix[cnt] + " ");
}//end for loop
System.out.println();//end of line
System.out.println();//blank line

}//end displayMatrix
}//end class GUI
//==============================//

原文链接: https://www.developer.com/net/vb/article.php/626051/Java-2D-Graphics-Simple-Affine-Transforms.htm