Detecting if a display supports 30-bit Color

1.5k views Asked by At

Apple recently enabled 30-bit color support on OS X. They’ve posted some sample code that shows how to enable this. However, they don’t seem to provide an example for how you can detect when your app is running on a display that supports 30-bit color.

We’d like to be able to detect when the display(s) support 30-bit color and only enable 30-bit color for displays that support it and revert to 24-bit color otherwise.

Does anyone know how to do that?

So far I’ve tried using the CGDisplay APIs (CGDisplayCopyDisplayMode and CGDisplayModeCopyPixelEncoding) to query the display’s pixel encoding. But these seem to always return 24-bit encodings and CGDisplayModeCopyPixelEncoding was deprecated in Mac OS X 10.11. I’ve also tried using NSScreen’s “depth” property, but this also returns 24-bits per pixel.

The built-in System Information app is obviously able to get at this information, I just can’t figure out how they’re doing it. Any hints?

3

There are 3 answers

0
Cutterpillow On BEST ANSWER

As of macOS 10.12 Apple has some new APIs that allow you to detect if a display is capable of wide gamut color (i.e. deep color). There are a few ways to do this:

  1. Use NSScreen's - (BOOL)canRepresentDisplayGamut:(NSDisplayGamut)displayGamut

    NSArray<NSScreen *> * screens = [NSScreen screens];
    BOOL hasWideGamutScreen = NO;
    
    for ( NSScreen * screen in screens )
    {
        if ( [screen canRepresentDisplayGamut:NSDisplayGamutP3] )
        {
            hasWideGamutScreen = YES;
            break;
        }
    }
    
  2. Use CGColorSpaceIsWideGamutRGB(...):

    hasWideGamutScreen = CGColorSpaceIsWideGamutRGB( screen.colorSpace.CGColorSpace );
    
  3. NSWindow also has - (BOOL)canRepresentDisplayGamut:(NSDisplayGamut)displayGamut.

I don't know that you're guaranteed to be on a 30-bit capable display when the display is considered "wide gamut RGB" or capable of NSDisplayGamutP3, but this appears to be Apple's official way of determining if a display is capable of wide gamut color.

1
Ken Thomases On

There are various bad options.

First, if you log a display mode (i.e. cast to id and pass to NSLog(@"%@", ...)), you'll find that the real pixel encoding is in there. That's interesting, but you really don't want to parse that description.

If you pass (__bridge CFDictionaryRef)@{ (__bridge NSString*)kCGDisplayShowDuplicateLowResolutionModes: @YES } as the options parameter to CGDisplayCopyAllDisplayModes(), you'll find that you get a bunch of additional display modes. This key is documented in the headers but not the reference documentation. For a Retina display, some of the extra modes are the 2x-scaled counterparts of unscaled display modes. Others are the 24-bit counterparts of the 30-bit-masquerading-as-24-bit modes. These are identical in every way you can query via the API, but the logging shows the difference. (By the way, attempting to switch to one of those modes will fail.)

I think, but you'd have to verify, that you don't get these pairs of seemingly-identical modes except for a 30-bit-color-capable display.

You may be able to get the information from IOKit. You'd have to use the deprecated function CGDisplayIOServicePort() to get the service port of the IOFramebuffer object representing the GPU-display pair. You could then use IORegistryEntrySearchCFProperty() to search up the containment hierarchy in the Service plane to find an object which has a property like "display-bpc" or "display-pixel-component-bits" and get its value. At least, there's such an object and properties on a couple of systems I'm able to test, although they both use AMD GPUs and the property is on an AMD-specific object, so it may not be reliable.

Finally, you can launch a subprocess to run system_profiler -xml SPDisplaysDataType and use the property-list-serialization API to build a property list object from the resulting XML. Then, you can find the information in there. You'd find the relevant display by matching _spdisplays_display-vendor-id with CGDisplayVendorNumber(), _spdisplays_display-product-id with CGDisplayModelNumber(), and _spdisplays_display-serial-number with CGDisplaySerialNumber(). Then, the depth is under the key spdisplays_depth, where a value of CGSThirtyBitColor means 30-bit color.

You should also file a bug report with Apple requesting a sane way to do this.

0
Belden Fox On

What worked for me is passing the depthLimit property of my NSWindow to NSBitsPerPixelFromDepth and checking that the return value is greater than 24. Caveat: I only have two iMacs to test on, one from 2012 and another from 2017, and they're both running High Sierra.