简体   繁体   中英

Accurately determine DPI in Windows (10)

I have code that I believe is (technically) correct for getting the DPI of a monitor in Windows 10.

[DllImport("shcore.dll")]
static extern UInt32 GetDpiForMonitor(IntPtr hmonitor,
                                      int dpiType,
                                      out UInt32 dpiX,
                                      out UInt32 dpiY);

[DllImport("user32.dll")]
static extern IntPtr MonitorFromWindow(IntPtr hwnd, UInt32 dwFlags);
...
var whdl = Process.GetCurrentProcess().MainWindowHandle;
var mhdl = MonitorFromWindow(whdl, 0);

GetDpiForMonitor(mhdl, 2, out DpiX, out DpiY); // 255, 256

var ct = PresentationSource.FromVisual(MainImage).CompositionTarget;
var scaleX = ct.TransformToDevice.M11;
var scaleY = ct.TransformToDevice.M22;

var pixelWidth = (int)(GridImage.ActualWidth * DpiX / 96.0);
var pixelHeight = (int)(GridImage.ActualHeight * DpiY / 96.0);

writeableBitmap = new WriteableBitmap(pixelWidth, 
                                      pixelHeight,
                                      DpiX,
                                      DpiY,
                                      System.Windows.Media.PixelFormats.Bgr32,
                                      null);

I tested this with a 17" (laptop) 4K panel (3840x2160 with 250% scaling). I used a simple WPF grid control which contains an Image control. The DPI that comes from GetDpiForMonitor is (X:Y) 255:256. The problem is that this isn't accurate. When I draw a simple grid (lines DPI apart) the squares are not 1", they are 17 (raw) pixels short. The WriteableBitmap is inside a WPF Image control and that control has Stretch="None" .

Anyone know why these values are inaccurate or how to get accurate values. Is there a way to get a monitor's resolution and dimensions (so I could calculate the DPI)?

Update

Well this question has been down voted and I still don't have an answer, but I find this little puzzle both interesting and irritating ("interating", "irriresting"). Anyway, here's some additional info sent into the future for anyone that runs across it.

First this is a HP Envy 4K laptop (W2K87UA#ABA) running Windows 10 Pro Build 15063.483. Using the GetDevCaps I queried for the system monitor dimensions

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

private const int HORZSIZE = 4;
private const int VERTSIZE = 6;
private const double MM_TO_INCH_CONVERSION_FACTOR = 25.4;
...
var hDC = System.Drawing.Graphics.FromHwnd(whdl).GetHdc();
int horizontalSizeInMilliMeters = GetDeviceCaps(hDC, HORZSIZE);
double horizontalSizeInInches = horizontalSizeInMilliMeters / MM_TO_INCH_CONVERSION_FACTOR;
int vertivalSizeInMilliMeters = GetDeviceCaps(hDC, VERTSIZE);
double verticalSizeInInches = vertivalSizeInMilliMeters / MM_TO_INCH_CONVERSION_FACTOR;

This returns the following:

horizontalSizeInMilliMeters: 382
horizontalSizeInInches: 15.039370078740159
Measured by hand: 15 1/32 "      15.0625"   382.6 mm

vertivalSizeInMilliMeters: 214
verticalSizeInInches: 8.4251968503937018
Measured by hand:  8 15/32"       8.46875   215.1 mm

This all works out to be an X dpi of 255.329842931937 and a Y dpi of 256.373831775701, or approximately (255, 256) which is exactly what the GetDpiForMonitor returns.

Using a WriteableBitmap I draw a grid on the screen using the following code:

color = Colors.White;
int color_data = (color.R << 16)
               | (color.G << 8)
               | color.B;
dpiy = (int) DpiY;
for (var y = 0; y < height; y += dpiy) {
    for (var x = 0; x < width; x++) {
        var p = BackBuffer + (y * Stride) + (x * 4);
        unsafe {
            *((int*)p) = color_data;
            }
        }
    }
dpix = (int) DpiX;
for (var x = 0; x < width; x += dpix) {
    for (var y = 0; y < height; y++) {
        var p = BackBuffer + (y * Stride) + (x * 4);
        unsafe {
            *((int*)p) = color_data;
            }
        }
    }

When I measure the grid lines I find that I need to add about 17 pixels both ways to get a 1" unit square. If my "real" DPI were 272 (255 + 17) then the actual width of my screen would be 14.1176470588235" (3840/272).

Out of curiosity I opened Word 2016 set the scale to 100% and measured the Word "ruler" tool. It too is off ~15/16" instead of 1", I have to scale to between 105% and 106% in order to get a "true" inch. So whatever the issue is it also effects MS written apps.

WPF has per-monitor DPI support since .NET Framework 4.6.2. There is more information and an example available at GitHub: http://github.com/Microsoft/WPF-Samples/tree/master/PerMonitorDPI .

You may also want to check out the VisualTreeHelper.GetDpi method.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM