Merge branch 'dpi-docs-improve'
Improve high DPI support documentation. Closes #18889, #22011.
This commit is contained in:
@@ -2,7 +2,7 @@ High DPI Support in wxWidgets {#overview_high_dpi}
|
|||||||
=============================
|
=============================
|
||||||
[TOC]
|
[TOC]
|
||||||
|
|
||||||
Introduction
|
Introduction {#high_dpi_intro}
|
||||||
============
|
============
|
||||||
|
|
||||||
Many modern displays have way more pixels on the same surface than used to be
|
Many modern displays have way more pixels on the same surface than used to be
|
||||||
@@ -50,10 +50,10 @@ on high DPI displays is needed: one which allows to scale some pixel values
|
|||||||
drawing, which should remain unscaled to use the full available resolution).
|
drawing, which should remain unscaled to use the full available resolution).
|
||||||
|
|
||||||
|
|
||||||
Pixel Values in wxWidgets
|
Pixel Values in wxWidgets {#high_dpi_pixel_types}
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
Logical and Device-Independent Pixels
|
Logical and Device-Independent Pixels {#high_dpi_lp_and_dip}
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
Some systems like eg Apple's OSes automatically scale all the coordinates by
|
Some systems like eg Apple's OSes automatically scale all the coordinates by
|
||||||
@@ -69,55 +69,206 @@ pixels**, abbreviated as "DIP", that are always of the same size on all
|
|||||||
displays and all platforms.
|
displays and all platforms.
|
||||||
|
|
||||||
Thus, the first thing do when preparing your application for high DPI support
|
Thus, the first thing do when preparing your application for high DPI support
|
||||||
is to stop using raw pixel values. Actually, using any pixel values is not
|
is to stop using raw pixel values, as they mean different things under
|
||||||
recommended and replacing them with the values based on the text metrics, i.e.
|
different platforms when DPI scaling is used. Actually, using any pixel values
|
||||||
obtained using wxWindow::GetTextExtent(), or expressing them in dialog units
|
is not recommended and replacing them with the values based on the text
|
||||||
(see wxWindow::ConvertDialogToPixels()) is preferable. However the simplest
|
metrics, i.e. obtained using wxWindow::GetTextExtent(), or expressing them in
|
||||||
change is to just replace the pixel values with the values in DIP: for this,
|
dialog units (see wxWindow::ConvertDialogToPixels()) is preferable. However
|
||||||
just use wxWindow::FromDIP() to convert from one to the other.
|
the simplest change is to just replace the pixel values with the values in
|
||||||
|
DIP: for this, just use wxWindow::FromDIP() to convert from one to the other.
|
||||||
|
|
||||||
For example, if you have the existing code:
|
For example, if you have the existing code:
|
||||||
```cpp
|
~~~{cpp}
|
||||||
myFrame->SetClientSize(wxSize(400, 300));
|
myFrame->SetClientSize(wxSize(400, 300));
|
||||||
```
|
~~~
|
||||||
you can just replace it with
|
you can just replace it with
|
||||||
```cpp
|
~~~{cpp}
|
||||||
myFrame->SetClientSize(myFrame->FromDIP(wxSize(400, 300)));
|
myFrame->SetClientSize(myFrame->FromDIP(wxSize(400, 300)));
|
||||||
```
|
~~~
|
||||||
|
although replacing it with something like
|
||||||
|
~~~{cpp}
|
||||||
|
const wxSize sizeM = myFrame->GetTextExtent("M");
|
||||||
|
myFrame->SetClientSize(100*sizeM.x, 40*sizeM.y));
|
||||||
|
~~~
|
||||||
|
might be even better.
|
||||||
|
|
||||||
Physical Pixels
|
In any case, remember that window and wxDC or wxGraphicsContext coordinates
|
||||||
|
must be in logical pixels that can depend on the current DPI scaling, and so
|
||||||
|
should never be fixed at compilation time.
|
||||||
|
|
||||||
|
|
||||||
|
Physical Pixels {#high_dpi_pp}
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
In addition to (logical) pixels and DIPs discussed above, you may also need to
|
In addition to (logical) pixels and DIPs discussed above, you may also need to
|
||||||
work in physical pixel coordinates, corresponding to the actual display pixels.
|
work in physical pixel coordinates, corresponding to the actual display pixels.
|
||||||
Physical pixels are never scaled, on any platform, and must be used when
|
Physical pixels are never scaled, on any platform, and are used for the real
|
||||||
drawing graphics elements to ensure that the best possible resolution is used.
|
bitmap sizes. They are also used for drawing operations on wxGLCanvas, which
|
||||||
For example, all operations on wxGLCanvas use physical pixels.
|
is _different_ from wxDC and wxGraphicsContext that use logical pixels.
|
||||||
|
|
||||||
To convert between logical and physical pixels, you can use
|
Under MSW physical pixels are same as logical ones, but when writing portable
|
||||||
wxWindow::GetContentScaleFactor(): this is a value greater than or equal to 1,
|
code you need to convert between logical and physical pixels. This can be done
|
||||||
so a value in logical pixels needs to be multiplied by it in order to obtain
|
using convenience wxWindow::FromPhys() and wxWindow::ToPhys() functions
|
||||||
the value in physical pixels.
|
similar to the DIP functions above or by directly multiplying or dividing by
|
||||||
|
wxWindow::GetContentScaleFactor(): this function returns a value greater than
|
||||||
|
or equal to 1 (and always just 1 under MSW), so a value in logical pixels
|
||||||
|
needs to be multiplied by it in order to obtain the value in physical pixels.
|
||||||
|
|
||||||
For example, in a wxGLCanvas created with the size of 100 (logical) pixels, the
|
For example, in a wxGLCanvas created with the size of 100 (logical) pixels, the
|
||||||
rightmost physical pixel coordinate will be `100*GetContentScaleFactor()`.
|
rightmost physical pixel coordinate will be `100*GetContentScaleFactor()`
|
||||||
|
under all platforms.
|
||||||
|
|
||||||
|
You can convert from DIPs to physical pixels by converting DIPs to the logical
|
||||||
|
pixels first, but you can also do it directly, by using
|
||||||
|
wxWindow::GetDPIScaleFactor(). This function can return a value different from
|
||||||
|
1 even under MSW, i.e. it returns DPI scaling for physical display pixels.
|
||||||
|
|
||||||
|
**Warning:** It is possible that conversion between different pixel
|
||||||
|
coordinates is not lossless due to rounding. E.g. to create a window big
|
||||||
|
enough to show a bitmap 15 pixels wide, you need to use `FromPhys(15)`,
|
||||||
|
however the exact result of this function is not representable as an `int`
|
||||||
|
when using 200% DPI scaling. In this case, the value is always rounded
|
||||||
|
upwards, i.e. the function returns `8`, to ensure that a window of this size
|
||||||
|
in logical pixels is always big enough to show the bitmap, but this can only
|
||||||
|
be done at the price of having one "extra" pixel in the window.
|
||||||
|
|
||||||
|
|
||||||
High-Resolution Images and Artwork
|
Summary of Different Pixel Kinds {#high_dpi_pixel_conversions}
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Under MSW, logical pixels are always the same as physical pixels, but are
|
||||||
|
different from DIPs, while under all the other platforms with DPI scaling
|
||||||
|
support (currently only GTK 3 and macOS), logical pixels are the same as DIP,
|
||||||
|
but different from physical pixels.
|
||||||
|
|
||||||
|
However under all platforms the following functions can be used to convert
|
||||||
|
between different kinds of pixels:
|
||||||
|
|
||||||
|
* From DIP to logical pixels: use wxWindow::FromDIP() or wxWindow::ToDIP().
|
||||||
|
* From physical to logical pixels: use wxWindow::FromPhys() or wxWindow::ToPhys().
|
||||||
|
* From DIP to physical pixels: multiply or divide by wxWindow::GetDPIScaleFactor().
|
||||||
|
|
||||||
|
Or, in the diagram form:
|
||||||
|
|
||||||
|
@dot
|
||||||
|
digraph Pixels
|
||||||
|
{
|
||||||
|
node [shape = hexagon, style = filled, fontname = Helvetica];
|
||||||
|
|
||||||
|
LP [fillcolor = lightblue, label = "Logical\nPixels"];
|
||||||
|
DIP [fillcolor = yellow, label = "DI\nPixels"];
|
||||||
|
PP [fillcolor = green, label = "Physical\nPixels"];
|
||||||
|
|
||||||
|
LP -> PP [fontname = Helvetica, labeldistance = 5, labelangle = 30, dir = both, weight = 2, minlen = 3, headlabel = "ToPhys()", taillabel = "FromPhys()"];
|
||||||
|
LP -> DIP [fontname = Helvetica, labeldistance = 6, dir = both, weight = 2, minlen = 3, headlabel = "ToDIP()", taillabel = "FromDIP()"];
|
||||||
|
DIP -> PP [fontname = Helvetica, dir = both, minlen = 10, label = "GetDPIScaleFactor()" headlabel = "multiply by", taillabel = "divide by", constraint = false] ;
|
||||||
|
}
|
||||||
|
@enddot
|
||||||
|
|
||||||
|
|
||||||
|
High-Resolution Images and Artwork {#high_dpi_artwork}
|
||||||
==================================
|
==================================
|
||||||
|
|
||||||
In order to benefit from the increased detail on High DPI devices you might want
|
In order to really benefit from the increased detail on High DPI devices you need
|
||||||
to provide the images or artwork your application uses in higher resolutions as
|
to provide the images or artwork your application uses in higher resolutions as
|
||||||
well. Note that it is not recommended to just provide a high-resolution version
|
well. Note that it is not recommended to just provide a high-resolution version
|
||||||
and let the system scale that down on 1x displays. Apart from performance
|
and let the system scale that down on 1x displays. Apart from performance
|
||||||
consideration also the quality might suffer, contours become more blurry.
|
consideration also the quality might suffer, contours become more blurry, so
|
||||||
|
for best results it is recommended to use the images that can be used without
|
||||||
|
scaling at the common DPI values, i.e. at least 100% and 200% scaling. If you
|
||||||
|
don't want providing several copies of all bitmaps, you can use a single
|
||||||
|
vector image in [SVG format][1] instead.
|
||||||
|
|
||||||
You can use vector based graphics like SVG or you can add the same image at different
|
[1]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics
|
||||||
sizes / resolutions.
|
|
||||||
|
|
||||||
[comment]: # (TODO: API and Use Cases)
|
In either case, you must use wxBitmapBundle class representing several
|
||||||
|
different versions of the same bitmap (or even potentially just a single one,
|
||||||
|
which makes it upwards-compatible with wxBitmap). Most functions accepting
|
||||||
|
wxBitmap or wxImage in wxWidgets API have been updated to work with
|
||||||
|
wxBitmapBundle instead, which allows the library to select the appropriate
|
||||||
|
size depending on the current DPI and, for the platforms supporting it
|
||||||
|
(currently only MSW and macOS), even update the bitmap automatically if the
|
||||||
|
DPI changes, as can happen, for example, when the window showing the bitmap is
|
||||||
|
moved to another monitor with a different resolution.
|
||||||
|
|
||||||
Platform-Specific Build Issues
|
Note that other than creating wxBitmapBundle instead of wxBitmap, no other
|
||||||
|
changes are needed. Moreover, when upgrading the existing code it is possible
|
||||||
|
to replace some wxBitmap objects by wxBitmapBundle while keeping the others.
|
||||||
|
|
||||||
|
Using Multiple Bitmaps {#high_dpi_bundle_bitmaps}
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
When using multiple bitmaps, the simplest way to create a wxBitmapBundle is to
|
||||||
|
do it from a vector of bitmaps of different sizes. In the most common case of
|
||||||
|
just two bitmaps, a special constructor taking these bitmaps directly can be
|
||||||
|
used rather than having to construct a vector from them:
|
||||||
|
|
||||||
|
~~~{cpp}
|
||||||
|
wxBitmap normal(32, 32);
|
||||||
|
wxBitmap highDPI(64, 64);
|
||||||
|
... initialize the bitmaps somehow ...
|
||||||
|
wxBitmapBundle bundle(normal, bitmap);
|
||||||
|
|
||||||
|
// Now the bundle can be passed to any wxWidgets control using bitmaps.
|
||||||
|
~~~
|
||||||
|
|
||||||
|
For the platforms where it is common to embed bitmaps in the resources, it is
|
||||||
|
possible to use wxBitmapBundle::FromResources() to create a bundle containing
|
||||||
|
all bitmaps using the given base name, i.e. `foo` for the normal bitmap and
|
||||||
|
`foo_2x` for the bitmap for 200% scaling (for fractional values decimal
|
||||||
|
separator must be replaced with underscore, e.g. use `foo_1_5x` for the bitmap
|
||||||
|
to use 150% scaling).
|
||||||
|
|
||||||
|
It is also possible to create wxBitmapBundle from the files using the same
|
||||||
|
naming convention with wxBitmapBundle::FromFiles(). And, to abstract the
|
||||||
|
differences between the platforms using resources and the other ones, a helper
|
||||||
|
wxBITMAP_BUNDLE_2() macro which uses resources if possible or files otherwise
|
||||||
|
is provided, similar to wxBITMAP_PNG() macro for plain bitmaps.
|
||||||
|
|
||||||
|
Independently of the way in which the bundle was created, it will provide the
|
||||||
|
bitmap closest in size to the expected size at the current DPI, while trying
|
||||||
|
to avoid having scale it. This means that at 175% DPI scaling, for example,
|
||||||
|
the high DPI (i.e. double-sized) bitmap will be used _without_ scaling rather
|
||||||
|
than scaling it by 0.875, which almost certainly wouldn't look good. However
|
||||||
|
if the current DPI scaling is 300%, the 2x bitmap will be scaled, if it's the
|
||||||
|
closest one available, as using it without scaling would appear in bitmaps too
|
||||||
|
small to use. The cut-off for the decision whether to scale bitmap or use an
|
||||||
|
existing one without scaling is the factor of 1.5: if the mismatch between the
|
||||||
|
required and closest available size is equal or greater than 1.5, the bitmap
|
||||||
|
will be scaled. Otherwise it will be used in its natural size.
|
||||||
|
|
||||||
|
If this behaviour is inappropriate for your application, it is also possible
|
||||||
|
to define a custom wxBitmapBundle implementation, see wxBitmapBundleImpl for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
Using Vector Graphics {#high_dpi_bundle_vector}
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
As an alternative to providing multiple versions of the bitmaps, it is
|
||||||
|
possible to use a single SVG image and create the bitmap bundle using either
|
||||||
|
wxBitmapBundle::FromSVG() or wxBitmapBundle::FromSVGFile(). Such bitmap
|
||||||
|
bundles will always produce bitmaps of exactly the required size, at any
|
||||||
|
resolution. At normal DPI, i.e. without any scaling, the size of the resulting
|
||||||
|
bitmap will be the default bundle size, which must be provided when creating
|
||||||
|
this kind of bitmap bundle, as SVG image itself doesn't necessarily contain
|
||||||
|
this information.
|
||||||
|
|
||||||
|
Note that wxWidgets currently uses [NanoSVG][1] library for SVG support and so
|
||||||
|
doesn't support all SVG standard features and you may need to simplify or
|
||||||
|
tweak the SVG files to make them appear correctly.
|
||||||
|
|
||||||
|
[1]: https://github.com/memononen/nanosvg
|
||||||
|
|
||||||
|
wxBitmapBundle and XRC {#high_dpi_bundle_xrc}
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
XRC format has been updated to allow specifying wxBitmapBundle with
|
||||||
|
`<bitmaps>` tag in all the places where wxBitmap could be specified using
|
||||||
|
`<bitmap>` before (of course, using the latter tag is still supported). Either
|
||||||
|
multiple bitmaps or a single SVG image can be used.
|
||||||
|
|
||||||
|
|
||||||
|
Platform-Specific Build Issues {#high_dpi_platform_specific}
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
Generally speaking, all systems handle applications not specifically marked as
|
Generally speaking, all systems handle applications not specifically marked as
|
||||||
@@ -126,7 +277,7 @@ up, resulting in blurry graphics and fonts, but globally preserving the
|
|||||||
application appearance. For the best results, the application needs to be
|
application appearance. For the best results, the application needs to be
|
||||||
explicitly marked as DPI-aware in a platform-dependent way.
|
explicitly marked as DPI-aware in a platform-dependent way.
|
||||||
|
|
||||||
MSW
|
MSW {#high_dpi_platform_msw}
|
||||||
---
|
---
|
||||||
|
|
||||||
The behaviour of the application when running on a high-DPI display depends on
|
The behaviour of the application when running on a high-DPI display depends on
|
||||||
@@ -141,7 +292,7 @@ full, per-monitor DPI awareness supported by Windows 10 version 1703 or later.
|
|||||||
[1]: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
|
[1]: https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests
|
||||||
[2]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
[2]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
||||||
|
|
||||||
macOS
|
macOS {#high_dpi_platform_mac}
|
||||||
-----
|
-----
|
||||||
|
|
||||||
DPI-aware applications must set their `NSPrincipalClass` to `wxNSApplication`
|
DPI-aware applications must set their `NSPrincipalClass` to `wxNSApplication`
|
||||||
|
|||||||
@@ -199,7 +199,8 @@ project, there are several things you must do.
|
|||||||
`defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO`
|
`defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO`
|
||||||
- Set the variables for use with the launch agent (application to OSX 10.10
|
- Set the variables for use with the launch agent (application to OSX 10.10
|
||||||
and up)
|
and up)
|
||||||
```
|
|
||||||
|
~~~{xml}
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
@@ -218,7 +219,7 @@ launchctl setenv WXWIN /Users/dconnet/devtools/wx/wxWidgets-3.1.5
|
|||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
```
|
~~~
|
||||||
|
|
||||||
### Other IDEs
|
### Other IDEs
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user