Trong phần này chúng ta sẽ kết hợp các đối tượng hình học với nhau.
Lớp AlphaComposite
Trong bài trước chúng ta đã dùng lớp AlphaComposite
với giá trị rule để tạo hiệu ứng trong suốt, trong phần này chúng ta tiếp tục tìm hiểu về giá trị này để trộn các đối tượng với nhau.
Giả sử bạn vẽ 2 hình chữ nhật lên JPanel
, trong Java đối tượng đầu tiên được vẽ được gọi là đối tượng đích (Destination – DST), đối tượng thứ hai được gọi là đối tượng nguồn (Source – SRC), giả sử bạn dùng rule là AlphaComposite.SRC_OVER
, thì khi vẽ ra đối tượng nguồn sẽ nằm đè lên đối tượng đích.
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private final int rules[] = { AlphaComposite.DST, AlphaComposite.DST_ATOP, AlphaComposite.DST_OUT, AlphaComposite.SRC, AlphaComposite.SRC_ATOP, AlphaComposite.SRC_OUT }; private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); for (int x = 20, y = 20, i = 0; i < rules.length; x += 60, i++) { AlphaComposite ac = AlphaComposite.getInstance(rules[i], 0.8f); BufferedImage buffImg = new BufferedImage(60, 60, BufferedImage.TYPE_INT_ARGB); Graphics2D gbi = buffImg.createGraphics(); gbi.setPaint(Color.blue); gbi.fillRect(0, 0, 40, 40); gbi.setComposite(ac); gbi.setPaint(Color.green); gbi.fillRect(5, 5, 40, 40); g2d.drawImage(buffImg, x, y, null); gbi.dispose(); } g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class CompositionEx extends JFrame { public CompositionEx() { add(new Surface()); setTitle("Composition"); setSize(400, 120); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { CompositionEx ex = new CompositionEx(); ex.setVisible(true); } }); } }
Trong đoạn code trên chúng ta vẽ hai hình vuông cắt nhau với 6 rule khác nhau.
private final int rules[] = { AlphaComposite.DST, AlphaComposite.DST_ATOP, AlphaComposite.DST_OUT, AlphaComposite.SRC, AlphaComposite.SRC_ATOP, AlphaComposite.SRC_OUT };
Chúng ta lưu sẵn các rule trong mảng rules
.
AlphaComposite ac = AlphaComposite.getInstance(rules[i], 0.8f);
Tiếp theo là tạo đối tượng AlphaComposite
.
BufferedImage buffImg = new BufferedImage(60, 60, BufferedImage.TYPE_INT_ARGB);
Ở đây chúng ta không dùng đối tượng g2d
để vẽ trực tiếp mà vẽ trong một BufferedImage
rồi mới đưa buffer này vào trong g2d
. Bạn có thể hiểu BufferedImage
là một tờ giấy, chúng ta vẽ lên tờ giấy đó rồi dán lên tường là JPanel
.
Graphics2D gbi = buffImg.createGraphics();
Để vẽ lên BufferedImage thì chúng ta dùng đối tượng Graphics2D
tạo từ buffer với phương thức createGraphics()
chứ không dùng g2d
.
g2d.drawImage(buffImg, x, y, null);
Sau khi đã vẽ lên buffer thì chúng ta dùng g2d
để vẽ lên JPanel
với phương thức drawImage()
.
gbi.dispose();
Nhớ phải loại bỏ tất cả các đối tượng Graphics2D
sau khi dùng xong.
Ví dụ 1
AlphaComposite
thông qua ví dụ dưới đây.import java.awt.AlphaComposite; 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.image.BufferedImage; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private Image sun; private Image cloud; private Timer timer; private float alpha = 1f; private final int DELAY = 600; public Surface() { loadImages(); initTimer(); } private void loadImages() { sun = new ImageIcon("sun.png").getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT); cloud = new ImageIcon("cloud.png").getImage().getScaledInstance(200, 200, Image.SCALE_DEFAULT); } private void initTimer() { timer = new Timer(DELAY, this); timer.start(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); BufferedImage buffImg = new BufferedImage(220, 140, BufferedImage.TYPE_INT_ARGB); Graphics2D gbi = buffImg.createGraphics(); AlphaComposite ac = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); gbi.drawImage(sun, 40, 30, null); gbi.setComposite(ac); gbi.drawImage(cloud, 0, 0, null); g2d.drawImage(buffImg, 20, 20, null); gbi.dispose(); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void step() { alpha -= 0.1; if (alpha <= 0) { alpha = 0; timer.stop(); } } @Override public void actionPerformed(ActionEvent e) { step(); repaint(); } } public class SunAndCloudEx extends JFrame { public SunAndCloudEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Sun and cloud"); setSize(300, 210); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { SunAndCloudEx ex = new SunAndCloudEx(); ex.setVisible(true); } }); } }
Trong ví dụ này, chúng ta vẽ mặt trời và đám mây, đám mây sẽ từ từ biến mất nhường chỗ cho mặt trời.
private void loadImages() { sun = new ImageIcon("sun.png").getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT); cloud = new ImageIcon("cloud.png").getImage().getScaledInstance(200, 200, Image.SCALE_DEFAULT); }
Chúng ta dùng lớp ImageIcon
để load ảnh và thay đổi lại kích thước cho phù hợp nếu cần.
AlphaComposite ac = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha);
Ở đây chúng ta dùng rule là SRC_OVER
để trộn màu đám mây với màu nền.
Ví dụ 2
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private final int RADIUS = 50; private Image image; private int iw; private int ih; private int x; private int y; private boolean mouseIn; public Surface() { initUI(); } private void initUI() { loadImage(); iw = image.getWidth(null); ih = image.getHeight(null); addMouseMotionListener(new MyMouseAdapter()); addMouseListener(new MyMouseAdapter()); } private void loadImage() { image = new ImageIcon("penguin.png").getImage(); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); int midX = (getWidth() - iw) / 2; int midY = (getHeight() - ih) / 2; BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D bigr = bi.createGraphics(); if (mouseIn) { bigr.setPaint(Color.white); bigr.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2); bigr.setComposite(AlphaComposite.SrcAtop); bigr.drawImage(image, midX, midY, iw, ih, this); } bigr.setComposite(AlphaComposite.SrcOver.derive(0.1f)); bigr.drawImage(image, midX, midY, iw, ih, this); bigr.dispose(); g2d.drawImage(bi, 0, 0, getWidth(), getHeight(), this); g2d.dispose(); } private class MyMouseAdapter extends MouseAdapter { @Override public void mouseExited(MouseEvent e) { mouseIn = false; repaint(); } @Override public void mouseEntered(MouseEvent e) { mouseIn = true; } @Override public void mouseMoved(MouseEvent e) { x = e.getX(); y = e.getY(); repaint(); } } } public class SpotlightEx extends JFrame { public SpotlightEx() { initUI(); } private void initUI() { add(new Surface()); setSize(350, 300); setTitle("Spotlight"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { SpotlightEx ex = new SpotlightEx(); ex.setVisible(true); } }); } }
Trong ví dụ này, chúng ta tạo hiệu ứng đèn pin.
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Ảnh nền của chúng ta có phần nền trong suốt (chỉ có ảnh PNG mới có) nên ở đây chúng ta dùng kiểu BufferedImage.TYPE_INT_ARGB
.
if (mouseIn) { bigr.fillOval(x - RADIUS, y - RADIUS, RADIUS * 2, RADIUS * 2); bigr.setComposite(AlphaComposite.SrcAtop); bigr.drawImage(image, midX, midY, iw, ih, this); }
Chúng ta vẽ một hình tròn tại vị trí của chuột. Giá trị AlphaComposite.SrcAtop
vẽ hình rõ hoàn toàn, không mờ, tức là mặc định rule này dùng alpha là 1.0
bigr.setComposite(AlphaComposite.SrcOver.derive(0.1f)); bigr.drawImage(image, midX, midY, iw, ih, this);
Hai dòng trên vẽ toàn bộ hình còn lại. Giá trị AlphaComposite.SrcOver.derive(0.1f)
vẽ hình gần như mờ hoàn toàn.
g2d.drawImage(bi, 0, 0, getWidth(), getHeight(), this);
Tất cả những thứ trên chúng ta vẽ trong buffer vì thế cuối cùng chúng ta vẽ lại toàn bộ buffer và JPanel
.