Java 2D – Mô phỏng một số hiệu ứng đơn giản

4.7/5 - (3 votes)

Trong phần này chúng ta sẽ tập tành làm một số hiệu ứng đơn giản.

Ví dụ 1

Trong ví dụ đầu tiên, chúng ta sẽ mô phỏng hiệu ứng bong bóng.

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
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 java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

class Surface extends JPanel
        implements ActionListener {

    private final Color colors[] = {
        Color.blue, Color.cyan, Color.green,
        Color.magenta, Color.orange, Color.pink,
        Color.red, Color.yellow, Color.lightGray, Color.white
    };

    private Ellipse2D.Float[] ellipses;
    private double esize[];
    private float estroke[];
    private double maxSize = 0;
    private final int NUMBER_OF_ELLIPSES = 25;
    private final int DELAY = 30;
    private final int INITIAL_DELAY = 150;    
    private Timer timer;

    public Surface() {

        initSurface();
        initEllipses();
        initTimer();
    }

    private void initSurface() {

        setBackground(Color.black);
        ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES];
        esize = new double[ellipses.length];
        estroke = new float[ellipses.length];
    }

    private void initEllipses() {

        int w = 350;
        int h = 250;

        maxSize = w / 10;

        for (int i = 0; i < ellipses.length; i++) {

            ellipses[i] = new Ellipse2D.Float();
            posRandEllipses(i, maxSize * Math.random(), w, h);
        }
    }

    private void initTimer() {

        timer = new Timer(DELAY, this);
        timer.setInitialDelay(INITIAL_DELAY);
        timer.start();
    }

    private void posRandEllipses(int i, double size, int w, int h) {

        esize[i] = size;
        estroke[i] = 1.0f;
        double x = Math.random() * (w - (maxSize / 2));
        double y = Math.random() * (h - (maxSize / 2));
        ellipses[i].setFrame(x, y, size, size);
    }

    private void doStep(int w, int h) {

        for (int i = 0; i < ellipses.length; i++) { 
            estroke[i] += 0.025f; 
            esize[i]++; 
            if (esize[i] > maxSize) {
                posRandEllipses(i, 1, w, h);
            } else {
                ellipses[i].setFrame(ellipses[i].getX(), 
                                     ellipses[i].getY(),
                                     esize[i], 
                                     esize[i]);
            }
        }
    }

    private void drawEllipses(Graphics2D g2d) {

        for (int i = 0; i < ellipses.length; i++) {

            g2d.setColor(colors[i % colors.length]);
            g2d.setStroke(new BasicStroke(estroke[i]));
            g2d.draw(ellipses[i]);
        }
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g.create();

        RenderingHints rh
                = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);

        rh.put(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);

        g2d.setRenderingHints(rh);

        Dimension size = getSize();
        doStep(size.width, size.height);
        drawEllipses(g2d);
        
        g2d.dispose();
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        repaint();
    }
}

public class BubblesEx extends JFrame {

    public BubblesEx() {

        initUI();
    }

    private void initUI() {

        add(new Surface());
        
        setTitle("Bubbles");
        setSize(350, 250);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                BubblesEx ex = new BubblesEx();
                ex.setVisible(true);
            }
        });
    }
}

Các bong bóng sẽ xuất hiện tại các vị trí ngẫu nhiên trên màn hình với kích thước và màu sắc khác nhau, kích thước của chúng sẽ tăng dần đến một mức độ nào đó thì biến mất.

private final Color colors[] = {
    Color.blue, Color.cyan, Color.green,
    Color.magenta, Color.orange, Color.pink,
    Color.red, Color.yellow, Color.lightGray, Color.white
};

Màu sắc sẽ được lưu vào một mảng để gọi đến cho tiện.

private void initSurface() {

    setBackground(Color.black);
    ellipses = new Ellipse2D.Float[NUMBER_OF_ELLIPSES];
    esize = new double[ellipses.length];
    estroke = new float[ellipses.length];
}

Phương thức initSurface() thực hiện công việc khởi tạo ban đầu, chúng ta thiết lập màu nền màn hình là màu đen. Sau đó khởi tạo 3 mảng, mảng ellipses lưu các đối tượng elip, mảng esize lưu kích thước của các elip, mảng etstroke lưu độ dày của nét vẽ.

private void initEllipses() {

    int w = 350;
    int h = 250;
            
    maxSize = w / 10;
    
    for (int i = 0; i < ellipses.length; i++) {
        
        ellipses[i] = new Ellipse2D.Float();
        posRandEllipses(i, maxSize * Math.random(), w, h);
    }
}

Phương thức initEllipses() thực hiện khởi tạo từng đối tượng Ellipse2D rồi gọi phương thức posRandEllipse() để tiếp tục khởi tạo các thông tin cần thiết.

private void posRandEllipses(int i, double size, int w, int h) {

    esize[i] = size;
    estroke[i] = 1.0f;
    double x = Math.random() * (w - (maxSize / 2));
    double y = Math.random() * (h - (maxSize / 2));
    ellipses[i].setFrame(x, y, size, size);
}

Phương thức posRandEllipses() lưu kích thước và độ dày của nét vẽ vào hai mảng esizeestroke, sau đó khởi tạo ngẫu nhiên vị trí của từng hình elip rồi thiết lập thông tin đó bằng phương thức setFrame().

private void doStep(int w, int h) {

    for (int i = 0; i < ellipses.length; i++) { 
        estroke[i] += 0.025f; 
        esize[i]++; 
        if (esize[i] > maxSize) {            
            posRandEllipses(i, 1, w, h);
        } else {            
            ellipses[i].setFrame(ellipses[i].getX(), 
                                 ellipses[i].getY(),
                                 esize[i], 
                                 esize[i]);
        }
    }
}

Phương thức doStep() thực hiện phần animation (tạo hiệu ứng). Cứ mỗi lần lặp, chúng ta tăng kích thước và độ dày của từng hình elip lên, nếu hình elip nào có kích thước vượt quá ngưỡng maxSize thì chúng ta khởi tạo lại kích thước mới với vị trí mới cho hình elip đó.

private void drawEllipses(Graphics2D g2d) {

    for (int i = 0; i < ellipses.length; i++) {

        g2d.setColor(colors[i % colors.length]);
        g2d.setStroke(new BasicStroke(estroke[i]));
        g2d.draw(ellipses[i]);
    }
}

Phương thức drawEllipses() sẽ thực hiện việc vẽ toàn bộ các hình elip lên màn hình với màu được chọn ngẫu nhiên.

Dimension size = getSize();
doStep(size.width, size.height);

Ngoài ra trong phương thức doDrawing() chúng ta cũng phải lấy lại kích thước cửa sổ để đề phòng kích thước cửa sổ thay đổi thì vị trí các hình elip cũng phải thay đổi theo cho phù hợp.

Capture

Ví dụ 2

Trong ví dụ này chúng ta tạo một ngôi sao chuyển động.

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 java.awt.geom.GeneralPath;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

class Surface extends JPanel
        implements ActionListener {

    private final int points[][] = {
        {0, 85}, {75, 75}, {100, 10}, {125, 75},
        {200, 85}, {150, 125}, {160, 190}, {100, 150},
        {40, 190}, {50, 125}, {0, 85}
    };
    
    private Timer timer;
    private double angle = 0;
    private double scale = 1;
    private double delta = 0.01;
    
    private final int DELAY = 10;

    public Surface() {

        initTimer();
    }
    
    private void initTimer() {
        
        timer = new Timer(DELAY, this);
        timer.start();        
    }

    private void doDrawing(Graphics g) {
        
        int h = getHeight();
        int w = getWidth();

        Graphics2D g2d = (Graphics2D) g.create();

        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
                RenderingHints.VALUE_RENDER_QUALITY);

        g2d.translate(w / 2, h / 2);
        GeneralPath star = new GeneralPath();
        star.moveTo(points[0][0], points[0][1]);

        for (int k = 1; k < points.length; k++) {
            
            star.lineTo(points[k][0], points[k][1]);
        }

        g2d.rotate(angle);
        g2d.scale(scale, scale);
        g2d.fill(star);        
        
        g2d.dispose();
    }
    
    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
    
    private void step() {
        
        if (scale < 0.01) { 
            delta = -delta; 
        } else if (scale > 0.99) {            
            delta = -delta;
        }

        scale += delta;
        angle += 0.01;        
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        step();
        repaint();
    }
}

public class StarDemoEx extends JFrame {

    public StarDemoEx() {

        initUI();
    }

    private void initUI() {
        
        add(new Surface());

        setTitle("Star");
        setSize(420, 250);
        setLocationRelativeTo(null);        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                StarDemoEx ex = new StarDemoEx();
                ex.setVisible(true);
            }
        });
    }
}

Ngôi sao sẽ tự xoay quanh mình và tự phóng to/thu nhỏ trong một phạm vi cố định, cho cảm giác giống như ngôi sao đang nhảy lên nhảy xuống.

private final int points[][] = {
    {0, 85}, {75, 75}, {100, 10}, {125, 75},
    {200, 85}, {150, 125}, {160, 190}, {100, 150},
    {40, 190}, {50, 125}, {0, 85}
};

Chúng ta vẽ ngôi sao bằng cách vẽ một đa giác, các điểm của đa giác được lưu trong mảng points.

private double angle = 0;
private double scale = 1;
private double delta = 0.01;

Biến angle lưu hướng xoay của ngôi sao, biến scale lưu tỉ lệ phóng to/thu nhỏ của ngôi sao, biến delta lưu tốc độ phóng to/thu nhỏ của ngôi sao.

g2d.translate(w / 2, h / 2);

Chúng ta tịnh tiến vào giữa màn hình.

if (scale < 0.01) { 
    delta = -delta; 
} else if (scale > 0.99) {    
    delta = -delta;
}

Ngôi sao chỉ được phóng to và thu nhỏ trong một phạm vi cố định.

Untitled

Ví dụ 3

Trong ví dụ này chúng ta mô phỏng hiệu ứng Puff rất được hay dùng trong phim ảnh.

import java.awt.AlphaComposite;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
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 x = 1;
    private float alpha = 1;
    private final int DELAY = 15;
    private final int INITIAL_DELAY = 200;

    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();
        
        RenderingHints rh =
            new RenderingHints(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);

        rh.put(RenderingHints.KEY_RENDERING,
               RenderingHints.VALUE_RENDER_QUALITY);

        g2d.setRenderingHints(rh);

        Font font = new Font("Dialog", Font.PLAIN, x);
        g2d.setFont(font);

        FontMetrics fm = g2d.getFontMetrics();
        String s = "PhoCode";
        Dimension size = getSize();

        int w = (int) size.getWidth();
        int h = (int) size.getHeight();

        int stringWidth = fm.stringWidth(s);
        AlphaComposite ac = AlphaComposite.getInstance(
                AlphaComposite.SRC_OVER, alpha);
        g2d.setComposite(ac);

        g2d.drawString(s, (w - stringWidth) / 2, h / 2);        
        
        g2d.dispose();
    }

    @Override
    public void paintComponent(Graphics g) {
        
        super.paintComponent(g);        
        doDrawing(g);
    }   
    
    private void step() {
        
        x += 1;

        if (x > 40)
            alpha -= 0.01;

        if (alpha <= 0.01)
            timer.stop();        
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        
        step();
        repaint();
    }        
}

public class PuffEx extends JFrame {    
    
    public PuffEx() {
        
        initUI();
    }
    
    private void initUI() {
        
        setTitle("Puff");

        add(new Surface());

        setSize(400, 300);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);        
    }

    public static void main(String[] args) {
        
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {

                PuffEx ex = new PuffEx();
                ex.setVisible(true);
            }
        });      
    }
}

Trong hiệu ứng Puff, một đoạn text hiện ra rồi từ từ to dần và biến mất.

FontMetrics fm = g2d.getFontMetrics();

Lớp FontMetrics lưu thông tin về kiểu Font tương ứng với từng loại màn hình, để lấy thông tin đó thì chúng ta dùng phương thức getFontMetrics().

int stringWidth = fm.stringWidth(s);

Chúng ta lấy kích thước của đoạn text bằng phương thức FontMetrics.stringWidth().

AlphaComposite ac = AlphaComposite.getInstance(
        AlphaComposite.SRC_OVER, alpha);
g2d.setComposite(ac);

Chúng ta thiết lập độ trong suốt cho đoạn text.

g2d.drawString(s, (w - stringWidth) / 2, h / 2);

Dòng trên vẽ đoạn text ra giữa màn hình.

if (x > 40)
    alpha -= 0.01;

Khi kích thước của đoạn text lớn hơn 40 thì bắt đầu cho mờ dần.

Untitled

0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments