Determine font color based on background color
Asked Answered
P

23

304

Given a system (a website for instance) that lets a user customize the background color for some section but not the font color (to keep number of options to a minimum), is there a way to programmatically determine if a "light" or "dark" font color is necessary?

I'm sure there is some algorithm, but I don't know enough about colors, luminosity, etc to figure it out on my own.

Prosecute answered 6/12, 2009 at 17:0 Comment(0)
S
523

I encountered similar problem. I had to find a good method of selecting contrastive font color to display text labels on colorscales/heatmaps. It had to be universal method and generated color had to be "good looking", which means that simple generating complementary color was not good solution - sometimes it generated strange, very intensive colors that were hard to watch and read.

After long hours of testing and trying to solve this problem, I found out that the best solution is to select white font for "dark" colors, and black font for "bright" colors.

Here's an example of function I am using in C#:

Color ContrastColor(Color color)
{
    int d = 0;
    
    // Counting the perceptive luminance - human eye favors green color...      
    double luminance = (0.299 * color.R + 0.587 * color.G + 0.114 * color.B)/255;
    
    if (luminance > 0.5)
       d = 0; // bright colors - black font
    else
       d = 255; // dark colors - white font
                
    return  Color.FromArgb(d, d, d);
}

This was tested for many various colorscales (rainbow, grayscale, heat, ice, and many others) and is the only "universal" method I found out.

Edit
Changed the formula of counting a to "perceptive luminance" - it really looks better! Already implemented it in my software, looks great.

Edit 2 @WebSeed provided a great working example of this algorithm: http://codepen.io/WebSeed/full/pvgqEq/

Softball answered 6/12, 2009 at 17:9 Comment(11)
It's probably not important, but you might want a better function to compute the brightness #596716Quinquennium
Where do your perceptive Luminance weightings come from?Carpel
From this answer: #596716Softball
This is old, I know but I don't think the switch to black is at a good spot with this algorithm. Take something like [135,135,135] that is on the darker side of gray, yet that shows up as needing a black font. It isn't a bad choice, but white font is clearly better here.Cyprinodont
@Cyprinodont it is possible. But remember that there are two main problems: 1. it is not easy task to create universal measure of "perceptive luminance". 2. different displays (screens) show colors different ways. Until you have some good calibration tools (and I mean not just software, but also hardware), the results for different screen displays can differ a lot.Softball
@Softball My screen is calibrated (135, 135, 135) is the background color white as the font color is definitely better here as shown in the example: htmlcsscolor.com/hex/878787 is there a way to adjust the algorithm to shift the luminance perception to favour white for shades of colours being on the darker side as is the case for (135, 135, 135)?Permutation
@Permutation sure, just play around with the weighting coefficients for counting luminance based on RGB values (0.299...).Softball
This is freakin' brilliant. I had the same problem with inverses, this solved my issueGabbey
It would be nice if it included where did you got the luminance factors. Most probably it's Lab or something similar, but just to let the user go through some rabbit holes :) Nah, I mean, so the constants do not look 'magick'.Libel
@MarcosDione I really do not remember the exact scientific paper that was the source for these parameters. You can see similar parameters (even more inclined towards greens) in relative luminance formula: en.wikipedia.org/wiki/Relative_luminanceSoftball
Edit 2 is superb :-)Fears
B
33

Based on Gacek's answer but directly returning color constants (additional modifications see below):

public Color ContrastColor(Color iColor)
{
  // Calculate the perceptive luminance (aka luma) - human eye favors green color... 
  double luma = ((0.299 * iColor.R) + (0.587 * iColor.G) + (0.114 * iColor.B)) / 255;

  // Return black for bright colors, white for dark colors
  return luma > 0.5 ? Color.Black : Color.White;
}

Note: I removed the inversion of the luma value to make bright colors have a higher value, what seems more natural to me and is also the 'default' calculation method.
(Edit: This has since been adopted in the original answer, too)

I used the same constants as Gacek from here since they worked great for me.


You can also implement this as an Extension Method using the following signature:

public static Color ContrastColor(this Color iColor)

You can then easily call it via
foregroundColor = backgroundColor.ContrastColor().

Breedlove answered 27/4, 2016 at 11:12 Comment(0)
G
14

My Swift implementation of Gacek's answer:

func contrastColor(color: UIColor) -> UIColor {
    var d = CGFloat(0)

    var r = CGFloat(0)
    var g = CGFloat(0)
    var b = CGFloat(0)
    var a = CGFloat(0)

    color.getRed(&r, green: &g, blue: &b, alpha: &a)

    // Counting the perceptive luminance - human eye favors green color...
    let luminance = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))

    if luminance < 0.5 {
        d = CGFloat(0) // bright colors - black font
    } else {
        d = CGFloat(1) // dark colors - white font
    }

    return UIColor( red: d, green: d, blue: d, alpha: a)
}
Gump answered 19/5, 2016 at 3:31 Comment(1)
In swift, since r/g/b are CGFloats, you don't need the "/255" to calculate luminance: let luminance = 1 - ((0.299 * r) + (0.587 * g) + (0.114 * b))Havildar
N
14

Thank you @Gacek. Here's a version for Android:

@ColorInt
public static int getContrastColor(@ColorInt int color) {
    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;

    int d;
    if (a < 0.5) {
        d = 0; // bright colors - black font
    } else {
        d = 255; // dark colors - white font
    }

    return Color.rgb(d, d, d);
}

And an improved (shorter) version:

@ColorInt
public static int getContrastColor(@ColorInt int color) {
    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(color) + 0.587 * Color.green(color) + 0.114 * Color.blue(color)) / 255;
    return a < 0.5 ? Color.BLACK : Color.WHITE;
}
Nondescript answered 26/12, 2016 at 19:9 Comment(2)
It would be even shorter and easier to read, if you'd apply the changes of @Marcus Mangelsdorf (get rid of the 1 - ... part and rename a to luminanceTam
Using the first one, you could capture alpha as well.Mouse
S
14

Short Answer:

Calculate the luminance (Y) of the given color, and flip the text either black or white based on a pre-determined middle contrast figure. For a typical sRGB display, flip to white when Y < 0.36 (i.e. 36%)

EDIT TO ADD: this question is asked often enough that I set a repo with a ready to go JS at https://github.com/Myndex/max-contrast

It's this:

    // Simple: send it sRGB values 0-255.
   // If Ys is > 0.342 it returns 'black' otherwise returns 'white'
  // See  https://github.com/Myndex/max-contrast 


function maxContrast (Rs = 164, Gs = 164, Bs = 164) {
  
  const flipYs = 0.342; // based on APCA™ 0.98G middle contrast BG

  const trc = 2.4, Rco = 0.2126729, Gco = 0.7151522, Bco = 0.0721750; // 0.98G

  let Ys = (Rs/255.0)**trc*Rco + (Gs/255.0)**trc*Gco + (Bs/255.0)**trc*Bco; 

  return Ys < flipYs ? 'white' : 'black'
}

Longer Answer

Not surprisingly, nearly every answer here presents some misunderstanding, and/or is quoting incorrect coefficients. The only answer that is actually close is that of Seirios, though it relies on WCAG 2 contrast which is known to be incorrect itself.

If I say "not surprisingly", it is due in part to the massive amount of misinformation on the internet on this particular subject. The fact this field is still a subject of active research and unsettled science adds to the fun. I come to this conclusion as the result of the last few years of research into a new contrast prediction method for readability.

The field of visual perception is dense and abstract, as well as developing, so it is common for misunderstandings to exist. For instance, HSV and HSL are not even close to perceptually accurate. For that you need a perceptually uniform model such as CIELAB or CIELUV or CIECAM02 etc.

Some misunderstandings have even made their way into standards, such as the contrast part of WCAG 2 (1.4.3), which has been demonstrated as incorrect over much of its range.

First Fix:

The coefficients shown in many answers here are (.299, .587, .114) and are wrong, as they pertain to a long obsolete system known as NTSC YIQ, the analog broadcast system in North America some decades ago. While they may still be used in some YCC encoding specs for backwards compatibility, they should not be used in an sRGB context.

The coefficients for sRGB and Rec.709 (HDTV) are:

  • Red: 0.2126
  • Green: 0.7152
  • Blue: 0.0722

Other color spaces like Rec2020 or AdobeRGB use different coefficients, and it is important to use the correct coefficients for a given color space.

The coefficients can not be applied directly to 8 bit sRGB encoded image or color data. The encoded data must first be linearized, then the coefficients applied to find the luminance (light value) of the given pixel or color.

For sRGB there is a piecewise transform, but as we are only interested in the perceived lightness contrast to find the point to "flip" the text from black to white, we can take a shortcut via the simple gamma method.

Andy's Shortcut to Luminance & Lightness

Divide each sRGB color by 255.0, then raise to the power of 2.2, then multiply by the coefficients and sum them to find estimated luminance.

 let cieY = Math.pow(sR/255.0,2.2) * 0.2126 +
          Math.pow(sG/255.0,2.2) * 0.7152 +
          Math.pow(sB/255.0,2.2) * 0.0722; // Andy's Easy Standard Luminance for sRGB. For Rec709 HDTV change the 2.2 to 2.4

Here, Y is the relative luminance from an sRGB monitor, on a 0.0 to 1.0 scale. This is not relative to perception though, and we need further transforms to fit our human visual perception of the relative lightness, and also of the perceived contrast.

The 36% Flip

But before we get there, if you are only looking for a basic point to flip the text from black to white or vice versa, the cheat is to use the Y we just derived, and make the flip point about Y = 0.36;. so for colors higher than 0.36 Y, make the text black #000 and for colors darker than 0.36 Y, make the text white #fff.

  let textColor = (cieY < 0.36) ? "#fff" : "#000"; // Low budget down and dirty text flipper.

Why 36% and not 50%? Our human perception of lightness/darkness and of contrast is not linear. For a self illuminated display, it so happens that 0.36 Y is about middle contrast under most typical conditions.

Yes it varies, and yes this is an over simplification. But if you are flipping text black or white, the simple answer is a useful one. (The optimum value may vary between ~34% and ~42%, depending on ambient conditions and the stimulus spatial characteristics).

Perceptual Bonus Round

Predicting the perception of a given color and lightness is still a subject of active research, and not entirely settled science. The L* (Lstar) of CIELAB or LUV has been used to predict perceptual lightness, and even to predict perceived contrast. However, L* works well for surface colors in a very defined/controlled environment, and does not work as well for self illuminated displays.

While this varies depending on not only the display type and calibration, but also your environment and the overall page content, if you take the Y from above, and raise it by around ^0.66 to ^0.7, you'll find that 0.5 is about the middle point to flip the text from white to black.

  let textColor = (Math.pow(cieY,0.678) < 0.5) ? "#fff" : "#000"; // perceptual text flipper.

Using the exponent 0.6 will make the text color swap on a darker color, and using 0.8 will make the text swap on a lighter color.

Spatial Frequency Double Bonus Round

It is useful to note that contrast is NOT just the distance between two colors. Spatial frequency, in other words font weight and size, are also CRITICAL factors that cannot be ignored.

That said, you may find that when colors are in the midrange, that you'd want to increase the size and or weight of the font.

  let textSize = "16px";
  let textWeight = "normal"; 
  let Ls = Math.pow(cieY,0.678);

  if (Ls > 0.33 && Ls < 0.66) {
      textSize = "18px";
      textWeight = "bold";
      }  // scale up fonts for the lower contrast mid luminances.

Hue R U

It's outside the scope of this post to delve deeply, but above we are ignoring hue and chroma. Hue and chroma do have an effect, such as Helmholtz Kohlrausch, and the simpler luminance calculations above do not always predict intensity due to saturated hues.

To predict these more subtle aspects of perception, a complete appearance model is needed. R. Hunt, M. Fairshild, E. Burns are a few authors worth looking into if you want to plummet down the rabbit hole of human visual perception...

For this narrow purpose, we could re-weight the coefficients slightly, knowing that green makes up the majority of of luminance, and pure blue and pure red should always be the darkest of two colors. What tends to happen using the standard coefficients, is middle colors with a lot of blue or red may flip to black at a lower than ideal luminance, and colors with a high green component may do the opposite.

That said, I find this is best addressed by increasing font size and weight in the middle colors.

Putting it all together

So we'll assume you'll send this function a hex string, and it will return a style string that can be sent to a particular HTML element.

Check out the CODEPEN inspired by the one Seirios did. One of the things the Codepen code does is increase the text size for the lower contrast midrange.

UPDATE: NEW Fancy Font Flipping GitHub repo

A more complete demonstrator.

Samples

Here's a sample where the flip point is cieY 42, which is too high: Samples of the fancy font flipper with flip point too high

Here's a sample where the flip point is cieY 36: Samples of the fancy font flipper with flip point Ys 36

And if you want to play around with some of the underlying concepts, see the SAPC development site, clicking on "research mode" provides interactive experiments to demonstrate these concepts.

Terms of enlightenment

  • Luminance: Y (relative) or L (absolute cd/m2) a spectrally weighted but otherwise linear measure of light. Not to be confused with "Luminosity".

  • Luminosity: light over time, useful in astronomy.

  • Lightness: L* (Lstar) perceptual lightness as defined by the CIE. Some models have a related lightness J*.

Shulem answered 7/11, 2021 at 5:9 Comment(0)
S
12

Javascript [ES2015]

const hexToLuma = (colour) => {
    const hex   = colour.replace(/#/, '');
    const r     = parseInt(hex.substr(0, 2), 16);
    const g     = parseInt(hex.substr(2, 2), 16);
    const b     = parseInt(hex.substr(4, 2), 16);

    return [
        0.299 * r,
        0.587 * g,
        0.114 * b
    ].reduce((a, b) => a + b) / 255;
};
Sorgo answered 18/6, 2017 at 12:55 Comment(0)
E
6

Thanks for this post.

For whoever might be interested, here's an example of that function in Delphi:

function GetContrastColor(ABGColor: TColor): TColor;
var
  ADouble: Double;
  R, G, B: Byte;
begin
  if ABGColor <= 0 then
  begin
    Result := clWhite;
    Exit; // *** EXIT RIGHT HERE ***
  end;

  if ABGColor = clWhite then
  begin
    Result := clBlack;
    Exit; // *** EXIT RIGHT HERE ***
  end;

  // Get RGB from Color
  R := GetRValue(ABGColor);
  G := GetGValue(ABGColor);
  B := GetBValue(ABGColor);

  // Counting the perceptive luminance - human eye favors green color...
  ADouble := 1 - (0.299 * R + 0.587 * G + 0.114 * B) / 255;

  if (ADouble < 0.5) then
    Result := clBlack  // bright colors - black font
  else
    Result := clWhite;  // dark colors - white font
end;
Elasticize answered 17/12, 2015 at 17:15 Comment(1)
There's a small error in the 4th from last line. Result:= clBlack should not have a semi-colon after it;Miry
W
6

Ugly Python if you don't feel like writing it :)

'''
Input a string without hash sign of RGB hex digits to compute
complementary contrasting color such as for fonts
'''
def contrasting_text_color(hex_str):
    (r, g, b) = (hex_str[:2], hex_str[2:4], hex_str[4:])
    return '000' if 1 - (int(r, 16) * 0.299 + int(g, 16) * 0.587 + int(b, 16) * 0.114) / 255 < 0.5 else 'fff'
Wellmeaning answered 2/6, 2016 at 23:1 Comment(0)
A
5

This is such a helpful answer. Thanks for it!

I'd like to share an SCSS version:

@function is-color-light( $color ) {

  // Get the components of the specified color
  $red: red( $color );
  $green: green( $color );
  $blue: blue( $color );

  // Compute the perceptive luminance, keeping
  // in mind that the human eye favors green.
  $l: 1 - ( 0.299 * $red + 0.587 * $green + 0.114 * $blue ) / 255;
  @return ( $l < 0.5 );

}

Now figuring out how to use the algorithm to auto-create hover colors for menu links. Light headers get a darker hover, and vice-versa.

Anchorite answered 4/7, 2016 at 4:6 Comment(0)
T
4

Flutter implementation

Color contrastColor(Color color) {
  if (color == Colors.transparent || color.alpha < 50) {
    return Colors.black;
  }
  double luminance = (0.299 * color.red + 0.587 * color.green + 0.114 * color.blue) / 255;
  return luminance > 0.5 ? Colors.black : Colors.white;
}
Tamikotamil answered 21/4, 2019 at 21:46 Comment(1)
All I did was prefix with 'static.' Thanks!Fannie
E
3

I had the same problem but i had to develop it in PHP. I used @Garek's solution and i also used this answer: Convert hex color to RGB values in PHP to convert HEX color code to RGB.

So i'm sharing it.

I wanted to use this function with given Background HEX color, but not always starting from '#'.

//So it can be used like this way:
$color = calculateColor('#804040');
echo $color;

//or even this way:
$color = calculateColor('D79C44');
echo '<br/>'.$color;

function calculateColor($bgColor){
    //ensure that the color code will not have # in the beginning
    $bgColor = str_replace('#','',$bgColor);
    //now just add it
    $hex = '#'.$bgColor;
    list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x");
    $color = 1 - ( 0.299 * $r + 0.587 * $g + 0.114 * $b)/255;

    if ($color < 0.5)
        $color = '#000000'; // bright colors - black font
    else
        $color = '#ffffff'; // dark colors - white font

    return $color;
}
Element answered 16/5, 2018 at 10:57 Comment(0)
Q
3

Based on Gacek's answer, and after analyzing @WebSeed's example with the WAVE browser extension, I've come up with the following version that chooses black or white text based on contrast ratio (as defined in W3C's Web Content Accessibility Guidelines (WCAG) 2.1), instead of luminance.

This is the code (in javascript):

// As defined in WCAG 2.1
var relativeLuminance = function (R8bit, G8bit, B8bit) {
  var RsRGB = R8bit / 255.0;
  var GsRGB = G8bit / 255.0;
  var BsRGB = B8bit / 255.0;

  var R = (RsRGB <= 0.03928) ? RsRGB / 12.92 : Math.pow((RsRGB + 0.055) / 1.055, 2.4);
  var G = (GsRGB <= 0.03928) ? GsRGB / 12.92 : Math.pow((GsRGB + 0.055) / 1.055, 2.4);
  var B = (BsRGB <= 0.03928) ? BsRGB / 12.92 : Math.pow((BsRGB + 0.055) / 1.055, 2.4);

  return 0.2126 * R + 0.7152 * G + 0.0722 * B;
};

var blackContrast = function(r, g, b) {
  var L = relativeLuminance(r, g, b);
  return (L + 0.05) / 0.05;
};

var whiteContrast = function(r, g, b) {
  var L = relativeLuminance(r, g, b);
  return 1.05 / (L + 0.05);
};

// If both options satisfy AAA criterion (at least 7:1 contrast), use preference
// else, use higher contrast (white breaks tie)
var chooseFGcolor = function(r, g, b, prefer = 'white') {
  var Cb = blackContrast(r, g, b);
  var Cw = whiteContrast(r, g, b);
  if(Cb >= 7.0 && Cw >= 7.0) return prefer;
  else return (Cb > Cw) ? 'black' : 'white';
};

A working example may be found in my fork of @WebSeed's codepen, which produces zero low contrast errors in WAVE.

Quimby answered 10/6, 2020 at 12:47 Comment(0)
L
2

As Kotlin / Android extension:

fun Int.getContrastColor(): Int {
    // Counting the perceptive luminance - human eye favors green color...
    val a = 1 - (0.299 * Color.red(this) + 0.587 * Color.green(this) + 0.114 * Color.blue(this)) / 255
    return if (a < 0.5) Color.BLACK else Color.WHITE
}
Latton answered 3/11, 2017 at 21:57 Comment(0)
V
1

iOS Swift 3.0 (UIColor extension):

func isLight() -> Bool
{
    if let components = self.cgColor.components, let firstComponentValue = components[0], let secondComponentValue = components[1], let thirdComponentValue = components[2] {
        let firstComponent = (firstComponentValue * 299)
        let secondComponent = (secondComponentValue * 587)
        let thirdComponent = (thirdComponentValue * 114)
        let brightness = (firstComponent + secondComponent + thirdComponent) / 1000

        if brightness < 0.5
        {
            return false
        }else{
            return true
        }
    }  

    print("Unable to grab components and determine brightness")
    return nil
}
Variety answered 10/1, 2017 at 21:26 Comment(1)
The function is doing as expected but be careful with linting and force castingsDanley
T
1

An implementation for objective-c

+ (UIColor*) getContrastColor:(UIColor*) color {
    CGFloat red, green, blue, alpha;
    [color getRed:&red green:&green blue:&blue alpha:&alpha];
    double a = ( 0.299 * red + 0.587 * green + 0.114 * blue);
    return (a > 0.5) ? [[UIColor alloc]initWithRed:0 green:0 blue:0 alpha:1] : [[UIColor alloc]initWithRed:255 green:255 blue:255 alpha:1];
}
Tavi answered 2/4, 2018 at 17:34 Comment(1)
You should add some description here to let other users know what's going on in your codeJacobite
D
1

Swift 4 Example:

extension UIColor {

    var isLight: Bool {
        let components = cgColor.components

        let firstComponent = ((components?[0]) ?? 0) * 299
        let secondComponent = ((components?[1]) ?? 0) * 587
        let thirdComponent = ((components?[2]) ?? 0) * 114
        let brightness = (firstComponent + secondComponent + thirdComponent) / 1000

        return !(brightness < 0.6)
    }

}

UPDATE - Found that 0.6 was a better test bed for the query

Danley answered 8/6, 2018 at 17:45 Comment(1)
This is actually quite likely to fail in several situations, as it assumes an RGB colour space. The number of elements in CGColor.components varies depending on the colour space: for example, UIColor.white, when cast to a CGColor, has only two: [1.0, 1.0] representing a grayscale (at fully white) colour with a full alpha. A better means of extracting the RGB elements of a UIColor is UIColor.getRed(_ red:, green:, blue:, alpha:)Foreshore
E
1

Note there is an algorithm for this in the google closure library that references a w3c recommendation: http://www.w3.org/TR/AERT#color-contrast. However, in this API you provide a list of suggested colors as a starting point.

/**
 * Find the "best" (highest-contrast) of the suggested colors for the prime
 * color. Uses W3C formula for judging readability and visual accessibility:
 * http://www.w3.org/TR/AERT#color-contrast
 * @param {goog.color.Rgb} prime Color represented as a rgb array.
 * @param {Array<goog.color.Rgb>} suggestions Array of colors,
 *     each representing a rgb array.
 * @return {!goog.color.Rgb} Highest-contrast color represented by an array.
 */
goog.color.highContrast = function(prime, suggestions) {
  var suggestionsWithDiff = [];
  for (var i = 0; i < suggestions.length; i++) {
    suggestionsWithDiff.push({
      color: suggestions[i],
      diff: goog.color.yiqBrightnessDiff_(suggestions[i], prime) +
          goog.color.colorDiff_(suggestions[i], prime)
    });
  }
  suggestionsWithDiff.sort(function(a, b) { return b.diff - a.diff; });
  return suggestionsWithDiff[0].color;
};


/**
 * Calculate brightness of a color according to YIQ formula (brightness is Y).
 * More info on YIQ here: http://en.wikipedia.org/wiki/YIQ. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb Color represented by a rgb array.
 * @return {number} brightness (Y).
 * @private
 */
goog.color.yiqBrightness_ = function(rgb) {
  return Math.round((rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000);
};


/**
 * Calculate difference in brightness of two colors. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
 * @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
 * @return {number} Brightness difference.
 * @private
 */
goog.color.yiqBrightnessDiff_ = function(rgb1, rgb2) {
  return Math.abs(
      goog.color.yiqBrightness_(rgb1) - goog.color.yiqBrightness_(rgb2));
};


/**
 * Calculate color difference between two colors. Helper method for
 * goog.color.highContrast()
 * @param {goog.color.Rgb} rgb1 Color represented by a rgb array.
 * @param {goog.color.Rgb} rgb2 Color represented by a rgb array.
 * @return {number} Color difference.
 * @private
 */
goog.color.colorDiff_ = function(rgb1, rgb2) {
  return Math.abs(rgb1[0] - rgb2[0]) + Math.abs(rgb1[1] - rgb2[1]) +
      Math.abs(rgb1[2] - rgb2[2]);
};
Equanimity answered 13/10, 2019 at 14:19 Comment(0)
K
1

base R version of @Gacek's answer to get luminance (you can apply your own threshold easily)

# vectorized
luminance = function(col) c(c(.299, .587, .114) %*% col2rgb(col)/255)

Usage:

luminance(c('black', 'white', '#236FAB', 'darkred', '#01F11F'))
# [1] 0.0000000 1.0000000 0.3730039 0.1629843 0.5698039
Kistner answered 18/4, 2020 at 3:35 Comment(0)
I
0

If you're manipulating color spaces for visual effect it's generally easier to work in HSL (Hue, Saturation and Lightness) than RGB. Moving colours in RGB to give naturally pleasing effects tends to be quite conceptually difficult, whereas converting into HSL, manipulating there, then converting back out again is more intuitive in concept and invariably gives better looking results.

Wikipedia has a good introduction to HSL and the closely related HSV. And there's free code around the net to do the conversion (for example here is a javascript implementation)

What precise transformation you use is a matter of taste, but personally I'd have thought reversing the Hue and Lightness components would be certain to generate a good high contrast colour as a first approximation, but you can easily go for more subtle effects.

Irritable answered 6/12, 2009 at 17:21 Comment(2)
Yes, but consider also that the human eye can see green much more dominantly than other colors, and blue less so (which is why blue gets less color bits in image formats).Cockspur
Indeed. If we're going to be moving to HSL, we may as well make the full jump to YUV and take human perception into account.Satanic
E
0

You can have any hue text on any hue background and ensure that it is legible. I do it all the time. There's a formula for this in Javascript on Readable Text in Colour – STW* As it says on that link, the formula is a variation on the inverse-gamma adjustment calculation, though a bit more manageable IMHO. The menus on the right-hand side of that link and its associated pages use randomly-generated colours for text and background, always legible. So yes, clearly it can be done, no problem.

Elstan answered 19/1, 2016 at 17:48 Comment(0)
M
0

An Android variation that captures the alpha as well.

(thanks @thomas-vos)

/**
 * Returns a colour best suited to contrast with the input colour.
 *
 * @param colour
 * @return
 */
@ColorInt
public static int contrastingColour(@ColorInt int colour) {
    // XXX https://mcmap.net/q/99560/-determine-font-color-based-on-background-color

    // Counting the perceptive luminance - human eye favors green color...
    double a = 1 - (0.299 * Color.red(colour) + 0.587 * Color.green(colour) + 0.114 * Color.blue(colour)) / 255;
    int alpha = Color.alpha(colour);

    int d = 0; // bright colours - black font;
    if (a >= 0.5) {
        d = 255; // dark colours - white font
    }

    return Color.argb(alpha, d, d, d);
}
Mouse answered 6/3, 2018 at 16:12 Comment(0)
C
0

I would have commented on the answer by @MichaelChirico but I don't have enough reputation. So, here's an example in R with returning the colours:

get_text_colour <- function(
    background_colour,
    light_text_colour = 'white',
    dark_text_colour = 'black',
    threshold = 0.5
) {

    background_luminance <- c( 
        c( .299, .587, .114 ) %*% col2rgb( background_colour ) / 255
    )

    return(
        ifelse(
            background_luminance < threshold,
            light_text_colour,
            dark_text_colour
        )
    )
}
> get_text_colour( background_colour = 'blue' )
[1] "white"

> get_text_colour( background_colour = c( 'blue', 'yellow', 'pink' ) )
[1] "white" "black" "black"

> get_text_colour( background_colour = c('black', 'white', '#236FAB', 'darkred', '#01F11F') )
[1] "white" "black" "white" "white" "black"
Casemate answered 17/3, 2021 at 8:59 Comment(0)
U
0

In the case where you're trying to decide between known foreground colors (e.g., should text on this background be black or white?), this would be perfect: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-contrast Coming soon I hope!

Upswing answered 23/8, 2023 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.