[FIXED] UIColorPickerViewController is changing the input color slightly

Issue

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);

Solution

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

Answered By – Paul

Answer Checked By – Senaida (Easybugfix Volunteer)

Leave a Reply

(*) Required, Your email will not be published