Java小程式:貝茲曲線

這支程式是利用4點繪製一條貝茲曲線。

參考的程式碼在這裡,我主要是將成是改寫成Java Application並加上一點點註解。

程式碼



import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class BzWindow extends JFrame implements ActionListener {

/**
* Eclipse 自動產生的數值,請無視。
*/
private static final long serialVersionUID = -9060807186493274426L;

// 資料結構
private Point[] coordlist; // 控制點
private int numpoints; // 控制點數目

// 貝茲曲線參數
double t; // the time interval
double k = .025; // time step value for drawing curve
int moveflag = 5; // flag to notify if user is moving a point
boolean poly = true; // 是否要繪製控制點(沒畫還是可以移動控制點)

JButton restart;
JPanel pan;

public BzWindow() {
super("Bezier curve");

coordlist = new Point[4];
numpoints = 0;

// 繪圖的pan加入mouse處理並放中間
pan = new DrawPanel();
pan.setDoubleBuffered(true);
new MouseHandle(pan);

restart = new JButton("restart");
restart.addActionListener(this);

this.setLayout(new BorderLayout());
this.add(pan, BorderLayout.CENTER);
this.add(restart, BorderLayout.SOUTH);
}

@Override
public void actionPerformed(ActionEvent e) {
// 只有一個清除的按鈕,就不判斷為哪個物件產生的事件了。
numpoints = 0;
repaint();
poly = true;
}

/**
* 執行用主程式。
* @param args 參數沒有意義
*/
public static void main(String[] args) {
BzWindow f = new BzWindow();
f.setSize(500, 400);
f.setLocationRelativeTo(null); // 視窗置中
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}

/**
* 處理滑鼠事件,連動影像資料結構。
*/
private class MouseHandle extends MouseAdapter implements
MouseMotionListener {

public MouseHandle(JPanel target) {
// 自動加入指定物件的
target.addMouseListener(this);
target.addMouseMotionListener(this);
}

@Override
public void mouseDragged(MouseEvent e) {
// 移動貝茲曲線的參數點
// MousePress時會抓哪個點是要被移動,然後將index寫入moveflag
if (moveflag < numpoints) {
coordlist[moveflag].setLocation(e.getPoint());
repaint();
}
}

@Override
public void mouseReleased(MouseEvent e) {
// 這實際上應該是拖曳後,放開滑鼠要做的事情
// 不過
moveflag = 5;
}

@Override
public void mousePressed(MouseEvent e) {
Point point = e.getPoint();

// 如果控制點還沒有到達四個,點滑鼠就只會加控制點。
if (numpoints < 4) {
coordlist[numpoints] = point;
numpoints++;
repaint();
}
// 已經點好四個控制點,就會判斷是否點到控制點。
// 判斷是否點到控制點,內部的i,l迴圈就是容許點擊誤差(不然很難點)
else {
for (int i = 0; i<numpoints; ++i) {
for (int j = -2; j < 3; ++j)
for (int l = -2; l < 3; ++l)
if (point.equals(new Point(coordlist[i].x + j,
coordlist[i].y + l)))
moveflag = i;
}
}
}
}

/**
* 繪製貝茲曲線用的畫布。
* 由於要在視窗上面加入Button,直接畫在JFrame會影像按鈕的繪製。
*/
private class DrawPanel extends JPanel {
/**
* Eclipse 自動產生的數值,請無視。
*/
private static final long serialVersionUID = -608471735940581102L;

/**
* 虛線的畫筆。
*/
final BasicStroke DashLineStoke = new BasicStroke(
2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER,
10.0f, new float[]{4.0f, 2.0f}, 0.0f);
/**
* 一般的畫筆。
*/
final BasicStroke LineStroke = new BasicStroke(1.0f);

public void update(Graphics g) {
paint(g);
}

public void paint(Graphics g) {
// 開啟反鋸齒功能
// 其實在繪製貝茲曲線時,有開反鋸齒跟沒開是差不多的,
// 不過繪製其他直線就會有差了。
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);

g.clearRect(0, 0, getWidth(), getHeight());

// 畫控制點與控制點間的連線
g.setColor(Color.black);
if (poly) {
for (int i = 0; i < numpoints; i++)
g.fillOval(coordlist[i].x - 2, coordlist[i].y - 2, 4, 4);

g2d.setStroke(DashLineStoke);
for (int i = 0; i < numpoints - 1; ++i)
g2d.drawLine(coordlist[i].x, coordlist[i].y,
coordlist[i + 1].x, coordlist[i + 1].y);

}

// 利用3次方的貝茲曲線公式計算兩個點,再用直線連接起來
// 所以畫出來的線上可能會覺得不是很平滑。
g.setColor(Color.blue);
g2d.setStroke(LineStroke);
if (numpoints == 4) {
double x1, x2, y1, y2;
x1 = coordlist[0].x;
y1 = coordlist[0].y;
for (t = k; t <= 1 + k; t += k) {
x2 = (coordlist[0].x + t *(-coordlist[0].x * 3
+ t * (3 * coordlist[0].x - coordlist[0].x * t)))
+ t * (3 * coordlist[1].x
+ t *(-6 * coordlist[1].x + coordlist[1].x * 3 * t))
+ t * t * (coordlist[2].x * 3 - coordlist[2].x * 3 * t)
+ coordlist[3].x * t * t * t;
y2 = (coordlist[0].y + t * (-coordlist[0].y * 3
+ t * (3 * coordlist[0].y - coordlist[0].y * t)))
+ t * (3 * coordlist[1].y
+ t * (-6 * coordlist[1].y + coordlist[1].y * 3 * t))
+ t * t * (coordlist[2].y * 3 - coordlist[2].y * 3 * t)
+ coordlist[3].y * t * t * t;
g.drawLine((int) x1, (int) y1, (int) x2, (int) y2);
x1 = x2;
y1 = y2;
}
}
}
}
}



留言