设置渲染器的QGraphicsSvgItem预处理svg xml文档非常缓慢 [英] Set renderer of QGraphicsSvgItem on preprocessed svg xml document very slow

查看:949
本文介绍了设置渲染器的QGraphicsSvgItem预处理svg xml文档非常缓慢的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用 QGraphicsSvgItem 子类,从文件读取一些内容,将内容放入 QDomDocument 一些初始处理,然后将处理的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)); //非常慢
}
void ChangeThingslater();
void ChangeSomeThingslater()
{
ChangeThingslater(); // this modifies _svgXML
renderer() - > load(_svgXML.toByteArray()); //非常慢 - 没有文件涉及
}
protected:
QDomDocument _svgXML;
};

在将DOM分配给渲染器的行中似乎有一个明显缓慢的处理。 p>

  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)

使用给定的父构造一个新的渲染器并加载内容



QSvgRenderer :: QSvgRenderer(const QByteArray& contents,QObject *
parent = 0)

使用给定的父构造一个新的渲染器,并从内容指定的字节数组中加载
SVG数据。



QSvgRenderer :: QSvgRenderer(QXmlStreamReader * contents ,QObject *
parent = 0)

使用给定的父构造一个新的渲染器,并使用由内容指定的流读取器加载
SVG数据。


查看 QXmlStreamReader类,我发现它的构造函数是相似的!此外,它表示


在某些情况下,它也可能是一个更快更方便的替代方案, tree


我似乎在圈子中,虽然已经在DOM中有一个良好的XML,似乎我不能优点:



我的替代方法是从预处理的DOM加载渲染器,或者使用某种不同的方式预处理xml除了DOM之外,渲染器可以快速读取?



qt 4.8。可以使用在线程队列上执行的工作方法中执行所有DOM处理。




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



注意:


  1. 由于您在工作线程中创建渲染器,因此必须将其移动到null线程在工作线程中使用它。



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


  2. 在Qt 4.8中,有一个错误QGraphicsSvgItem :: setSharedRenderer :它没有正确连接渲染器的 repaintNeeded 信号到其更新方法。


这将阻止GUI被阻止通过长处理。



从项目内部重新进入事件循环是一个bug的来源,从设计的角度来看是一个非常糟糕的主意。请使用文件对话框的非阻塞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; }; //仅需要Qt 4

类RendererGenerator {
QString m_fileName;
void process(QDomDocument&){
Thread :: sleep(3); / *让我们假装我们在这里长时间处理DOM * /
}
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
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
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 \至= \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); //将svg资源放入工作目录
write(svgCircle,circle.svg);

QGraphicsScene场景;
UserSvgItem item(circle.svg);
QGraphicsView view(& scene);
scene.addItem(& item);
item.setWaitAnimation(QByteArray :: fromRawData(svgThrobber,sizeof(svgThrobber)-1));
view.show();

return app.exec();
}

#includemain.moc


I am using QGraphicsSvgItem subclass, that reads some content from a file, places content into a QDomDocument, does some initial processing, then sets the processed DOM onto a renderer.

During program processing, additional changes are required on a copy of pre-processed DOM, so the DOM is stored in class. After changes, the DOM is placed on renderer.

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;
};

There seems to be a significant slow processing during the lines that assign the DOM to the renderer.

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

If I skip the DOM processing - if I set the renderer to the file - the code speeds up considerably:

Leaving all the code in, but replacing

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

with

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

So it seems the bottleneck is loading the svg renderer from QByteArray.

I looked for alternatives... there is no mention of performance in documentation

QSvgRenderer::QSvgRenderer(const QString & filename, QObject * parent = 0)
Constructs a new renderer with the given parent and loads the contents of the SVG file with the specified filename.

QSvgRenderer::QSvgRenderer(const QByteArray & contents, QObject * parent = 0)
Constructs a new renderer with the given parent and loads the SVG data from the byte array specified by contents.

QSvgRenderer::QSvgRenderer(QXmlStreamReader * contents, QObject * parent = 0)
Constructs a new renderer with the given parent and loads the SVG data using the stream reader specified by contents.

Looking in QXmlStreamReader Class, I find that its constructors are similar ! In addition, it says

In some cases it might also be a faster and more convenient alternative for use in applications that would otherwise use a DOM tree

I seem to be going in circles, and even though having already a well formed xml in the DOM, it seems I cannot take advantage of it !

What are my alternatives, to either loading the renderer from the pre-processed DOM, or to a different way of pre-processing the xml - using something other than DOM that the renderer can read fast ?

qt 4.8. c++

解决方案

You can do all the DOM processing in a worker method that's executed on the thread queue using QtConcurrent::run.

You can use the QSvgRenderer directly in your item. Initialize it in the worker method, and load from QByteArray and not a file. You can then pass the renderer to the GUI thread and use it to render the graphics item by setting it on the QGraphicsSvgItem.

Caveats:

  1. Since you create the renderer in a worker thread, you must move it to a null thread after you've done using it in the worker thread. Conversely, you must move it to the GUI thread once it has been received by the GUI thread.

    Recall that moveToThread can only be called from the object's current thread, or any thread if thread() == 0.

  2. In Qt 4.8, there's a bug in QGraphicsSvgItem::setSharedRenderer: it doesn't properly connect the renderer's repaintNeeded signal to its update method. The work around this to connect the signal manually to your own update slot.

This will prevent the GUI from getting blocked by the long processing.

Reentering the event loop, as you do, from within the item is a source of bugs and just a very bad idea from the design standpoint. Use the non-blocking API of the file dialog instead.

Below is an example that demonstrates this technique. It also displays a small spinner when the item is being loaded/processed. There's a simulated delay for this purpose.

#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"

这篇关于设置渲染器的QGraphicsSvgItem预处理svg xml文档非常缓慢的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆