在预处理的svg xml文档上设置QGraphicsSvgItem的渲染器非常慢

时间:2015-06-22 14:53:12

标签: c++ xml performance qt svg

我正在使用QGraphicsSvgItem子类,它从文件中读取一些内容,将内容放入QDomDocument,进行一些初始处理,然后将处理后的DOM设置到渲染器上。

在程序处理期间,预处理DOM的副本需要进行其他更改,因此DOM存储在类中。更改后,DOM将放置在渲染器上。

class MyGraphicsSvgItem : public QGraphicsSvgItem
{
public:
    MyGraphicsSvgItem (QGraphicsItem *parent = 0):
        QGraphicsSvgItem(parent),
        _svgXML() {}
    ~MyGraphicsSvgItem () { delete renderer(); }
    void CheckAndChangeSomeThings() {}
    void LoadStuff (QString fileName)
    {
        QFile file(fileName);
        file.open(QFile::ReadOnly | QFile::Text);
        QTextStream in(&file);
        QString svgContent = in.readAll();
        file.close();
        _svgXML.setContent(svgContent);
        CheckAndChangeSomeThings();   // this modifies _svgXML
        QByteArray _data = _svgXML.toByteArray();
        setSharedRenderer(new QSvgRenderer(_data));  // very slow
    }
    void ChangeThingslater();
    void ChangeSomeThingslater() 
    {
        ChangeThingslater(); // this modifies _svgXML
        renderer()->load(_svgXML.toByteArray());  // very slow - no file involved
    }
protected:
    QDomDocument _svgXML;
};

在将DOM分配给渲染器的行中似乎存在显着的缓慢处理。

QByteArray _data = _svgXML.toByteArray();
setSharedRenderer(new QSvgRenderer(_data));

如果我跳过DOM处理 - 如果我将渲染器设置为文件 - 代码会大大加快:

保留所有代码,但替换

setSharedRenderer(new QSvgRenderer(_data));   // VERY SLOW 

setSharedRenderer(new QSvgRenderer(fileName));   // FAST

所以似乎瓶颈是从QByteArray加载svg渲染器。

我寻找替代方案......文档中没有提及性能

  

QSvgRenderer :: QSvgRenderer(const QString& filename,QObject * parent   = 0)
  使用给定父级构造一个新的渲染器,并使用指定的文件名加载SVG文件的内容。

     

QSvgRenderer :: QSvgRenderer(const QByteArray& contents,QObject *   parent = 0)
  使用给定的父级和装载构造新的渲染器   来自内容指定的字节数组的SVG数据。

     

QSvgRenderer :: QSvgRenderer(QXmlStreamReader *内容,QObject *   parent = 0)
  使用给定的父级和装载构造新的渲染器   SVG数据使用内容指定的流阅读器。

查看QXmlStreamReader Class,我发现它的构造函数是相似的!另外,它说

  

在某些情况下,它可能是一种更快速,更方便的替代方案,可用于否则将使用DOM树的应用程序

我好像是在圈子里,虽然在DOM中已经有一个格式良好的xml,但似乎我无法利用它!

我可以选择从预处理的DOM加载渲染器,还是采用不同的方式预处理xml - 使用渲染器可以快速读取的除DOM之外的其他东西?

qt 4.8。 C ++

1 个答案:

答案 0 :(得分:2)

您可以使用QtConcurrent::run在线程队列上执行的worker方法中执行所有DOM处理。

您可以直接在商品中使用QSvgRenderer。在worker方法中初始化它,并从QByteArray而不是文件加载。然后,您可以将渲染器传递给GUI线程,并通过在QGraphicsSvgItem上设置它来使用它来渲染图形项。

注意事项:

  1. 由于您在工作线程中创建渲染器,因此必须在工作线程中使用它后将其移动到空线程。相反,一旦GUI线程收到GUI线程,就必须将其移动到GUI线程。

    回想一下moveToThread只能从对象的当前线程或thread() == 0的任何线程调用。

  2. 在Qt 4.8中,QGraphicsSvgItem::setSharedRenderer中存在一个错误:它无法将渲染器的repaintNeeded信号正确连接到其update方法。解决此问题的方法是手动将信号连接到您自己的更新槽。

  3. 这将阻止GUI被长时间处理阻止。

    从项目中重新进入事件循环是一个错误的来源,从设计的角度来看,这只是一个非常糟糕的主意。请改用文件对话框的非阻塞API。

    以下是演示此技术的示例。在加载/处理项目时,它还会显示一个小的微调器。为此目的存在模拟延迟。

    #include <QGraphicsView>
    #include <QGraphicsSvgItem>
    #include <QGraphicsSceneMouseEvent>
    #include <QFileDialog>
    #include <QSvgRenderer>
    #include <QDomDocument>
    #include <QtConcurrentRun>
    #include <QFutureWatcher>
    #include <QThread>
    #include <QApplication>
    
    struct Thread : public QThread { using QThread::sleep; }; // Needed for Qt 4 only
    
    class RendererGenerator {
       QString m_fileName;
       void process(QDomDocument &) {
          Thread::sleep(3); /* let's pretend we process the DOM for a long time here */
       }
       QByteArray generate(const QByteArray & data) {
          QDomDocument dom;
          dom.setContent(data);
          process(dom);
          return dom.toByteArray();
       }
    public:
       typedef QSvgRenderer * result_type;
       RendererGenerator(const QString & fileName) : m_fileName(fileName) {}
       QSvgRenderer * operator()() {
          QFile file(m_fileName);
          if (file.open(QIODevice::ReadOnly)) {
             QByteArray data = file.readAll();
             QScopedPointer<QSvgRenderer> renderer(new QSvgRenderer);
             renderer->load(generate(data));
             renderer->moveToThread(0);
             return renderer.take();
          }
          return 0;
       }
    };
    
    class UserSvgItem : public QGraphicsSvgItem {
       Q_OBJECT
       QSvgRenderer m_spinRenderer, * m_lastRenderer;
       QScopedPointer<QSvgRenderer> m_renderer;
       QFuture<QSvgRenderer*> m_future;
       QFutureWatcher<QSvgRenderer*> m_watcher;
       QGraphicsView * aView() const {
          QList<QGraphicsView*> views = scene()->views();
          return views.isEmpty() ? 0 : views.first();
       }
       Q_SLOT void update() { QGraphicsSvgItem::update(); }
       void mousePressEvent(QGraphicsSceneMouseEvent * event) {
          if (event->button() == Qt::LeftButton) askForFile();
       }
       void setRenderer(QSvgRenderer * renderer) {
          if (m_lastRenderer) disconnect(m_lastRenderer, SIGNAL(repaintNeeded()), this, SLOT(update()));
          setSharedRenderer(renderer);
          m_lastRenderer = renderer;
          connect(renderer, SIGNAL(repaintNeeded()), SLOT(update()));
          if (aView()) aView()->centerOn(this);
       }
       void askForFile() {
          QFileDialog * dialog = new QFileDialog(aView());
          connect(dialog, SIGNAL(fileSelected(QString)), SLOT(loadFile(QString)));
          dialog->setAcceptMode(QFileDialog::AcceptOpen);
          dialog->setAttribute(Qt::WA_DeleteOnClose);
          dialog->show();
       }
       Q_SLOT void loadFile(const QString & file) {
          if (m_future.isRunning()) return;
          setRenderer(&m_spinRenderer);
          m_future = QtConcurrent::run(RendererGenerator(file));
          m_watcher.setFuture(m_future);
       }
       Q_SLOT void rendererReady() {
          m_renderer.reset(m_future.result());
          m_renderer->moveToThread(thread());
          setRenderer(m_renderer.data());
       }
    public:
       UserSvgItem(const QString & fileName = QString(), QGraphicsItem *parent = 0) :
          QGraphicsSvgItem(fileName, parent), m_lastRenderer(0) {
          connect(&m_watcher, SIGNAL(finished()), SLOT(rendererReady()));
          setFlags(QGraphicsItem::ItemClipsToShape);
          setCacheMode(QGraphicsItem::NoCache);
       }
       void setWaitAnimation(const QByteArray & data) { m_spinRenderer.load(data); }
    };
    
    namespace {
       const char svgCircle[] =
          "<svg height=\"100\" width=\"100\"><circle cx=\"50\" cy=\"50\" r=\"40\" stroke=\"black\" stroke-width=\"3\" fill=\"red\" /></svg>";
       const char svgRectangle[] =
          "<svg width=\"400\" height=\"110\"><rect width=\"300\" height=\"100\" style=\"fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)\"></svg>";
       const char svgThrobber[] =
          "<svg width=\"16\" height=\"16\" viewBox=\"0 0 300 300\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\"><path d=\"M 150,0 a 150,150 0 0,1 106.066,256.066 l -35.355,-35.355 a -100,-100 0 0,0 -70.711,-170.711 z\" fill=\"#3d7fe6\"><animateTransform attributeName=\"transform\" attributeType=\"XML\" type=\"rotate\" from=\"0 150 150\" to=\"360 150 150\" begin=\"0s\" dur=\"1s\" fill=\"freeze\" repeatCount=\"indefinite\" /></path></svg>";
    
       void write(const char * str, const QString & fileName) {
          QFile out(fileName);
          if (out.open(QIODevice::WriteOnly | QIODevice::Truncate)) out.write(str);
       }
    }
    
    int main(int argc, char *argv[])
    {
       QApplication app(argc, argv);
       write(svgRectangle, "rectangle.svg"); // Put svg resources into the working directory
       write(svgCircle, "circle.svg");
    
       QGraphicsScene scene;
       UserSvgItem item("circle.svg");
       QGraphicsView view(&scene);
       scene.addItem(&item);
       item.setWaitAnimation(QByteArray::fromRawData(svgThrobber, sizeof(svgThrobber)-1));
       view.show();
    
       return app.exec();
    }
    
    #include "main.moc"