How to save a QGraphicsScene to an SVG file?



  • I have a QGraphicsScene composed of various QGraphicsItems, including QGraphicsLineItems and QGraphicsSvgItems. I'm currently displaying this scene in a QGraphicsView. In addition, I'd like to save the scene as an SVG file. The documentation for the QSvgGenerator class says it's possible to create an SVG file with code like the following:

    @ generator = QSvgGenerator()
    generator.setFileName(path)
    generator.setSize(QSize(200, 200))

    painter = QPainter()
    painter.begin(generator)
    ...
    painter.end()@

    But what goes between painter.begin(generator) and painter.end()? How do you get a QGraphicsScene to render to a QPainter?

    For yuks, I tried:

    @ scene.render(painter)@

    This kind of worked: it created an SVG file, but the QGraphicsSvgItems within the scene were rendered as bitmapped images rather than scalable vectors (despite originally being scalable vectors). Naturally, I'd prefer to create SVG files that contain no bitmaps, just scalable vectors. Is this possible? If so, how?

    For the record: I'm using Qt 4.7, PySide 1.0.2, and Python 2.6, though my question really isn't specific to Python. If it would be more appropriate, I can re-post the question in a more general Qt forum.



  • I think, at first you have to make in QGraphicsItem something like "exporting to XML" method
    for example:
    @
    QDomNode QGraphicsItem::toSvg()
    {
    // to do something...
    }
    @
    And then you have to export all SVG objects to file.(I am not working in Python...)



  • I don't understand. Why would I need to define a toSvg() method? One would think that a QGraphicsSvgItem would already know how to convert itself to SVG. Further, who would call the toSvg() method? It's not defined in the base class, so it's not something that QPainter would know to call.

    Surely there must be some established way to save a QGraphicsScene to an SVG file.



  • I don't think so. It can't know what are you currently drawing. Your own inherited class should have this method and do something in that method...



  • Hello, try this. It works for me
    @
    MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
    ui->setupUi(this);

    QGraphicsScene scene( this );
    ui->view->setScene( &scene );
    
    scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
    scene.addText( "sdfsdfsdfsdfsdfsdfsd" );
    
    qDebug() << " Scene has " << scene.items().count() << " items" ;
    
    QSvgGenerator svgGen;
    
    svgGen.setFileName( "/home/nikolay/scene2svg.svg" );
    svgGen.setSize(QSize(200, 200));
    svgGen.setViewBox(QRect(0, 0, 200, 200));
    svgGen.setTitle(tr("SVG Generator Example Drawing"));
    svgGen.setDescription(tr("An SVG drawing created by the SVG Generator "
                                "Example provided with Qt."));
    
    QPainter painter( &svgGen );
    scene.render( &painter );
    

    }
    @



  • That's essentially what I tried: create and initialize a QSvgGenerator, create a QPainter and associate it with the generator, then pass the painter to scene.render(). It does create an SVG file. But the contents of the file are bitmapped. I'd much rather the contents were scalable vectors. I imagine this ought to be possible, since each QGraphicsItem in my scene is composed of vector information. It would seem, though, that the painter is rendering the graphics items to a backing store. I'm looking for other options.



  • The above code generates following file:
    @
    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <svg width="70.5556mm" height="70.5556mm"
    viewBox="0 0 200 200"
    xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny">
    <title>SVG Generator Example Drawing</title>
    <desc>An SVG drawing created by the SVG Generator Example provided with Qt.</desc>
    <defs>
    </defs>
    <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="#00ff00" fill-opacity="1" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M0,0 L100,0 L100,200 L0,200 L0,0"/>
    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#141312" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    <text fill="#141312" fill-opacity="1" stroke="none" xml:space="preserve" x="4" y="15" font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    sdfsdfsdfsdfsdfsdfsd</text>
    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>

    <g fill="none" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)"
    font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"

    </g>
    </g>
    </svg>
    @

    It can be opened and edited by Inkscape. Rectangle and text are recognized as two different objects and they are editable. Isn`t this what you want?



  • I wish I could get output like that! Rather, I can, if I stick with QGraphicsLineItems and the like. But each QGraphicsSvgItem results in output like the following:

    @
    ...
    <image x="35" y="-1" width="38" height="38" preserveAspectRatio="none" xlink:hre
    f="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAABHNCSVQ
    ICAgIfAhkiAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAWNJREFUWIXt1TFIF2EYwOFHsxACBcNEpAjSxaA
    2IzACQWiNxrZGaQiaxMHZzVWhQRykIdwKWpraMpAKEVpqaVA0yjLE+ju898c7j4TE84u433Z338f73Df
    cUVdXV1dZrehLjTjYFSxhGW2JLYUu4QcaeJiWUm5CwL6iN7Gl0BmsCtzCEfafxcCxinKNClgDI3+x7w4
    +4j1OV+ACTwRsRZziYV3GM/svs46rVcH68C0bNP6HNe2YxHa27jdmca4qVLNH2cDvuHjg2W18sH9Kb3C
    9alCzNrzNBi9m9y7gaQ70BQ9w6qRQzW7mEI+xlbueR89Jg/LN5TANvMOtlKBm57EpUC9U+Ck4SmMC9hP
    9iS2FWvFa4J4ntpQawi+Bu5vYUmpGwD6J/+I/UxfWBG4qsaXUfQHbwWBiS6EWvBK4l4ktpa5hV+DuJba
    Umhawz+hMbCnUIVAbGE5sKXUD3akRdXV1/217FRRPx7bagcYAAAAASUVORK5CYII=" />
    ...
    @

    I can load the results in Illustrator, and each item is recognized as a different object. But when you zoom into the illustration, it's clear that these items are just pixels, not scalable vectors.

    I'll tell you what I'm trying to do, so you can see why I find this behavior less than optimal: I'm drawing charts that are composed of lines and symbols. The symbols are stored in an SVG file created in Illustrator. Each symbol is contained within its own group—for example:

    @<g id="ssk">
    <rect x="-18" y="54" fill="none" width="9" height="9"/>
    <line fill="none" stroke="#000000" stroke-width="0.65" stroke-linejoin="bevel" stroke-miterlimit="10" x1="-16.2" y1="55.801" x2="-10.8" y2="61.201"/>
    <line fill="none" stroke="#000000" stroke-width="0.65" stroke-linejoin="bevel" stroke-miterlimit="10" x1="-13.5" y1="58.5" x2="-16.2" y2="61.201"/>
    </g>@

    This lets me load the symbols with code like the following:
    @
    symbols = QSvgRenderer('symbols.svg')
    symbol = QGraphicsSvgItem()
    symbol.setSharedRenderer(symbols)
    symbol.setElementId(abbr)@

    So the symbols are stored in memory as vector data. Yet I can't get QSvgGenerator and QPainter to produce that vector data when saving the scene. I have to wonder: is it a basic limitation of the way QPainter interacts with QGraphicsSvgItems?



  • I think I understand what you want. You got a scene with a lot of SVG items and you want this scene to be saved as another SVG. So you need something like a SVG with embedded SVGs. Unfortunately, I think it is not possible :(



  • That's it exactly: I have a scene composed partly of simple lines and partly of SVG data, and I want to save the scene as scalable SVG.

    The more I look into this, the more I think it may be a bug. Consider: if I create a scene composed of a QGraphicsLine and a QGraphicsSvgItem, and display the scene within a QGraphicsView, then within the paint() methods for the line and the SVG item, the paint device for the line is of type PaintDeviceFlags.Widget and the paint device for the SVG item is of type PaintDeviceFlags.Pixmap. So far, so good: the line is being painted with calls to native widget code, and the SVG item is being rendered to a backing-store pixmap before being copied to the QGraphicsView widget. I can deal with that.

    But if the same scene is saved to an SVG file via...

    @generator = QSvgGenerator()
    generator.setFileName(path)
    generator.setSize(QSize(200, 200))

    painter = QPainter()
    painter.begin(generator)
    scene.render(painter)
    painter.end()@

    ...then when QGraphicsLine.paint() is called, the paint device is the QSvgGenerator shown above BUT when QGraphicsSvgItem.paint() is called, the paint device is of type PaintDeviceFlags.Pixmap. In other words, the QSvgGenerator is being asked to draw the line but the SVG item is being rendered to a backing-store pixmap.

    This doesn't make any sense to me. Why should a call to scene.render() cause a QGraphicsSvgItem to be rendered to a pixmap?

    It's especially puzzling because each QGraphicsSvgItem retains a reference to the QSvgRenderer that loaded it in the first place. And that renderer is capable of producing the necessary SVG code. That is, if you call the QSvgRenderer instead of scene.render(), as on line 7 below...

    @generator = QSvgGenerator()
    generator.setFileName(path)
    generator.setSize(QSize(200, 200))

    painter = QPainter()
    painter.begin(generator)
    svgItem.renderer().render(painter, svgItem.elementId())
    painter.end()@

    ...you get an SVG file containing SVG code for the item: no bitmaps, just clean vectors. Unfortunately, the item isn't positioned or styled correctly.

    So the next thing for me to try, it would seem, is to redefine QGraphicsSvgItem.paint() such that it sets the given painter appropriately before calling renderer().render(). Still, I don't think a workaround of this sort should be necessary. You would think that a painter that's been given a QSvgGenerator for use as a paint device would actually use that device when painting the QGraphicsSvgItems within a scene... wouldn't you?



  • New discovery: although the docs for QGraphicsItem state that cacheMode() returns NoCache by default, in reality the default cache mode for QGraphicsSvgItems is QGraphicsItem.DeviceCoordinateCache. In other words, the default is to use a backing-store pixmap.

    Changing the cache mode for each QGraphicsSvgItem to NoCache...

    @symbols = QSvgRenderer('symbols.svg')
    symbol = QGraphicsSvgItem()
    symbol.setSharedRenderer(symbols)
    symbol.setElementId(abbr)
    symbol.setCacheMode(QGraphicsItem.CacheMode.NoCache)@

    ...lets QSvgGenerator do its job and create SVG files with scalable (rather than bitmapped) elements.

    Unfortunately, it also prevents the SVG items from being cached when being displayed in a QGraphicsView, but I can live with that.

    Note related problem, "bug #11409":http://bugreports.qt.nokia.com/browse/QTBUG-11409.




Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.