Reputation: 5789
I want to show some images like this example
The fill color is decided by a field in the data base with the color in hex (ex:ClassX -> Color: #66FFFF). Now, I want to show data above a fill with the selected color (like in the image above) but i need to know if the color is dark or light so i know if the words should be in white or black. Is there a way? tks
Upvotes: 399
Views: 163054
Reputation: 23173
I take no credit for this code as it's not mine, but I leave it here for others to find quickly in the future:
Based on Mark Ransoms answer, here's a code snippet for the simple version:
function colorIsDarkSimple(bgColor) {
let color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
let r = parseInt(color.substring(0, 2), 16); // hexToR
let g = parseInt(color.substring(2, 4), 16); // hexToG
let b = parseInt(color.substring(4, 6), 16); // hexToB
return ((r * 0.299) + (g * 0.587) + (b * 0.114)) <= 186;
}
and here's the code snippet for the advanced version:
function colorIsDarkAdvanced(bgColor) {
let color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
let r = parseInt(color.substring(0, 2), 16); // hexToR
let g = parseInt(color.substring(2, 4), 16); // hexToG
let b = parseInt(color.substring(4, 6), 16); // hexToB
let uicolors = [r / 255, g / 255, b / 255];
let c = uicolors.map((col) => {
if (col <= 0.03928) {
return col / 12.92;
}
return Math.pow((col + 0.055) / 1.055, 2.4);
});
let L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
return L <= 0.179;
}
To use them just call:
let bgColor = '#EEACAE' // this can be any color
let textColor = colorIsDarkSimple(bgColor) ? '#FFFFFF' : '#000000';
Also, thanks Alx
and chetstone
.
Upvotes: 119
Reputation: 718
If like me you were looking for a RGBA version that takes alpha into account, here is one that works really well to have high contrast.
function getContrastColor(R, G, B, A) {
const brightness = R * 0.299 + G * 0.587 + B * 0.114 + (1 - A) * 255;
return brightness > 186 ? "#000000" : "#FFFFFF";
}
Upvotes: 23
Reputation: 200
For anyone searching for a Python3 implementation of SudoPlz's advanced function:
def get_text_color_based_on_background_color(bgColor, lightColor, darkColor):
color = bgColor.lstrip("#").rstrip(";")
r, g, b = hex_to_rgb(color)
uicolors = [r/255, g/255, b/255]
adjusted = []
for col in uicolors:
col2 = col
if col <= 0.03928:
col2 = col/12.92
col2 = pow((col2 + 0.055)/1.055,2.4)
adjusted.append(col2)
L = (0.2126 * adjusted[0] + 0.7152 * adjusted[1] + (0.072 * adjusted[2]))
return darkColor if L > 0.179 else lightColor
Which depends on hex_to_rgb
from this venerable question.
def hex_to_rgb(value):
value = value.lstrip('#').rstrip(";")
lv = len(value)
return tuple(int(value[i:i + lv // 3], 16) for i in range(0, lv, lv // 3))
Upvotes: 1
Reputation: 8668
Ruby solution based on @SudoPlz's answer
def get_contrast_color(bg_color, light_color = '#FFFFFF', dark_color = '#000000')
color = bg_color.gsub('#', '')
r = color&.slice(0, 2).to_i(16)
g = color&.slice(2, 2).to_i(16)
b = color&.slice(4, 2).to_i(16)
intensity = (r * 0.299) + (g * 0.587) + (b * 0.114)
intensity > 186 ? dark_color : light_color
end
get_contrast_color('#222222') #=> '#FFFFFF'
Upvotes: 0
Reputation: 377
Just in case anybody cares for a SCSS
version of Mark Ransom's answer:
@use 'sass:color' as *;
@use 'sass:math' as *;
@function col_r($color) {
@if $color <= 0.03928 {
@return $color / 12.92;
} @else {
@return pow((($color + 0.055) / 1.055), (2.4));
}
}
@function pickTextColorBasedOnBgColorAdvanced(
$bgColor,
$lightColor,
$darkColor
) {
$r: red($bgColor);
$g: green($bgColor);
$b: blue($bgColor);
$ui_r: $r / 255;
$ui_g: $g / 255;
$ui_b: $b / 255;
$ui_r_c: col_r($ui_r);
$ui_g_c: col_r($ui_g);
$ui_b_c: col_r($ui_b);
$L: (0.2126 * $ui_r_c) + (0.7152 * $ui_g_c) + (0.0722 * $ui_b_c);
@if ($L > 0.179) {
@return $darkColor;
} @else {
@return $lightColor;
}
}
Upvotes: 0
Reputation: 308081
Building on my answer to a similar question.
You need to break the hex code into 3 pieces to get the individual red, green, and blue intensities. Each 2 digits of the code represent a value in hexadecimal (base-16) notation. I won't get into the details of the conversion here, they're easy to look up.
Once you have the intensities for the individual colors, you can determine the overall intensity of the color and choose the corresponding text.
if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff
The threshold of 186 is based on theory, but can be adjusted to taste. Based on the comments below a threshold of 150 may work better for you.
Edit: The above is simple and works reasonably well, and seems to have good acceptance here at StackOverflow. However, one of the comments below shows it can lead to non-compliance with W3C guidelines in some circumstances. Herewith I derive a modified form that always chooses the highest contrast based on the guidelines. If you don't need to conform to W3C rules then I'd stick with the simpler formula above. For an interesting look into the problems with this see Contrast Ratio Math and Related Visual Issues.
The formula given for contrast in the W3C Recommendations (WCAG 2.0) is (L1 + 0.05) / (L2 + 0.05)
, where L1
is the luminance of the lightest color and L2
is the luminance of the darkest on a scale of 0.0-1.0. The luminance of black is 0.0 and white is 1.0, so substituting those values lets you determine the one with the highest contrast. If the contrast for black is greater than the contrast for white, use black, otherwise use white. Given the luminance of the color you're testing as L
the test becomes:
if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff
This simplifies down algebraically to:
if L > sqrt(1.05 * 0.05) - 0.05
Or approximately:
if L > 0.179 use #000000 else use #ffffff
The only thing left is to compute L
. That formula is also given in the guidelines and it looks like the conversion from sRGB to linear RGB followed by the ITU-R recommendation BT.709 for luminance.
for each c in r,g,b:
c = c / 255.0
if c <= 0.04045 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b
The threshold of 0.179 should not be changed since it is tied to the W3C guidelines. If you find the results not to your liking, try the simpler formula above.
Upvotes: 606
Reputation: 194
Here's my own method that I've been using and haven't had a problem with so far 😄
function hexToRgb(hex) {
const hexCode = hex.charAt(0) === '#'
? hex.substr(1, 6)
: hex;
const hexR = parseInt(hexCode.substr(0, 2), 16);
const hexG = parseInt(hexCode.substr(2, 2), 16);
const hexB = parseInt(hexCode.substr(4, 2), 16);
// Gets the average value of the colors
const contrastRatio = (hexR + hexG + hexB) / (255 * 3);
return contrastRatio >= 0.5
? 'black'
: 'white';
}
Upvotes: 13
Reputation: 503
Additionally to the arithmetic solutions, it's also possible to use an AI neural network. The advantage is that you can tailor it to your own taste and needs (ie. off-white text on bright saturated reds looks good and is just as readable as black).
Here's a neat Javascript demo that illustrates the concept. You can also generate your own JS formula right in the demo.
https://harthur.github.io/brain/
Below are some charts that helped me get my mind around the problem. In the first chart, lightness is a constant 128, while hue and saturation vary. In the second chart, saturation is a constant 255, while hue and lightness vary.
Upvotes: 31
Reputation: 10038
Accepted answer somehow never worked on Android when using androidx.compose.ui.graphics.Color
. Then i found there is actually built in luminance()
function in Android Jetpack Compose which returns luminance value between [0,1]. Documentation says "Based on the formula for relative luminance defined in WCAG 2.0, W3C Recommendation 11 December 2008.".
Here is the official source code.
Example usage:
fun Color.generateOnColor()
: Color {
return if (luminance() > 0.5f) {
Color.Black.copy(alpha = .8f)
} else {
Color.White
}
}
Upvotes: 2
Reputation: 1578
I did a function based on the advanced one proposed by @SudoPlz which takes also account of light and dark colors:
function getTextColor (bgColor, lightColor = '#FFFFFF', darkColor = '#000000') {
const getLuminance = function (hexColor) {
var color = (hexColor.charAt(0) === '#') ? hexColor.substring(1, 7) : hexColor
var r = parseInt(color.substring(0, 2), 16) // hexToR
var g = parseInt(color.substring(2, 4), 16) // hexToG
var b = parseInt(color.substring(4, 6), 16) // hexToB
var uicolors = [r / 255, g / 255, b / 255]
var c = uicolors.map(col => col <= 0.03928 ? col / 12.92 : ((col + 0.055) / 1.055) ** 2.4)
return (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
}
var L = getLuminance(bgColor)
var L1 = getLuminance(lightColor)
var L2 = getLuminance(darkColor)
return (L > Math.sqrt((L1 + 0.05) * (L2 + 0.05)) - 0.05) ? darkColor : lightColor;
}
Therefore if the dark text is not black but maroon, the recommended text color on a grey background becomes white:
getTextColor('#808080')
"#000000"
getTextColor('#808080', '#FFFFFF', '#800000')
"#FFFFFF"
Upvotes: 4
Reputation: 8461
I'm using the tinyColor library which can also do the job.
import { TinyColor } from '@ctrl/tinycolor'
// ...
getColorContrast(color = '#66FFFF'): string {
if(new TinyColor(color).getLuminance() > 0.179) { // 0.179 -> Mark Ransom answer
return '#000'
} else {
return '#fff'
}
}
This method would also accept rgb color like rgb(102,255,255)
Upvotes: 3
Reputation: 2018
This is an R version of Mark Ransom's answer, using base R only.
hex_bw <- function(hex_code) {
myrgb <- as.integer(col2rgb(hex_code))
rgb_conv <- lapply(myrgb, function(x) {
i <- x / 255
if (i <= 0.03928) {
i <- i / 12.92
} else {
i <- ((i + 0.055) / 1.055) ^ 2.4
}
return(i)
})
rgb_calc <- (0.2126*rgb_conv[[1]]) + (0.7152*rgb_conv[[2]]) + (0.0722*rgb_conv[[3]])
if (rgb_calc > 0.179) return("#000000") else return("#ffffff")
}
> hex_bw("#8FBC8F")
[1] "#000000"
> hex_bw("#7fa5e3")
[1] "#000000"
> hex_bw("#0054de")
[1] "#ffffff"
> hex_bw("#2064d4")
[1] "#ffffff"
> hex_bw("#5387db")
[1] "#000000"
Upvotes: 3
Reputation: 2517
Here is my code for Java Swing based on Mark's amazing answer:
public static Color getColorBasedOnBackground(Color background, Color darkColor, Color lightColor) {
// Calculate foreground color based on background (based on https://stackoverflow.com/a/3943023/)
Color color;
double[] cL = new double[3];
double[] colorRGB = new double[] {background.getRed(), background.getGreen(), background.getBlue()};
for (int i = 0; i < colorRGB.length; i++)
cL[i] = (colorRGB[i] / 255.0 <= 0.03928) ? colorRGB[i] / 255.0 / 12.92 :
Math.pow(((colorRGB[i] / 255.0 + 0.055) / 1.055), 2.4);
double L = 0.2126 * cL[0] + 0.7152 * cL[1] + 0.0722 * cL[2];
color = (L > Math.sqrt(1.05 * 0.05) - 0.05) ? darkColor : lightColor;
return color;
}
Upvotes: 0
Reputation: 153
What about testing with all 24 bits colors ?
Be aware that YIQ method will return a minimum contrast ratio of 1.9:1, that doesn't pass AA and AAA WCAG2.0 tests, assuming a 128 threshold.
For W3C method, it will return a minimum contrast ratio of 4.58:1, that pass AA and AAA tests for large text, and AA test for small text, it won't pass AAA test for small text for every colors.
Upvotes: 0
Reputation: 422
Based on different inputs from the link Make foregroundcolor black or white depending on background and this thread, I made an extension class for Color that gives you required contrast colors.
The code goes as below:
public static class ColorExtension
{
public static int PerceivedBrightness(this Color c)
{
return (int)Math.Sqrt(
c.R * c.R * .299 +
c.G * c.G * .587 +
c.B * c.B * .114);
}
public static Color ContrastColor(this Color iColor, Color darkColor,Color lightColor)
{
// Counting the perceptive luminance (aka luma) - human eye favors green color...
double luma = (iColor.PerceivedBrightness() / 255);
// Return black for bright colors, white for dark colors
return luma > 0.5 ? darkColor : lightColor;
}
public static Color ContrastColor(this Color iColor) => iColor.ContrastColor(Color.Black);
public static Color ContrastColor(this Color iColor, Color darkColor) => iColor.ContrastColor(darkColor, Color.White);
// Converts a given Color to gray
public static Color ToGray(this Color input)
{
int g = (int)(input.R * .299) + (int)(input.G * .587) + (int)(input.B * .114);
return Color.FromArgb(input.A, g, g, g);
}
}
Upvotes: 1
Reputation: 4207
This is a swift version of Mark Ransom's answer as an extension of UIColor
extension UIColor {
// Get the rgba components in CGFloat
var rgba: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (red, green, blue, alpha)
}
/// Return the better contrasting color, white or black
func contrastColor() -> UIColor {
let rgbArray = [rgba.red, rgba.green, rgba.blue]
let luminanceArray = rgbArray.map({ value -> (CGFloat) in
if value < 0.03928 {
return (value / 12.92)
} else {
return (pow( (value + 0.55) / 1.055, 2.4) )
}
})
let luminance = 0.2126 * luminanceArray[0] +
0.7152 * luminanceArray[1] +
0.0722 * luminanceArray[2]
return luminance > 0.179 ? UIColor.black : UIColor.white
} }
Upvotes: 6
Reputation: 1156
Objective-c version code for iOS based on Mark's answer:
- (UIColor *)contrastForegroundColor {
CGFloat red = 0, green = 0, blue = 0, alpha = 0;
[self getRed:&red green:&green blue:&blue alpha:&alpha];
NSArray<NSNumber *> *rgbArray = @[@(red), @(green), @(blue)];
NSMutableArray<NSNumber *> *parsedRGBArray = [NSMutableArray arrayWithCapacity:rgbArray.count];
for (NSNumber *item in rgbArray) {
if (item.doubleValue <= 0.03928) {
[parsedRGBArray addObject:@(item.doubleValue / 12.92)];
} else {
double newValue = pow((item.doubleValue + 0.055) / 1.055, 2.4);
[parsedRGBArray addObject:@(newValue)];
}
}
double luminance = 0.2126 * parsedRGBArray[0].doubleValue + 0.7152 * parsedRGBArray[1].doubleValue + 0.0722 * parsedRGBArray[2].doubleValue;
return luminance > 0.179 ? UIColor.blackColor : UIColor.whiteColor;
}
Upvotes: 0
Reputation: 23929
From hex to black or white:
function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? [
parseInt(result[1], 16),
parseInt(result[2], 16),
parseInt(result[3], 16)
]
: [0, 0, 0];
}
function lum(hex) {
var rgb = hexToRgb(hex)
var lrgb = [];
rgb.forEach(function(c) {
c = c / 255.0;
if (c <= 0.03928) {
c = c / 12.92;
} else {
c = Math.pow((c + 0.055) / 1.055, 2.4);
}
lrgb.push(c);
});
var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
return lum > 0.179 ? "#000000" : "#ffffff";
}
Upvotes: 1
Reputation: 14530
Mark's detailed answer works great. Here is an implementation in javascript:
function lum(rgb) {
var lrgb = [];
rgb.forEach(function(c) {
c = c / 255.0;
if (c <= 0.03928) {
c = c / 12.92;
} else {
c = Math.pow((c + 0.055) / 1.055, 2.4);
}
lrgb.push(c);
});
var lum = 0.2126 * lrgb[0] + 0.7152 * lrgb[1] + 0.0722 * lrgb[2];
return (lum > 0.179) ? '#000000' : '#ffffff';
}
Then can call this function lum([111, 22, 255])
to get white or black.
Upvotes: 2
Reputation: 408
Based on the answer of @MarkRansom, I created a PHP script you can find here:
function calcC($c) {
if ($c <= 0.03928) {
return $c / 12.92;
}
else {
return pow(($c + 0.055) / 1.055, 2.4);
}
}
function cutHex($h) {
return ($h[0] == "#") ? substr($h, 1, 7) : $h;
}
function hexToR($h) {
return hexdec(substr(cutHex($h), 0, 2));
}
function hexToG($h) {
return hexdec(substr(cutHex($h), 2, 2)); // Edited
}
function hexToB($h) {
return hexdec(substr(cutHex($h), 4, 2)); // Edited
}
function computeTextColor($color) {
$r = hexToR($color);
$g = hexToG($color);
$b = hexToB($color);
$uicolors = [$r / 255, $g / 255, $b / 255];
$c = array_map("calcC", $uicolors);
$l = 0.2126 * $c[0] + 0.7152 * $c[1] + 0.0722 * $c[2];
return ($l > 0.179) ? '#000000' : '#ffffff';
}
Upvotes: 6
Reputation: 2252
Here is my solution in Java for Android:
// Put this method in whichever class you deem appropriate
// static or non-static, up to you.
public static int getContrastColor(int colorIntValue) {
int red = Color.red(colorIntValue);
int green = Color.green(colorIntValue);
int blue = Color.blue(colorIntValue);
double lum = (((0.299 * red) + ((0.587 * green) + (0.114 * blue))));
return lum > 186 ? 0xFF000000 : 0xFFFFFFFF;
}
// Usage
// If Color is represented as HEX code:
String colorHex = "#484588";
int color = Color.parseColor(colorHex);
// Or if color is Integer:
int color = 0xFF484588;
// Get White (0xFFFFFFFF) or Black (0xFF000000)
int contrastColor = WhateverClass.getContrastColor(color);
Upvotes: 8
Reputation: 3315
LESS has a nice contrast()
function that worked nicely for me, see http://lesscss.org/functions/#color-operations-contrast
"Choose which of two colors provides the greatest contrast with another. This is useful for ensuring that a color is readable against a background, which is also useful for accessibility compliance. This function works the same way as the contrast function in Compass for SASS. In accordance with WCAG 2.0, colors are compared using their gamma-corrected luma value, not their lightness."
Example:
p {
a: contrast(#bbbbbb);
b: contrast(#222222, #101010);
c: contrast(#222222, #101010, #dddddd);
d: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 30%);
e: contrast(hsl(90, 100%, 50%), #000000, #ffffff, 80%);
}
Output:
p {
a: #000000 // black
b: #ffffff // white
c: #dddddd
d: #000000 // black
e: #ffffff // white
}
Upvotes: 1
Reputation: 46
@SoBiT, I was looking at your answer, which looks good, but there is a small mistake in it. Your function hexToG, and hextoB need a minor edit. The last number in substr is the length of the string, and so in this case itshould be "2", rather than 4 or 6.
function hexToR($h) {
return hexdec(substr(cutHex($h), 0, 2));
}
function hexToG($h) {
return hexdec(substr(cutHex($h), 2, 2));
}
function hexToB($h) {
return hexdec(substr(cutHex($h), 4, 2));
}
Upvotes: 0
Reputation: 6124
How about this (JavaScript code)?
/**
* Get color (black/white) depending on bgColor so it would be clearly seen.
* @param bgColor
* @returns {string}
*/
getColorByBgColor(bgColor) {
if (!bgColor) { return ''; }
return (parseInt(bgColor.replace('#', ''), 16) > 0xffffff / 2) ? '#000' : '#fff';
}
Upvotes: 25
Reputation: 23141
I use this JavaScript function to convert rgb
/rgba
to 'white'
or 'black'
.
function getTextColor(rgba) {
rgba = rgba.match(/\d+/g);
if ((rgba[0] * 0.299) + (rgba[1] * 0.587) + (rgba[2] * 0.114) > 186) {
return 'black';
} else {
return 'white';
}
}
You can enter any of these formats and it will output 'black'
or 'white'
rgb(255,255,255)
rgba(255,255,255,0.1)
color:rgba(255,255,255,0.1)
255,255,255,0.1
Upvotes: 2
Reputation: 35953
This is just an example that will change the color of an SVG checkmark when clicking on an element. It will set the checkmark color to black or white based on the background-color of the clicked element.
checkmarkColor: function(el) {
var self = el;
var contrast = function checkContrast(rgb) {
// @TODO check for HEX value
// Get RGB value between parenthesis, and remove any whitespace
rgb = rgb.split(/\(([^)]+)\)/)[1].replace(/ /g, '');
// map RGB values to variables
var r = parseInt(rgb.split(',')[0], 10),
g = parseInt(rgb.split(',')[1], 10),
b = parseInt(rgb.split(',')[2], 10),
a;
// if RGBA, map alpha to variable (not currently in use)
if (rgb.split(',')[3] !== null) {
a = parseInt(rgb.split(',')[3], 10);
}
// calculate contrast of color (standard grayscale algorithmic formula)
var contrast = (Math.round(r * 299) + Math.round(g * 587) + Math.round(b * 114)) / 1000;
return (contrast >= 128) ? 'black' : 'white';
};
$('#steps .step.color .color-item .icon-ui-checkmark-shadow svg').css({
'fill': contrast($(self).css('background-color'))
});
}
onClickExtColor: function(evt) {
var self = this;
self.checkmarkColor(evt.currentTarget);
}
https://gist.github.com/dcondrey/183971f17808e9277572
Upvotes: 2
Reputation: 3699
I've never done anything like this, but what about writing a function to check the values of each of the colors against the median color of Hex 7F (FF / 2). If two of the three colors are greater than 7F, then you're working with a darker color.
Upvotes: 1