using WPFluent.Interop; using WPFluent.Win32; namespace WPFluent.Hardware; /// /// Provides access to various DPI-related methods. /// internal static class DpiHelper { /// /// Default DPI value. /// internal const int DefaultDpi = 96; [ThreadStatic] private static Matrix _transformToDevice; [ThreadStatic] private static Matrix _transformToDip; public static float ScaleX => GetScale().X; public static float ScaleY => GetScale().Y; private static (float X, float Y) GetScale() { nint hdc = User32.GetDC(0); float scaleX = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.LOGPIXELSX); float scaleY = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.LOGPIXELSY); User32.ReleaseDC(IntPtr.Zero, hdc); return new(scaleX / 96f, scaleY / 96f); } /// /// Convert a point in system coordinates to a point in device independent pixels (1/96"). /// /// Returns the parameter converted to the device independent coordinate system. public static Point DevicePixelsToLogical(Point devicePoint, double dpiScaleX, double dpiScaleY) { _transformToDip = Matrix.Identity; _transformToDip.Scale(1d / dpiScaleX, 1d / dpiScaleY); return _transformToDip.Transform(devicePoint); } public static Rect DeviceRectToLogical(Rect deviceRectangle, double dpiScaleX, double dpiScaleY) { Point topLeft = DevicePixelsToLogical( new Point(deviceRectangle.Left, deviceRectangle.Top), dpiScaleX, dpiScaleY); Point bottomRight = DevicePixelsToLogical( new Point(deviceRectangle.Right, deviceRectangle.Bottom), dpiScaleX, dpiScaleY); return new Rect(topLeft, bottomRight); } public static Size DeviceSizeToLogical(Size deviceSize, double dpiScaleX, double dpiScaleY) { Point pt = DevicePixelsToLogical(new Point(deviceSize.Width, deviceSize.Height), dpiScaleX, dpiScaleY); return new Size(pt.X, pt.Y); } // TODO: Look into utilizing preprocessor symbols for more functionality // ---- // There is an opportunity to check against NET46 if we can use // VisualTreeHelper in this class. We are currently not utilizing // it because it is not available in .NET Framework 4.6 (available // starting 4.6.2). For now, there is no need to overcomplicate this // solution for some infrequent DPI calculations. However, if this // becomes more central to various implementations, we may want to // look into fleshing it out a bit further. // ---- // Reference: https://docs.microsoft.com/en-us/dotnet/standard/frameworks /// /// Gets the DPI values from . /// /// /// The DPI values from . If the property cannot be accessed, the default value is returned. /// public static DisplayDpi GetSystemDpi() { System.Reflection.PropertyInfo? dpiXProperty = typeof(SystemParameters).GetProperty( "DpiX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (dpiXProperty == null) { return new DisplayDpi(DefaultDpi, DefaultDpi); } System.Reflection.PropertyInfo? dpiYProperty = typeof(SystemParameters).GetProperty( "Dpi", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (dpiYProperty == null) { return new DisplayDpi(DefaultDpi, DefaultDpi); } return new DisplayDpi((int)dpiXProperty.GetValue(null, null)!, (int)dpiYProperty.GetValue(null, null)!); } /* /// /// Occurs when application DPI is changed. /// public static event EventHandler DpiChanged; */ /// /// Gets DPI of the selected . /// /// The window that you want to get information about. public static DisplayDpi GetWindowDpi(Window? window) { if (window is null) { return new DisplayDpi(DefaultDpi, DefaultDpi); } return GetWindowDpi(new WindowInteropHelper(window).Handle); } /// /// Gets DPI of the selected based on it's handle. /// /// Handle of the window that you want to get information about. public static DisplayDpi GetWindowDpi(IntPtr windowHandle) { if (windowHandle == IntPtr.Zero/* || !UnsafeNativeMethods.IsValidWindow(windowHandle)*/) { return new DisplayDpi(DefaultDpi, DefaultDpi); } var windowDpi = (int)User32.GetDpiForWindow(windowHandle); return new DisplayDpi(windowDpi, windowDpi); } /// /// Convert a point in device independent pixels (1/96") to a point in the system coordinates. /// /// A point in the logical coordinate system. /// Horizontal DPI scale. /// Vertical DPI scale. /// Returns the parameter converted to the system's coordinates. public static Point LogicalPixelsToDevice(Point logicalPoint, double dpiScaleX, double dpiScaleY) { _transformToDevice = Matrix.Identity; _transformToDevice.Scale(dpiScaleX, dpiScaleY); return _transformToDevice.Transform(logicalPoint); } public static Rect LogicalRectToDevice(Rect logicalRectangle, double dpiScaleX, double dpiScaleY) { Point topLeft = LogicalPixelsToDevice( new Point(logicalRectangle.Left, logicalRectangle.Top), dpiScaleX, dpiScaleY); Point bottomRight = LogicalPixelsToDevice( new Point(logicalRectangle.Right, logicalRectangle.Bottom), dpiScaleX, dpiScaleY); return new Rect(topLeft, bottomRight); } public static Size LogicalSizeToDevice(Size logicalSize, double dpiScaleX, double dpiScaleY) { Point pt = LogicalPixelsToDevice(new Point(logicalSize.Width, logicalSize.Height), dpiScaleX, dpiScaleY); return new Size { Width = pt.X, Height = pt.Y }; } public static Thickness LogicalThicknessToDevice(Thickness logicalThickness, double dpiScaleX, double dpiScaleY) { Point topLeft = LogicalPixelsToDevice( new Point(logicalThickness.Left, logicalThickness.Top), dpiScaleX, dpiScaleY); Point bottomRight = LogicalPixelsToDevice( new Point(logicalThickness.Right, logicalThickness.Bottom), dpiScaleX, dpiScaleY); return new Thickness(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); } }