7

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.

user2957782
  • 695
  • 5
  • 10
  • I *believe* this is a case of a different conversion used between a 16 and 32 bit integer, however this may be way off. I remember some years ago running into a problem with sound files and converting from a byte array. In the end I just rounded the figure up to the nearest whole int. – dave Mar 01 '16 at 14:38
  • 1
    I support the idea of dave. One thing which might be useful is to notice that the difference between the original value of 100 and the rounded result of 99.76471 is 60/255, and 255 = 2^8-1 (It is quite common to store rgb values on 8 bits). The same is true for 99.76471 and 99.52941. I don't have a complete theory, but seems like basic arithmetic went wrong. – elias Mar 08 '16 at 12:37

2 Answers2

1

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.

Community
  • 1
  • 1
Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
  • Well it seems that the more precise double doesn't solve my problem because it's still rounding to an int and there might be the problem that I don't get the same value back – user2957782 Oct 03 '17 at 12:02
0

Don't miss the mirrored procedure from the source link. Next is the translation to the Kotlin lang.

    fun hsvToRGB(hsv: DoubleArray): Int {
        val i = floor(hsv[0] * 6).toInt()
        val f = hsv[0] * 6 - i
        val p = hsv[2] * (1 - hsv[1])
        val q = hsv[2] * (1 - f * hsv[1])
        val t = hsv[2] * (1 - (1 - f) * hsv[1])
        val r: Double
        val g: Double
        val b: Double
        when (i % 6) {
            0 -> {r = hsv[2]; g = t; b = p}
            1 -> {r = q; g = hsv[2]; b = p}
            2 -> {r = p; g = hsv[2]; b = t}
            3 -> {r = p; g = q; b = hsv[2]}
            4 -> {r = t; g = p; b = hsv[2]}
            5 -> {r = hsv[2]; g = p; b = q}
            else -> {r = 0.0; g = 0.0; b = 0.0}
        }
        return Color.rgb((r * 255).roundToInt(), (g * 255).roundToInt(), (b * 255).roundToInt())
    }

    fun rgbToHSV(color: Int, target: DoubleArray) {
        val r = Color.red(color).toDouble()
        val g = Color.green(color).toDouble()
        val b = Color.blue(color).toDouble()
        val max = kotlin.math.max(r, kotlin.math.max(g, b))
        val min = kotlin.math.min(r, kotlin.math.min(g, b))
        val diff = max - min
        target[1] =  if (max == 0.0)  {0.0} else {diff / max}
        target[2] = max / 255.0
        target[0] = if (min == max) {
            0.0
        } else if (r == max) {
            var tempH = (g - b) + diff * if (g < b) { 6} else {0}
            tempH /= 6 * diff
            tempH
        } else if (g == max) {
            var tempH = (b - r) + diff * 2
            tempH /= 6 * diff
            tempH
        } else {
            var tempH = (r - g) + diff * 4
            tempH /= 6 * diff
            tempH
        }
    }
A. Petrov
  • 910
  • 13
  • 18