Skip to content

Instantly share code, notes, and snippets.

@marler8997
Last active August 30, 2023 16:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marler8997/9f39458d26e2d8521d48e36530fbb459 to your computer and use it in GitHub Desktop.
Save marler8997/9f39458d26e2d8521d48e36530fbb459 to your computer and use it in GitHub Desktop.
Win32 DPI And Monitor Scaling

Win32DPI and MonitorScale

In the win32 API, DPI doesn't actually mean "Dots Per Inch". Instead, it's just another way to represent the MonitorScale. I'll use the term "Win32DPI" to differentiate it from a real DPI. These two values have a one-to-one mapping:

Win32DPI = 96 * MonitorScale
MonitorScale = Win32DPI / 96

Since these values are just 2 different ways of looking at the same thing, if things get confusing you can always forget about one and just think about the other. MonitorScale is the value exposed to end users, controlled by going to "Display Settings" > "Scale and Layout" whereas Win32DPI is the value used by the win32 API. Monitor scaling was introduced to Windows to address programs becoming "too small" when resolution density increased. This means MonitorScale is always >= 100% which in turn also means Win32DPI >= 96.

The reason why 100% Monitor scale maps to a Win32DPI value of 96 is because this used to be the typical physical DPI of monitors. Nowadaws monitors regularly have physical DPI's over 300.

The Win32DPI/MonitorScale concept biforcates coordinates based on whether they are "DPI Aware". A "DPI Aware" coordinate maps to a pixel value on the screen, whereas a "DPI Unaware" coordinate can map to a square of 1 or more fractional pixels. The reason for doing this is that as monitors became more dense, programs written at a time when they were less dense became smaller. In some cases this made the program harder to use. To address this windows introduced the ability to scale your entire system up to accomodate these legacy programs without having to change them. Windows would take the pixels rastered by a program and scale them up afterwards. Note this has a performance cost and can make the program look "fuzzy". Windows also provided new APIs that allow a program to opt-in to use "DPI Aware" coordinate and handle scaling themselves. An application doing this can look more clean/crisp because it gives them access to all pixels. However, the developer needs to be careful about mixing coordinates that may or may not be "DPI Aware". Also, the DPI can change while the program is running so they should listen for changes and respond accordingly.

Microsoft High DPI Application Development

https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows?redirectedfrom=MSDN

When updating a System DPI-aware application, some common steps to follow are:

  • Mark the process as per-monitor DPI aware (V2) using an application manifest (or other method, depending on the UI framework(s) used).
  • Make UI layout logic reusable and move it out of application-initialization code such that it can be reused when a DPI change occurs (WM_DPICHANGED in the case of Windows (Win32) programming).
  • Invalidate any code that assumes that DPI-sensitive data (DPI/fonts/sizes/etc.) never need to be updated. It is a very common practice to cache font sizes and DPI values at process initialization. When updating an application to become per-monitor DPI aware, DPI-sensitive data must be reevaluated whenever a new DPI is encountered.
  • When a DPI change occurs, reload (or re-rasterize) any bitmap assets for the new DPI or, optionally, bitmap stretch the currently loaded assets to the correct size.
  • Grep for APIs that are not Per-Monitor DPI aware and replace them with Per-Monitor DPI-aware APIs (where applicable). Example: replace GetSystemMetrics with GetSystemMetricsForDpi.
    • GetSystemMetrics, GetSystemMetricsForDpi
    • AdjustWindowRectEx, AdjustWindowRectExForDpi
    • SystemParametersInfo, SystemParametersInfoForDpi
    • GetDpiForMonitor, GetDpiForWindow
  • Test your application on a multiple-display/multi-DPI system.
  • For any top-level windows in your application that you are unable to update to properly DPI scale, use mixed-mode DPI scaling (described below) to allow bitmap stretching of these top-level windows by the system.

High DPI Api Reference: https://learn.microsoft.com/en-us/windows/win32/hidpi/high-dpi-reference

Blog about how one project starte using per-monitor aware DPI: https://blog.marcocantu.com/blog/2018-nov-vcl-permonitorv2_getsystemmetrics.html

Handling WM_DPICHANGED properly

https://stackoverflow.com/questions/45110655/the-recommended-window-size-sent-with-the-wm-dpichanged-message-is-too-big

Question: The recommended window size sent with the WM_DPICHANGED message is too big

Solution: The problem occurs due to a Windows bug, which causes the new window size to be incorrectly calculated. The bug can be worked around by handling the WM_GETDPISCALEDSIZE message and calculating the new window size yourself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment