From 42bdf924b17a91203264693a36da6039ba8bbc53 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 13 Jan 2022 00:52:40 +0100 Subject: [PATCH 1/6] Fix code snippets markup in Doxygen overviews Markdown ``` doesn't work here, Doxygen-specific ~~~ needs to be used. --- docs/doxygen/overviews/high_dpi.md | 8 ++++---- docs/doxygen/overviews/install.md | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 7cf7ac07e1..2634fe6b8d 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -77,13 +77,13 @@ 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: -```cpp +~~~{cpp} myFrame->SetClientSize(wxSize(400, 300)); -``` +~~~ you can just replace it with -```cpp +~~~{cpp} myFrame->SetClientSize(myFrame->FromDIP(wxSize(400, 300))); -``` +~~~ Physical Pixels --------------- diff --git a/docs/doxygen/overviews/install.md b/docs/doxygen/overviews/install.md index 0e300cad06..f059d212bd 100644 --- a/docs/doxygen/overviews/install.md +++ b/docs/doxygen/overviews/install.md @@ -199,7 +199,8 @@ project, there are several things you must do. `defaults write com.apple.dt.Xcode UseSanitizedBuildSystemEnvironment -bool NO` - Set the variables for use with the launch agent (application to OSX 10.10 and up) -``` + +~~~{xml} @@ -218,7 +219,7 @@ launchctl setenv WXWIN /Users/dconnet/devtools/wx/wxWidgets-3.1.5 -``` +~~~ ### Other IDEs From caec339bc1756f60bd0a77ec97133ccf353da5f3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 13 Jan 2022 03:46:39 +0100 Subject: [PATCH 2/6] Improve documentation of various pixel types Also add a diagram showing the functions to use to convert between them, perhaps this can be more clear than textual description. Co-Authored-By: Stefan Csomor --- docs/doxygen/overviews/high_dpi.md | 79 ++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 14 deletions(-) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 2634fe6b8d..15321c08ef 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -69,12 +69,13 @@ pixels**, abbreviated as "DIP", that are always of the same size on all displays and all platforms. 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 -recommended and replacing them with the values based on the text metrics, i.e. -obtained using wxWindow::GetTextExtent(), or expressing them in dialog units -(see wxWindow::ConvertDialogToPixels()) is preferable. However 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. +is to stop using raw pixel values, as they mean different things under +different platforms when DPI scaling is used. Actually, using any pixel values +is not recommended and replacing them with the values based on the text +metrics, i.e. obtained using wxWindow::GetTextExtent(), or expressing them in +dialog units (see wxWindow::ConvertDialogToPixels()) is preferable. However +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: ~~~{cpp} @@ -84,23 +85,73 @@ you can just replace it with ~~~{cpp} 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. + +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 --------------- 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. -Physical pixels are never scaled, on any platform, and must be used when -drawing graphics elements to ensure that the best possible resolution is used. -For example, all operations on wxGLCanvas use physical pixels. +Physical pixels are never scaled, on any platform, and are used for the real +bitmap sizes. They are also used for drawing operations on wxGLCanvas, which +is _different_ from wxDC and wxGraphicsContext that use logical pixels. -To convert between logical and physical pixels, you can use -wxWindow::GetContentScaleFactor(): this is a value greater than or equal to 1, -so a value in logical pixels needs to be multiplied by it in order to obtain -the value in physical pixels. +Under MSW physical pixels are same as logical ones, but when writing portable +code you need to convert between logical and physical pixels using +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 -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. + +Summary of Different Pixel Kinds +-------------------------------- + +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(). +* Logical pixels and physical pixels: multiply or divide by wxWindow::GetContentScaleFactor(). +* 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, label = "GetContentScaleFactor()", headlabel = "multiply by", taillabel = "divide by"]; + 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 From c1532c8d3c7ed0093077d71a26a445e039c19b12 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 13 Jan 2022 04:24:53 +0100 Subject: [PATCH 3/6] Fix table of contents generation for high DPI overview Add the required anchors, just the headers are not taken into account for TOC generation. --- docs/doxygen/overviews/high_dpi.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 15321c08ef..06ef470544 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -2,7 +2,7 @@ High DPI Support in wxWidgets {#overview_high_dpi} ============================= [TOC] -Introduction +Introduction {#high_dpi_intro} ============ 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). -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 @@ -97,7 +97,7 @@ must be in logical pixels that can depend on the current DPI scaling, and so should never be fixed at compilation time. -Physical Pixels +Physical Pixels {#high_dpi_pp} --------------- In addition to (logical) pixels and DIPs discussed above, you may also need to @@ -121,7 +121,7 @@ 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. -Summary of Different Pixel Kinds +Summary of Different Pixel Kinds {#high_dpi_pixel_conversions} -------------------------------- Under MSW, logical pixels are always the same as physical pixels, but are @@ -154,7 +154,7 @@ digraph Pixels @enddot -High-Resolution Images and Artwork +High-Resolution Images and Artwork {#high_dpi_artwork} ================================== In order to benefit from the increased detail on High DPI devices you might want @@ -168,7 +168,7 @@ sizes / resolutions. [comment]: # (TODO: API and Use Cases) -Platform-Specific Build Issues +Platform-Specific Build Issues {#high_dpi_platform_specific} ============================== Generally speaking, all systems handle applications not specifically marked as @@ -177,7 +177,7 @@ up, resulting in blurry graphics and fonts, but globally preserving the application appearance. For the best results, the application needs to be 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 @@ -192,7 +192,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 [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` From b4f0ddd81e0b9f95c9beb07403a6f730e6a8f2cb Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Thu, 13 Jan 2022 04:25:22 +0100 Subject: [PATCH 4/6] Add wxBitmapBundle section to high DPI overview Explain how it can be created and used. --- docs/doxygen/overviews/high_dpi.md | 98 ++++++++++++++++++++++++++++-- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 06ef470544..1d038818c5 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -157,16 +157,104 @@ digraph Pixels 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 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 -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 -sizes / resolutions. +[1]: https://en.wikipedia.org/wiki/Scalable_Vector_Graphics + +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. + +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 +`` tag in all the places where wxBitmap could be specified using +`` before (of course, using the latter tag is still supported). Either +multiple bitmaps or a single SVG image can be used. -[comment]: # (TODO: API and Use Cases) Platform-Specific Build Issues {#high_dpi_platform_specific} ============================== From 74e1404a5af10e54bcfedc21f6b939a687b9b6d3 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 14 Jan 2022 00:43:14 +0100 Subject: [PATCH 5/6] Document using wxWindow::{From,To}Phys() for LP<->PP conversions This is simpler and more clear than dividing or multiplying by GetContentScaleFactor(). --- docs/doxygen/overviews/high_dpi.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 1d038818c5..2662e25dbd 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -107,7 +107,9 @@ bitmap sizes. They are also used for drawing operations on wxGLCanvas, which is _different_ from wxDC and wxGraphicsContext that use logical pixels. Under MSW physical pixels are same as logical ones, but when writing portable -code you need to convert between logical and physical pixels using +code you need to convert between logical and physical pixels. This can be done +using convenience wxWindow::FromPhys() and wxWindow::ToPhys() functions +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. @@ -133,7 +135,7 @@ 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(). -* Logical pixels and physical pixels: multiply or divide by wxWindow::GetContentScaleFactor(). +* 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: @@ -147,7 +149,7 @@ digraph Pixels 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, label = "GetContentScaleFactor()", headlabel = "multiply by", taillabel = "divide by"]; + 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] ; } From ead3e95bf43190d7bf095030ebcff06650c8b692 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 14 Jan 2022 00:43:56 +0100 Subject: [PATCH 6/6] Add a note about rounding the logical pixels up when converting This might be unexpected, so explicitly warn about possibly not getting back exactly the same value after a round-trip. --- docs/doxygen/overviews/high_dpi.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/doxygen/overviews/high_dpi.md b/docs/doxygen/overviews/high_dpi.md index 2662e25dbd..5ff3362215 100644 --- a/docs/doxygen/overviews/high_dpi.md +++ b/docs/doxygen/overviews/high_dpi.md @@ -123,6 +123,16 @@ 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. + + Summary of Different Pixel Kinds {#high_dpi_pixel_conversions} --------------------------------