spacr.qt.hidpi¶
Pictures rendered for the panel they land on.
Why a module for two lines of arithmetic¶
QPixmap.scaled(w, h, ...) takes DEVICE pixels but every caller in a GUI
means LOGICAL ones – the size the picture should occupy on screen. On a
display with a device pixel ratio of 2, which is every retina Mac and plenty
of Windows and Linux machines, Qt then stretches that logical bitmap across
twice as many real pixels in each direction: a 200 px render blown up to
400 px, from a 3334 px source. The picture is not low-resolution; the
scaling threw the resolution away.
The whole fix is to render at device pixels and then say so:
ratio = widget.devicePixelRatioF()
picture = source.scaled(round(px * ratio), round(px * ratio), ...)
picture.setDevicePixelRatio(ratio)
Missing the second line is worse than missing both. The picture comes out correct in pixels and twice the intended SIZE, because Qt has no way to know those pixels are dense ones.
A rule applied by hand at each site holds until the next site is written, so
what ships is scaled_for() – source, widget, logical size – and every
call site asking it. The next picture is then right without its author
knowing this problem exists.
At a device pixel ratio of 1 scaled_for() is byte-for-byte what
.scaled() did before: the same pixel dimensions, and a device pixel ratio
of 1 is what a pixmap already carries. No ordinary display changes.
Reading a picture back¶
QPixmap.width() counts device pixels, so a picture rendered through this
module reports twice its on-screen width on a 2x display. Anything that
compares a pixmap against widget coordinates – centring it, mapping a mouse
position onto it, fitting a label round it – wants logical_size(),
which is the size it OCCUPIES whatever it was rendered at.
Moving a window between screens¶
The ratio belongs to the screen, not the application, and a window can be
dragged from a retina laptop onto an ordinary external monitor. A picture
rendered at 2x and moved to a 1x screen is merely wasteful; the reverse is
blurry. follow_device_ratio() subscribes a widget to the change Qt
already sends, for the pictures whose source is still in memory and whose
re-render is a single call. Pictures scaled inside paintEvent need
nothing: they ask for the ratio again on the next frame.
Functions¶
|
How many real pixels |
|
Re-render |
|
The size |
|
|
|
Find a display without making its Python wrapper a child of a window. |
Module Contents¶
- spacr.qt.hidpi.device_ratio(target: Any = None) float[source]¶
How many real pixels
targetgets per logical pixel.Asks the widget, then the screen the widget is on, then the primary screen, and answers 1.0 when nothing will say – which is a plain display and the behaviour every call site had before.
targetmay be any object with adevicePixelRatioaccessor: a widget, a window, a screen, a paint device, or a stand-in in a test.
- spacr.qt.hidpi.follow_device_ratio(widget: Any, redraw: Callable[[], None]) _RatioWatcher | None[source]¶
Re-render
widget’s picture when it moves to a different screen.Qt sends
DevicePixelRatioChangewhen a window is dragged from a retina laptop onto an ordinary monitor or back.redrawis the widget’s own “put the picture on again” call – it must scale from the source it kept, not from what is currently on the label, or each move re-scales an already-scaled picture.Returns the watcher (for tests to drive) or
Noneif the widget cannot take an event filter.- Parameters:
widget – the
QObject(normally a widget) to watch; it becomes the watcher’s parent. Anything that is not aQObjectgivesNone.redraw – zero-argument callable run when the widget’s device pixel ratio actually changes; an exception it raises is logged at debug level and swallowed.
- spacr.qt.hidpi.logical_size(picture: Any) PySide6.QtCore.QSize[source]¶
The size
pictureOCCUPIES, whatever it was rendered at.QPixmap.width()counts device pixels; this counts the widget coordinates the picture covers, which is what centring, hit-testing and layout are all measured in. A null or missing picture is 0 x 0.- Parameters:
picture – a
QPixmaporQImage(orNone); its device-independent size is used when it has one, otherwise its pixel size divided by its device pixel ratio.
- spacr.qt.hidpi.scaled_for(source: Any, target: Any, width: SizeLike, height: SizeLike | None = None, *, aspect: PySide6.QtCore.Qt.AspectRatioMode = Qt.KeepAspectRatio, mode: PySide6.QtCore.Qt.TransformationMode = Qt.SmoothTransformation) Any[source]¶
sourceatwidthxheightLOGICAL pixels ontarget.The one call every picture in the application should make.
sourceis aQPixmapor aQImage;targetis the widget the picture will be shown on (anythingdevice_ratio()understands); the size is a side length, a(w, h)pair, aQSize, or two arguments.The result is rendered at
size * ratioreal pixels and carries that ratio, so it lays out atsizeand draws at full panel resolution.A null or missing source comes back untouched – a caller that already checks
isNull()keeps its own answer, and one that does not is no worse off than it was with a bare.scaled().- Parameters:
source – the
QPixmaporQImageto scale;Noneor a null picture is returned unchanged.target – the widget (or window, screen or paint device) the picture will be shown on; its device pixel ratio is read through
device_ratio().width – logical width in widget pixels, or the whole size as a single side length, a
(w, h)pair or aQSizewhenheightis not given.
- spacr.qt.hidpi.screen_for_widget(widget: Any = None)[source]¶
Find a display without making its Python wrapper a child of a window.
PySide can associate
QWidget.screen()’s shared QScreen wrapper with that widget. Closing it then invalidates the wrapper; cyclic collection can even destroy the display object still used by QApplication. Static QGuiApplication lookups leave display lifetime with Qt.- Parameters:
widget – QWidget or QWindow whose center selects the display, or None for the primary display. An unshown child uses its parent. Non-Qt stand-ins may supply their own
screen()accessor.- Returns:
QScreen at that position, the primary display as fallback, or None when there is no GUI application/display.