Chào mừng bạn đến với series lập trình đồ họa 2D với Java. Series này chủ yếu nhắm đến các bạn mới bắt đầu bước vào lập trình đồ họa cơ bản.
Đồ họa Vector
Đồ họa trong máy tính có 2 loại là đồ họa vector và đồ họa raster. Trong đó đồ họa raster được dựng nên từ tập hợp các điểm ảnh (pixel), còn vector được dựng từ các đối tượng đồ họa cơ sở như điểm, đường thẳng, đường cong… các đối tượng đồ họa này được tính toán bằng các phương trình toán học. Cả hai loại đồ họa này đều có ưu và nhược điểm riêng. Đồ họa vector có các ưu điểm sau:
- Kích thước nhỏ
- Có thể zoom không giới hạn
- Di chuyển, phóng to, xoay hình… không làm giảm chất lượng hình
Thư viện đồ họa 2D của Java hỗ trợ cả đồ họa vector và raster.
Cơ chế vẽ của Java
Bât cứ đoạn code nào bạn dùng để vẽ ra thứ gì cũng được đặt trong phương thức paintComponent(),
phương thức này được override từ lớp JPanel
. Phương thức này tự động được gọi bởi phương thức paint()
(trong lớp java.awt.Component
), ngoài phương thức paintComponent()
còn có 2 phương thức khác được gọi cùng nữa là paintBorder()
và paintChildren()
. Nếu muốn bạn có thể override cả 2 phương thức đó cũng được nhưng thường thì cũng không cần thiết lắm. Trong series này thì chúng ta chỉ dùng đến paintComponent()
.
Đối tượng Graphics
Tham số của phương thức paintComponent()
là một đối tượng lớp Graphics.
Đối tượng này cho phép chúng ta vẽ các vật thể lên JPanel.
Ngoài ra trong Java còn có lớp Graphics2D
kế thừa từ lớp Graphics
cung cấp các phương thức cao cấp hơn để hỗ trợ cho người lập trình vẽ một cách dễ dàng hơn.
Đối tượng này được khởi tạo bởi hệ thống và tự động được truyền vào phương thức paintComponent()
khi phương thức này được gọi. Ngoài phương thức paintComponent()
đối tượng Graphics
còn được truyền vào hai phương thức đã nói trên là paintBorder()
và paintChildren()
. Nếu một trong các phương thức này làm thay đổi trạng thái của đối tượng Graphics
thì có thể dẫn đến một số vấn đề nên thường thì chúng ta sẽ tạo một đối tượng khác từ đối tượng Graphics
gốc bằng phương thức create()
và hủy đối tượng mới này với phương thức dispose().
Nếu chúng ta chỉ thay đổi các thuộc tính bình thường như màu, font chữ… thì không cần tạo mới, nhưng nếu chúng ta thay đổi các thuộc tính cao cấp bằng các phép biến đổi hình như cắt hình, xoay hình, zoom to nhỏ… thì chúng ta phải tạo một đối tượng Graphics
khác.
Code ví dụ mẫu
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; g2d.drawString("Java 2D", 50, 50); } @Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); } } public class BasicEx extends JFrame { public BasicEx() { initUI(); } private void initUI() { add(new Surface()); setTitle("Simple Java 2D example"); setSize(300, 200); setLocationRelativeTo(null); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { BasicEx ex = new BasicEx(); ex.setVisible(true); } }); } }
Đoạn code trên vẽ một đoạn text lên JPanel
.
class Surface extends JPanel { ... }
Chúng ta tạo lớp Surface
kế thừa từ lớp JPanel
. Lớp này là lớp chính dùng để vẽ.
Graphics2D g2d = (Graphics2D) g;
Chúng ta tạo đối tượng Graphics2D
để tận dụng một số tính năng cao cấp mà lớp Graphics
không có.
g2d.drawString("Java 2D", 50, 50);
Phương thức drawString()
“vẽ” một đoạn text tại vị trí (50, 50).
@Override public void paintComponent(Graphics g) { super.paintComponent(g); doDrawing(g); }
Tất cả mọi thứ đều được vẽ bên trong phương thức paintComponent()
. Trong đó super.paintComponent()
gọi lại phương thức paintComponent()
của lớp cha là lớp JPanel
, mà trong phương thức này ở lớp cha chỉ đơn giản là vẽ một cửa sổ trống không, tức là khi gọi phương thức này chúng ta làm công việc là xóa trắng toàn bộ cửa sổ, việc này rất có ích khi tạo hiệu ứng chuyển động. Ngoài ra ở đây mình không vẽ trực tiếp trong phương thức paintComponent()
mà tạo phương thức doDrawing()
rồi vẽ trong đó, mình làm thế là vì… thích 🙂
private void initUI() { ... }
Phương thức initUI()
làm công việc khởi tạo giao diện cho ứng dụng.
add(new Surface());
Chúng ta thêm Surface
vào JFrame
.
EventQueue.invokeLater(new Runnable() { @Override public void run() { BasicEx ex = new BasicEx(); ex.setVisible(true); } });
Phương thức invokeLater()
sẽ chạy ứng dụng của chúng ta trong một luồng do EventQueue
quản lý. Thực ra bạn cũng không cần đến EventQueue
làm gì, chỉ cần tạo một đối tượng BasicEx
rồi setVisible(true)
là có thể chạy được nhưng trên tài liệu của Oracle thì lại khuyên chúng ta đặt bên trong EventQueue
vì lý do là làm như vậy sẽ đảm bảo an toàn cho ứng dụng (mình cũng không hiểu tại sao) nên thôi thì mình cứ làm vậy 🙂