This page is the latest incarnation of my official website. It’s a compendium of things I have published and other stuff I made during my spare time.
In this blog, I will keep track of the updates of this site. If you are interested, just subscribe to the RSS feed.
|Display P3 vs sRGB in Color Palettes|
Sun, 08 Apr 2018 17:44:37 +0100
In my previous blog post I talked about the Display P3 color space, a wide gamut RGB color space used by Apple. I tried to visualize the difference between displayP3 and sRGB colors using Self-Organizing Maps, but they were of no much use to see visualize the difference.
Since then, I've been working on an app I called Palettist to compute color palettes using Display P3. The app, still not out in the App Store, includes two interesting options to produce examples of displayP3-only images. First, you can select "displayP3 - sRGB" as the input color space, so only colors outside the sRGB gamut, but inside the displayP3 gamut, are used in the color palette. Then, you can select an "sRGB comparison shape" to render a shape inside each color bin where the color is clamped to its equivalent color in sRGB color space. This is great to evaluate different displays and Display P3 capabilities. Check the online manual for details.
Here's the first example of displayP3 only colors:
Display P3 color palettes by color categoryFor reference, I've created more of these displayP3-only palettes, clustered by color category, so it's easier to appreciate the gamut differences depending on the color.
Display P3 in Metal SDKTo close, just some programming notes. It took me a while to figure out how to render displayP3 colors in Metal. I wrote an extensive post in Stack Overflow. I'll summarize here the main points. MTKViews have a colorSpace property in macOS, but not on iOS. I suspect the difference is because color management on iOS is targeted (see Best practices for color management). So how does it work, then? The solution is simple. You can set an output surface in Metal with a texture in extended range pixel format. For instance, bgra10_xr_srgb. When you do this, if you output (1, 0, 0) in your shader, you will obtain the expected sRGB red color, but if you make its value greater than one, the color will be even redder (provided that your iPhone/iPad display supports it), and it will fall inside the Display P3 gamut. So if your rendering pipeline assumes Display P3 linear color space all the way through, before you render your colors to the output texture, multiply by the 3x3 matrix that linearly converts from displayP3 to sRGB (you can find the matrix in my previous blogpost). DO NOT CLAMP OR SATURATE the output! Some values will be negative, and some will be greater than 1. And that's OK. Just make sure the pixel format of your output texture is one of the extended range ones. If it ends in "_srgb", it will also apply the gamma for you. And that's all! Check the Stack overflow post for details. Please let me know if this is useful to you. Also, please download Palettist and try generating some displayP3 palettes yourself 😊 Come back here or follow me on twitter to find out when the app gets published. Hopefully, somewhere around next week, if Apple doesn't find any issues. I'm a bit anxious because it's the first app I release using a Metal-only library, my VidEngine. Wish me luck.
|Exploring the display-P3 color space|
Sun, 11 Mar 2018 11:49:01 +0000
About display-P3I recently found out that the iPhoneX supports a color space called display-P3, or DCI-P3. I read about it in Apple's Human Interface Guidelines, and I immediately started thinking of ways of generating examples to illustrate the difference between sRGB and P3, on a display that supports it. My Mac screen doesn't, but both my iPad and the iPhoneX do. I found a few interesting examples here: Wide Color Gamut examples.
Visualizing P3-sRGBWhat I thought I could do is to generate samples that are in the P3 gamut, but out the sRGB gamut, i.e. the difference between P3 and sRGB, and perhaps generate a palette of P3-only colors, just to see how it looks. You can visualize the volume difference of the 2 color spaces easily with the ColorSync Utility on Mac. I've captured a couple of anim gifs, with both the XYZ and the L*a*b* axis. See below, where the bigger white volume is the P3 color space,
Computing the difference P3-sRGBI've created a series of unit tests to test color conversions, so you can jump straight to the point. But I'll explain a bit about it here. The first thing to read is about Color Management is OS X and iOS. In short, you can use use UIColor to easily create color instances in both sRGB and displayP3. But notice that those colors will have the gamma already applied to them. If you need linear values, or other color spaces like XYZ, you will need to use Core Graphics directly, the CGColor class. The problem of those conversions, apart from how cumbersome is to do any simple operation with all those classes because of all the wrapping and unwrapping, is that the values are automatically clamped. I need unclamped values because I need to know if a value is outside the gamut. There might be a way to do that programmatically with those classes, but I couldn't find one. Anyway, I wanted to understand the color conversion in detail, so I implemented my own set of conversions, and I used the unit tests to compare with Apple's classes and the ColorSync Utility. All my conversions are in this file. In BruceLindbloom.com you can find the formulas to convert from an RGB color space to XYZ, and viceversa. You will need a series of primaries, that you can also find in that page for sRGB. The sRGB color primaries adapted to a D50 white point are these,
public static let sRGB = RGBColorSpace( // primaries adapted to D50 red: CiexyY(x: 0.648431, y: 0.330856, Y: 0.222491), green: CiexyY(x: 0.321152, y: 0.597871, Y: 0.716888), blue: CiexyY(x: 0.155886, y: 0.066044, Y: 0.060621), white: .D50)Note that the white point is D50, not D65 as I wrongly assumed at the beginning... It took my a while to realize that I was wrong, until I started creating unit tests... Although I couldn't find any mention to D50 in the CGColor documentation, you can verify the white point of the sRGB color profile with the ColorSync Utility. The problem was finding the primaries for displayP3. The DCI-P3 page in Wikipedia, and in this article, say these are the values (assuming Y=1),
public static let dciP3 = RGBColorSpace( red: CiexyY(x: 0.680, y: 0.320), green: CiexyY(x: 0.265, y: 0.690), blue: CiexyY(x: 0.150, y: 0.060), white: .D65)If I use those, the RGB to XYZ conversion matrix (column-major) results in,
0.486569 0.265673 0.198187 0.228973 0.691752 0.0792749 0.0 0.0451143 1.04379But checking the primaries in the "Display P3.icc" profile results in,
0.5151 0.292 0.1571 0.2412 0.6922 0.0666 -0.0011 0.0419 0.7841So I went for those, representing the primaries directly in XYZ color space, instead of xyY. Finally, if you multiply the matrices to convert from P3 to XYZ, and then from XYZ to sRGB, you obtain this matrix for direct conversion between linear displayP3 and linear sRGB,
1.2249 -0.2247 0 -0.0420 1.0419 0 -0.0197 -0.0786 1.0979In my code, that matrix can simply be obtained with,
let m = RGBColorSpace.sRGB.toRGB * RGBColorSpace.dciP3.toXYZThe opposite, from sRGB to P3, is given by this matrix,
0.8225 0.1774 0 0.0332 0.9669 0 0.0171 0.0724 0.9108
displayP3 size compared to sRGBI created a sample app, SampleColorPalette to compute the P3 minus sRGB difference. As explained in the introduction, I use only 7 bits per channel, since that gives me already lots of samples. The count of samples out of the sRGB gamut is 625154, out of the 2 million values of the 7-bit color space. So approximately 29%. The Wikipedia says that P3 has a 25% larger color gamut than the sRGB, but by these accounts it looks as if it's 42% larger (2M/(2M-600K)). According to BruceLindbloom.com, the Lab gamut efficiency of sRGB is only 35%, while Wide Gamut RGB is 77.6%. I guess displayP3 should be somewhere in-between. But I can't seem to reconcile the difference of my account and Wikipedia's.
displayP3 in Metal texturesI used 16-bit RGBA textures to do all the processing. I applied the gamma manually in the shader, extracted the bytes in the right order, and stored the image with the appropriate color space in a UIImage, that I later saved as a PNG. The interesting bits are in TextureInit.swift and Image.swift. I was slightly confused by the Digital Color Meter in Mac, because I saved the image below and if I select "display P3", the values are not (255, 0, 0), but (237, 49, 19). I think this is because the image is in displayP3 (the embedded profile in the PNG file says so), but my monitor can't display that. So it must convert it to the profile of my screen, and the P3 value is the value of going back from the display profile to P3. If I select instead "Display native values", then I see (255, 0, 0), which must be the underlying value of the image before applying any color conversion. Perhaps someone out there reading this can clarify... 😅
Wed, 10 Jan 2018 22:17:47 +0000
For 2016 I tried to do some kind of retrospective: 2016 retrospective
I failed to do so before the end of 2017, but I'll try to write some notes now.
AchievementsAlthough in 2016 I managed to release 3 apps, this year I've been a bit more busy. I've only released,
Power up!I think I've done lots of learning,
Power down...A brief interlude with some slightly negative things,
FunCat! We got a cute 8-month old cat to close the year. Her name is Poppy 😊 Trips!