QGraphicsSvgItem crash when rendering into pixmap
-
I'm experiencing a crash I don't quite understand when using a
QGraphicsSvgItem
.The application allows the user to select an item from a
QTreeView
and dragging it into theQGraphicsScene
. Quite a while back, I implemented a function to render aQGraphicsItem
into aQPixmap
outside aQGraphicsScene
. The reason for this is simple: When the user drags an item from the item library tree, I want to render the item into aQPixmap
and then set that pixmap inQDrag
. This way, when the user starts dragging, he already sees the visual representation of the item and it will just "drop" into the scene. This provides a more natural feeling and greatly increases the precision of where the item will be dropped at in the scene.To achieve this, I wrote a function to render a
QGraphicsItem
into aQPixmap
quite a few years ago: https://blog.insane.engineer/post/qgraphicsitem_to_qpixmap/
The function looks like this:QPixmap Item::toPixmap(QPointF& hotSpot, qreal scale) { // Retrieve the bounding rect QRectF rectF = boundingRect(); rectF = rectF.united(childrenBoundingRect()); // Adjust the rectangle as the QPixmap doesn't handle negative coordinates rectF.setWidth(rectF.width() - rectF.x()); rectF.setHeight(rectF.height() - rectF.y()); const QRect& rect = rectF.toRect(); if (rect.isNull() || !rect.isValid()) return QPixmap(); // Provide the hot spot hotSpot = -rectF.topLeft(); // Create the pixmap QPixmap pixmap(rect.size() * scale); pixmap.fill(Qt::transparent); // Render QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, _settings.antialiasing); painter.setRenderHint(QPainter::TextAntialiasing, _settings.antialiasing); painter.scale(scale, scale); painter.translate(hotSpot); paint(&painter, nullptr, nullptr); for (QGraphicsItem* child : childItems()) { if (!child) continue; painter.save(); painter.translate(child->pos()); child->paint(&painter, nullptr, nullptr); painter.restore(); } painter.end(); return pixmap; }
The only "strange" part in this function is that we have to render each child manually as we're rendering outside of a
QGraphicsScene
. This is explained in more detail in the linked blog post.So far, this function hasn't failed me. However, now that I added a
QGraphicsSvgItem
as a child to one of the items, the function crashes the application.
The crash occurs inside theQGraphicsSvgItem::paint()
function.I had a look at the
QGraphicsSvgItem::paint()
implementation (Qt5.15.5
) in hopes of getting a clue on this:void QGraphicsSvgItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // Q_UNUSED(option); Q_UNUSED(widget); Q_D(QGraphicsSvgItem); if (!d->renderer->isValid()) return; if (d->elemId.isEmpty()) d->renderer->render(painter, d->boundingRect); else d->renderer->render(painter, d->elemId, d->boundingRect); if (option->state & QStyle::State_Selected) qt_graphicsItem_highlightSelected(this, painter, option); }
But nothing stood out. The
d->renderer
pointer seems to be initialized in the private implementation.Does anybody have a clue what is going on here and how I can prevent that crash?
Rendering the item where I added theQGraphicsSvgItem
as a child inside of aQGraphicsScene
works just fine. -
@Joel-Bodenmann
It would be helpful if you have Qt 5.15 compiled from source and you let it "crash" under the debugger? -
Hi,
How are you creating the new item on the scene ?
-
Even when you find the bug inside Qt, you may not be able to fix it. But you can file a bug or try a newer version of Qt. How about create QGraphicsItem with svg image? Instead of using QGraphicsSvgItem. Whenever the size of image is changed, simply create it from your svg file and the resolution of your image will not be affected. .
-
@JonB said in QGraphicsSvgItem crash when rendering into pixmap:
It would be helpful if you have Qt 5.15 compiled from source and you let it "crash" under the debugger?
That is true. However, that would come with quite a sizable time investment which I'd like to avoid in case this is a "common issue" or something else that someone more knowledgeable than me can point me towards :)
@SGaist said in QGraphicsSvgItem crash when rendering into pixmap:
How are you creating the new item on the scene ?
The
QGraphicsSvgItem
is simply a member of anotherQGraphicsObject
. Roughly like this:class foo : public QGraphicsObject { public: void set_icon_path(const QString& path) { if (m_svg_item) delete m_svg_item; m_icon_filepath = filepath; if (m_icon_filepath.isEmpty() || !QFileInfo::exists(filepath)) return; m_svg_item = new QGraphicsSvgItem(m_icon_filepath, this); m_svg_item->setPos(ICON_PADDING, ICON_PADDING); } private: QGraphicsSvgItem* m_svg_item = nullptr; QString m_icon_filepath; };
That's pretty much everything related to the
QGraphicsSvgItem
in my code base. I simply create the item and set the parent accordingly in the constructor.@JoeCFD said in QGraphicsSvgItem crash when rendering into pixmap:
Even when you find the bug inside Qt, you may not be able to fix it. But you can file a bug or try a newer version of Qt. How about create QGraphicsItem with svg image? Instead of using QGraphicsSvgItem. Whenever the size of image is changed, simply create it from your svg file and the resolution of your image will not be affected. .
I'm not necessarily expecting a bug in Qt. I started looking at the
QGraphicsSvgItem
implementation in hopes of figuring out whether I am missing something crucial that I also missed when reading the docs.
Your workaround of course makes sense but I'd much rather not do that. After all,QGraphicsSvgItem
is working well within theQGraphicsScene
. My issue is solely related to rendering it outside of a scene. -
One maybe silly question... Why do you need that class at all ? It seems to be a wrapper that reduces the QGraphicsSvgItem API.
-
@SGaist said in QGraphicsSvgItem crash when rendering into pixmap:
One maybe silly question... Why do you need that class at all ? It seems to be a wrapper that reduces the QGraphicsSvgItem API.
Not a silly question by any means. The
foo
class above is just an excerpt that shows everything related to the handling of theQGraphicsSvgItem
. In the actual application, the classfoo
first of all isn't namedfoo
and secondly contains a whole lot more code (which is 100% unrelated to the SVG item hence not shown here) :) -
Ok, then another comprehension question. Is your goal to update the QDrag pixmap while you are moving over the scene ?
-
@SGaist said in QGraphicsSvgItem crash when rendering into pixmap:
Is your goal to update the QDrag pixmap while you are moving over the scene ?
Nope. The
QDrag
pixmap gets generated when the drag starts (inQTreeView::startDrag()
) and never changes afterwards. -
In that case, why not simply load the file in a QPixmap ? The QtSvg module provides an image format plugin so there's no need for that item.
-
Looking at these questions I think it is reasonable that I provide some background information not given in the original post as I deemed it "out of scope" - so where we go :)
I have an application using the Qt Graphics Framework. In that application, a user can create a flowchart-like diagram. Here's a screenshot of the application:
This is done using the open-source QSchematic library (disclaimer: I'm the author).In the screenshot above, the blocks with the blue header and the white body is based on the
QSchematic::Node
class (which itself inherits fromQGraphicsObject
). Each node can have zero or more connectors. A connector (QSchematic::Connector
) inherits fromQGraphicsItem
. These connectors are children of theQSchematic::Node
. Nothing special here.Those nodes can also have an icon. In the screenshot above, only the top-left node shows an icon. As of now, I simply keep a
QIcon
inside my node class and paint that manually in the node'spaint()
override. The problem here is that when the user zooms in, the icons don't look pretty. The icons themselves (the resources/assets) are SVGs but as I load them into aQIcon
they get rasterized and end up looking unpleasant when zooming into the scene.
To solve this issue, I decided to add another child item to the node class - the aforementionedQGraphicsSvgItem
.As mentioned in the opening post the
QGraphicsSvgItem
works just fine inside the scene. The part where my application crashes is when I render a preview of the node using thetoPixmap()
function shown in my opening post.
The reason why I am using thistoPixmap()
function is to render the node before it actually becomes part of the scene. There is an item library built around aQTreeView
from which the user can drag an node and drop it onto the scene/view. To make things look nice and to provide the user with a visual reference of where the block will be placed, I render the block into aQPixmap
and assign that to theQDrag
object. Here is a video of that in action: https://www.veed.io/view/4d2b2da8-3016-4b26-b55f-ea27db793f98The summary of this is still: I have a custom graphics item composed of several child items, everything works fine inside the scene but when I render it outside the scene using the
toPixmap()
function shown in the opening post, the application crashes onceQGraphicsSvgItem::paint()
is called. The crash is caused by a seg fault.@SGaist said in QGraphicsSvgItem crash when rendering into pixmap:
In that case, why not simply load the file in a QPixmap ? The QtSvg module provides an image format plugin so there's no need for that item.
I wasn't aware that
QPixmap
can gain SVG support viaQtSVG
- that's good intel, thank you!
However, would this still raster the image? Or can I "assign" an SVG image to aQPixmap
, render theQPixmap
in aQGraphicsItem
and it won't look "pixelated" when the user zooms in?
The reason why I choiceQGraphicsSvgItem
is two fold: I didn't know thatQPixmap
can receive SVG support and it just handles all the scaling and stuff automatically. -
To show the SVG on the scene, the item is the right tool.
I would simply use the pixmap for the drag image rendering. In case the result is not what you expect, there's also the QSvgRenderer class that might be of interest. -
It has been a while but I'm an advocate for posting the solution even if a long time has passed...
The problem was that in
QSchematic::Items::Item::toPixmap()
(snipped shown in the opening post here) where I render theQGraphicsItem
into aQPixmap
I passednullptr
for theQStyleOptionGraphicsItem
parameter.
When I create a default-constructedQStyleOptionGraphicsItem
and pass a pointer to that instead everything works as expected.I assume (!) that
QGraphicsSvgItem
requiresQStyleOptionGraphicsItem
to be non-null.QSchematic upstream commits fixing this:
-
J Joel Bodenmann has marked this topic as solved on
-
@Joel-Bodenmann said in QGraphicsSvgItem crash when rendering into pixmap:
I assume (!) that
QGraphicsSvgItem
requiresQStyleOptionGraphicsItem
to be non-null.That is correct. Beyond that, the documentation doesn't suggest that anything other than a valid object is expected.