May 21, 2011

jcbriar jcbriar
Lab Rat
7 posts

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:

  1.   generator = QSvgGenerator()
  2.   generator.setFileName(path)
  3.   generator.setSize(QSize(200, 200))
  4.  
  5.   painter = QPainter()
  6.   painter.begin(generator)
  7.   ...
  8.   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:

  1.   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.

11 replies

May 21, 2011

Peppy Peppy
Hobby Entomologist
389 posts

I think, at first you have to make in QGraphicsItem something like “exporting to XML” method
for example:

  1. {
  2. // to do something...
  3. }

And then you have to export all SVG objects to file.(I am not working in Python…)

May 22, 2011

jcbriar jcbriar
Lab Rat
7 posts

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.

May 22, 2011

Peppy Peppy
Hobby Entomologist
389 posts

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…

May 22, 2011

task_struct task_struct
Hobby Entomologist
344 posts

Hello, try this. It works for me

  1. MainWindow::MainWindow(QWidget *parent) :
  2.     QMainWindow(parent),
  3.     ui(new Ui::MainWindow)
  4. {
  5.     ui->setupUi(this);
  6.  
  7.     QGraphicsScene scene( this );
  8.     ui->view->setScene( &scene );
  9.  
  10.     scene.addRect(QRectF(0, 0, 100, 200), QPen(Qt::black), QBrush(Qt::green));
  11.     scene.addText( "sdfsdfsdfsdfsdfsdfsd" );
  12.  
  13.     qDebug() << " Scene has " << scene.items().count() << " items" ;
  14.  
  15.     QSvgGenerator svgGen;
  16.  
  17.     svgGen.setFileName( "/home/nikolay/scene2svg.svg" );
  18.     svgGen.setSize(QSize(200, 200));
  19.     svgGen.setViewBox(QRect(0, 0, 200, 200));
  20.     svgGen.setTitle(tr("SVG Generator Example Drawing"));
  21.     svgGen.setDescription(tr("An SVG drawing created by the SVG Generator "
  22.                                 "Example provided with Qt."));
  23.  
  24.     QPainter painter( &svgGen );
  25.     scene.render( &painter );
  26.  
  27. }

 Signature 

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.”
- Linus Torvalds

May 23, 2011

jcbriar jcbriar
Lab Rat
7 posts

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.

May 24, 2011

task_struct task_struct
Hobby Entomologist
344 posts

The above code generates following file:

  1. <?xml version="1.0" encoding="UTF-8" standalone="no"?>
  2. <svg width="70.5556mm" height="70.5556mm"
  3.  viewBox="0 0 200 200"
  4.  xmlns:xlink="http://www.w3.org/1999/xlink"  version="1.2" baseProfile="tiny">
  5. <title>SVG Generator Example Drawing</title>
  6. <desc>An SVG drawing created by the SVG Generator Example provided with Qt.</desc>
  7. <defs>
  8. </defs>
  9. <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >
  10.  
  11. <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)"
  12. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  13. >
  14. </g>
  15.  
  16. <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)"
  17. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  18. >
  19. </g>
  20.  
  21. <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)"
  22. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  23. >
  24. <path vector-effect="non-scaling-stroke" fill-rule="evenodd" d="M0,0 L100,0 L100,200 L0,200 L0,0"/>
  25. </g>
  26.  
  27. <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)"
  28. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  29. >
  30. </g>
  31.  
  32. <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)"
  33. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  34. >
  35. </g>
  36.  
  37. <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)"
  38. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  39. >
  40. </g>
  41.  
  42. <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)"
  43. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  44. >
  45. </g>
  46.  
  47. <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)"
  48. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  49. >
  50. <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"
  51.  >sdfsdfsdfsdfsdfsdfsd</text>
  52. </g>
  53.  
  54. <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)"
  55. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  56. >
  57. </g>
  58.  
  59. <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)"
  60. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  61. >
  62. </g>
  63.  
  64. <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)"
  65. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  66. >
  67. </g>
  68.  
  69. <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)"
  70. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  71. >
  72. </g>
  73.  
  74. <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)"
  75. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  76. >
  77. </g>
  78.  
  79. <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)"
  80. font-family="Ubuntu" font-size="9" font-weight="400" font-style="normal"
  81. >
  82. </g>
  83. </g>
  84. </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?

 Signature 

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.”
- Linus Torvalds

May 25, 2011

jcbriar jcbriar
Lab Rat
7 posts

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:

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

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:

  1. <g id="ssk">
  2.   <rect x="-18" y="54" fill="none" width="9" height="9"/>
  3.   <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"/>
  4.   <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"/>
  5. </g>

This lets me load the symbols with code like the following:

  1. symbols = QSvgRenderer('symbols.svg')
  2. symbol = QGraphicsSvgItem()
  3. symbol.setSharedRenderer(symbols)
  4. 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?

May 25, 2011

task_struct task_struct
Hobby Entomologist
344 posts

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 :(

 Signature 

“Most good programmers do programming not because they expect to get paid or get adulation by the public, but because it is fun to program.”
- Linus Torvalds

May 26, 2011

jcbriar jcbriar
Lab Rat
7 posts

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…

  1. generator = QSvgGenerator()
  2. generator.setFileName(path)
  3. generator.setSize(QSize(200, 200))
  4.  
  5. painter = QPainter()
  6. painter.begin(generator)
  7. scene.render(painter)
  8. 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…

  1. generator = QSvgGenerator()
  2. generator.setFileName(path)
  3. generator.setSize(QSize(200, 200))
  4.  
  5. painter = QPainter()
  6. painter.begin(generator)
  7. svgItem.renderer().render(painter, svgItem.elementId())
  8. 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?

May 27, 2011

jcbriar jcbriar
Lab Rat
7 posts

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…

  1. symbols = QSvgRenderer('symbols.svg')
  2. symbol = QGraphicsSvgItem()
  3. symbol.setSharedRenderer(symbols)
  4. symbol.setElementId(abbr)
  5. 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 [bugreports.qt.nokia.com].

May 27, 2011

jcbriar jcbriar
Lab Rat
7 posts

Reported as bug #19563 [bugreports.qt.nokia.com].

 
  ‹‹ Will PySide build with VS2005 x64?      [Moved] ’C:\Qt\4.7.2\binmoc.exe’ is not recognized as an internal or external command ››

You must log in to post a reply. Not a member yet? Register here!