2

I have a bunch of SVG files that I've loaded them (as QGraphicsSvgItem) into a QGraphicsScene for designing and everything is Ok, now I want to save the scene into another output SVG file (including all the Svg items) with QSvgGenerator with the code below, but when it is exporting SVG items turn into images in the output file and their vectors are not anymore scalable.

I'm looking forward for XML-manipulation methods if there is no direct solution with this Qt framework.

QSvgGenerator generator;
generator.setFileName(savePath);
generator.setSize(QSize(static_cast<int>(currentScene->width()), static_cast<int>(currentScene->height())));
generator.setViewBox(currentScene->sceneRect());
generator.setTitle(tr("SVG Generated using SVG Generator"));
generator.setDescription(tr("an SVG drawing used by Software Package"));
QPainter painter;
painter.begin(&generator);
currentScene->clearSelection();
currentScene->render(&painter);
painter.end();

I'm expecting an output svg file that contains tags and nodes of the included internal SVG items (not converting them to images data)

right now it is converting internal svg items into these image tags:

<image x="131" y="127" width="102" height="102" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...

UPDATE 1 this tiny application will show an svg file (QGraphicsSvgItem) in the graphical scene and will use QSvgGenerator to export the scene into another output svg file:

#include <QApplication>
#include <QtSvg>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // Creating and showing an svg icon in the graphical scene view
    QGraphicsSvgItem *item = new QGraphicsSvgItem(":The Pirate Bay Logo.svg");
    QGraphicsScene *scene = new QGraphicsScene;
    scene->setSceneRect(0, 0, 1000, 1000);
    QGraphicsView *view = new QGraphicsView(scene);

    scene->addItem(item);
    item->setPos(view->rect().center());

    view->show();

    // Saving the graphical scene to another svg output device file using QSvgGenerator
    QSvgGenerator generator;
    generator.setFileName("output.svg");
    generator.setSize(QSize(static_cast<int>(scene->width()), static_cast<int>(scene->height())));
    generator.setViewBox(scene->sceneRect());
    generator.setTitle("SVG Generated using SVG Generator");
    generator.setDescription("an SVG drawing used by Software Package");
    QPainter painter;
    painter.begin(&generator);
    scene->clearSelection();
    scene->render(&painter);
    painter.end();

    return a.exec();
}

but what I get is an svg file includes converted corrupted bits of a very low quality image of the initial svg file. what I expect is that QSvgGenerator takes the initial svg elements from the source file (maybe saved in the QGraphicsSvgItem in the scene) and put them into the last generated file.

  • I deleted my answer because I think it was wrong. `QSvgGenerator` should, or can, in fact write vector SVG files from QPainter (as evidenced by the Qt example). So there must be something else going on in relation to the graphics view, scene, or the individual items involved. I'd be interested in testing a more complete (but minimal :) example if you could provide one. – Maxim Paperno Sep 24 '19 at 05:38
  • thanks, I've edited my question and made a tiny application for reproducing the situation. – Morteza Sherafati Sep 24 '19 at 09:27
  • 1
    Well it certainly seems to matter what is doing the rendering. With your example it rasterizes even the simplest single-color, one-path SVG icon I can find. Same if I load the SVG into a `QIcon` and use that to paint. OTOH if I use a `QSvgWidget` to load the image and then use that to render to `QSvgRenderer` then it comes out as a vector graphic! Even the PB logo with all the gradients. Also if I load the SVG into a `QSvgRenderer` and use that to render to the generator, it outputs vectors. So this is strange and doesn't seem to match what the docs claim about all painting being the same. – Maxim Paperno Sep 24 '19 at 10:19
  • 1
    OK, makes sense... has to do with how the widget/icon/etc paints itself. `QIcon` paints using bitmaps, so that's what SVG generator outputs. Apparently so does [QGraphicsItem](https://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp.html#9838) (because it caches itself as a pixmap). So the `QSvgGenerator` ends up here: https://code.woboq.org/qt5/qtsvg/src/svg/qsvggenerator.cpp.html#_ZN15QSvgPaintEngine10drawPixmapERK6QRectFRK7QPixmapS2_ whereas `QWidget`s and `QSvgRenderer` paint geometry directly to the paint device (using `drawRect`, `drawText` and so on). – Maxim Paperno Sep 24 '19 at 10:34
  • wow, I am surprised, I went into the codes of Svg Generator for hours to figure out how to avoid generate svgs as pixmaps but I never tried QSvgWidget, it seems it is maintaining itself as vector which is very good. now I have to figure it out how to implement a thing to use QSvgWidget for my exports. thank you btw – Morteza Sherafati Sep 24 '19 at 11:04
  • and if you know maybe other components or projects to help me out throw this I'd be very appreciated – Morteza Sherafati Sep 24 '19 at 11:05
  • 1
    Just thought of something... and it seems to work! Set the `QGraphicsItem::cacheMode` to `QGraphicsItem::NoCache`. So in your example I just added `item->setCacheMode(QGraphicsItem::NoCache);` just below where `QGraphicsSvgItem` is created, and now it saves in vector format. Basically what this does is force the painting to be done every time instead of cached in a bitmap. Please confirm and we can add this as an answer. – Maxim Paperno Sep 24 '19 at 12:00
  • yes, exactly I was searching around documentations and I got this function also, so I tested the cacheMode to be NoCache using setCacheMode in my QGraphiccsSvgItem constructor and now they are begin saved as vectors not pixmaps. – Morteza Sherafati Sep 24 '19 at 12:50

1 Answers1

2

The quick answer turned out to be disabling cache mode on each QGraphicsItem before rendering it to SVG*.

QGraphicsSvgItem *item = new QGraphicsSvgItem(":The Pirate Bay Logo.svg");
item->setCacheMode(QGraphicsItem::NoCache);

The reason is that when cache mode is enabled, the graphics item caches its painting in a bitmap image (which of course is raster format). So when asked to render itself on to the QSvgGenerator device, it just draws the cached bitmap. Which the generator correctly encodes as a bitmap image, not vectors.

This extends to any painting which uses bitmaps/QPixmap to paint or cache itself. For example QSvgIconEngine (which generates QIcons from SVG files) will use bitmaps for painting (even when the original source is a vector). So rendering a QIcon to QSvgGenerator produces raster images.

* I would consider leaving the cache enabled and only disabling it before rendering back to SVG, then re-enable after. You'd have to loop over all the scene items, but OTOH the performance gain with cache on the rest of the time could far outweigh that.

Maxim Paperno
  • 4,485
  • 2
  • 18
  • 22
  • I'm having this same issue with plain QGraphicsItems. The output is bitmapped. I tried to disable the cache, but it didn't help. Any ideas? – juzzlin Jul 12 '20 at 12:44
  • @juzzlin Not sure what you mean by "plain QGraphicsItem" because that is an abstract class -- specifically, what happens in the overridden `paint()` method of subclasses is the important part. For example [`QGraphicsPixmapItem`](https://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp.html#9838) can only output bitmaps, whereas [`QGraphicsRectItem`](https://code.woboq.org/qt5/qtbase/src/widgets/graphicsview/qgraphicsitem.cpp.html#_ZN17QGraphicsRectItem5paintEP8QPainterPK24QStyleOptionGraphicsItemP7QWidget) would always output in a vector format. – Maxim Paperno Jul 13 '20 at 02:50
  • I meant that my problem was not about `QGraphicsSvgItem`. I now figured out it was my drop shadow effect that forced everything to bitmaps for some reason. Disabling the effect produced proper SVG. Also, according to documentation the cache mode is `NoCache` by default so apparently it didn't have any effect. – juzzlin Jul 13 '20 at 19:24
  • 1
    Indeed, `QGraphicsSvgItem` specifically sets the default cache mode to `DeviceCoordinateCache`, vs. `NoCache` on the other built-in `QGraphicsItem` subclasses. – Maxim Paperno Jul 14 '20 at 04:56