[Development] High-DPI on Win

Mark De Wit mark.dewit at iesve.com
Tue Dec 29 10:45:19 CET 2015


Keep in mind that from Windows 8.1 onwards, DPI can be set per-screen (I myself have a 4K screen (240 dpi, 250% scaling) + regular HD screen (120 dpi, 125% scaling)).  Qt appears to already handle the appropriate new dpi-aware APIs (Qt defaults to per-monitor DPI aware on Windows 8.1+).

Sadly, I must agree with you that Qt does indeed not handle high-dpi windows very well ☹   I have Qt applications that I cannot use on my system, and have resorted to qt.conf to turn off DPI-awareness for them; fuzzy scaling was preferable to not being able to use the application.

Mind you, I have not found many Windows applications that handle high-dpi correctly, software is definitely lagging behind hardware here.

Mark

From: Development [mailto:development-bounces at qt-project.org] On Behalf Of Die Waldmanns
Sent: 28 December 2015 20:51
To: development at qt-project.org
Subject: [Development] High-DPI on Win

Hey Everyone,

first, I'd like to apologize if that is the wrong place for the following, but I've seen the "High-DPI 5.6 beta update" thread, which encouraged me to post here.
Second, you'll quickly realize that I'm new to QT, so not everything I'm saying might be prefectly on spot, I beg for pardon, but I think the main message is, so please rather look for that. (BTW, I'm using PyQT5.5 as provided by WinPython-64bit-3.4.3.5 on a Win7 Pc and a Win8.1 high-dpi laptop).
Third, I also write because after my first amazement with how nicely the first steps with PyQT5 worked out I was - yes - shocked to see how badly QT handles high-dpi on Win. I think one can't be happy with the current situation, and - although I can't claim to really have penetrated QT5.6 beta1 - I'm worried that it won't be satisfactorily resolved in QT5.6 either, even though it is - IMHO - of immediate relevance.

I'd like to suggest this:

(1) Support of fractional scaling (devicePixelRatio).
This ratio is qreal, but in QT5.5 it nevertheless seems not be possible to set it to fractional values (I could get it only to 1.0,2.0,3.0 etc). In 5.6-beta1 the new environment SCALE mechanisms seem to allow setting it to real values, but the examples in the docu are mentioning only devicePixelRatios of 1 and 2, which makes me wonder. If in QT5.6 it's now possible to acieve devicePixelRatios of 1.0, 1.25, 1.5, 2.0, and 2.5, then this point is obsolete; if not I think this neeeds to be enabled.

(2) Allow setting the devicePixelRatio programatically.
The current method via an environment variable might not be considered acceptable, or a programmer might want to modify the built-in method of how it is determined automatically. Im not sure if this could be achieved now by e.g. calling QHighDpiScaling::setGlobalFactor(). Anyway, I think the mechanism should be part of the API, i.e. be simple, official, and documented (e.g. a virtual method which can be overloaded). It would avoid clumpsy work-arounds such as calling os.environ['QT_DEVICE_PIXEL_RATIO'] = str( int(winScale) ) from within the code.

Ideally the mechanism would allow one to set devicePixelRatio at a point in time where the fonts are already accessible (app.font(),QFontInfo()). (not sure if that's possible, but the current mechanism seems to set the pixel ratio in two steps, so I recon it might be)

(3) Don't calculate the scale from the display resolution (QPlatformScreen::pixelDensity()).
In QT5.5 the devicePixelRatio is obviously set to either 1.0 or 2.0 depending on the resolution of the display. I'm not sure for Qt5.6, but it seems to be alike here. However, in Win the user can set a scaling such as 100%, 125%, 150%, 200%, and 250%, and the user can do so - and may want to do so - for whatever display she/he is using (anyone with ailing eyes is happy to use 125% on regular screens, or 250% on HDPI screens ;)). So - IMHO - the devicePixelRatio should actually be set to exactly this value, independent on any display resolution.

In QT5.5 the font sizes are apparently set "correctly", i.e. scale with the Windows scaling set by the user. Thus, for the 125%, 150%, and 250% scalings ugly GUIs result, since the font sizes do not fit to the renderings produced by QT5.5 (which are scaled only by 1.0, 2.0, 3.0,...). I'm not sure how exactly QT manages the font sizing, but I guess it's just that the font sizes are specified as points, and Win when does the correct scaling. Anyway, since the fonts scale with the Windows scaling it seems obvious to me that also the rest of the GUI should be scaled with this very same factor, at least it's the best bet on getting a properly rendered GUI.

As a work-around to the limitations of QT5.5 I tried doing all scaling by hand, i.e. to enforce devicePixelRatio=1.0 and scale all Widgets by using my own scale factors. This works quite well ... for most parts, it fails e.g. for a QMessageBox. So, this approach doesn't yield a fully satisfying result either (I note that QtDesigner suffers from the same artefacts, implying that even expert Qt programmers didn't get around it).

I got however the clear impression that if I could set devicePixelRatio to 2.5 when using a scaling of 250%, I would get a perfect GUI, which suggests the above 3 points, since with (1) and (2) one could determine the Windows scale and set devicePixelRatio accordingly. Ideally, Qt would be "smart" enough to do that by itself, which is (3).

As regards determining the Windows scale, I could not identify an API function which would give it directly. The most robust approach I found is to go via the fonts:

winScale = ( 3.0 * QFontInfo(app.font()).pixelSize() )/( 4.0 * QFontInfo(app.font()).pointSizeF() )

I tested this for a couple of fonts and font sizes, and in all cases without exception this formula delivered the correct Windows scaling. Unfortunately, this works only after the app is created (app = QApplication([])).

As alternative I found:

winScaledYRes = GetSystemMetrics(1) #1 = SM_CYSCREEN
dc= windll.user32.GetDC(0)
winYRes = windll.gdi32.GetDeviceCaps(dc,117) #117 = DESKTOPVERTRES
winScale = float(winYRes)/float(winScaledYRes)

This works before the app is created, and thus can be used to set the devicePixelration by calling os.environ['QT_DEVICE_PIXEL_RATIO'] = str( int(winScale) ), but, at least on my systems, it provides the correct value only for the 100%, 150%, 200%, and 250% scalings. For some reasons I don't understand it yields 1.0 instead of 1.25 for the 125%
scaling.

Maybe better methods exist. Anyway, since the font method works so well it would be nice if fonts could be accessed in (2).

Sorry for the long post, to summarize in a nutshell:
I came to the conclusion that one would/could get nice QT GUIs, for all Windows scalings and any display, if one could set the devicePixelRatio to real values which match the Windows user scaling. This led me to the above 3 suggestions. I'd like to add again, that a working HDPI is IMHO of immediate importance, and thus would think that it should become part of QT5.6, besides its feature freeze. I think the 3 points are easy to implement, which warrants this.

Finally, again, if any of the above just reflects my inablitity to discover the proper Qt methods to deal with high-DPI on Win systems, then please accept my applogies; please consider when to provide a bit more guidance in the docu :)

Cheers, and thanks for all your great work !!
Olli
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.qt-project.org/pipermail/development/attachments/20151229/ebbe7e94/attachment.html>


More information about the Development mailing list