關于貝塞爾曲線的詳細數學原理及公式可參考:
貝塞爾曲線_百度百科
我們來談談貝塞爾曲線
本文給出了一種用Java實作貝塞爾曲線的方法,并且可以用滑鼠拖動改變錨點。
效果示範圖:
這裡引用百度百科給出的公式:
二次方公式 三次方公式
核心算法代碼:
// 二次貝塞爾曲線
for (double k = t; k <= 1 + t; k += t) {
double r = 1 - k;
x = Math.pow(r, 2) * keyPointP[0].getX() + 2 * k * r * keyPointP[1].getX()
+ Math.pow(k, 2) * keyPointP[2].getX();
y = Math.pow(r, 2) * keyPointP[0].getY() + 2 * k * r * keyPointP[1].getY()
+ Math.pow(k, 2) * keyPointP[2].getY();
g.drawOval((int) x, (int) y, 1, 1);// 畫圓的方式比下面注釋掉的畫線效果更好
// g.drawLine((int) x, (int) y, (int) x, (int) y);
}
// 三次貝塞爾曲線
for (double k = t; k <= 1 + t; k += t) {
double r = 1 - k;
x = Math.pow(r, 3) * keyPointP[0].getX() + 3 * k * Math.pow(r, 2) * keyPointP[1].getX()
+ 3 * Math.pow(k, 2) * (1 - k) * keyPointP[2].getX() + Math.pow(k, 3) * keyPointP[3].getX();
y = Math.pow(r, 3) * keyPointP[0].getY() + 3 * k * Math.pow(r, 2) * keyPointP[1].getY()
+ 3 * Math.pow(k, 2) * (1 - k) * keyPointP[2].getY() + Math.pow(k, 3) * keyPointP[3].getY();
g.drawOval((int) x, (int) y, 1, 1);
}
完整代碼:
package test;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* @author: happyaaakkk
* @date : 2020年4月28日
* @Description:貝塞爾曲線測試
*/
public class TestBezier {
/**
* 存關鍵點的x、y的數組
*/
private static Point2D[] keyPointP;
/**
* 存關鍵點的x、y、width、height的數組
*/
private static Ellipse2D.Double[] keyPointE;
/**
* 關鍵點數
*/
private static int keyPointNum;
/**
* 偏移量,越小曲線越精細
*/
private static double t = 0.001;
/**
* 顯示藍色輔助線的标記
*/
private static boolean flagShow = true;
static class BezierPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* 關鍵點編号
*/
private int keyPointID = -1;
BezierPanel() {
this.addMouseListener(new MouseAction());
this.addMouseMotionListener(new MouseMotion());
}
@Override
protected void paintComponent(Graphics gs) {
// 重寫repaint
super.paintComponent(gs);
Graphics2D g = (Graphics2D) gs;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE);
if (flagShow) {
for (int i = 0; i < keyPointNum; i++) { // 繪制圓點
if (i > 0 && i < (keyPointNum - 1)) {
g.fill(keyPointE[i]);// 中間的關鍵點畫實心圓
} else {
g.draw(keyPointE[i]);// 第一個和最後一個畫空心圓
}
if (keyPointNum > 1 && i < (keyPointNum - 1)) {
g.drawLine((int) keyPointP[i].getX(), (int) keyPointP[i].getY(), (int) keyPointP[i + 1].getX(),
(int) keyPointP[i + 1].getY());// 畫關鍵點之間連接配接的直線
}
if (i == 0) {
g.drawString("A", (int) keyPointE[i].x, (int) keyPointE[i].y);
} else if (i == 1) {
g.drawString("B", (int) keyPointE[i].x, (int) keyPointE[i].y);
} else if (i == 2) {
g.drawString("C", (int) keyPointE[i].x, (int) keyPointE[i].y);
} else if (i == 3) {
g.drawString("D", (int) keyPointE[i].x, (int) keyPointE[i].y);
}
}
}
// 二次貝塞爾曲線
if (keyPointNum == 3) {
double x, y;
g.setColor(Color.RED);
for (double k = t; k <= 1 + t; k += t) {
double r = 1 - k;
x = Math.pow(r, 2) * keyPointP[0].getX() + 2 * k * r * keyPointP[1].getX() + Math.pow(k, 2) * keyPointP[2].getX();
y = Math.pow(r, 2) * keyPointP[0].getY() + 2 * k * r * keyPointP[1].getY() + Math.pow(k, 2) * keyPointP[2].getY();
g.drawOval((int) x, (int) y, 1, 1);// 畫圓的方式比下面注釋掉的畫線效果更好
// g.drawLine((int) x, (int) y, (int) x, (int) y);
}
}
// 三次貝塞爾曲線
if (keyPointNum == 4) {
double x, y;
g.setColor(Color.RED);
for (double k = t; k <= 1 + t; k += t) {
double r = 1 - k;
x = Math.pow(r, 3) * keyPointP[0].getX() + 3 * k * Math.pow(r, 2) * keyPointP[1].getX()
+ 3 * Math.pow(k, 2) * (1 - k) * keyPointP[2].getX() + Math.pow(k, 3) * keyPointP[3].getX();
y = Math.pow(r, 3) * keyPointP[0].getY() + 3 * k * Math.pow(r, 2) * keyPointP[1].getY()
+ 3 * Math.pow(k, 2) * (1 - k) * keyPointP[2].getY() + Math.pow(k, 3) * keyPointP[3].getY();
g.drawOval((int) x, (int) y, 1, 1);
}
}
}
class MouseAction extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
// 點選滑鼠左鍵
if (e.getButton() == MouseEvent.BUTTON1) {
if (keyPointNum < 4) {
double x = e.getX();
double y = e.getY();
keyPointP[keyPointNum] = new Point2D.Double(x, y);
keyPointE[keyPointNum] = new Ellipse2D.Double(x - 4, y - 4, 8, 8);
keyPointNum++;
repaint();
}
} // 點選滑鼠右鍵
else if (e.getButton() == MouseEvent.BUTTON3) {
flagShow = false;// 隐藏藍色輔助線,但并不能真正移除
repaint();
}
}
@Override
public void mousePressed(MouseEvent e) {
// 按下滑鼠,判斷是不是點選了關鍵點
for (int i = 0; i < keyPointNum; i++) {
if (keyPointE[i].contains((Point2D) e.getPoint())) {
keyPointID = i;
break;
} else {
keyPointID = -1;
}
}
}
}
class MouseMotion extends MouseMotionAdapter {
@Override
public void mouseDragged(MouseEvent e) {
// 滑鼠拖動關鍵點
if (keyPointID != -1) {
double x = e.getX();
double y = e.getY();
keyPointP[keyPointID] = new Point2D.Double(x, y);
keyPointE[keyPointID] = new Ellipse2D.Double(x - 4, y - 4, 8, 8);
repaint();
}
}
}
}
public TestBezier() {
JFrame f = new JFrame();
f.setTitle("Bezier Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(800, 600);
f.setLocationRelativeTo(null);
keyPointNum = 0;
keyPointP = new Point2D[4];
keyPointE = new Ellipse2D.Double[4];
BezierPanel bezierPanel = new BezierPanel();
bezierPanel.setPreferredSize(new Dimension(800, 600));
bezierPanel.setBackground(Color.WHITE);
f.setContentPane(bezierPanel);
f.setVisible(true);
}
public static void main(String[] agrs) {
new TestBezier();
}
}