Trong bài này chúng ta sẽ tìm hiểu về độ trong suốt và làm một số hiệu ứng cơ bản.
Độ trong suốt (Transparency)
Trong đồ họa máy tính, độ trong suốt của một điểm ảnh được tính toán bằng cách pha trộn điểm ảnh cần làm trong suốt với điểm ảnh nền, các điểm ảnh nền được gọi chung là kênh alpha (alpha channel), các điểm ảnh nền là một số 8 bit để biểu diễn 256 cấp độ trong suốt.
Java cung cấp lớp AlphaComposite
dùng để biểu diễn độ trong suốt, lớp này thực hiện các phép tính toán để pha trộn điểm ảnh nguồn và điểm ảnh đích để tạo ra hiệu ứng trong suốt. Lớp này làm việc với 2 giá trị chính là rule (thường chúng ta dùng hằng số AlphaComposite.SRC_OVER
) và value (từ 0.0f
là vô hình đến 1.0f
là hiển thị rõ hoàn toàn),
Ví dụ
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import javax.swing.JFrame; import javax.swing.JPanel; class Surface extends JPanel { private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setPaint(Color.blue); for (int i = 1; i <= 10; i++) { float alpha = i * 0.1f; AlphaComposite alcom = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); g2d.setComposite(alcom); g2d.fillRect(50 * i, 20, 40, 40); } g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class TransparentRectanglesEx extends JFrame { public TransparentRectanglesEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Transparent rectangles"); setSize(590, 120); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { TransparentRectanglesEx ex = new TransparentRectanglesEx(); ex.setVisible(true); } }); } }
Chúng ta vẽ 10 hình chữ nhật với 10 cấp độ trong suốt khác nhau.
float alpha = i * 0.1f;
Giá trị alpha tăng dần qua vòng lặp.
AlphaComposite alcom = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha);
Phương thức AlphaComposite.getInstance()
tạo một đối tượng AlphaComposite.
g2d.setComposite(alcom);
Tiếp theo chúng ta gọi phương thức setComposite()
để thiết lập thuộc tính trong suốt.
Hiệu ứng làm mờ
import java.awt.AlphaComposite; 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 javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private Image img; private Timer timer; private float alpha = 1f; private final int DELAY = 40; private final int INITIAL_DELAY = 500; public Surface() { loadImage(); setSurfaceSize(); initTimer(); } private void loadImage() { img = new ImageIcon("mushrooms.jpg").getImage(); } private void setSurfaceSize() { int h = img.getHeight(this); int w = img.getWidth(this); setPreferredSize(new Dimension(w, h)); } private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); AlphaComposite acomp = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); g2d.setComposite(acomp); g2d.drawImage(img, 0, 0, null); g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } private void step() { alpha += -0.01f; if (alpha <= 0) { alpha = 0; timer.stop(); } } @Override public void actionPerformed(ActionEvent e) { step(); repaint(); } } public class FadeOutEx extends JFrame { public FadeOutEx() { initUI(); } private void initUI() { add(new Surface()); pack(); setTitle("Fade out"); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { FadeOutEx ex = new FadeOutEx(); ex.setVisible(true); } }); } }
Trong ví dụ trên, chúng ta hiển thị một ảnh và dần dần làm mờ nó qua thời gian.
private void setSurfaceSize() { int h = img.getHeight(this); int w = img.getWidth(this); setPreferredSize(new Dimension(w, h)); }
Phương thức setSurfaceSize()
cùng với phương thức pack()
thiết lập kích thước cửa sổ bằng với kích thước của ảnh.
private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); }
Phương thức initTimer()
chạy đồng hồ. Cứ sau một khoảng thời gian thì đồng hồ sẽ giải phóng sự kiện và gọi tới phương thức actionPerformed(),
chúng ta cho giảm dần dần giá trị alpha và gọi phương thức repaint()
.
private void step() { alpha += -0.01f; if (alpha <= 0) { alpha = 0; timer.stop(); } }
Phương thức step()
biểu diễn quy trình làm mờ. Thuộc tính alpha giảm dần giá trị qua thời gian và không được là số âm.
repaint();
Phương thức repaint()
chỉ đơn giản là gọi đến phương thức paintComponent()
.
Đồng hồ chờ
Trong ví dụ dưới đây, chúng ta sẽ làm một đồng hồ chờ giống như khi bạn xem Youtube mà bị nghẽn mạng :).
import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.EventQueue; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.Timer; class Surface extends JPanel implements ActionListener { private Timer timer; private int count; private final int INITIAL_DELAY = 200; private final int DELAY = 80; private final int NUMBER_OF_LINES = 8; private final int STROKE_WIDTH = 3; private final double[][] trs = { {0.0, 0.15, 0.30, 0.5, 0.65, 0.80, 0.9, 1.0}, {1.0, 0.0, 0.15, 0.30, 0.5, 0.65, 0.8, 0.9}, {0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65, 0.8}, {0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5, 0.65}, {0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3, 0.5}, {0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15, 0.3}, {0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0, 0.15}, {0.15, 0.3, 0.5, 0.65, 0.8, 0.9, 1.0, 0.0} }; public Surface() { initTimer(); } private void initTimer() { timer = new Timer(DELAY, this); timer.setInitialDelay(INITIAL_DELAY); timer.start(); } private void doDrawing(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); int width = getWidth(); int height = getHeight(); g2d.setStroke(new BasicStroke(STROKE_WIDTH, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2d.translate(width / 2, height / 2); for (int i = 0; i < NUMBER_OF_LINES; i++) { float alpha = (float) trs[count % NUMBER_OF_LINES][i]; AlphaComposite acomp = AlphaComposite.getInstance( AlphaComposite.SRC_OVER, alpha); g2d.setComposite(acomp); g2d.rotate(Math.PI / 4f); g2d.drawLine(0, -10, 0, -40); } g2d.dispose(); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } @Override public void actionPerformed(ActionEvent e) { repaint(); count++; } } public class WaitingEx extends JFrame { public WaitingEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Waiting"); setSize(300, 200); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { WaitingEx ex = new WaitingEx(); ex.setVisible(true); } }); } }
Chúng ta mô phỏng hiệu ứng này bằng cách vẽ 8 đoạn thẳng với 8 giá trị alpha khác nhau.
private final double[][] trs = { ... };
Mảng hai chiều trs
lưu trữ giá trị alpha cho 8 đoạn thẳng. Mảng này có 8 hàng, các đoạn thẳng sẽ thay phiên nhau sử dụng những giá trị này.
g2d.rotate(Math.PI/4f); g2d.drawLine(0, -10, 0, -40);
Chúng ta dùng phương thức rotate()
để xoay các đoạn thẳng xung quanh một hình tròn. Chúng ta sẽ tìm hiểu thêm về cách xoay hình trong các bài sau.