QGraphicsSimpleTextItem的setRotation方法缓慢的zommig和pannig交互

问题描述

我有一个GUI显示行和与每一行对齐的文本,我对文本使用QGraphicsSimpleTextItem()。

问题:

如果QGraphicsSimpleTextItem()上没有使用Rotation方法,则缩放和平移(子类QGraphcisView)交互运行很快,但是如果将Rotation分配给这些项目,则文本缩放和平移交互将变得非常慢。

问题:

我有Line Profiler来查找在类中消耗更多时间的行,但是没有什么真正脱颖而出的,如下所示。有什么理由会发生这种情况吗?我该如何改善呢?

设置文本旋转度(ang)

enter image description here

注释文本旋转线(ang)

enter image description here

每次点击时间不会显示出很大的增加或减少,但是注释行#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
  • 甚至必须考虑简单的对齐方式,可能是根据systemwidget甚至是text option的布局方向
  • 很多其他东西...

考虑一下(它并不是完全像这样工作,但这只是为了示例):在您的文本中,您大约有20个可绘制的字符。
想象每个字符都是QPainterPath的一个创建的实例,其中包含很多条线和贝塞尔曲线(几乎所有字符都如此)。大约有4000条具有自己曲线的路径,每条每次创建都会绘制它们。
然后,由于旋转,您还需要应用一个转换矩阵(如前所述, 剪切平移)。

我需要指出,以上内容是文本绘制方式的 over 简化(因为Qt还部分依赖底层的系统字体渲染)。

那么,有解决方案吗?
好吧,“不是真的”和“并非总是”。

首先,不用滚动setSceneRect(),可以通过滚动场景的内容来获得一些改进。这是通过设置一个(很大)更大的sceneRect并使用set<Orientation>ScrollBarPolicyScrollBarAlwaysOff隐藏滚动条,然后通过在滚动条值上设置增量位置来移动可见区域来完成的。移动滚动条只会导致视口的重新绘制,而setSceneRect()还需要基于递归滚动条大小的(递归)计算可见区域。

然后是OpenGL替代方案,它可能会改善performance

为了准确,快速地将变换和效果应用于项目,“图形视图”的构建假设用户的硬件能够为浮点指令提供合理的性能。 [...] 因此,某些种类的效果可能会比某些设备上预期的要慢。通过在其他方面进行优化,可能可以弥补这种性能下降;例如,使用OpenGL渲染场景。

请参见OpenGL Rendering,但请注意,并非总是可以保证更好的性能。

最后,如果您需要显示许多单独的文本项,并且每个文本项都有自己的旋转方式,您必须期望性能会急剧下降。唯一可能的选择是尝试将这些文本项呈现为(更大)图像,然后使用QGraphicsPixmapItem,但是为了获得可靠的结果(由于基于位图的对象在转换时容易出现混叠),您需要使用更大的尺寸每个项目。