设置渲染器的QGraphicsSvgItem预处理svg xml文档非常缓慢 [英] Set renderer of QGraphicsSvgItem on preprocessed svg xml document very slow
问题描述
我使用 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
上设置图形项来渲染图形项。
注意:
-
由于您在工作线程中创建渲染器,因此必须将其移动到null线程在工作线程中使用它。
调用
moveToThread
只能从对象的当前线程或任何线程调用thread()== 0
。 -
在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:
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 ifthread() == 0
.In Qt 4.8, there's a bug in
QGraphicsSvgItem::setSharedRenderer
: it doesn't properly connect the renderer'srepaintNeeded
signal to itsupdate
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屋!