变换
最后修改于 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 教程的这一部分,我们讨论了变换。