1

I need to allow the user to choose a color on iOS. I use the following code to fire up the color picker:

    var picker = new UIColorPickerViewController();
    picker.SupportsAlpha = true;
    picker.Delegate = this;
    picker.SelectedColor = color.ToUIColor();   

    PresentViewController(picker, true, null); 

When the color picker displays, the color is always slightly off. For example:

input RGBA: (220, 235, 92, 255)

the initial color in the color picker might be:

selected color: (225, 234, 131, 255)

(these are real values from tests). Not a long way off... but enough to notice if you are looking for it.

I was wondering if the color picker grid was forcing the color to the nearest color entry - but if that were true, you would expect certain colors to stay fixed (i.e. if the input color exactly matches one of the grid colors, it should stay unchanged). That does not happen.

p.s. I store colors in a cross platform fashion using simple RGBA values. The ToUIColor converts to local UIColor using

new UIColor((nfloat)rgb.r, (nfloat)rgb.g, (nfloat)rgb.b, (nfloat)rgb.a);
Paul
  • 481
  • 5
  • 10
  • Then you should go with custom color picker. Or use the color from color picker grid as initial color – Zeeshan Ahmad II Aug 21 '22 at 10:22
  • From the docs, its not clear how to restrict the user's choice of color to only be from the Grid, and not the Sliders or Spectrum. Also... when I do choose a color from the grid, it still changes slightly when the grid is reopened. Lastly... this is a cross platform app with data sharing, so color choices on other device types get shared to this device. So limiting the color options would be problematic. – Paul Aug 21 '22 at 22:07
  • custom color picker is a good option for this situation I guess ? – Zeeshan Ahmad II Aug 22 '22 at 04:21
  • You are probably right. It's a lot of work that I'd love to avoid. – Paul Aug 22 '22 at 07:54
  • @Paul - on iOS you are *probably* running into a difference between P3 and sRGB colors. This SO answer is worth a look: https://stackoverflow.com/a/49040628/6257435 ... Since you **only** want to use a grid (not the Sliders or Spectrum), and you need color choices to be compatible cross-platform, a (pretty easy to create) custom grid color picker is likely your best route. – DonMag Aug 26 '22 at 20:03
  • On first read of that SO answer - I think that is most likely the reason - I'm going to need to dig in to the way I convert my device independent color to {NS,UI}Color. Note that I don't only want a grid but I want to offer all color choices across all device types, and the slider/spectrum is very useful. Thanks muchly for your help. – Paul Aug 31 '22 at 00:08
  • @DonMag - That is definitively the problem. The picker is using displayP3 colourspace. I used sRGB. I can convert to displayP3 before handing the color *to* the picker, but when I receive a color from the picker, converting to sRGB is not always possible due to the wider gamut of displayP3. So I need to switch my cross platform color storage to extended sRGB which can handle the wider gamut that the picker will return. – Paul Aug 31 '22 at 04:25
  • This series of articles is really good: https://bjango.com/articles/colourmanagementgamut/ – Paul Aug 31 '22 at 04:26

1 Answers1

0

From the hints in comments by @DonMag, I've got some way towards an answer, and also a set of resources that can help if you are struggling with this.

The key challenge is that mac and iOS use displayP3 as the ColorSpace, but most people use default {UI,NS,CG}Color objects, which use the sRGB ColorSpace (actually... technically they are Extended sRGB so they can cover the wider gamut of DisplayP3). If you want to know the difference between these three - there's resources below.

When you use the UIColorPickerViewController, it allows the user to choose colors in DisplayP3 color space (I show an image of the picker below, and you can see the "Display P3 Hex Colour" at the bottom).

If you give it a color in sRGB, I think it gets converted to DisplayP3. When you read the color, you need to convert back to sRGB, which is the step I missed.

However I found that using CGColor.CreateByMatchingToColorSpace, to convert from DisplayP3 to sRGB never quite worked. In the code below I convert to and from DisplayP3 and should have got back my original color, but I never did. I tried removing Gamma by converting to a Linear space on the way but that didn't help.

    cg = new CGColor(...values...); // defaults to sRGB

    // sRGB to DisplayP3
    tmp = CGColor.CreateByMatchingToColorSpace(
        CGColorSpace.CreateWithName("kCGColorSpaceDisplayP3"),
        CGColorRenderingIntent.Default, cg, null);

    //  DisplayP3 to sRGB
    cg2 = CGColor.CreateByMatchingToColorSpace(
        CGColorSpace.CreateWithName("kCGColorSpaceExtendedSRGB"),
        CGColorRenderingIntent.Default, tmp, null);

Then I found an excellent resource: http://endavid.com/index.php?entry=79 that included a set of matrices that can perform the conversions. And that seems to work.

So now I have extended CGColor as follows:

        public static CGColor FromExtendedsRGBToDisplayP3(this CGColor c)
    {
        if (c.ColorSpace.Name != "kCGColorSpaceExtendedSRGB")
            throw new Exception("Bad color space");

        var mat = LinearAlgebra.Matrix<float>.Build.Dense(3, 3, new float[] { 0.8225f, 0.1774f, 0f, 0.0332f, 0.9669f, 0, 0.0171f, 0.0724f, 0.9108f });
        var vect = LinearAlgebra.Vector<float>.Build.Dense(new float[] { (float)c.Components[0], (float)c.Components[1], (float)c.Components[2] });
        vect = vect * mat;
        var cg = new CGColor(CGColorSpace.CreateWithName("kCGColorSpaceDisplayP3"), new nfloat[] { vect[0], vect[1], vect[2], c.Components[3] });
        return cg;
    }

    public static CGColor FromP3ToExtendedsRGB(this CGColor c)
    {
        if (c.ColorSpace.Name != "kCGColorSpaceDisplayP3")
            throw new Exception("Bad color space");

        var mat = LinearAlgebra.Matrix<float>.Build.Dense(3, 3, new float[] { 1.2249f, -0.2247f, 0f, -0.0420f, 1.0419f, 0f, -0.0197f, -0.0786f, 1.0979f });
        var vect = LinearAlgebra.Vector<float>.Build.Dense(new float[] { (float)c.Components[0], (float)c.Components[1], (float)c.Components[2] });
        vect = vect * mat;
        var cg = new CGColor(CGColorSpace.CreateWithName("kCGColorSpaceExtendedSRGB"), new nfloat[] { vect[0], vect[1], vect[2], c.Components[3] });
        return cg;
    }

Note: there's lots of assumptions in the matrices w.r.t white point and gammas. But it works for me. Let me know if there are better approaches out there, or if you can tell me why my use of CGColor.CreateByMatchingToColorSpace didn't quite work.

Reading Resources: Reading this: https://stackoverflow.com/a/49040628/6257435 then this: https://bjango.com/articles/colourmanagementgamut/ are essential starting points.

Image of the iOS Color Picker:

iOS Color Picker

Paul
  • 481
  • 5
  • 10