'Is there any way to find the scaling coefficient for a CGDisplay?

I'm currently doing some tests to see if my app runs correctly on Retina Macs. I have installed Quartz Debug for this purpose and I'm currently running a scaled mode. My screen mode is now 960x540 but of course the physical size of the monitor is still Full HD, i.e. 1920x1080 pixels.

When querying the monitor database using CGGetActiveDisplayList() and then using CGDisplayBounds() on the individual monitors in the list, the returned monitor size is 960x540. This is what I expected because CGDisplayBounds() is said to use the global display coordinate space, not pixels.

To my surprise, however, CGDisplayPixelsWide() and CGDisplayPixelsHigh() also return 960x540, although they're explicitly said to return pixels so I'd expect them to return 1920x1080 instead. But they don't.

This leaves me wondering how can I retrieve the real physical resolution of the monitor instead of the scaled mode using the CGDisplay APIs, i.e. 1920x1080 instead of 960x540 in my case? Is there any way to get the scaling coefficient for a CGDisplay so that I can compute the real physical resolution on my own?

I know I can get this scaling coefficient using the backingScaleFactor method but this is only possible for NSScreen, how can I get the scaling coefficient for a CGDisplay?



Solution 1:[1]

You need to examine the mode of the display, not just the display itself. Use CGDisplayCopyDisplayMode() and then CGDisplayModeGetPixelWidth() and CGDisplayModeGetPixelHeight(). These last two are relatively newer functions and the documentation primarily exists in the headers.

And, of course, don't forget to CGDisplayModeRelease() the mode object.

Solution 2:[2]

From Ken's answer it is not obvious how you find the native mode(s). To do this, call CGDisplayModeGetIOFlags and choose from the modes that have the kDisplayModeNativeFlag set (see IOKit/IOGraphicsTypes.h, the value is 0x02000000).

const int kFlagNativeMode = 0x2000000;  // see IOGraphicsTypes.h
const CGFloat kNoSize = 100000.0;

NSScreen *screen = NSScreen.mainScreen;    
NSDictionary *desc = screen.deviceDescription;
unsigned int displayID = [[desc objectForKey:@"NSScreenNumber"] unsignedIntValue];
CGSize displaySizeMM = CGDisplayScreenSize(displayID);
CGSize nativeSize = CGSizeMake(kNoSize, kNoSize);
CFStringRef keys[1] = { kCGDisplayShowDuplicateLowResolutionModes };
CFBooleanRef values[1] = { kCFBooleanTrue };
CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void**)keys, (const void**)values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
CFArrayRef modes = CGDisplayCopyAllDisplayModes(displayID, options);
int n = CFArrayGetCount(modes);
for (int i = 0;  i < n;  i++) {
    CGDisplayModeRef mode = (CGDisplayModeRef) CFArrayGetValueAtIndex(modes, i);
    if (CGDisplayModeGetIOFlags(mode) & kFlagNativeMode) {
        int w = CGDisplayModeGetWidth(mode);
        // We get both high resolution (screen.backingScaleFactor > 1)
        // and the "low" resolution, in CGFloat units. Since screen.frame
        // is CGFloat units, we want the lowest native resolution.
        if (w < nativeSize.width) {
            nativeSize.width = w;
            nativeSize.height = CGDisplayModeGetHeight(mode);
        }
    }
    // printf("mode: %dx%d %f dpi 0x%x\n", (int)CGDisplayModeGetWidth(mode), (int)CGDisplayModeGetHeight(mode), CGDisplayModeGetWidth(mode) / displaySizeMM.width * 25.4, CGDisplayModeGetIOFlags(mode));
}
if (nativeSize.width == kNoSize) {
    nativeSize = screen.frame.size;
}
CFRelease(modes);
CFRelease(options);

float scaleFactor = screen.frame.size.width / nativeSize.width;

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ken Thomases
Solution 2 prewett