How does on-screen color inversion work in OS X?

纵饮孤独 提交于 2019-12-03 03:50:42
Nikolai Ruhe

The way Mac OS does the color inversion is (probably) by using Quartz Display Services to modify the graphics card's gamma table.

Graphics cards have two of these tables to modify color output after composition into the final frame buffer. One of them can be modified by applications to alter the way the screen shows any RGB value.

Here's code to invert the display:

//ApplicationServices includes CoreGraphics
#import <ApplicationServices/ApplicationServices.h>

int main(int argc, const char * argv[])
{
    CGGammaValue table[] = {1, 0};
    CGSetDisplayTransferByTable(CGMainDisplayID(), sizeof(table) / sizeof(table[0]), table, table, table);
    sleep(3);
    return 0;
}

To some degree, you can do this with Core Image filters. However, this is private API, so you need to be careful because these things might change or go away in future OS X releases and you obviously cannot submit your app to the App Store. I don't think something like this is possible with public APIs.

Edit: See Nikolai Ruhe's answer for a better method that uses public APIs. You can do some things with Core Image filters that you couldn't do with a gamma table (e.g. applying blur filters and the like), so I'll leave my answer here.

Here's an example of how to invert what's behind a window:

//Declarations to avoid compiler warnings (because of private APIs):
typedef void * CGSConnection;
typedef void * CGSWindowID;
extern OSStatus CGSNewConnection(const void **attributes, CGSConnection * id);
typedef void *CGSWindowFilterRef;
extern CGError CGSNewCIFilterByName(CGSConnection cid, CFStringRef filterName, CGSWindowFilterRef *outFilter);
extern CGError CGSAddWindowFilter(CGSConnection cid, CGSWindowID wid, CGSWindowFilterRef filter, int flags);
extern CGError CGSSetCIFilterValuesFromDictionary(CGSConnection cid, CGSWindowFilterRef filter, CFDictionaryRef filterValues);

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    [self.window setOpaque:NO];
    [self.window setAlphaValue:1.0];
    [self.window setBackgroundColor:[NSColor colorWithCalibratedWhite:0.0 alpha:0.1]];
    self.window.level = NSDockWindowLevel;

    CGSConnection thisConnection;
    CGSWindowFilterRef compositingFilter;
    int compositingType = 1; // under the window
    CGSNewConnection(NULL, &thisConnection);
    CGSNewCIFilterByName(thisConnection, CFSTR("CIColorInvert"), &compositingFilter);
    NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:3.0] forKey:@"inputRadius"];
    CGSSetCIFilterValuesFromDictionary(thisConnection, compositingFilter, (CFDictionaryRef)options);
    CGSAddWindowFilter(thisConnection, (CGSWindowID)[self.window windowNumber], compositingFilter, compositingType);    
}

@end

(adapted from Steven Troughton Smith's article here)

The effect isn't perfect because for some reason it's necessary that the window has a background color that isn't fully transparent, but it's pretty close.

To affect the whole screen, you could create a borderless window that has ignoresMouseEvents set to YES (so you can click through it).

You can experiment with other filters, but not all of them may work for this. There's some info about the CGS... functions in this reverse-engineered header: http://code.google.com/p/undocumented-goodness/source/browse/trunk/CoreGraphics/CGSPrivate.h

The way OS X itself does it is through a set of undocumented CoreGraphics API calls. I don't think they're declared in any official header file, but you can always just declare the prototypes yourself.

// clang -g -O2 -std=c11 -Wall -framework ApplicationServices

#include <stdio.h>
#include <ApplicationServices/ApplicationServices.h>

CG_EXTERN bool CGDisplayUsesInvertedPolarity(void);
CG_EXTERN void CGDisplaySetInvertedPolarity(bool invertedPolarity);

int
main(int argc, char** argv)
{
    bool isInverted = CGDisplayUsesInvertedPolarity();
    printf("isInverted = %d\n", isInverted);

    sleep(2);
    CGDisplaySetInvertedPolarity(!isInverted);
    printf("Polarity is now: %d\n", CGDisplayUsesInvertedPolarity());

    sleep(2);
    CGDisplaySetInvertedPolarity(isInverted);
    printf("Polarity is now: %d\n", CGDisplayUsesInvertedPolarity());

    return 0;
}

There are similar API calls for other accessibility features, such as grayscale:

CG_EXTERN bool CGDisplayUsesForceToGray(void);
CG_EXTERN void CGDisplayForceToGray(bool forceToGray);

This article explains how another user inverted his colors, however that wouldn't be an overlay, it would still accomplish your goal. Although this is in C# that would be your basic premise perhaps,a s those same ideas can work in Obj-c.

Basically this article says to convert your RGB color in HSV mode then invert the Hue value. To do this you must change Hue by 360-Hue and then convert back to RGB mode.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!