GNOME Bugzilla – Bug 681784
colorspaces used in gtk+ and cairo quartz backends do not match
Last modified: 2016-10-21 21:39:50 UTC
Created attachment 221050 [details] [review]
cairo quartz backend uses CGColorSpaceCreateDeviceRGB() as working colorspace and gdk quartz backend uses CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB).
Apple documentation ( https://developer.apple.com/library/mac/documentation/graphicsimaging/reference/CGColorSpace/Reference/reference.html#//apple_ref/doc/uid/TP30000949-CH1g-F16994 ) state this:
In OS X v10.4 and later, this color space is no longer device-dependent and is replaced by the generic counterpart—kCGColorSpaceGenericRGB—described in “Color Space Names”. If you use this function in OS X v10.4 and later, colors are mapped to the generic color spaces. If you want to bypass color matching, use the color space of the destination context.
So it would appear that there shouldn't be a problem. In reality that statement is just plain wrong, and this mismatch causes visible color difference between stuff rendered by gtk and cairo with matching color definitions (test program is attached). While color-managing GUI widgets isn't a bad idea, I suggest changing gdk quartz backend to use CGColorSpaceCreateDeviceRGB too (patch is attached). Cairo doesn't support specifying color space, so if we change cairo code to match gtk, there would be no way to draw exact colors you want, which is needed for example if program wants to display an image, which it already color managed for user monitor. Also since GTK on Linux doesn't do color managing, this change will make GTK programs to look the same on both platforms.
Created attachment 221051 [details]
test case confirmed to fail on Tiger, Snow Leopard and Lion. patch confirmed to work on all 3 platforms.
this should be applied to gtk-2-24 and master.
I tested it on Mountain Lion, so it's a safe bet that this bug is present on all OS X versions released to current date. Of course Apple may decide in future to actually follow it's own documentation and implement notice i cited, but I guess this would have to be dealt with when and if it happens.
(In reply to comment #2)
> test case confirmed to fail on Tiger, Snow Leopard and Lion. patch confirmed to
> work on all 3 platforms.
> this should be applied to gtk-2-24 and master.
OK. Are you going to commit it?
Javier marked the attachment as "reviewed" for you, but you should mark it as "accepted, commit now" unless you just do it, in which case you should mark it as "committed".
I am unwilling to commit it due to my skittishness regarding git.
But is absolutely should get committed. It is self-evidently and practically correct.
Seems totally obvious to me too, will take care of applying.
I started to apply this to master (planning to backport it to 2-24 with git cherry-pick) and found that it doesn't apply. The file gdkcolor-quartz.c has been removed altogether. Digging a bit deeper, there are 7 calls to CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB) in 2-24, but only one (in gdkwindow-quartz.c, which *isn't* one of the places in 2-24) in master. There is also a call to CGColorSpaceCreateWithName (kCGColorSpaceGenericGray) in gdkcolor-quartz.c.
ISTM that I should replace all instances just like the patch, but that makes me a bit nervous. Is there a good way to test that it's doing the right thing?
There's CGColorSpaceCreateDeviceGray() as alternative to CGColorSpaceCreateWithName(kCGColorSpaceGenericGray), but I haven't tested is there difference between them or not. But again, if there isn't - then nothing will change, if there is - it will behave more like Linux (and other operating systems probably). I guess it's logical to change all CGColorSpaceCreateWithName to these device-dependant functions, it shouldn't break anything, but of course some testing should be done.
Mitch pointed me to this patch last week. I have a vague recollection that something was up with the device colorspace calls, but I cannot remember what exactly (or whether this recollection is right at all). Might be worth checking the git history.
(In reply to comment #7)
> I started to apply this to master (planning to backport it to 2-24 with git
> cherry-pick) and found that it doesn't apply. The file gdkcolor-quartz.c has
> been removed altogether. Digging a bit deeper, there are 7 calls to
> CGColorSpaceCreateWithName (kCGColorSpaceGenericRGB) in 2-24, but only one (in
> gdkwindow-quartz.c, which *isn't* one of the places in 2-24) in master.
This has completely changed when the code was moved over from 2.x to 3.x. This is because in 3.x Cairo contexts are created differently / at different locations in the code. GdkColor was part of the code in GDK that could handle drawing to pixmaps and windows (and for OS X basically implemented in terms of CoreGraphics), which is no longer there.
> is also a call to CGColorSpaceCreateWithName (kCGColorSpaceGenericGray) in
IIRC this was used for implementing drawing of bitmaps, which are used for example as masks.
> ISTM that I should replace all instances just like the patch, but that makes me
> a bit nervous. Is there a good way to test that it's doing the right thing?
Apart from testing with usual applications, especially test applications that are using the old-style GDK drawing (so the pre-Cairo way of doing things), applications drawing into pixmaps with such calls and applications creating GdkBitmaps. I recall testing most of this using the tests in tests/ (for bitmaps, look at tests creating DnD cursors using a mask and bitmap).
OK, I made the changes to gtk-2-24. I ran those of the tests that work on quartz and gtk-demo and everything (pixmaps, icons, etc.) looked reasonable, so I went ahead and committed and pushed.
After beating on the testcase enough to get it to compile with Gtk3 and to show the cairo-drawn square, though, the one call in gtkwindow.c doesn't have any effect... but changing the color string in main.c's gtk_rc_parse_string() call doesn't either. The window background is a light gray, lighter than the cairo square. I'm out of time for now, but I want to dig into it a bit more before I change the one CGColorSpaceCreateWithName () call.
I got it to work more or less by using gtk_widget_override_background_color(), and the shades match perfectly. This is of course what one would expect since Gtk3 is using Cairo primitives directly, but it confirms that the problem doesn't exist on Gtk3.
I've recently re-investigated this issue and wanted to put my corrected findings here in case someone stumbles on this bug report.
Apple documentation has updated since then, the notice I cited is now absent from there. Both old and current documentation is wrong (I tested 10.8 and 10.10 OS X versions) - CGColorSpaceCreateDeviceRGB doesn't correspond to either monitor (no correction applied) or GenericRGB profile, in fact it seems to be equal to SRGB one (so OS X indeed does internal color management of cairo and gtk).
Actual difference between CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB) and CGColorSpaceCreateDeviceRGB()/CGColorSpaceCreateWithName(kCGColorSpaceSRGB) is gamma, first one uses 1.8, second one is the same as sRGB (~2.2). That's why attached test case failed. Judging by how Safari displays colors and by screenshots taken with Gram utility sRGB profile is the correct one to use on OS X.
It might be a good idea to change CGColorSpaceCreateDeviceRGB() to CGColorSpaceCreateWithName(kCGColorSpaceSRGB) in both gtk and cairo to guard against changes in future releases of OS X. Meaning of CGColorSpaceCreateDeviceRGB is ambiguous, while kCGColorSpaceSRGB is sRGB.
While the solution of using CGColorSpaceCreateWithName(kCGColorSpaceSRGB) is fine if one wants to have a color-managed GUI, where it is assumed that the widget colors are expressed in the sRGB colorspace, this solution is seriously limiting for graphical applications that need to accurately represent colors on wide-gamut displays (for example photo-editing programs like Rawtherapee and Darktable).
In the current scheme, the pixel data is first converted to sRGB by the user application, and then it is converted to the wide-gamut display colorspace by the underlying OSX graphic engine. The net result is that any color outside of the sRGB gamut is clipped in the first step and cannot be reproduced on wide-gamut displays.
An alternative solution would be to use CGDisplayCopyColorSpace(CGDirectDisplayID display), although in this case one needs to pass to GTK/CAIRO the ID of the display in which the widgets are drawn (only relevant in a multi-head setup).
In this case, OSX color management is completely bypassed and should be handled by the user application. This is how things work under Linux and, I guess, Windows...
Also consider what happens when window is dragged from one monitor to another. Color managing isn't something that can be fixed with a hack, cairo has to gain support for specifying color profiles, only then this issue can be solved correctly. And I agree that this is a real problem, I myself stumbled on it when porting darktable, so ATM it just uses sRGB profile on OSX.
I totally agree. The only workaround I could figure out at the moment is to let the user application detect the change of active monitor, and pass the corresponding ID to CAIRO/GTK via a global variable.
That's really dirty, but I see no other solution if we want proper color management for photographic applications under OSX.
Do you have any idea if CAIRO surface color management is being developed? Are there any chances to see that in the near future?
global variable won't work, because application can have several windows, residing on different monitors... I've no idea about cairo development plans.
(In reply to parafin from comment #16)
> global variable won't work, because application can have several windows,
> residing on different monitors... I've no idea about cairo development plans.
Do you know if there is a supported way to access the parent GdkWindow associated to a GdkDrawable object?
If that is possible, then it would be at least possible to properly take control of the color management for several GTK2 widgets, including drawing areas (which are commonly used to display images). This because it would be possible to detect the monitor to which a specific GdkDrawable is associated, and retrieve the proper CGColorSpace.
For GTK3 and Cairo surfaces, I don't know...