剪裁
最后修改于 2023 年 7 月 17 日
在 Java 2D 教程的这一部分,我们讨论裁剪。
剪裁
裁剪是将绘图限制在特定区域。这是出于效率原因,也是为了创建各种效果。在处理裁剪时,我们必须使用 `Graphics` 对象的副本,或者恢复原始的裁剪属性。更改裁剪不会影响现有的像素;它仅影响未来的渲染。
在下面的示例中,我们将图像裁剪成圆形。
package com.zetcode; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private int pos_x = 8; private int pos_y = 8; private final int RADIUS = 90; private final int DELAY = 35; private Timer timer; private Image image; private final double delta[] = { 3, 3 }; public Surface() { loadImage(); determineAndSetImageSize(); initTimer(); } private void loadImage() { image = new ImageIcon("mushrooms.jpg").getImage(); } private void determineAndSetImageSize() { int h = image.getHeight(this); int w = image.getWidth(this); setPreferredSize(new Dimension(w, h)); } private void initTimer() { timer = new Timer(DELAY, this); timer.start(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS)); g2d.drawImage(image, 0, 0, null); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } @Override public void actionPerformed(ActionEvent e) { moveCircle(); repaint(); } private void moveCircle() { int w = getWidth(); int h = getHeight(); if (pos_x < 0) { delta[0] = Math.random() % 4 + 5; } else if (pos_x > w - RADIUS) { delta[0] = -(Math.random() % 4 + 5); } if (pos_y < 0 ) { delta[1] = Math.random() % 4 + 5; } else if (pos_y > h - RADIUS) { delta[1] = -(Math.random() % 4 + 5); } pos_x += delta[0]; pos_y += delta[1]; } } public class ClippingEx extends JFrame { public ClippingEx() { initUI(); } private void initUI() { setTitle("Clipping"); add(new Surface()); pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { ClippingEx cl = new ClippingEx(); cl.setVisible(true); } }); } }
一个圆在屏幕上移动,并显示底层图像的一部分。这就好像我们透过一个洞在看一样。
Graphics2D g2d = (Graphics2D) g.create();
我们创建了一个 `Graphics2D` 对象的副本。因此,更改裁剪不会影响 `Graphics2D` 对象被重用的其他 Swing 部分。
g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS));
`clip` 方法将现有裁剪与参数中给定的形状结合起来。将结果的交集设置为裁剪。在我们的例子中,结果裁剪是圆形。
if (pos_x < 0) { delta[0] = Math.random() % 4 + 5; } else if (pos_x > w - RADIUS) { delta[0] = -(Math.random() % 4 + 5); }
如果圆圈碰到窗口的左侧或右侧,其移动方向会随机改变。顶部和底部边缘也是如此。
g2d.dispose();
完成绘制后,我们必须释放 `Graphics2D` 对象的副本。
裁剪形状
在下面的示例中,我们将裁剪到两个形状的交集:一个矩形和一个圆形。
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.RenderingHints; import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.GeneralPath; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private Timer timer; private double rotate = 1; private int pos_x = 8; private int pos_y = 8; private final double delta[] = {1, 1}; private final int RADIUS = 60; public Surface() { initTimer(); } private void initTimer() { timer = new Timer(10, this); timer.start(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); Shape oldClip = g2d.getClip(); int w = getWidth(); int h = getHeight(); Rectangle rect = new Rectangle(0, 0, 200, 80); AffineTransform tx = new AffineTransform(); tx.rotate(Math.toRadians(rotate), w / 2, h / 2); tx.translate(w / 2 - 100, h / 2 - 40); Ellipse2D circle = new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS); GeneralPath path = new GeneralPath(); path.append(tx.createTransformedShape(rect), false); g2d.clip(circle); g2d.clip(path); g2d.setPaint(new Color(110, 110, 110)); g2d.fill(circle); g2d.setClip(oldClip); g2d.draw(circle); g2d.draw(path); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } public void step() { int w = getWidth(); int h = getHeight(); rotate += 1; if (pos_x < 0) { delta[0] = 1; } else if (pos_x > w - RADIUS) { delta[0] = -1; } if (pos_y < 0) { delta[1] = 1; } else if (pos_y > h - RADIUS) { delta[1] = -1; } pos_x += delta[0]; pos_y += delta[1]; } @Override public void actionPerformed(ActionEvent e) { step(); repaint(); } } public class ClippingShapesEx extends JFrame { public ClippingShapesEx() { initUI(); } private void initUI() { setTitle("Clipping shapes"); add(new Surface()); setSize(350, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { ClippingShapesEx ex = new ClippingShapesEx(); ex.setVisible(true); } }); } }
在我们的例子中,我们有一个弹跳的圆和一个旋转的矩形。当这些形状重叠时,结果区域会填充颜色。
Shape oldClip = g2d.getClip();
由于我们没有创建 `Graphics2D` 对象的副本,我们存储了旧的裁剪以供以后使用。最后,我们必须将裁剪重置为原始裁剪。
Rectangle rect = new Rectangle(0, 0, 200, 80); AffineTransform tx = new AffineTransform(); tx.rotate(Math.toRadians(rotate), w / 2, h / 2); tx.translate(w / 2 - 100, h / 2 - 40);
矩形正在旋转。它总是位于面板的中间。
GeneralPath path = new GeneralPath(); path.append(tx.createTransformedShape(rect), false);
这里我们获取旋转矩形的形状。
g2d.clip(circle); g2d.clip(path); g2d.setPaint(new Color(110, 110, 110)); g2d.fill(circle);
在这里,我们将绘图限制在两个形状的交集内。如果它们重叠,则会用颜色填充结果形状的内部。 `clip` 方法将初始裁剪(组件的客户端区域)与给定的两个形状结合起来。
g2d.setClip(oldClip);
使用 `setClip` 方法,我们在绘制形状之前将裁剪区域重置为旧的裁剪。与 `clip` 方法不同,`setClip` 不会组合裁剪区域。它将裁剪重置为新区域。因此,此方法应专门用于恢复旧裁剪。

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