Trong phần này chúng ta sẽ học cách cắt hình.
Cắt hình trong Java là loại bỏ một phần của cửa sổ, chỉ vẽ một khu vực nào đó để tạo một số hiệu ứng. Khi cắt hình thì chúng ta nên tạo một đối tượng Graphics
mới hoặc phục hồi đối tượng cũ về trạng thái ban đầu.
Ví dụ 1
Trong ví dụ này, chúng ta sẽ cắt hình thành một hình tròn.
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); } }); } }
Đoạn code trên tạo một hình tròn di chuyển trên màn hình, hình tròn di chuyển đến đâu thì ảnh hiện ra ở đó.
Graphics2D g2d = (Graphics2D) g.create();
Cắt hình làm thay đổi một số trạng thái của đối tượng Graphics2D
do đó chúng ta nên tạo một đối tượng Graphics2D
mới để không làm ảnh hưởng đến việc vẽ các đối tượng khác mặc dù ở đây chúng ta chỉ vẽ một đối tượng.
g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS));
Phương thức clip()
sẽ thiết lập vùng được vẽ là một hình tròn (tạo từ lớp Elippse2D
). Java sẽ không vẽ bất cứ thứ gì bên ngoài hình elip này.
if (pos_x < 0) { delta[0] = Math.random() % 4 + 5; } else if (pos_x > w - RADIUS) { delta[0] = -(Math.random() % 4 + 5); }
Ngoài ra chúng ta di chuyển hình tròn quanh cửa sổ cho thêm sinh động. Đoạn code trên kiểm tra tọa độ của hình tròn để không cho hình tròn di chuyển ra bên ngoài cửa sổ.
Ví dụ 2
Trong ví dụ dưới đây, chúng ta sẽ thực hiện việc giao vùng vẽ của một hình chữ nhật và một hình tròn.
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); } }); } }
Chúng ta vẽ một hình tròn di chuyển xung quanh màn hình, một hình chữ nhật tự xoay tròn ở giữa màn hình. Chúng ta tô màu xám đen cho vùng được giao giữa hình chữ nhật và hình tròn.
Shape oldClip = g2d.getClip();
Khác với các ví dụ trước là chúng ta tạo một bản copy của đối tượng Graphics2D
, ở đây chúng ta lưu lại trạng thái thuộc tính clip của đối tượng Graphics2D
bằng phương thức getClip()
rồi lưu vào đối tượng oldClip
.
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);
Bốn dòng code trên xoay hình chữ nhật xung quanh tâm của nó sử dụng lớp AffineTransform
(chúng ta sẽ tìm hiểu về lớp này trong bài sau), tâm của hình chữ nhật luôn nằm giữa màn hình.
GeneralPath path = new GeneralPath(); path.append(tx.createTransformedShape(rect), false);
Phương thức createTransformedShape()
tạo một hình chữ nhật từ lớp AffineTransform
sau khi lớp này thực hiện một số phép biến đổi.
g2d.clip(circle); g2d.clip(path); g2d.setPaint(new Color(110, 110, 110)); g2d.fill(circle);
Chúng ta gọi 2 phương thức clip với 2 đối tượng hình học để chỉ cho java biết rằng chỉ được vẽ ở những vùng có cả hình chữ nhật và hình tròn rồi gọi phương thức setPaint()
và fill()
để tô màu cho vùng đó.
g2d.setClip(oldClip); g2d.draw(circle); g2d.draw(path);
Sau khi chúng ta vẽ ra phần tô màu xám đen được giao giữa hình chữ nhật và hình tròn thì chúng ta gọi phương thức setClip(oldClip)
để lấy lại trạng thái của vùng được vẽ lúc chưa được cắt hình (ở đây toàn màn hình) rồi gọi 2 phương thức draw()
để vẽ 2 hình chữ nhật và hình tròn.