Как нарисовать сплайн c PyQt5.QPainter?
Есть вот такая программа, на данный момент представляющая из себя простой холст, на котором должен отрисовываться сплайн:
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen, QPixmap, QColor
from PyQt5.QtWidgets import (QStyleFactory, QWidget, QLabel,
QLineEdit, QApplication, QMainWindow, QVBoxLayout)
class Canvas(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.pixmap = QPixmap()
self.setPixmap(self.pixmap)
self.arr = []
self.flag = False
self.installEventFilter(self)
def paintEvent(self, event):
super().paintEvent(event)
self.qp = QPainter(self.pixmap)
self.qp.setRenderHint(QPainter.Antialiasing)
self.qp.begin(self)
pen = QPen(Qt.black, 1, Qt.SolidLine)
#self.qp.drawPoint(event.pos().x(), event.pos().y())
self.qp.setPen(pen)
if self.flag:
self.drawSpline(self.qp, self.arr)
self.qp.end()
def drawSpline(self, qp, array):
L = [[0, 0], [0, 0], [0, 0], [0, 0]]
dt = 0.004
t = 0
term = 1 + dt / 2
Ppred = array[0]
Pt = array[0]
Pv1X = 4 * (array[1][0] - array[0][0])
Pv1Y = 4 * (array[1][1] - array[0][1])
Pv2X = 4 * (array[3][0] - array[2][0])
Pv2Y = 4 * (array[3][1] - array[2][1])
L[0][0] = 2 * array[0][0] - 2 * array[3][0] + Pv1X + Pv2X
L[0][1] = 2 * array[0][1] - 2 * array[3][1] + Pv1Y + Pv2Y
L[1][0] = -3 * array[0][0] + 3 * array[3][0] - 2 * Pv1X - Pv2X
L[1][1] = -3 * array[0][1] + 3 * array[3][1] - 2 * Pv1Y - Pv2Y
L[2][0] = Pv1X
L[2][1] = Pv1Y
L[3][0] = array[0][0]
L[3][1] = array[0][1]
while t < term:
xt = ((L[0][0] * t + L[1][0]) * t + L[2][0]) * t + L[3][0]
yt = ((L[0][1] * t + L[1][1]) * t + L[2][1]) * t + L[3][1]
Pt[0] = round(xt)
Pt[1] = round(yt)
print(f'{xt}, {yt}') # после первой же итерации, числа перестают округляться
qp.drawLine(Ppred[0], Ppred[1], Pt[0], Pt[1])
Ppred = Pt
t = t + dt
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == QtCore.Qt.LeftButton:
pointPosition = [event.pos().x(), event.pos().y()]
self.arr.append(pointPosition)
elif event.button() == QtCore.Qt.RightButton:
self.flag = True
return super().eventFilter(source, event)
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.initUI()
def initUI(self):
self.resize(1000, 950)
self.canvas = Canvas()
w = QWidget()
l = QVBoxLayout()
w.setLayout(l)
l.addWidget(self.canvas)
self.setCentralWidget(w)
if __name__ == '__main__':
app = QApplication([])
app.setStyle(QStyleFactory.create('Fusion'))
application = MainWindow()
application.setWindowTitle("Drawning")
application.show()
sys.exit(app.exec_())
Есть несколько проблем в этом коде, которые я никак не могу решить:
- сплайн рисуется только тогда, когда я как-либо изменяю размер окна;
- сплайн рисуется прерывистой линией, а по идее должен был сплошной.
В чем суть метода drawSpline:
происходит расчет координат точек, которые в последствии соединяются и образовывают сплайн. Итоговая форма сплайн получается верной, однако выглядит не очень)
После ресайза окна по каким-то причинам видимо paintEvent вызывается снова и рисует уже вот это:
Как изменить поведение paintEvent и как добиться более красивого отображения сплайна?
Ответы (1 шт):
Автор решения: Alexander Chernin
→ Ссылка
Это не точки, а ваши отрезки сплайна, но обо всем по-порядку:
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPainter, QPen, QColor
from PyQt5.QtWidgets import (QStyleFactory, QWidget, QApplication, QMainWindow, QVBoxLayout)
# Класс Canvas наследуем от QWidget
# на котором можно прекрасно порисовать.
# Не нужны QLabel и QPixmap
class Canvas(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
# Массив точек
self.arr = []
def paintEvent(self, event):
# не нужно делать паинтер объектом класса (self.qp)
qp = QPainter()
qp.begin(self)
qp.setRenderHint(QPainter.Antialiasing)
# По умолчанию ручка черная, давайте установим синию
pen = QPen(Qt.blue)
qp.setPen(pen)
# Если массив содержит достаточно точек рассчитываем и рисуем сплайн
if len(self.arr) >= 4:
self.drawSpline(qp)
qp.end()
def drawSpline(self, qp):
# Создаем ссылку array на наш массив self.array
array = self.arr
L = [[0, 0], [0, 0], [0, 0], [0, 0]]
dt = 0.004
t = 0
term = 1 + dt / 2
Ppred = array[0]
Pt = [0, 0]
Pv1X = 4 * (array[1][0] - array[0][0])
Pv1Y = 4 * (array[1][1] - array[0][1])
Pv2X = 4 * (array[3][0] - array[2][0])
Pv2Y = 4 * (array[3][1] - array[2][1])
L[0][0] = 2 * array[0][0] - 2 * array[3][0] + Pv1X + Pv2X
L[0][1] = 2 * array[0][1] - 2 * array[3][1] + Pv1Y + Pv2Y
L[1][0] = -3 * array[0][0] + 3 * array[3][0] - 2 * Pv1X - Pv2X
L[1][1] = -3 * array[0][1] + 3 * array[3][1] - 2 * Pv1Y - Pv2Y
L[2][0] = Pv1X
L[2][1] = Pv1Y
L[3][0] = array[0][0]
L[3][1] = array[0][1]
while t < term:
xt = ((L[0][0] * t + L[1][0]) * t + L[2][0]) * t + L[3][0]
yt = ((L[0][1] * t + L[1][1]) * t + L[2][1]) * t + L[3][1]
Pt[0] = round(xt)
Pt[1] = round(yt)
print(f'{Ppred} - {Pt}') # Не xt и yt надо выводить :)
qp.drawLine(Ppred[0], Ppred[1], Pt[0], Pt[1])
# Если просто присвоить Ppred = Pt вы создаете ссылку на Pt
# а не копию (и когда внесете новые данные в Pt - там где вы округляете,
# то и Ppred будет работать с ними же),
# поэтому у вас получаются точки на графике, а не линии,
# и поэтому, чтобы этого не было, надо создать
# копию текущей точки
Ppred = Pt.copy()
t = t + dt
def mousePressEvent(self, event):
super().mousePressEvent(event)
# Нам нужны четыре точки, поэтому если длина массива уже равна 4,
# то вынимаем из него первую
if len(self.arr) == 4:
self.arr.pop(0)
# добавляем новую точку в массив
# и, в соответствии, с предыдущим условием длина
# массива всегда будет <= 4-м
self.arr.append([event.pos().x(), event.pos().y()])
# вызываем перерисовку канвы
self.update()
if __name__ == '__main__':
app = QApplication([])
app.setStyle(QStyleFactory.create('Fusion'))
application = Canvas()
application.setWindowTitle("Drawning")
application.show()
sys.exit(app.exec_())
Пример рабочий. Скорее запускайте красотень!

