How to read the physical screen size of OSX?
Asked Answered
A

2

20

I would like to know the physical screen size under Mac OSX. But NSDeviceResolution is always reporting wrong value (72), so the calculation result of resolution / dpi is wrong.

Inside "About This Mac", there is a Mac model string there, mine is "15-inch, Early 2011". So I'm wondering should there be a way (in obj-c probably), to read that string and then I can use that as the physical screen size.

Any help is appreciated.

Anywise answered 25/9, 2012 at 18:44 Comment(0)
I
28

You can use CGDisplayScreenSize to get the physical size of a screen in millimetres. From that you can compute the DPI given that you already know the resolution.

So e.g.

NSScreen *screen = [NSScreen mainScreen];
NSDictionary *description = [screen deviceDescription];
NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue];
CGSize displayPhysicalSize = CGDisplayScreenSize(
            [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);

NSLog(@"DPI is %0.2f", 
         (displayPixelSize.width / displayPhysicalSize.width) * 25.4f); 
         // there being 25.4 mm in an inch

That @"NSScreenNumber" thing looks dodgy but is the explicit documented means of obtaining a CGDirectDisplayID from an NSScreen.

Inattentive answered 25/9, 2012 at 19:29 Comment(1)
Just one remark - NSDeviceSize contains size in system points, not physical pixels. On retina display (for example iMac 27 retina, or new macbook pro) system point is typically 2 pixels. So you have to multiply NSDeviceSize by [[NSScreen mainScreen] backingScaleFactor] to get physical pixels. There is even more mess if somebody sets "Scaled" Resolution in macOS System Preferences->DisplaysDunlop
B
8

Tommy’s answer above is excellent — I’ve ported it to Swift (for my own use) and am posting that here as a reference, but Tommy’s should be consider canonical.

import Cocoa

public extension NSScreen {
    var unitsPerInch: CGSize {
        let millimetersPerInch:CGFloat = 25.4
        let screenDescription = deviceDescription
        if let displayUnitSize = (screenDescription[NSDeviceDescriptionKey.size] as? NSValue)?.sizeValue,
            let screenNumber = (screenDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value {
            let displayPhysicalSize = CGDisplayScreenSize(screenNumber)
            return CGSize(width: millimetersPerInch * displayUnitSize.width / displayPhysicalSize.width,
                          height: millimetersPerInch * displayUnitSize.height / displayPhysicalSize.height)
        } else {
            return CGSize(width: 72.0, height: 72.0) // this is the same as what CoreGraphics assumes if no EDID data is available from the display device — https://developer.apple.com/documentation/coregraphics/1456599-cgdisplayscreensize?language=objc
        }
    }
}

if let screen = NSScreen.main {
    print("main screen units per inch \(screen.unitsPerInch)")
}

Please note that the value returned is kind of effectively the ‘points per inch’ (but not for all definitions; see below) and almost never the ‘pixels per inch’ — modern Macs have a number of pixels per point that depends on the current “Resolution” setting in System Preferences and the inherent resolution of the device (Retina displays have a lot more pixels).

What you do know about the return value is that if you draw a line with code like

CGRect(origin: .zero, size: CGSize(width: 10, height: 1)).fill()

the line will be 1 / pointsPerInch.height inches high and 1 / pointsPerInch.width inches wide if you measure it with a very precise ruler held up to your screen.

(For a long time graphics frameworks have defined a ‘point’ as both “1/72nd of an inch in the real world” and also as “whatever the width or height of a box that’s 1 x 1 units ends up being on the current monitor at the current resolution — two definitions that are usually in conflict with each other.)

So for this code I use the word ‘unit’ to make it clear we’re not dealing with 1/72nd of an inch, nor 1 physical pixel.

Betray answered 28/4, 2019 at 0:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.