贝塞尔曲线就不介绍了,如果不会的话可以看看别人的文章,或者好好听许老师上课讲的课。主要就是使用 伯恩斯坦多项式 根据给定的控制顶点绘制曲线。
控制顶点(其实就是普通的点)定义如下。
class Point():
def __init__(self, x: int, y: int):
self.x = x
self.y = y
贝塞尔曲线定义如下
首先贝塞尔曲线需要多个控制顶点,因此使用self.points列表存储贝塞尔曲线的顶点
add_point和delete_point定义了贝塞尔曲线删除和增加顶点的方法(贝塞尔曲线至少要有两个控制顶点)
fun函数则计算了当t = u时,B(t)计算出的x,y坐标。
from point import Point
import math
class BezierCurve():
def __init__(self, points: list):
self.points = points
self.length = len(points)
def add_point(self, x, y):
t = Point(x, y)
self.points.append(t)
self.length = self.length + 1
def delete_point(self):
if self.length > 2:
self.points.pop()
self.length = self.length - 1
def fun(self, u: float):
# 定义组合数计算函数
def com(k: int, n: int):
return math.factorial(n) // (math.factorial(k) * math.factorial(n - k))
ans_x = 0
# print(ans_x)
for i in range(0, self.length):
# print(ans_x, "round", i)
ans_x = (ans_x + self.points[i].x *
com(i, self.length - 1) * (u ** i) * ((1 - u) ** (self.length - 1 - i)))
ans_y = 0
for i in range(0, self.length):
# print(ans_y)
ans_y = (ans_y + self.points[i].y *
com(i, self.length - 1) * (u ** i) * ((1 - u) ** (self.length -1 - i)))
return ans_x, ans_y
绘画模块定义如下
mouseMoveEvent根据鼠标和贝塞尔曲线顶点的距离判断鼠标点击时选择的是哪一个顶点。drawpoints函数绘制贝塞尔曲线上的点,根据这些点组成了曲线。(其实就是调用了上文贝塞尔曲线中定义的fun函数1000次,获得了当t=0,0.001,0.002,0.003…………0.999,1的值,并绘制出这些点。因为点足够密集就形成了曲线。
import sys,math
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from bezierCurve import BezierCurve
from point import Point
class Drawing(QWidget):
def __init__(self, parent=None):
super(Drawing, self).__init__(parent)
self.resize(700, 700)
self.setWindowTitle('绘制三阶贝塞尔曲线')
# 初始化贝塞尔曲线中的三个点
point1 = Point(100, 400)
point2 = Point(200, 200)
point3 = Point(500, 300)
points = [point1, point2, point3]
# 初始化贝塞尔曲线
self.curve = BezierCurve(points)
# 鼠标选中的点序号
self.n = 1
# def mousePressEvent(self, event):
# def lenth_of_two_points_nosqrt(x1, y1, x2, y2):
# return (x1 - x2) ** 2 + (y1 - y2) ** 2
# x = event.x
# y = event.y
# # 鼠标点击可以离点的最大距离的平方
# length = 100
# if lenth_of_two_points_nosqrt(x, y, self.curve.x1, self.curve.y1) < length:
# self.mouseMoveEvent(event)
# self.n = 1
# if lenth_of_two_points_nosqrt(x, y, self.curve.x2, self.curve.y2) < length:
# self.mouseMoveEvent(event)
# self.n = 2
# if lenth_of_two_points_nosqrt(x, y, self.curve.x3, self.curve.y3) < length:
# self.mouseMoveEvent(event)
# self.n = 3
# if lenth_of_two_points_nosqrt(x, y, self.curve.x4, self.curve.y4) < length:
# self.mouseMoveEvent(event)
# self.n = 4
def mouseMoveEvent(self, event):
def lenth_of_two_points_nosqrt(x1, y1, x2, y2):
# print(type(x1), type(y1), type(x2), type(y2))
return (x1 - x2) ** 2 + (y1 - y2) ** 2
x = event.x()
y = event.y()
print(x, y)
# 鼠标点击可以离点的最大距离的平方
length = 100
no_choose_flag = True
for i in range(0, self.curve.length):
print()
print("当是i=", i, "时,距离等于", lenth_of_two_points_nosqrt(x, y, self.curve.points[i].x, self.curve.points[i].y))
if lenth_of_two_points_nosqrt(x, y, self.curve.points[i].x, self.curve.points[i].y) < length:
# no_choose_flag = False
self.n = i
print(self.n)
# 由于cpu性能问题,如果设置无法选取会导致刷新速度跟不上鼠标移动速度,
# 最终超出鼠标离开点的最大距离,不再更新
# if no_choose_flag:
# return
x = event.x()
y = event.y()
# print(x, y)
for i in range(0, self.curve.length):
if(i == self.n):
self.curve.points[self.n].x = x
self.curve.points[self.n].y = y
self.paintEvent(event)
self.update()
def paintEvent(self, event):
qp = QPainter()
qp.begin(self)
self.drawPoints(qp)
qp.end()
def drawPoints(self, qp):
size = self.size()
# 设置绘制笔的大小
pen = QPen(Qt.blue)
pen.setWidth(10)
qp.setPen(pen)
# 绘制控制顶点
for i in range(0, self.curve.length):
# print(self.curve.points[i].x, self.curve.points[i].y)
qp.drawPoint(self.curve.points[i].x, self.curve.points[i].y)
# 设置第2条直线的画笔
pen.setColor(Qt.black) # 设置画笔颜色为蓝色
pen.setStyle(Qt.DashLine) # 设置画笔样式为由一些像素分割的短线
pen.setWidth(2) # 设置画笔宽度
qp.setPen(pen) # 设置画笔
for i in range(0, self.curve.length-1):
# 绘制直线
# print(self.curve.points[i].x, self.curve.points[i].y,
# self.curve.points[i+1].x, self.curve.points[i+1].y)
qp.drawLine(self.curve.points[i].x, self.curve.points[i].y,
self.curve.points[i+1].x, self.curve.points[i+1].y)
# 设置绘制笔的大小
pen = QPen(Qt.red)
pen.setWidth(2)
qp.setPen(pen)
for i in range(1000):
x, y = self.curve.fun(i/1000)
# print(x, y)
qp.drawPoint(x, y)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
drawing = Drawing()
drawing.show()
sys.exit(app.exec_())
主函数定义如下
增加了增加点和减少点的按钮,并调用了相应函数重新绘制。
import sys,math
from point import Point
from drawing_new import Drawing
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from ThreeOrderedBezierCurve import ThreeOrderedBezierCurve
class Stats():
def __init__(self):
self.window = QMainWindow()
self.window.resize(900, 900)
self.window.move(10, 10)
self.window.setWindowTitle('绘制贝塞尔曲线')
self.drawing = Drawing(self.window)
self.drawing.move(0, 0)
self.drawing.resize(900, 700)
# 增加一个点
self.button1 = QPushButton(self.window)
self.button1.setText("添加一个点")
self.button1.move(150, 700)
self.button1.clicked.connect(self.add_point)
# 减少一个点
self.button2 = QPushButton(self.window)
self.button2.setText("减少一个点")
self.button2.move(600, 700)
self.button2.clicked.connect(self.delete_point)
# # 增加一个点
# self.label1 = QLabel(self.window)
# pixmap = QPixmap('path_to_your_image.png') # 替换为你的图片路径
# self.label1.setPixmap(pixmap)
# self.label1.setAlignment(Qt.AlignCenter)
# self.label1.setScaledContents(True)
# self.label1.mousePressEvent = self.add_point
def add_point(self):
self.drawing.curve.add_point(self.drawing.curve.points[-1].x + 10, self.drawing.curve.points[-1].y + 10)
self.drawing.update()
def delete_point(self):
self.drawing.curve.delete_point()
self.drawing.update()
if __name__ == '__main__':
app = QApplication([])
stats = Stats()
stats.window.show()
app.exec_()
最终效果如下
总结:
贝塞尔曲线原理还是蛮简单的,但是做这个作业需要掌握一种能将曲线显示出来的技术(web,qt,pyqt之类的)希望我的文章能帮助到不是很了解这些技术的同学。
ps:我用的pyqt