PyQt рисует QPixmap по QPainterPath
Я хочу рисовать шаблоны (предпочтительно QPixmap) вдоль QGraphicsPathItem внутри моего QGraphicsView. Это должно работать аналогично заполнению QBrush текстурой с использованиемQBrush.setTexture("example.png")
. Есть ли способ сделать это на QPen? Я не смог найти ничего связанного с этим в документации PyQt.
Текстуру нужно разместить так же, как на этом примере изображения.
Не имеет значения, сохраняет ли текстура собственную ширину или масштабируется до ширины QPen.
Есть ли способ обхода этого? Я думал об использовании QTransform для преобразования QPixmap в форму желаемого QGraphicsPathItem.
2 ответа
Короче говоря, нет простого способа добиться этого, особенно если требуется использовать QPixmap.
Это не значит, что это совершенно невозможно, но сделать это, используя общие возможности Qt, довольно сложно.
По сути, вы хотите добиться «наложения текстур». Даже если это делается на двумерной поверхности (что можно легко сделать с помощью простых преобразований), то же самое для криволинейных путей требует гораздо более сложных вычислений. Хотя простые кривые (например, круги и эллипсы) можно легко получить с помощью относительно простых матричных преобразований, на самом деле эти кривые являются упрощением чрезвычайно сложных математических задач, которые в конечном итоге возникнут в случае квадратичных или кубических кривых.
См. соответствующий вопрос. Как спроецировать прямоугольную текстуру в область изогнутой формы?, в котором ОП объясняет, что этого можно достичь с помощью сплайновых преобразований тонкой пластины. Qt не предоставляет средств для этого, поэтому вам придется полагаться на внешние библиотеки: похоже, что модуль scikit-image может предоставить такую функцию (см. проблему 2429 , но это все еще делается из Python, что может привести к очень медленная обработка сложных путей, поскольку каждый образец каждой кривой необходимо правильно сопоставить.
Тем не менее, существует возможность, если шаблон прост, как тот, о котором идет речь, все это можно упростить до одномерной структуры (линейной последовательности цветов с заданной шириной): данный шаблон можно затем интерпретировать. в виде пунктирной линии, чередующей два цвета, каждый из которых имеет ширину 15 пикселей.
В этом случае хитрость заключается в том, чтобы правильно использовать возможности QPen для отображения «штрихов», что значительно упрощает весь подход, даже если и не является полностью надежным. Просто помните, что узоры штрихов основаны на ширине пера, поэтому их фактическую длину необходимо разделить на нее.
Концепция основана на основном QGraphicsPathItem, который использует штриховой шаблон для рисования своих основных сегментов («первый цвет» шаблона) и переопределяет его
Обратите внимание, что этот подход далек от совершенства, поскольку рисование выполняется слоями, а сложные контуры могут создавать графические артефакты, поскольку наложенные «пунктирные линии» всегда рисуются одна над другой.
Тем не менее, он может обеспечить приемлемые результаты для простых случаев, и некоторый уровень частичного улучшения все еще возможен.
Следующий пример класса принимает несколько возможных аргументов: кроме аргументов по умолчанию, которые требует QGraphicsPathItem, он также принимает итерацию для «шаблона» и «ширины пера», что является фундаментальным для обеспечения единообразия рисования.
«Узор» состоит из пар цветов и экстентов, указывающих «размер» каждого цвета в результате рисования.
Затем, когда установлено более пары цветов/размеров, над основным контуром рисуются дополнительные «пунктирные пути».
class PatternPath(QGraphicsPathItem):
_pattern = None
def __init__(self, path=None, penWidth=5, pattern=None, parent=None):
super().__init__(path, parent)
self.penWidth = max(1, penWidth)
self.otherPens = []
self.setPattern(pattern)
def setPattern(self, pattern):
if not pattern:
pattern = [(Qt.black, 1)]
if self._pattern != pattern:
self._pattern = pattern
if len(pattern) == 1:
self.setPen(QPen(pattern[0], self.penWidth, cap=Qt.FlatCap))
return
patternSize = 0
for _, size in pattern:
patternSize += size
patternSize /= self.penWidth
self.otherPens.clear()
color, size = pattern[0]
size /= self.penWidth
basePen = QPen(color, self.penWidth, cap=Qt.FlatCap)
basePen.setDashPattern((size, patternSize - size))
self.setPen(basePen)
delta = size
for color, size in pattern[1:]:
size /= self.penWidth
pen = QPen(color, self.penWidth, cap=Qt.FlatCap)
pen.setDashPattern((size, patternSize - size))
pen.setDashOffset(delta)
delta += size
self.otherPens.append(pen)
def paint(self, qp, opt, widget=None):
super().paint(qp, opt, widget)
if self.otherPens:
path = self.path()
for pen in self.otherPens:
qp.setPen(pen)
qp.drawPath(path)
Вот простой пример, который случайным образом создает пути и шаблоны, чтобы показать результат:
from random import randrange, choice
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
pathFuncs = (
(QPainterPath.lineTo, 1),
(QPainterPath.quadTo, 2),
(QPainterPath.cubicTo, 3)
)
def createPath(count=5, rect=QRectF(0, 0, 500, 500)):
xRange = (rect.x(), rect.width())
yRange = (rect.y(), rect.height())
randPoint = lambda: QPointF(randrange(*xRange), randrange(*yRange))
path = QPainterPath(randPoint())
for i in range(count):
func, pointCount = choice(pathFuncs)
func(path, *(randPoint() for _ in range(pointCount)))
return path
def createPattern(count=2, colorSize=5):
colorData = []
for i in range(max(1, count)):
colorData.append((
QColor(randrange(255), randrange(255), randrange(255)),
colorSize
))
return colorData
class PatternPath(QGraphicsPathItem):
# as above
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
scene = QGraphicsScene()
view = QGraphicsView(scene)
view.setRenderHint(QPainter.Antialiasing)
pathItem = PatternPath(createPath(2), penWidth=25,
pattern=createPattern(4, colorSize=15))
scene.addItem(pathItem)
view.resize(600, 600)
view.show()
sys.exit(app.exec())
Вот возможный результат:
Как видите, в правом нижнем углу есть некоторые проблемы, вызванные наложенным рисунком, описанным выше. Это более понятно для более сложного пути:
На изображении выше ясно показаны проблемы, связанные с «многослойной» живописью.
Тем не менее, если рисуемые вами пути не так сложны, решение вполне приемлемо, учитывая его скорость и результаты.
Возможно, можно частично улучшить результат, разделив общий путь на более мелкие, но вам необходимо учитывать частичные смещения тире и потенциальные проблемы с альфа-каналами или стилями пера/заглавными буквами.
Что касается последнего пункта, как вы можете видеть, я всегда использовал
Я все еще не нашел подходящего решения. Но для людей, которым не требуется выравнивание шаблона вдоль пути, вы можете использовать этот фрагмент кода для своего QPen:
pen = QPen()
patternBrush = QBrush(QPixmap('patter.png'))
pen.setBrush(patternBrush)
pen.setWidth(5)
Он заполняет ваш QGraphicsPathItem следующим образом
Я надеюсь, что это поможет другим.