androidcolorshsv

Inaccurate HSV conversion on Android


I am trying to create a wallpaper and am using the HSV conversion in the "android.graphics.color" class. I was very surprised when i realized that a conversion of a created HSV color with a specified hue (0..360) to a rgb color (an integer) and a back conversion to a HSV color will not result in the same hue. This is my code:

int c = Color.HSVToColor(new float[] { 100f, 1, 1 });
float[] f = new float[3];
Color.colorToHSV(c, f);
alert(f[0]);

I am starting with a hue of 100 degree and the result is 99.76471. I wonder why there is that (in my opinion) relatively big inaccuracy.

But a much bigger problem is, that when you put that value in the code again, the new result decreases again.

int c = Color.HSVToColor(new float[] { 99.76471f, 1, 1 });
float[] f = new float[3];
Color.colorToHSV(c, f);
alert(f[0]);

If I start with 99.76471, I get 99.52941. This is kind of a problem for me. I did something similar in java with the "java.awt.Color" class where I did not have those problems. Unfortunately, I cannot use this class in android.


Solution

  • This is an interesting problem. It's not avoidable with the android class because of low float precision. However, I found a similar solution written in javascript here.

    If it's important enough for you to want to define your own method/class to do the conversions, here is a Java conversion which should give you better precision:

    @Size(3)
    /** Does the same as {@link android.graphics.Color#colorToHSV(int, float[])} */
    public double[] colorToHSV(@ColorInt int color) {
        //this line copied vertabim
        return rgbToHsv((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF);
    }
    
    @Size(3)
    public double[] rgbToHsv(double r, double g, double b) {
        final double max = Math.max(r,  Math.max(g, b));
        final double min = Math.min(r, Math.min(g, b));
        final double diff = max - min;
        final double h;
        final double s = ((max == 0d)? 0d : diff / max);
        final double v = max / 255d;
        if (min == max) {
            h = 0d;
        } else if (r == max) {
            double tempH = (g - b) + diff * (g < b ? 6: 0);
            tempH /= 6 * diff;
            h = tempH;
        } else if (g == max) {
            double tempH = (b - r) + diff * 2;
            tempH /= 6 * diff;
            h = tempH;
        } else {
            double tempH = (r - g) + diff * 4;
            tempH /= 6 * diff;
            h = tempH;
        }
        return new double[] { h, s, v };
    }
    

    I have to confess ignorance here - I've done quick conversion and not had time to test properly. There might be a more optimal solution, but this should get you started at least.