Whether Qt Can Render Its Interface To A Vector Image Format
-
Occasionally, I want to screenshot my applications in a way that is scalable. Because current compositors appear to exclusively render pixmaps, this isn't possible at that level. Consequently, I am hoping that Qt may be able to, because modern Q{Quick}Styles appear to be comprised of non-bitmap components.
To summarise, can I implement a button in a standard QWidget QApplication, which captures the current interface as a static vectoral image (of which SVG is probably all that I care about)?
-
What about
QWidget::render()with aQSvgGenerator? -
Hi!
I do not think it is possible as - as far as I know and I am only happy to be corrected - Widgets are rendered on-screen as raster images. Hence QWidget::grab() produces QPixmap.I'd think that providing styles in vector formats has benefit of applying appropriate scale before rendering to raster to limit the artefacts.
But perhaps someone with better knowledge has better answers for you?
-
Hi!
I do not think it is possible as - as far as I know and I am only happy to be corrected - Widgets are rendered on-screen as raster images. Hence QWidget::grab() produces QPixmap.I'd think that providing styles in vector formats has benefit of applying appropriate scale before rendering to raster to limit the artefacts.
But perhaps someone with better knowledge has better answers for you?
QWidget::grab()producesQPixmap.discourse.slicer.org/t/32877/3appears to demonstrate so. Perhaps, I'd need to see what APIs it calls.I'd think that providing styles in vector formats has benefit of applying appropriate scale before rendering to raster to limit the artefacts.
Consequently, @artwaw, was my assumption, that most styles are vectoral, incorrect? I'd assumed it solely because Fusion and Breeze appearing crisp with fractional scaling enabled, and when rendered on high-resolution displays, appeared to demonstrate so. Alternatively, do I misunderstand what you mean?
-
QStyle API is QPainter based, see QStyle::drawPrimitive for example.
Internally the style can be whatever it needs, raster or vector based, but it's not exposed above that style API surface. A pixmap and a painter go into a black box and a rasterized UI element comes out.To add to that platform style plugins often use internally OS provided APIs for rendering UI elements, which is also usually bitmap based.
You could write a vector based style, but there would still be no Qt API exposing its internals, so no way to get it back like that.Consequently there's no vector representation retained anywhere, so it can't be rendered out as such. The way scaling works is that Qt just renders the pixmaps with the scaling factor applied, so if you change scaling factor the element is re-painted bigger/smaller and cached for future use.
-
R RokeJulianLockhart has marked this topic as solved
-
Appears that it would require a modification to Qt (and a conformant style), then. Thanks.
-
What about
QWidget::render()with aQSvgGenerator? -
@GrecKo Hm, well, I didn't think of that... Should work to an extent. The parts where the OS provided bitmaps are used will probably end up being just embedded binary image elements, so won't scale good and the resulting file could be very large, but some elements, like frames and text should be ok. Mostly depends on how the given style renders things - with painter shapes or with pixmaps. Good catch anyway.
-
R RokeJulianLockhart has marked this topic as solved
-
@GrecKo Hm, well, I didn't think of that... Should work to an extent. The parts where the OS provided bitmaps are used will probably end up being just embedded binary image elements, so won't scale good and the resulting file could be very large, but some elements, like frames and text should be ok. Mostly depends on how the given style renders things - with painter shapes or with pixmaps. Good catch anyway.
@Chris-Kawa, using
lookandfeeltool -a org.kde.breeze.desktop, that isn't a problem:-
#!/usr/bin/env python3 import sys from PySide6.QtCore import Qt, QRect from PySide6.QtGui import QAction from PySide6.QtSvg import QSvgGenerator from PySide6.QtWidgets import ( QApplication, QCheckBox, QComboBox, QFormLayout, QGroupBox, QLabel, QLineEdit, QMainWindow, QPushButton, QSpinBox, QVBoxLayout, QWidget, ) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("SVG Export Example") central = QWidget() self.setCentralWidget(central) layout = QVBoxLayout(central) group = QGroupBox("Settings") layout.addWidget(group) form = QFormLayout(group) self.name = QLineEdit("Roke") self.number = QSpinBox() self.number.setValue(42) self.combo = QComboBox() self.combo.addItems(["Alpha", "Beta", "Gamma"]) self.check = QCheckBox("Enable feature") self.check.setChecked(True) form.addRow("Name:", self.name) form.addRow("Number:", self.number) form.addRow("Mode:", self.combo) form.addRow("", self.check) self.label = QLabel( "This interface is rendered directly into SVG." ) self.label.setWordWrap(True) layout.addWidget(self.label) self.export_button = QPushButton("Export SVG") self.export_button.clicked.connect(self.export_svg) layout.addWidget(self.export_button) export_action = QAction("Export SVG", self) export_action.triggered.connect(self.export_svg) self.menuBar().addAction(export_action) def export_svg(self): svg = QSvgGenerator() svg.setFileName("ui.svg") svg.setTitle("Qt Widget Export") svg.setDescription("Exported using QWidget.render()") size = self.size() svg.setSize(size) svg.setViewBox(QRect(0, 0, size.width(), size.height())) self.render(svg) print("Exported to ui.svg") app = QApplication(sys.argv) window = MainWindow() window.resize(500, 300) window.show() sys.exit(app.exec()) -
<?xml version="1.0" encoding="UTF-8"?> <svg width="176.39mm" height="105.83mm" baseProfile="tiny" version="1.2" viewBox="0 0 500 300" xmlns="http://www.w3.org/2000/svg"> <title>Qt Widget Export</title> <desc>Exported using QWidget.render()</desc> <g fill-rule="evenodd" font-family="Monospace" font-size="10" font-weight="400" stroke-linecap="square" stroke-linejoin="bevel"> <g fill="#eff0f1"> <rect width="500" height="300"/> </g> <g fill="#dee0e2" stroke="#000" stroke-linecap="square" stroke-linejoin="bevel" stroke-opacity="0"> <rect width="500" height="31"/> </g> <g fill="#dee0e2" stroke="#b9bbbd" stroke-linecap="square" stroke-linejoin="bevel"> <polyline points="0 30 499 30" fill="none"/> </g> <g transform="translate(6 36)" fill="#f4f5f5" stroke="#c6c8c9" stroke-linecap="square" stroke-linejoin="bevel"> <path d="m0.5005 5.0005c0-2.4853 2.0147-4.5 4.5-4.5h478c2.485 0 4.5 2.0147 4.5 4.5v168c0 2.485-2.015 4.5-4.5 4.5h-478c-2.4853 0-4.5-2.015-4.5-4.5v-168z" fill-rule="evenodd"/> </g> <g transform="translate(6 36)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="212.313" y="20.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Settings</text> </g> <g transform="translate(30 70)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text y="20.5469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Name:</text> </g> <g transform="translate(75 70)" fill="#fff"> <rect x="6" y="6" width="399" height="20"/> </g> <g transform="translate(75 70)" fill="#fff" stroke="#c6c8c9" stroke-linecap="square" stroke-linejoin="bevel"> <path d="m0.5005 5.0005c0-2.4853 2.0147-4.5 4.5-4.5h401c2.485 0 4.5 2.0147 4.5 4.5v21.999c0 2.4853-2.015 4.5-4.5 4.5h-401c-2.4853 0-4.5-2.0147-4.5-4.5v-21.999z" fill-rule="evenodd"/> </g> <g transform="translate(75 70)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="8" y="20.8906" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Roke</text> </g> <g transform="translate(14 108)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text y="20.5469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Number:</text> </g> <g transform="translate(75 108)" fill="#fff" stroke="#c6c8c9" stroke-linecap="square" stroke-linejoin="bevel"> <path d="m0.5005 5.0005c0-2.4853 2.0147-4.5 4.5-4.5h46.999c2.4853 0 4.5 2.0147 4.5 4.5v21.999c0 2.4853-2.0147 4.5-4.5 4.5h-46.999c-2.4853 0-4.5-2.0147-4.5-4.5v-21.999z" fill-rule="evenodd"/> </g> <g transform="translate(75 108)" fill="none" stroke="#444749" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2" stroke-width="1.001"> <polyline points="40.5 12.5 45 8 49.5 12.5" fill="none"/> </g> <g transform="translate(75 108)" fill="none" stroke="#444749" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2" stroke-width="1.001"> <polyline points="40.5 19.5 45 24 49.5 19.5" fill="none"/> </g> <g transform="translate(81 114)" fill="#fff"> <rect width="31" height="20"/> </g> <g transform="translate(81 114)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="2" y="14.8906" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">42</text> </g> <g transform="translate(30 146)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text y="20.5469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Mode:</text> </g> <g transform="translate(75 146)" fill="none" stroke="#000" stroke-linecap="square" stroke-linejoin="bevel" stroke-opacity=".125" stroke-width="1.001"> <path d="m1.5015 6.001c0-2.485 2.0145-4.4995 4.4995-4.4995h64.998c2.485 0 4.4995 2.0145 4.4995 4.4995v20.999c0 2.485-2.0145 4.4995-4.4995 4.4995h-64.998c-2.485 0-4.4995-2.0145-4.4995-4.4995v-20.999z" fill-rule="evenodd"/> </g> <g transform="translate(75 146)" fill="#fcfcfc" stroke="#d1d1d2" stroke-linecap="square" stroke-linejoin="bevel" stroke-width="1.001"> <path d="m1.5015 6.0015c0-2.4853 2.0147-4.5 4.5-4.5h64.997c2.4853 0 4.5 2.0147 4.5 4.5v19.997c0 2.4853-2.0147 4.5-4.5 4.5h-64.997c-2.4853 0-4.5-2.0147-4.5-4.5v-19.997z" fill-rule="evenodd"/> </g> <g transform="translate(75 146)" fill="none" stroke="#444649" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2" stroke-width="1.001"> <polyline points="60.5 14.5 65 19 69.5 14.5" fill="none"/> </g> <g transform="translate(75 146)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="7" y="21.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Alpha</text> </g> <g transform="translate(75 184)" fill="#fcfcfc" stroke="#a6a6a6" stroke-linecap="square" stroke-linejoin="bevel" stroke-width="1.001"> <path d="m2.5005 7.5005c0-2.2091 1.7909-4 4-4h6.999c2.2091 0 4 1.7909 4 4v6.999c0 2.2091-1.7909 4-4 4h-6.999c-2.2091 0-4-1.7909-4-4v-6.999z" fill-rule="evenodd"/> </g> <g transform="translate(75 184)" fill="#a6a6a6" fill-opacity=".33001" stroke="#a6a6a6" stroke-linecap="square" stroke-linejoin="bevel" stroke-width="1.001"> <path d="m2.5005 7.5005c0-2.2091 1.7909-4 4-4h6.999c2.2091 0 4 1.7909 4 4v6.999c0 2.2091-1.7909 4-4 4h-6.999c-2.2091 0-4-1.7909-4-4v-6.999z" fill-rule="evenodd"/> </g> <g transform="translate(75 184)" fill-opacity="0" stroke="#232629" stroke-linecap="square" stroke-linejoin="miter" stroke-miterlimit="2" stroke-width="2.002"> <path d="m6 11 3 3 5.5-5.5" fill-rule="evenodd"/> </g> <g transform="translate(75 184)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="24" y="16.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Enable feature</text> </g> <g transform="translate(6 220)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text y="22.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">This interface is rendered directly into SVG.</text> </g> <g transform="translate(6 260)" fill="none" stroke="#000" stroke-linecap="square" stroke-linejoin="bevel" stroke-opacity=".125" stroke-width="1.001"> <path d="m1.5015 6.001c0-2.485 2.0145-4.4995 4.4995-4.4995h476c2.485 0 4.5 2.0145 4.5 4.4995v22.999c0 2.485-2.015 4.4995-4.5 4.4995h-476c-2.485 0-4.4995-2.0145-4.4995-4.4995v-22.999z" fill-rule="evenodd"/> </g> <g transform="translate(6 260)" fill="#fcfcfc" stroke="#d1d1d2" stroke-linecap="square" stroke-linejoin="bevel" stroke-width="1.001"> <path d="m1.5015 6.0015c0-2.4853 2.0147-4.5 4.5-4.5h476c2.485 0 4.5 2.0147 4.5 4.5v21.997c0 2.4853-2.015 4.5-4.5 4.5h-476c-2.4853 0-4.5-2.0147-4.5-4.5v-21.997z" fill-rule="evenodd"/> </g> <g transform="translate(6 260)" fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="205.016" y="22.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Export SVG</text> </g> <g fill="none" stroke="#232629" stroke-linecap="square" stroke-linejoin="bevel"> <text x="10.0156" y="20.0469" fill="#232629" font-family="Monospace" font-size="10" font-weight="400" stroke="none" xml:space="preserve">Export SVG</text> </g> <g transform="translate(0 2)" fill="none" stroke="#808080" stroke-linecap="square" stroke-linejoin="bevel"> <polyline points="10 24 88 24" fill="none"/> </g> </g> </svg>
Thanks, @GrecKo! Now, I need to ascertain how feasible this is to attach to other applications, after they've been compiled. Perhaps, I'll need a kind of DLL injection software.
-