Java 2D – Clipping

Trong bài này, chúng ta nói về kĩ thuật clipping trong Java 2D

Clipping

Clipping là giới hạn việc vẽ vào khu vực nào đó trên panel. Khi làm việc với clip, chúng ta phải làm việc với bản copy của object Graphics hoặc khôi phục thuộc tính clip ban đầu. Việc thay đổi clip không tác động pixel hiện tại, nó chỉ tác động tới việc render sau đó.

Trong ví dụ sau, chúng ta clipping image thành hình tròn.

ClippingEx.java

package net.vncoding;

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);
            }
        });        
    }
}

Giải thích:
Hình tròn chuyển động trên màn hình và show một 1 phần image nằm bên dưới.

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

Chúng ta tạo 1 bản copy của object Graphics2D. Do việc thay đổi clip sẽ không ảnh hưởng đến phần khác nơi mà object Graphics2D được sử dụng lại.

g2d.clip(new Ellipse2D.Double(pos_x, pos_y, RADIUS, RADIUS));

Phương thức clip() kết hợp clip hiện tại với hình được truyền vào làm đối số. Kết quả vùng giao nhau được đưa vào clip. Trong ví dụ này, clip là hình tròn.

if (pos_x < 0) {
    
    delta[0] = Math.random() % 4 + 5;
} else if (pos_x > w - RADIUS) {
    
    delta[0] = -(Math.random() % 4 + 5);
}

Nếu hình tròn chạm vào bên trái, phải của panel. Hướng chuyển động của hình tròn thay đổi ngẫu nhiên. Logic này cũng được áp dụng cho khi hình tròn chạm vào bên trên, dưới của panel.

g2d.dispose();

Khi hoàn thành công việc vẽ, phải giải phóng bản copy của object Graphics2D

Kết quả:

Java 2D - hiệu ứng clipping
Java 2D – hiệu ứng clipping

Clipping shape

Trong ví dụ này, chúng ta clipping vùng giao nhau giữa hình chữ nhật và hình tròn.

ClippingShapesEx.java

package net.vncoding;

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);
            }
        });
    }
}

Giải thích:
Trong ví dụ này, chúng ta có hình tròn chuyển động và hình nhữ nhật xoay tròn. Khi 2 hình overlap, vùng giao nhau giữa 2 hình được fill màu.

Shape oldClip = g2d.getClip();

Vì chúng ta không tạo bản copy của object Graphics2D, do vậy chúng ta lưu clip trước đó. Kết thúc, chúng ta phải reset clip thành trạng thái ban đầu.

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);

Xoay hình chữ nhật bởi phương thức rotate().

GeneralPath path = new GeneralPath();
path.append(tx.createTransformedShape(rect), false);

Chúng ta lấy hình của hình chữ nhật được quay.a

g2d.clip(circle);
g2d.clip(path);

g2d.setPaint(new Color(110, 110, 110));
g2d.fill(circle);

Chúng ta giới hạn vùng vẽ, chỉ vẽ vùng giao nhau giữa 2 hình. Nếu 2 hình overlap, vùng giao nhau được fill màu. Phương thức clip()kết hợp clip ban đầu với 2 hình đã cho.

g2d.setClip(oldClip);

Với phương thức clip(), chúng ta reset vùng clip thành clip cũ trước khi chúng ta vẽ hình. Khác với phương thức clip(), setClip() không kết hợp khu vực clipping. Nó reset clip cho khu vực mới. Do đó, phương thức này nên được sử dụng tường minh trong việc khôi phục clip cũ.

Kết quả:

Java 2D - Clipping shape
Java 2D – Clipping shape

Be the first to comment

Leave a Reply