变换
最后修改于 2023 年 7 月 17 日
在 Java 2D 编程教程的这一部分,我们将讨论变换。
仿射变换 由零个或多个线性变换(旋转、缩放或剪切)和平移(移动)组成。几个线性变换可以组合成一个单一的矩阵。旋转是一种围绕固定点移动刚体的变换。缩放是一种放大或缩小对象的变换。比例因子在所有方向上都是相同的。平移是一种在指定方向上将每个点移动恒定距离的变换。剪切是一种沿着给定轴的垂直方向移动对象的变换,其在一侧的值大于另一侧。
AffineTransform 是 Java 2D 中执行仿射变换的类。
平移
以下示例描述了一个简单的平移。
com/zetcode/TranslationEx.java
package com.zetcode;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(new Color(150, 150, 150));
g2d.fillRect(20, 20, 80, 50);
g2d.translate(150, 50);
g2d.fillRect(20, 20, 80, 50);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class TranslationEx extends JFrame {
public TranslationEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Translation");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
TranslationEx ex = new TranslationEx();
ex.setVisible(true);
}
});
}
}
该示例绘制了一个矩形。然后我们进行平移并再次绘制相同的矩形。
g2d.translate(150, 50);
这一行将 Graphics2D 上下文的原点移动到一个新点。
旋转
下一个示例演示了旋转。
com/zetcode/RotationEx.java
package com.zetcode;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(new Color(150, 150, 150));
g2d.fillRect(20, 20, 80, 50);
g2d.translate(180, -50);
g2d.rotate(Math.PI/4);
g2d.fillRect(80, 80, 80, 50);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class RotationEx extends JFrame {
public RotationEx() {
initUI();
}
private void initUI() {
setTitle("Rotation");
add(new Surface());
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
RotationEx ex = new RotationEx();
ex.setVisible(true);
}
});
}
}
该示例绘制一个矩形,进行平移和旋转,然后再次绘制相同的矩形。
g2d.rotate(Math.PI/4);
rotate 方法执行旋转。请注意,旋转参数是以弧度为单位的。
缩放
下一个示例演示了对象的缩放。缩放是通过 scale 方法完成的。在此方法中,我们提供两个参数。它们分别是 x 比例因子和 y 比例因子,坐标分别沿 x 轴或 y 轴按此因子缩放。
com/zetcode/ScalingEx.java
package com.zetcode;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(new Color(150, 150, 150));
g2d.fillRect(20, 20, 80, 50);
AffineTransform tx1 = new AffineTransform();
tx1.translate(110, 22);
tx1.scale(0.5, 0.5);
g2d.setTransform(tx1);
g2d.fillRect(0, 0, 80, 50);
AffineTransform tx2 = new AffineTransform();
tx2.translate(170, 20);
tx2.scale(1.5, 1.5);
g2d.setTransform(tx2);
g2d.fillRect(0, 0, 80, 50);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class ScalingEx extends JFrame {
public ScalingEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Scaling");
setSize(330, 160);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
ScalingEx ex = new ScalingEx();
ex.setVisible(true);
}
});
}
}
我们有一个矩形。首先我们将其缩小,然后稍微放大一点。
AffineTransform tx2 = new AffineTransform(); tx2.translate(170, 20); tx2.scale(1.5, 1.5);
将添加另一个缩放操作到第一个操作上。因此,我们需要创建一个新的仿射变换并应用它。
剪切
在下面的示例中,我们进行剪切。我们使用 share 方法。
com/zetcode/ShearingEx.java
package com.zetcode;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform tx1 = new AffineTransform();
tx1.translate(50, 90);
g2d.setTransform(tx1);
g2d.setPaint(Color.green);
g2d.drawRect(0, 0, 160, 50);
AffineTransform tx2 = new AffineTransform();
tx2.translate(50, 90);
tx2.shear(0, 1);
g2d.setTransform(tx2);
g2d.setPaint(Color.blue);
g2d.draw(new Rectangle(0, 0, 80, 50));
AffineTransform tx3 = new AffineTransform();
tx3.translate(130, 10);
tx3.shear(0, 1);
g2d.setTransform(tx3);
g2d.setPaint(Color.red);
g2d.drawRect(0, 0, 80, 50);
g2d.dispose();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class ShearingEx extends JFrame {
public ShearingEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Shearing");
setSize(330, 270);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
ShearingEx ex = new ShearingEx();
ex.setVisible(true);
}
});
}
}
在此示例中,我们绘制了三个不同颜色的矩形。它们构成了一个结构。其中两个被剪切了。
tx2.shear(0, 1);
这两个参数是坐标沿 x 轴和 y 轴移动的乘数。
甜甜圈
在下面的示例中,我们通过旋转椭圆来创建一个复杂的形状。
com/zetcode/DonutEx.java
package com.zetcode;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Surface extends JPanel {
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g.create();
RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
rh.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHints(rh);
Dimension size = getSize();
double w = size.getWidth();
double h = size.getHeight();
Ellipse2D e = new Ellipse2D.Double(0, 0, 80, 130);
g2d.setStroke(new BasicStroke(1));
g2d.setPaint(Color.gray);
for (double deg = 0; deg < 360; deg += 5) {
AffineTransform at =
AffineTransform.getTranslateInstance(w / 2, h / 2);
at.rotate(Math.toRadians(deg));
g2d.draw(at.createTransformedShape(e));
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
}
public class DonutEx extends JFrame {
public DonutEx() {
initUI();
}
private void initUI() {
add(new Surface());
setTitle("Donut");
setSize(370, 320);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
DonutEx ex = new DonutEx();
ex.setVisible(true);
}
});
}
}
在此示例中,我们创建了一个甜甜圈形状。
Ellipse2D e = new Ellipse2D.Double(0, 0, 80, 130); g2d.setStroke(new BasicStroke(1)); g2d.setPaint(Color.gray);
开始时有一个椭圆。
for (double deg = 0; deg < 360; deg += 5) {
AffineTransform at =
AffineTransform.getTranslateInstance(w / 2, h / 2);
at.rotate(Math.toRadians(deg));
g2d.draw(at.createTransformedShape(e));
}
经过几次旋转后,形成了一个甜甜圈。
在 Java 2D 教程的这一部分,我们讨论了变换。