问题描述
我有一个GUI显示行和与每一行对齐的文本,我对文本使用QGraphicsSimpleTextItem()。
问题:
如果QGraphicsSimpleTextItem()上没有使用Rotation方法,则缩放和平移(子类QGraphcisView)交互运行很快,但是如果将Rotation分配给这些项目,则文本缩放和平移交互将变得非常慢。
问题:
我有Line Profiler来查找在类中消耗更多时间的行,但是没有什么真正脱颖而出的,如下所示。有什么理由会发生这种情况吗?我该如何改善呢?
每次点击时间不会显示出很大的增加或减少,但是注释行#dict_Text [str(i)]。setRotation(ang [i])时,有关缩放和平移交互的用户体验却大不相同。
重现问题:
下面的代码重现了我遇到的麻烦,请按照原样运行该代码,您将有非常缓慢的缩放和平移交互,然后将dict_Text [str(i)]。setRotation(ang [ i]),缩放和平移交互将非常快。
代码:
from PyQt5 import QtWidgets,QtCore,QtGui
import numpy as np
import sys
print(QtCore.PYQT_VERSION_STR)
class GraphicsView(QtWidgets.QGraphicsView):
@profile
def __init__(self,scene,parent):
super(GraphicsView,self).__init__(scene,parent)
#Mouse Tracking
self.setMouseTracking(True)
#Zoom Anchor
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
#Antialiasing and indexing
self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.HighQualityAntialiasing | QtGui.QPainter.TextAntialiasing)
self.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
self.resetCachedContent()
scene.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
#Pan variable
self.pos_init_class = None
@profile
def mousepressEvent(self,event):
pos = self.mapToScene(event.pos())
#Mouse Pan
if event.button() == QtCore.Qt.MiddleButton:
self.pos_init_class = pos
super(GraphicsView,self).mousepressEvent(event)
@profile
def mouseReleaseEvent(self,event):
if self.pos_init_class and event.button() == QtCore.Qt.MiddleButton:
#Mouse Pan
self.pos_init_class = None
super(GraphicsView,self).mouseReleaseEvent(event)
@profile
def mouseMoveEvent(self,event):
if self.pos_init_class:
#Mouse Pan
delta = self.pos_init_class - self.mapToScene(event.pos())
r = self.mapToScene(self.viewport().rect()).boundingRect()
self.setSceneRect(r.translated(delta))
super(GraphicsView,self).mouseMoveEvent(event)
@profile
def wheelEvent(self,event):
#Mouse Zoom
if event.angleDelta().y() > 0:
self.scale(1.5,1.5)
else:
self.scale(1 / 1.5,1 / 1.5)
class Ui_MainWindow(object):
def __init__(self):
super(Ui_MainWindow,self).__init__()
def plt_plot(self):
#Create data set
size = 200
x = np.random.randint(0,high=1000,size=size,dtype=int)
y = np.random.randint(0,dtype=int)
ang = np.random.randint(1,high=360,dtype=int)
#Store Text in Dict
dict_Text = {}
for i in range(len(x)):
#Create Text Item
dict_Text[str(i)] = QtWidgets.QGraphicsSimpleTextItem()
#Set text
dict_Text[str(i)].setText('nn-mm \nL: 50.6 m \nD: 1500 mm')
#Set Pos
dict_Text[str(i)].setPos(x[i],y[i])
#Set rotation angle
dict_Text[str(i)].setRotation(ang[i])
#Add to Scene
self.graphicsView.scene().addItem(dict_Text[str(i)])
def setupUi(self,MainWindow):
#Central Widget
self.centralwidget = QtWidgets.QWidget(MainWindow)
MainWindow.setCentralWidget(self.centralwidget)
main_width,main_heigth = 1200,800
MainWindow.resize(main_width,main_heigth)
#Create GraphicsView and Scene
self.scene = QtWidgets.QGraphicsScene()
self.graphicsView = GraphicsView(scene=self.scene,parent=self.centralwidget)
#Set Geometry
self.graphicsView.setGeometry(QtCore.QRect(0,main_width,main_heigth))
#plot dummy data set
self.plt_plot()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
解决方法
尽管QGraphicsView框架文档指出它相当快,但这并不意味着它总是非常快。
渲染速度取决于许多因素,单个项目转换会大大降低整体性能。
请考虑所有项目的绘制是通过单个光栅绘制完成的(后端几乎是所有Qt自己的渲染:其优化虽然通常是好的,但并不完美) )。
对于每个具有单独变换的项目,画家将需要根据 that 变换进行绘画。
如果您有200个项目,每个项目都有自己的转换,则意味着要进行大量的计算。
注意:转换是matrix,可以转换绘画(这意味着所有都需要特殊和额外的计算)。
Qt转换相当标准:
- 翻译
- 规模
- shear
- [投影]
- 旋转(通过组合剪切和缩放来完成,因此很复杂)
- 透视(通过组合投影和缩放来完成)
然后,您必须添加以下事实:您不是在绘制简单的项目,而是在基于文本的项目。尽管Qt和底层系统提供了所有优化,但文本绘画仍需要大量的计算。
我不会深入探讨文本绘画的完成方式,但是您必须考虑多种方面;让我们考虑其中的很少:
- 每个字母由许多复杂的多边形组成(其中许多使用贝塞尔曲线)
- 每个字母具有不同的大小和间距,包括每个字母(和每个字母对)的间距,也称为字距调整
- 某些字体具有更高级的功能,例如ligature
- 甚至必须考虑简单的对齐方式,可能是根据system,widget甚至是text option的布局方向
- 很多其他东西...
考虑一下(它并不是完全像这样工作,但这只是为了示例):在您的文本中,您大约有20个可绘制的字符。
想象每个字符都是QPainterPath的一个新创建的实例,其中包含很多条线和贝塞尔曲线(几乎所有字符都如此)。大约有4000条具有自己曲线的路径,每条每次创建都会绘制它们。
然后,由于旋转,您还需要应用一个转换矩阵(如前所述, 剪切和平移)。
我需要指出,以上内容是文本绘制方式的 over 简化(因为Qt还部分依赖底层的系统字体渲染)。
那么,有解决方案吗?
好吧,“不是真的”和“并非总是”。
首先,不用滚动setSceneRect()
,可以通过滚动场景的内容来获得一些改进。这是通过设置一个(很大)更大的sceneRect并使用set<Orientation>ScrollBarPolicy
到ScrollBarAlwaysOff
隐藏滚动条,然后通过在滚动条值上设置增量位置来移动可见区域来完成的。移动滚动条只会导致视口的重新绘制,而setSceneRect()
还需要基于递归和滚动条大小的(递归)计算可见区域。
然后是OpenGL替代方案,它可能会改善performance:
为了准确,快速地将变换和效果应用于项目,“图形视图”的构建假设用户的硬件能够为浮点指令提供合理的性能。 [...] 因此,某些种类的效果可能会比某些设备上预期的要慢。通过在其他方面进行优化,可能可以弥补这种性能下降;例如,使用OpenGL渲染场景。
请参见OpenGL Rendering,但请注意,并非总是可以保证更好的性能。
最后,如果您需要显示许多单独的文本项,并且每个文本项都有自己的旋转方式,您必须期望性能会急剧下降。唯一可能的选择是尝试将这些文本项呈现为(更大)图像,然后使用QGraphicsPixmapItem,但是为了获得可靠的结果(由于基于位图的对象在转换时容易出现混叠),您需要使用更大的尺寸每个项目。