Home » Android » java – android color between two colors, based on percentage?

java – android color between two colors, based on percentage?

Posted by: admin April 23, 2020 Leave a comment

Questions:

I would like to calculate the color depending on a percentage value:

float percentage = x/total;
int color;
if (percentage >= 0.95) {
  color = Color.GREEN;
} else if (percentage <= 0.5) {
  color = Color.RED;
} else {
  // color = getColor(Color.Green, Color.RED, percentage);
}

How can I calculate that last thing? It would be OK if yellow appears at 50%.

I tried this:

private int getColor(int c0, int c1, float p) {
    int a = ave(Color.alpha(c0), Color.alpha(c1), p);
    int r = ave(Color.red(c0), Color.red(c1), p);
    int g = ave(Color.green(c0), Color.green(c1), p);
    int b = ave(Color.blue(c0), Color.blue(c1), p);
    return Color.argb(a, r, g, b);
}
private int ave(int src, int dst, float p) {
    return src + java.lang.Math.round(p * (dst - src));
}

Well this works, but I would like the colors at around 50% being more lightend as I use them on a grey background.. how can I accomplish that?

Thanks!

UPDATE
I tried to convert to YUV like it was suggested in the comments. But I still have the same problem that at 50% it’s to dark.
Additional in this solution I have at <5% now white as color. If I do not calculate float y = ave(...);, but just take float y = c0.y it’s a little better, but at <20% I have then cyan color … I’m not so much into color-formats :-/
Maybe I’m doing something wrong in the calculation? The constants are taken from Wikipedia

public class ColorUtils {

    private static class Yuv {
        public float y;
        public float u;
        public float v;

        public Yuv(int c) {
            int r = Color.red(c);
            int g = Color.green(c);
            int b = Color.blue(c);
            this.y = 0.299f * r + 0.587f * g + 0.114f * b;
            this.u = (b - y) * 0.493f;
            this.v = (r - y) * 0.877f;
        }
    }

    public static int getColor(int color0, int color1, float p) {
        Yuv c0 = new Yuv(color0);
        Yuv c1 = new Yuv(color1);
        float y = ave(c0.y, c1.y, p);
        float u = ave(c0.u, c1.u, p);
        float v = ave(c0.v, c1.v, p);

        int b = (int) (y + u / 0.493f);
        int r = (int) (y + v / 0.877f);
        int g = (int) (1.7f * y - 0.509f * r - 0.194f * b);

        return Color.rgb(r, g, b);
    }

    private static float ave(float src, float dst, float p) {
        return src + Math.round(p * (dst - src));
    }
}
How to&Answers:

You can try using ArgbEvaluator class from android API: http://developer.android.com/reference/android/animation/ArgbEvaluator.html :

new ArgbEvaluator().evaluate(0.75, 0x00ff00, 0xff0000);

Note that there is a bug ( http://code.google.com/p/android/issues/detail?id=36158 ) in alpha channel calculation so you should use values without alpha value.

Answer:

My $0.02, I found this answer and coded up the proper solution. (Thanks to Alnitak for the HSV tip!)

For Copy+Paste:

  private float interpolate(float a, float b, float proportion) {
    return (a + ((b - a) * proportion));
  }

  /** Returns an interpoloated color, between <code>a</code> and <code>b</code> */
  private int interpolateColor(int a, int b, float proportion) {
    float[] hsva = new float[3];
    float[] hsvb = new float[3];
    Color.colorToHSV(a, hsva);
    Color.colorToHSV(b, hsvb);
    for (int i = 0; i < 3; i++) {
      hsvb[i] = interpolate(hsva[i], hsvb[i], proportion);
    }
    return Color.HSVToColor(hsvb);
  }

Answer:

Interpolation like this is best done in HSL or HSV color spaces (and not YUV).

The reason the mid range colours look “muddy” is because if you simply linearly ramp up the red (#ff0000) at the same time as ramping down green (#00ff00) the middle colour ends up as #808000 instead of #ffff00.

Instead, find the HSL (or HSV) equivalent of your starting colour, and the same for the end colour. Interpolate in that colour space, and then for each point convert back to RGB again.

Since the S and L (or V) values are the same for fully saturated red and green, only the H (hue) variable will change, giving the proper effect of a spectrum of colour.

Answer:

Some of solutions of this thread didn’t work for me, so I create another solution. Maybe it is useful for someone.

/**
 * Get the color between two given colors
 * @param colorStart int color start of degradate
 * @param colorEnd int color end of degradate
 * @param percent int percent to apply (0 to 100)
 * @return int color of degradate for given percent
 */
public static int getColorOfDegradate(int colorStart, int colorEnd, int percent){
    return Color.rgb(
            getColorOfDegradateCalculation(Color.red(colorStart), Color.red(colorEnd), percent),
            getColorOfDegradateCalculation(Color.green(colorStart), Color.green(colorEnd), percent),
            getColorOfDegradateCalculation(Color.blue(colorStart), Color.blue(colorEnd), percent)
    );
}

private static int getColorOfDegradateCalculation(int colorStart, int colorEnd, int percent){
    return ((Math.min(colorStart, colorEnd)*(100-percent)) + (Math.max(colorStart, colorEnd)*percent)) / 100;
}

Answer:

Ok, after 2 hours of converting to yuv, hsv, etc pp… I give up. I now do it just like this:

public class ColorUtils {
    private static int FIRST_COLOR = Color.GREEN;
    private static int SECOND_COLOR = Color.YELLOW;
    private static int THIRD_COLOR = Color.RED;

    public static int getColor(float p) {
        int c0;
        int c1;
        if (p <= 0.5f) {
            p *= 2;
            c0 = FIRST_COLOR;
            c1 = SECOND_COLOR;
        } else {
            p = (p - 0.5f) * 2;
            c0 = SECOND_COLOR;
            c1 = THIRD_COLOR;
        }
        int a = ave(Color.alpha(c0), Color.alpha(c1), p);
        int r = ave(Color.red(c0), Color.red(c1), p);
        int g = ave(Color.green(c0), Color.green(c1), p);
        int b = ave(Color.blue(c0), Color.blue(c1), p);
        return Color.argb(a, r, g, b);
    }

    private static int ave(int src, int dst, float p) {
        return src + java.lang.Math.round(p * (dst - src));
    }
}

By explicity using yellow as the middle color, the generated colors are brighter 🙂

Anyway.. if someone has a good other solution, I would appreciate it.

Answer:

I just wanted to update Mark Renouf’s answer to handle alpha channel as well:

private float interpolate(float a, float b, float proportion) {
    return (a + ((b - a) * proportion));
}

/**
 * Returns an interpolated color, between <code>a</code> and <code>b</code>
 * proportion = 0, results in color a
 * proportion = 1, results in color b
 */
private int interpolateColor(int a, int b, float proportion) {

    if (proportion > 1 || proportion < 0) {
        throw new IllegalArgumentException("proportion must be [0 - 1]");
    }
    float[] hsva = new float[3];
    float[] hsvb = new float[3];
    float[] hsv_output = new float[3];

    Color.colorToHSV(a, hsva);
    Color.colorToHSV(b, hsvb);
    for (int i = 0; i < 3; i++) {
        hsv_output[i] = interpolate(hsva[i], hsvb[i], proportion);
    }

    int alpha_a = Color.alpha(a);
    int alpha_b = Color.alpha(b);
    float alpha_output = interpolate(alpha_a, alpha_b, proportion);

    return Color.HSVToColor((int) alpha_output, hsv_output);
}

Answer:

As an updated solution, you can use ColorUtils#blendARGB from the Android support or AndroidX APIs:

val startColor = ContextCompat.getColor(context, R.color.white)
val endColor = ContextCompat.getColor(context, R.color.yellow)
ColorUtils.blendARGB(startColor, endColor, 0.75)

Answer:

Here are 2 ways to interpolate:

private static float interpolate(final float a, final float b, final float proportion) {
    return a + (b - a) * proportion;
}

/** Returns an interpoloated color, between <code>a</code> and <code>b</code> */
public static int interpolateColorHsv(final int a, final int b, final float proportion) {
    final float[] hsva = new float[3];
    final float[] hsvb = new float[3];
    Color.colorToHSV(a, hsva);
    Color.colorToHSV(b, hsvb);
    for (int i = 0; i < 3; ++i) {
        hsvb[i] = interpolate(hsva[i], hsvb[i], proportion);
    }
    return Color.HSVToColor(hsvb);
}

public static int interpolateRGB(final int colorA, final int colorB, final float bAmount) {
    final float aAmount = 1.0f - bAmount;
    final int red = (int) (Color.red(colorA) * aAmount + Color.red(colorB) * bAmount);
    final int green = (int) (Color.green(colorA) * aAmount + Color.green(colorB) * bAmount);
    final int blue = (int) (Color.blue(colorA) * aAmount + Color.blue(colorB) * bAmount);
    return Color.rgb(red, green, blue);
}

Answer:

Here is a pseudocode function that interpolates linearly between 2 colors (staying in RGB space). I’m using a class called Color here instead of ints for clarity.

bAmount is between 0 and 1 (for interpolation)

Color interpolate(Color colorA, Color colorB, float bAmount) {
    Color colorOut;
    float aAmount = 1.0 - bAmount;
    colorOut.r =  colorA.r * aAmount + colorB.r * bAmount;
    colorOut.g =  colorA.g * aAmount + colorB.g * bAmount;
    colorOut.b =  colorA.b * aAmount + colorB.b * bAmount;
    return colorOut;
}