天天看點

用Java實作可互動的貝塞爾曲線(Bezier curve)

關于貝塞爾曲線的詳細數學原理及公式可參考:

貝塞爾曲線_百度百科

我們來談談貝塞爾曲線

 本文給出了一種用Java實作貝塞爾曲線的方法,并且可以用滑鼠拖動改變錨點。

效果示範圖:

用Java實作可互動的貝塞爾曲線(Bezier curve)

這裡引用百度百科給出的公式:

二次方公式
用Java實作可互動的貝塞爾曲線(Bezier curve)
三次方公式
用Java實作可互動的貝塞爾曲線(Bezier curve)

核心算法代碼:

// 二次貝塞爾曲線
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();
	}
}