HiDPI and SVG icon resolution
-
I have SVG icons that I want to use in my Qt app, but unfortunately they are rendered in a low resolution on my HiDPI display (external 4K display on macOS with scaling enabled).
For reference, this question was already asked here several years ago (HI-DPI and SVG icons), but the solution doesn't work for me because the dimensions of my SVG icons are actually a factor of 4 (24x24 in my case).
I have also asked this question on StackOverflow, but the suggested answers don't work either (setting a
QT_AUTO_SCREEN_SCALE_FACTOR
environment variable or rescaling the SVGs).How can I properly use SVGs on HiDPI systems?
Here's a minimal working example using PyQt5 (the required icon can be downloaded here):
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QAction from PyQt5.QtGui import QIcon app = QApplication(sys.argv) main = QMainWindow() icon = QIcon("waves-24px.svg") action = QAction(icon, "Test") toolbar = main.addToolBar("toolbar") toolbar.addAction(action) toolbar.show() main.show() sys.exit(app.exec_())
And this is a screenshot of the result (note the low res icon):
-
Hi
If using the SVG via QIcon, i noticed something.
It would not scale the SVG above the page rect ( ViewBox)
of the SVG.
It would then show as your image. ( pixelated)So try open
waves-24px.svg
in say InkScape and scale the svg to much bigger, like 512x512 and select
via the Document Properties "Resize page to drawing or selection" button.Then rerun sample and see.
Update:
Oh. icon is there. :)
The view box is currently 20x16 so it wont scale above that.
Try this one
https://www.dropbox.com/s/epbbn6xxslgpkj3/waves-24px.svg?dl=0
i changed to 512x512
(since it vector, it wont make file bigger or anything else)
Its just a bigger viewBox. -
@cle1109
Ok, thank you for testing.
It was worth a shot but its clearly related to HI-DPI then.
Sadly i have no 4k screen to test on. I wish i had. ;)Oh. should have looked at SO. Sorry. excactly same was discussed there.
But seems that was not it. In this case.
-
The problem might be that you leave it to Qt to render the SVG to pixels. What you can try instead is rendering the SVG into a QPixmap first and set this for the icon. Something similar to this (untested code, C++):
MainWindow *mw = ... QToolbar *toolbar = ... ... QImage svgImage(""waves-24px.svg""); QPixmap svgPixmap(toolbar->iconSize()); svgPixmap->setDevicePixelRatio(mw->pixelRatio()); QPainter painter; painter.begin(&svgPixmap); painter.drawImage(0,0,svgImage); painter.end(); QIcon icon(svgPixmap); QAction *action = new QAction(icon, "Test"); toolbar->addAction(action); ...
Maybe, you'll even need a larger pixmap:
QPixmap svgPixmap(toolbar->iconSize() * mw->pixelRatio());
As you can see, I am calling pixelRatio() on the MainWindow. The reason behind this is that with multiple monitors of different sizes and resolutions it is possible to have a different pixelRatio per screen (at least on Windows). This has extra problems if you move the widget from one screen to the other...
-
Thanks @SimonSchroeder, I've translated the code to Python as follows:
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QAction from PyQt5.QtGui import QIcon, QImage, QPixmap, QPainter app = QApplication(sys.argv) main = QMainWindow() toolbar = main.addToolBar("toolbar") image = QImage("waves-24px.svg") pixmap = QPixmap(toolbar.iconSize()) pixmap.setDevicePixelRatio(main.devicePixelRatio()) painter = QPainter() painter.begin(pixmap) painter.drawImage(0, 0, image) painter.end() icon = QIcon(pixmap) action = QAction(icon, "Test") toolbar.addAction(action) toolbar.show() main.show() sys.exit(app.exec_())
Note that a
QMainWindow
doesn't have apixelRatio
method so I've replaced that with a call todevicePixelRatio
.However, there seems to be something wrong because the result looks like this:
The actual content of the icon changes with each program execution BTW.
On a related note, I found out that this is indeed a macOS-specific issue, because everything looks fine on Linux and Windows using the same 4K monitor in scaled mode (I'm using the original code snippet from my initial post):
I guess this is indeed a Qt bug then and I'll report it. Still, I'd be interested in getting the workaround using
QPixmap
to actually work - then I could use that on macOS until they've fixed the underlying issue. -
@cle1109 have you tried it with a QSvgWidget ? Thats the widget you're supposed to use when drawing/showing svgs.
I assume its more sophisticated for the task, than a simple QIcon
-
@cle1109 That is unfortunate that the workaround does not work on MacOS. I have one more suggestion you could try. Instead of
pixmap = QPixmap(toolbar.iconSize()) pixmap.setDevicePixelRatio(main.devicePixelRatio())
just try
pixmap = QPixmap(toolbar.iconSize() * main.devicePixelRatio())
There is a slight chance that using
pixmap.setDevicePixelRatio
does not work properly with SVG on MacOS. In this case this would create a larger icon which is then scaled down when drawing it on the toolbar. -
@SimonSchroeder I think there is something wrong with the code because like in the first version, I get a more or less random icon with the second version as well. It looks like either the conversion
icon = QIcon(pixmap)
doesn't work or one of the preceding steps. -
I did
pixmap.save("pixmap.png")
after thepainter
stuff and the resulting PNG doesn't look right. So either the painter doesn't paint correctly into the pixmap or the image is not read in correctly. Can I debug this further to find out what the problem is? -
To me, this means that the painter does not paint correctly. I'm out of ideas.
Maybe, one last thing to check: I am not sure how a QPython project would be set up. For C++ I am using
qmake
. One thing I noticed is that in the end SVG icons are working. However, it would be more correct to add Qt's SVG module, i.e. I would add the lineQT += svg
to my
qmake
project file. Is there something similar for Python? -
I don't think this is necessary in Python. It is sufficient to import the required packages. In the example, I don't explicitly require the
PyQt5.QtSvg
module, but I don't think this is a problem (the stuff I'm using should automatically use functions from that module if needed).I will rewrite my example in C++ to see if this is a problem specific to the Python bindings. That way, it will be easier to decide where to file the bug report (since this is working on Windows and Linux). I'll keep you posted.
-
I tried this example in C++ and the result is exactly the same. The SVG icon is rendered in a very low resolution.
#include <QApplication> #include <QMainWindow> #include <QIcon> #include <QAction> #include <QToolBar> int main(int argc, char **argv) { QApplication app(argc, argv); QMainWindow window; QIcon icon("waves-24px.svg"); QAction action(icon, "Test"); QToolBar *toolbar = new QToolBar(&window); toolbar->addAction(&action); window.addToolBar(toolbar); window.show(); return app.exec(); }