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

在 Java 2D 教程的这一部分中,我们将讨论裁剪。

剪裁

剪裁将图形限制在某个区域。 这样做是出于效率原因并产生各种效果。 使用剪裁时,我们必须使用Graphics对象的副本,或者恢复原始的剪裁属性。 更改剪裁不会影响现有像素; 它仅影响将来的渲染。

在以下示例中,我们将图像裁剪为圆形。

ClippingEx.java

  1. package com.zetcode;
  2. import java.awt.Dimension;
  3. import java.awt.EventQueue;
  4. import java.awt.Graphics;
  5. import java.awt.Graphics2D;
  6. import java.awt.Image;
  7. import java.awt.event.ActionEvent;
  8. import java.awt.event.ActionListener;
  9. import java.awt.geom.Ellipse2D;
  10. import javax.swing.ImageIcon;
  11. import javax.swing.JFrame;
  12. import javax.swing.JPanel;
  13. import javax.swing.Timer;
  14. class Surface extends JPanel
  15. implements ActionListener {
  16. private int pos_x = 8;
  17. private int pos_y = 8;
  18. private final int RADIUS = 90;
  19. private final int DELAY = 35;
  20. private Timer timer;
  21. private Image image;
  22. private final double delta[] = { 3, 3 };
  23. public Surface() {
  24. loadImage();
  25. determineAndSetImageSize();
  26. initTimer();
  27. }
  28. private void loadImage() {
  29. image = new ImageIcon("mushrooms.jpg").getImage();
  30. }
  31. private void determineAndSetImageSize() {
  32. int h = image.getHeight(this);
  33. int w = image.getWidth(this);
  34. setPreferredSize(new Dimension(w, h));
  35. }
  36. private void initTimer() {
  37. timer = new Timer(DELAY, this);
  38. timer.start();
  39. }
  40. private void doDrawing(Graphics g) {
  41. Graphics2D g2d = (Graphics2D) g.create();
  42. g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS));
  43. g2d.drawImage(image, 0, 0, null);
  44. g2d.dispose();
  45. }
  46. @Override
  47. public void paintComponent(Graphics g) {
  48. super.paintComponent(g);
  49. doDrawing(g);
  50. }
  51. @Override
  52. public void actionPerformed(ActionEvent e) {
  53. moveCircle();
  54. repaint();
  55. }
  56. private void moveCircle() {
  57. int w = getWidth();
  58. int h = getHeight();
  59. if (pos_x < 0) {
  60. delta[0] = Math.random() % 4 + 5;
  61. } else if (pos_x > w - RADIUS) {
  62. delta[0] = -(Math.random() % 4 + 5);
  63. }
  64. if (pos_y < 0 ) {
  65. delta[1] = Math.random() % 4 + 5;
  66. } else if (pos_y > h - RADIUS) {
  67. delta[1] = -(Math.random() % 4 + 5);
  68. }
  69. pos_x += delta[0];
  70. pos_y += delta[1];
  71. }
  72. }
  73. public class ClippingEx extends JFrame {
  74. public ClippingEx() {
  75. initUI();
  76. }
  77. private void initUI() {
  78. setTitle("Clipping");
  79. add(new Surface());
  80. pack();
  81. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  82. setLocationRelativeTo(null);
  83. }
  84. public static void main(String[] args) {
  85. EventQueue.invokeLater(new Runnable() {
  86. @Override
  87. public void run() {
  88. ClippingEx cl = new ClippingEx();
  89. cl.setVisible(true);
  90. }
  91. });
  92. }
  93. }

屏幕上正在移动一个圆圈,并显示了一部分基础图像。 这就像我们从孔中看一样。

  1. Graphics2D g2d = (Graphics2D) g.create();

我们创建Graphics2D对象的副本。 因此,更改剪裁不会影响其他在Graphics2D对象被重用的 Swing 零件。

  1. g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS));

clip()方法将现有剪裁与作为参数给出的形状结合在一起。 所得的相交设置为片段。 在我们的例子中,最终的剪裁是圆形。

  1. if (pos_x < 0) {
  2. delta[0] = Math.random() % 4 + 5;
  3. } else if (pos_x > w - RADIUS) {
  4. delta[0] = -(Math.random() % 4 + 5);
  5. }

如果圆碰到窗口的左侧或右侧,则圆的移动方向会随机变化。 顶部和底部也一样。

  1. g2d.dispose();

完成绘画后,我们必须释放Graphics2D对象的副本。

剪裁形状

在下面的示例中,我们将剪切到两个形状的交点:矩形和圆形。

ClippingShapesEx.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.RenderingHints;
  8. import java.awt.Shape;
  9. import java.awt.event.ActionEvent;
  10. import java.awt.event.ActionListener;
  11. import java.awt.geom.AffineTransform;
  12. import java.awt.geom.Ellipse2D;
  13. import java.awt.geom.GeneralPath;
  14. import javax.swing.JFrame;
  15. import javax.swing.JPanel;
  16. import javax.swing.Timer;
  17. class Surface extends JPanel
  18. implements ActionListener {
  19. private Timer timer;
  20. private double rotate = 1;
  21. private int pos_x = 8;
  22. private int pos_y = 8;
  23. private final double delta[] = {1, 1};
  24. private final int RADIUS = 60;
  25. public Surface() {
  26. initTimer();
  27. }
  28. private void initTimer() {
  29. timer = new Timer(10, this);
  30. timer.start();
  31. }
  32. private void doDrawing(Graphics g) {
  33. Graphics2D g2d = (Graphics2D) g;
  34. g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
  35. RenderingHints.VALUE_ANTIALIAS_ON);
  36. g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
  37. RenderingHints.VALUE_RENDER_QUALITY);
  38. Shape oldClip = g2d.getClip();
  39. int w = getWidth();
  40. int h = getHeight();
  41. Rectangle rect = new Rectangle(0, 0, 200, 80);
  42. AffineTransform tx = new AffineTransform();
  43. tx.rotate(Math.toRadians(rotate), w / 2, h / 2);
  44. tx.translate(w / 2 - 100, h / 2 - 40);
  45. Ellipse2D circle = new Ellipse2D.Double(pos_x, pos_y,
  46. RADIUS, RADIUS);
  47. GeneralPath path = new GeneralPath();
  48. path.append(tx.createTransformedShape(rect), false);
  49. g2d.clip(circle);
  50. g2d.clip(path);
  51. g2d.setPaint(new Color(110, 110, 110));
  52. g2d.fill(circle);
  53. g2d.setClip(oldClip);
  54. g2d.draw(circle);
  55. g2d.draw(path);
  56. }
  57. @Override
  58. public void paintComponent(Graphics g) {
  59. super.paintComponent(g);
  60. doDrawing(g);
  61. }
  62. public void step() {
  63. int w = getWidth();
  64. int h = getHeight();
  65. rotate += 1;
  66. if (pos_x < 0) {
  67. delta[0] = 1;
  68. } else if (pos_x > w - RADIUS) {
  69. delta[0] = -1;
  70. }
  71. if (pos_y < 0) {
  72. delta[1] = 1;
  73. } else if (pos_y > h - RADIUS) {
  74. delta[1] = -1;
  75. }
  76. pos_x += delta[0];
  77. pos_y += delta[1];
  78. }
  79. @Override
  80. public void actionPerformed(ActionEvent e) {
  81. step();
  82. repaint();
  83. }
  84. }
  85. public class ClippingShapesEx extends JFrame {
  86. public ClippingShapesEx() {
  87. initUI();
  88. }
  89. private void initUI() {
  90. setTitle("Clipping shapes");
  91. add(new Surface());
  92. setSize(350, 300);
  93. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  94. setLocationRelativeTo(null);
  95. }
  96. public static void main(String[] args) {
  97. EventQueue.invokeLater(new Runnable() {
  98. @Override
  99. public void run() {
  100. ClippingShapesEx ex = new ClippingShapesEx();
  101. ex.setVisible(true);
  102. }
  103. });
  104. }
  105. }

在我们的示例中,我们有一个弹跳的圆和一个旋转的矩形。 当这些形状重叠时,结果区域将充满颜色。

  1. Shape oldClip = g2d.getClip();

由于我们没有创建Graphics2D对象的副本,因此我们将存储旧剪裁以供以后使用。 最后,我们必须将剪裁重置为原始剪裁。

  1. Rectangle rect = new Rectangle(0, 0, 200, 80);
  2. AffineTransform tx = new AffineTransform();
  3. tx.rotate(Math.toRadians(rotate), w / 2, h / 2);
  4. tx.translate(w / 2 - 100, h / 2 - 40);

矩形正在旋转。 它始终位于面板的中间。

  1. GeneralPath path = new GeneralPath();
  2. path.append(tx.createTransformedShape(rect), false);

在这里,我们得到了旋转矩形的形状。

  1. g2d.clip(circle);
  2. g2d.clip(path);
  3. g2d.setPaint(new Color(110, 110, 110));
  4. g2d.fill(circle);

在这里,我们将绘图限制为两个形状的交点。 如果它们重叠,则结果形状的内部将充满颜色。 clip()方法将初始剪裁(组件的客户区域)与给定的两个形状组合在一起。

  1. g2d.setClip(oldClip);

使用setClip()方法,我们在绘制形状之前将剪裁区域重置为旧剪裁。 与clip()方法不同,setClip()不合并剪切区域。 它将剪裁重置到新区域。 因此,此方法应专门用于还原旧剪裁。

剪裁 - 图1

图:剪裁形状

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