22

I have a user setup where they can choose the colors of the alerts. the Alert is the background color on a text or button. But the problem comes in that if they select a dark blue and we have black letters the contrast isnt enough and you cannot read it.

I have tried to make a function to get the reverse opposing color but havent got too far.

Is there such a function?

Mark Worsnop
  • 4,407
  • 15
  • 54
  • 79
  • 1
    There are at least two questions on here for this. One is http://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color. I know because I implemented an algorithm from one of them and it works well. – Nemi Jan 12 '11 at 18:10

13 Answers13

37

I found that the best solution for me is to convert the RGB values into YIQ values. As we are only interested in the brightness value (represented by Y), there is one single calculation to be done: Y = (299*R + 587*G + 114*B)/1000. The Java code for that would look like this:

public static Color getContrastColor(Color color) {
  double y = (299 * color.getRed() + 587 * color.getGreen() + 114 * color.getBlue()) / 1000;
  return y >= 128 ? Color.black : Color.white;
}

You can see that it simply decides to use black or white, based upon the brightness of the original color. And the result works very nice in my opinion. The weights (299, 587, 114) are proportional to the sensitivity of the eyes (or rather the sensitivity of the retina) to the corresponding color.

brimborium
  • 9,362
  • 9
  • 48
  • 76
  • 1
    Thanks. This may not cover all cases, but is pefect for my usecase (make text readable on colored background) – Hoang Tran Jul 26 '13 at 07:36
  • You always can read the text pretty good. The disadvantage is, that the text will be black or white, color information of the background is "lost". – brimborium Jul 26 '13 at 12:36
  • 1
    In JavaFx the value y will be between 0 and 1. So the last line of this method should look like this `return y >= 0.5 ? Color.valueOf("#000000") : Color.valueOf("#ffffff");` – Mustafa Jun 28 '20 at 10:52
  • 2
    To make it works on Android apps, I edit it: ``` private int getContrastColor(int color) { double y = (299 * Color.red(color) + 587 * Color.green(color) + 114 * Color.blue(color)) / 1000; return y >= 128 ? Color.BLACK : Color.WHITE; }``` – ZaFaR97 Feb 26 '22 at 09:22
32

Use complementary color:

Algo is simple, substract each color component from 255 to get new color components

Color textColor = Color.rgb(255-Color.red(bgColor),
                         255-Color.green(bgColor),
                         255-Color.blue(bgColor));

----- EDIT (As RGB based complement may not work always --------

These two links are very much helpful and on topic:

http://www.splitbrain.org/blog/2008-09/18-calculating_color_contrast_with_php

http://24ways.org/2010/calculating-color-contrast

Sarwar Erfan
  • 18,034
  • 5
  • 46
  • 57
  • 8
    Doesn't this give a big issue when the user selects the color 128/128/128 (or something really near). – Anthony Jan 12 '11 at 18:22
  • I have included a link from Wikipedia in the answer. RGB based complement is easiest to understand. HSV based complement will work in this case. @Shynhriir: Yes.. RGB based complement will not work in this range, you are absolutely right – Sarwar Erfan Jan 12 '11 at 18:27
  • How would you code into Android the complementary color from the Wiki link? – Mark Worsnop Jan 12 '11 at 18:32
  • @Mark: It is not very hard. I have previously wrote my own 3d perspective transformation code just reading the corresponding theories. – Sarwar Erfan Jan 12 '11 at 18:34
  • If you are new to Java and Android it is :) I got it though... see above – Mark Worsnop Jan 12 '11 at 18:59
  • @Mark: You have future I see. – Sarwar Erfan Jan 12 '11 at 19:07
  • OMFG, I have been messing with RGB/HSV conversion and hue shifting for hours without "nice" sucess and this is by far the best and so easy solution I've found for my need. Thanks. – Emmanuel Istace May 23 '18 at 05:29
16

Based on Marks solution I would suggest:

public static int getComplementaryColor(int colorToInvert) {
    float[] hsv = new float[3];
    Color.RGBToHSV(Color.red(colorToInvert), Color.green(colorToInvert),
            Color.blue(colorToInvert), hsv);
    hsv[0] = (hsv[0] + 180) % 360;
    return Color.HSVToColor(hsv);
}

And additionally I now created a similar method, for calculating a default background for a given color:

public static int getContrastVersionForColor(int color) {
    float[] hsv = new float[3];
    Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color),
            hsv);
    if (hsv[2] < 0.5) {
        hsv[2] = 0.7f;
    } else {
        hsv[2] = 0.3f;
    }
    hsv[1] = hsv[1] * 0.2f;
    return Color.HSVToColor(hsv);
}
Simon
  • 13,173
  • 14
  • 66
  • 90
  • You `getComplementaryColor ` is working perfectly but not `getContrastVersionForColor ` can you please fix? – Shajeel Afzal Sep 22 '15 at 19:21
  • 2
    What do you mean it does not work? You don't like the result? Can you provide a color int value for which it does not look right? I use it quite often and never had problems – Simon Sep 23 '15 at 20:09
  • I like this solution a lot. A perfect alternative if you don't like the black/white YIQ approach. – brimborium Sep 20 '18 at 09:26
8

As Sarwar Erfan pointed out, use complementary colors. For that, you can use an integer mask (which will be faster, than inverting R, G, B color components separately).

int textColor = bgColor ^ 0x00ffffff;
Lev Leontev
  • 2,538
  • 2
  • 19
  • 31
3

integer solution:

public static int getContrastColor(int color) {
        double y = (299 * Color.red(color) + 587 * Color.green(color) + 114 * Color.blue(color)) / 1000;
        return y >= 128 ? Color.BLACK : Color.WHITE;
    }
Eboo
  • 47
  • 4
  • 2
    Can you please explain on how it would solve the problem ? – Phani Dec 06 '15 at 04:15
  • "I have tried to make a function to get the reverse opposing color but havent got too far. " So here is a function ^^ it return black or white color. – Eboo Jan 18 '16 at 23:16
  • 1
    Going back to first question..if it doesn't resolve the issue..then delete it.. or else update the question with latest findings. – Phani Jan 19 '16 at 16:14
  • @brimborium has already posted an extremely similar response 3 years before yours. – Frontear Jun 10 '19 at 12:48
2

I got it working, I think :)

Here is the function:

public static int OpposeColor(int ColorToInvert)
{
     int RGBMAX = 255;

     float[] hsv = new float[3];
     float H;

     Log.i("HSV_H", "Start Color=" +  ColorToInvert);

     Color.RGBToHSV( Color.red( ColorToInvert),  RGBMAX - Color.green( ColorToInvert), Color.blue(ColorToInvert), hsv);

     Log.i("HSV_H", "Hue=" + hsv[0]);
     Log.i("HSV_H", "Saturation=" + hsv[1]);
     Log.i("HSV_H", "Value=" + hsv[2]);

    H = (float) (hsv[0] + 0.5);

    if (H > 1) H -= 1;

    Log.i("HSV_H", "Hue2=" + H);         

    Log.i("HSV_H", "Color=" +  Color.HSVToColor(hsv ));

    return Color.HSVToColor(hsv );


}
Mark Worsnop
  • 4,407
  • 15
  • 54
  • 79
  • why that "RGBMAX - "? and the hue is a value between 0 and 360 and not 0 and 1 I think, i postet an updated version below – Simon Dec 15 '14 at 15:32
1

Does the text have to be a color derived from the background color? What if it just alternates between white and black based on the r g b intensity? The idea being that white will always be visible on r g b values below a certain intensity and black always visible on the rest.

I don't have a working algorithm to share, but you could try something along the lines of:

int threshold = 50;
if(r < threshold && g < threshold && b < threshold) {
  // set your font color to white
} else {
  // set your font color to black
}

You'll probably have to fiddle with threshold a bit to get something good looking. You could also tint the font a bit based on which r g b value is dominant.

Nick
  • 8,181
  • 4
  • 38
  • 63
1

Slight modification of Simons' answer

float[] hsv = new float[3];
java.awt.Color.RGBtoHSB( bgColour.getRed(), bgColour.getGreen(), bgColour.getBlue(), hsv );
hsv[2] = (hsv[2] + 180) % 360;
java.awt.Color   invertedColour = java.awt.Color.getHSBColor( hsv[ 0 ], hsv[ 1 ], hsv[ 2 ] );
D.Price
  • 11
  • 5
1

There is ColorUtils.calculateLuminance(int color) which can be used to determine whether to use a dark or bright text color for the given background color.

example:

if (ColorUtils.calculateLuminance(backgroundColor) <= 0.5) textColor = <bright>
else textColor = <dark>

The threshold (0.5 in this example) could be chosen experimentally for the best visual results.

Documentation

davee44
  • 357
  • 3
  • 10
1

Another general solution for converting a color in integer format is:

private int getComplementaryColor( int color) {
    int R = color & 255;
    int G = (color >> 8) & 255;
    int B = (color >> 16) & 255;
    int A = (color >> 24) & 255;
    R = 255 - R;
    G = 255 - G;
    B = 255 - B;
    return R + (G << 8) + ( B << 16) + ( A << 24);
}
tm1701
  • 7,307
  • 17
  • 79
  • 168
0

You could calculate the difference between each color channel (red, green and blue), and get the average difference - then do some comparison based on that.

Mathias Lykkegaard Lorenzen
  • 15,031
  • 23
  • 100
  • 187
0
import android.graphics.Color;
import android.graphics.Paint;

/**
 * Utilities for performing common color-related tasks.
 * @author Ryan Ware
 *
 */
public class ColorUtils {

  public static int getComplimentColor(Paint paint) {
    return getComplimentColor(paint.getColor());
  }

  /**
   * Returns the complimentary (opposite) color.
   * @param color int RGB color to return the compliment of
   * @return int RGB of compliment color
   */
  public static int getComplimentColor(int color) {
    // get existing colors
    int alpha = Color.alpha(color);
    int red = Color.red(color);
    int blue = Color.blue(color);
    int green = Color.green(color);

    // find compliments
    red = (~red) & 0xff;
    blue = (~blue) & 0xff;
    green = (~green) & 0xff;

    return Color.argb(alpha, red, green, blue);
  }

  /**
   * Converts an int RGB color representation into a hexadecimal {@link String}.
   * @param argbColor int RGB color
   * @return {@link String} hexadecimal color representation
   */
  public static String getHexStringForARGB(int argbColor) {
    String hexString = "#";
    hexString += ARGBToHex(Color.alpha(argbColor));
    hexString += ARGBToHex(Color.red(argbColor));
    hexString += ARGBToHex(Color.green(argbColor));
    hexString += ARGBToHex(Color.blue(argbColor));

    return hexString;
  }

  /**
   * Converts an int R, G, or B value into a hexadecimal {@link String}.
   * @param rgbVal int R, G, or B value
   * @return {@link String} hexadecimal value
   */
  private static String ARGBToHex(int rgbVal) {
    String hexReference = "0123456789ABCDEF";

    rgbVal = Math.max(0,rgbVal);
    rgbVal = Math.min(rgbVal,255);
    rgbVal = Math.round(rgbVal);

    return String.valueOf( hexReference.charAt((rgbVal-rgbVal%16)/16) + "" + hexReference.charAt(rgbVal%16) );
  }
}

thanks to: http://www.java2s.com/Code/Android/2D-Graphics/Returnsthecomplimentaryoppositecolor.htm

Ahamadullah Saikat
  • 4,437
  • 42
  • 39
0

For API 24+. Warning, threshold != 0.5f, my variant is 0.29f

int color_inv = Color.luminance( color) > 0.29f ? Color.BLACK : Color.WHITE;
Style-7
  • 985
  • 12
  • 27