原文: https://zetcode.com/gfx/java2d/transformations/

在 Java 2D 编程教程的这一部分中,我们将讨论变换。

仿射变换由零个或多个线性变换(旋转,缩放或剪切)和平移(移位)组成。 几个线性变换可以组合成一个矩阵。 旋转是使刚体绕固定点移动的变换。 缩放是一种放大或缩小对象的变换。 比例因子在所有方向上都是相同的。 平移是使每个点在指定方向上移动恒定距离的变换。 剪切是一种使对象垂直于给定轴移动的变换,该值在轴的一侧比另一侧更大。 数据来源:(wikipedia.org,freedictionary.com)

AffineTransform是 Java 2D 中用于执行仿射变换的类。

平移

以下示例描述了一个简单的平移。

TranslationEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import javax.swing.JFrame;
  7. import javax.swing.JPanel;
  8. class Surface extends JPanel {
  9. private void doDrawing(Graphics g) {
  10. Graphics2D g2d = (Graphics2D) g.create();
  11. g2d.setPaint(new Color(150, 150, 150));
  12. g2d.fillRect(20, 20, 80, 50);
  13. g2d.translate(150, 50);
  14. g2d.fillRect(20, 20, 80, 50);
  15. g2d.dispose();
  16. }
  17. @Override
  18. public void paintComponent(Graphics g) {
  19. super.paintComponent(g);
  20. doDrawing(g);
  21. }
  22. }
  23. public class TranslationEx extends JFrame {
  24. public TranslationEx() {
  25. initUI();
  26. }
  27. private void initUI() {
  28. add(new Surface());
  29. setTitle("Translation");
  30. setSize(300, 200);
  31. setLocationRelativeTo(null);
  32. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  33. }
  34. public static void main(String[] args) {
  35. EventQueue.invokeLater(new Runnable() {
  36. @Override
  37. public void run() {
  38. TranslationEx ex = new TranslationEx();
  39. ex.setVisible(true);
  40. }
  41. });
  42. }
  43. }

该示例绘制一个矩形。 然后,我们进行平移并再次绘制相同的矩形。

  1. g2d.translate(150, 50);

这条线将Graphics2D上下文的原点移到新点。

变换 - 图1

图:平移

旋转

下一个示例演示了旋转。

RotationEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import javax.swing.JFrame;
  7. import javax.swing.JPanel;
  8. class Surface extends JPanel {
  9. private void doDrawing(Graphics g) {
  10. Graphics2D g2d = (Graphics2D) g.create();
  11. g2d.setPaint(new Color(150, 150, 150));
  12. g2d.fillRect(20, 20, 80, 50);
  13. g2d.translate(180, -50);
  14. g2d.rotate(Math.PI/4);
  15. g2d.fillRect(80, 80, 80, 50);
  16. g2d.dispose();
  17. }
  18. @Override
  19. public void paintComponent(Graphics g) {
  20. super.paintComponent(g);
  21. doDrawing(g);
  22. }
  23. }
  24. public class RotationEx extends JFrame {
  25. public RotationEx() {
  26. initUI();
  27. }
  28. private void initUI() {
  29. setTitle("Rotation");
  30. add(new Surface());
  31. setSize(300, 200);
  32. setLocationRelativeTo(null);
  33. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  34. }
  35. public static void main(String[] args) {
  36. EventQueue.invokeLater(new Runnable() {
  37. @Override
  38. public void run() {
  39. RotationEx ex = new RotationEx();
  40. ex.setVisible(true);
  41. }
  42. });
  43. }
  44. }

该示例绘制一个矩形,执行平移和旋转,然后再次绘制相同的矩形。

  1. g2d.rotate(Math.PI/4);

rotate()方法执行旋转。 请注意,旋转参数以弧度为单位。

变换 - 图2

图:旋转

缩放

下一个示例演示对象的缩放。 缩放是通过scale()方法完成的。 在此方法中,我们提供了两个参数。 它们是 x 比例因子和 y 比例因子,通过它们分别沿 x 或 y 轴缩放坐标。

ScalingEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.geom.AffineTransform;
  7. import javax.swing.JFrame;
  8. import javax.swing.JPanel;
  9. class Surface extends JPanel {
  10. private void doDrawing(Graphics g) {
  11. Graphics2D g2d = (Graphics2D) g.create();
  12. g2d.setColor(new Color(150, 150, 150));
  13. g2d.fillRect(20, 20, 80, 50);
  14. AffineTransform tx1 = new AffineTransform();
  15. tx1.translate(110, 22);
  16. tx1.scale(0.5, 0.5);
  17. g2d.setTransform(tx1);
  18. g2d.fillRect(0, 0, 80, 50);
  19. AffineTransform tx2 = new AffineTransform();
  20. tx2.translate(170, 20);
  21. tx2.scale(1.5, 1.5);
  22. g2d.setTransform(tx2);
  23. g2d.fillRect(0, 0, 80, 50);
  24. g2d.dispose();
  25. }
  26. @Override
  27. public void paintComponent(Graphics g) {
  28. super.paintComponent(g);
  29. doDrawing(g);
  30. }
  31. }
  32. public class ScalingEx extends JFrame {
  33. public ScalingEx() {
  34. initUI();
  35. }
  36. private void initUI() {
  37. add(new Surface());
  38. setTitle("Scaling");
  39. setSize(330, 160);
  40. setLocationRelativeTo(null);
  41. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  42. }
  43. public static void main(String[] args) {
  44. EventQueue.invokeLater(new Runnable() {
  45. @Override
  46. public void run() {
  47. ScalingEx ex = new ScalingEx();
  48. ex.setVisible(true);
  49. }
  50. });
  51. }
  52. }

我们有一个矩形。 首先,我们将其按比例缩小,然后再按比例放大。

  1. AffineTransform tx2 = new AffineTransform();
  2. tx2.translate(170, 20);
  3. tx2.scale(1.5, 1.5);

另一种缩放将添加到第一个。 因此,我们需要创建并应用新的仿射变换。

变换 - 图3

图:缩放

剪切

在以下示例中,我们执行剪切。 我们使用share()方法。

ShearingEx.java

  1. package com.zetcode;
  2. import java.awt.Color;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.Rectangle;
  7. import java.awt.geom.AffineTransform;
  8. import javax.swing.JFrame;
  9. import javax.swing.JPanel;
  10. class Surface extends JPanel {
  11. private void doDrawing(Graphics g) {
  12. Graphics2D g2d = (Graphics2D) g.create();
  13. AffineTransform tx1 = new AffineTransform();
  14. tx1.translate(50, 90);
  15. g2d.setTransform(tx1);
  16. g2d.setPaint(Color.green);
  17. g2d.drawRect(0, 0, 160, 50);
  18. AffineTransform tx2 = new AffineTransform();
  19. tx2.translate(50, 90);
  20. tx2.shear(0, 1);
  21. g2d.setTransform(tx2);
  22. g2d.setPaint(Color.blue);
  23. g2d.draw(new Rectangle(0, 0, 80, 50));
  24. AffineTransform tx3 = new AffineTransform();
  25. tx3.translate(130, 10);
  26. tx3.shear(0, 1);
  27. g2d.setTransform(tx3);
  28. g2d.setPaint(Color.red);
  29. g2d.drawRect(0, 0, 80, 50);
  30. g2d.dispose();
  31. }
  32. @Override
  33. public void paintComponent(Graphics g) {
  34. super.paintComponent(g);
  35. doDrawing(g);
  36. }
  37. }
  38. public class ShearingEx extends JFrame {
  39. public ShearingEx() {
  40. initUI();
  41. }
  42. private void initUI() {
  43. add(new Surface());
  44. setTitle("Shearing");
  45. setSize(330, 270);
  46. setLocationRelativeTo(null);
  47. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  48. }
  49. public static void main(String[] args) {
  50. EventQueue.invokeLater(new Runnable() {
  51. @Override
  52. public void run() {
  53. ShearingEx ex = new ShearingEx();
  54. ex.setVisible(true);
  55. }
  56. });
  57. }
  58. }

在此示例中,我们以三种不同的颜色绘制了三个矩形。 它们形成一个结构。 他们两个被剪掉了。

  1. tx2.shear(0, 1);

这两个参数是乘数,坐标在 x 和 y 轴的方向上移动。

变换 - 图4

图:抖动

甜甜圈

在下面的示例中,我们通过旋转椭圆来创建复杂的形状。

DonutEx.java

  1. package com.zetcode;
  2. import java.awt.BasicStroke;
  3. import java.awt.Color;
  4. import java.awt.Dimension;
  5. import java.awt.EventQueue;
  6. import java.awt.Graphics;
  7. import java.awt.Graphics2D;
  8. import java.awt.RenderingHints;
  9. import java.awt.geom.AffineTransform;
  10. import java.awt.geom.Ellipse2D;
  11. import javax.swing.JFrame;
  12. import javax.swing.JPanel;
  13. class Surface extends JPanel {
  14. private void doDrawing(Graphics g) {
  15. Graphics2D g2d = (Graphics2D) g.create();
  16. RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
  17. RenderingHints.VALUE_ANTIALIAS_ON);
  18. rh.put(RenderingHints.KEY_RENDERING,
  19. RenderingHints.VALUE_RENDER_QUALITY);
  20. g2d.setRenderingHints(rh);
  21. Dimension size = getSize();
  22. double w = size.getWidth();
  23. double h = size.getHeight();
  24. Ellipse2D e = new Ellipse2D.Double(0, 0, 80, 130);
  25. g2d.setStroke(new BasicStroke(1));
  26. g2d.setPaint(Color.gray);
  27. for (double deg = 0; deg < 360; deg += 5) {
  28. AffineTransform at =
  29. AffineTransform.getTranslateInstance(w / 2, h / 2);
  30. at.rotate(Math.toRadians(deg));
  31. g2d.draw(at.createTransformedShape(e));
  32. }
  33. }
  34. @Override
  35. public void paintComponent(Graphics g) {
  36. super.paintComponent(g);
  37. doDrawing(g);
  38. }
  39. }
  40. public class DonutEx extends JFrame {
  41. public DonutEx() {
  42. initUI();
  43. }
  44. private void initUI() {
  45. add(new Surface());
  46. setTitle("Donut");
  47. setSize(370, 320);
  48. setLocationRelativeTo(null);
  49. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  50. }
  51. public static void main(String[] args) {
  52. EventQueue.invokeLater(new Runnable() {
  53. @Override
  54. public void run() {
  55. DonutEx ex = new DonutEx();
  56. ex.setVisible(true);
  57. }
  58. });
  59. }
  60. }

在此示例中,我们创建一个甜甜圈形状。

  1. Ellipse2D e = new Ellipse2D.Double(0, 0, 80, 130);
  2. g2d.setStroke(new BasicStroke(1));
  3. g2d.setPaint(Color.gray);

刚开始时有一个椭圆。

  1. for (double deg = 0; deg < 360; deg += 5) {
  2. AffineTransform at =
  3. AffineTransform.getTranslateInstance(w / 2, h / 2);
  4. at.rotate(Math.toRadians(deg));
  5. g2d.draw(at.createTransformedShape(e));
  6. }

旋转几圈后,有一个甜甜圈。

在 Java 2D 教程的这一部分中,我们讨论了变换。