Comment décider de la couleur de la police en blanc ou en noir en fonction de la couleur d'arrière-plan?


223

Je veux montrer quelques images comme cet exempletexte alternatif

La couleur de remplissage est décidée par un champ dans la base de données avec la couleur en hexadécimal (ex: ClassX -> Couleur: # 66FFFF). Maintenant, je veux afficher des données au-dessus d'un remplissage avec la couleur sélectionnée (comme dans l'image ci-dessus) mais j'ai besoin de savoir si la couleur est sombre ou claire, donc je sais si les mots doivent être en blanc ou en noir. Y a-t-il un moyen? tks


Réponses:


348

S'appuyant sur ma réponse à une question similaire .

Vous devez diviser le code hexadécimal en 3 morceaux pour obtenir les intensités individuelles de rouge, vert et bleu. Chaque 2 chiffres du code représente une valeur en notation hexadécimale (base-16). Je n'entrerai pas dans les détails de la conversion ici, ils sont faciles à rechercher.

Une fois que vous avez les intensités pour les couleurs individuelles, vous pouvez déterminer l'intensité globale de la couleur et choisir le texte correspondant.

if (red*0.299 + green*0.587 + blue*0.114) > 186 use #000000 else use #ffffff

Le seuil de 186 est basé sur la théorie, mais peut être ajusté au goût. Sur la base des commentaires ci-dessous, un seuil de 150 peut fonctionner mieux pour vous.


Edit: ce qui précède est simple et fonctionne assez bien, et semble avoir une bonne acceptation ici à StackOverflow. Cependant, l'un des commentaires ci-dessous montre que cela peut conduire à la non-conformité aux directives du W3C dans certaines circonstances. Ci-joint, je dérive une forme modifiée qui choisit toujours le contraste le plus élevé en fonction des directives. Si vous n'avez pas besoin de vous conformer aux règles du W3C, je m'en tiendrai à la formule plus simple ci-dessus.

La formule donnée pour le contraste dans les Recommandations du W3C est (L1 + 0.05) / (L2 + 0.05), où L1est la luminance de la couleur la plus claire et L2la luminance de la plus sombre sur une échelle de 0,0 à 1,0. La luminance du noir est de 0,0 et celle du blanc de 1,0, donc la substitution de ces valeurs vous permet de déterminer celle avec le contraste le plus élevé. Si le contraste du noir est supérieur au contraste du blanc, utilisez le noir, sinon utilisez le blanc. Étant donné la luminance de la couleur que vous testez au fur et à mesure que Lle test devient:

if (L + 0.05) / (0.0 + 0.05) > (1.0 + 0.05) / (L + 0.05) use #000000 else use #ffffff

Cela simplifie algébriquement:

if L > sqrt(1.05 * 0.05) - 0.05

Ou environ:

if L > 0.179 use #000000 else use #ffffff

Il ne reste plus qu'à calculer L. Cette formule est également donnée dans les lignes directrices et ressemble à la conversion de sRGB en RVB linéaire suivie de la recommandation UIT-R BT.709 pour la luminance.

for each c in r,g,b:
    c = c / 255.0
    if c <= 0.03928 then c = c/12.92 else c = ((c+0.055)/1.055) ^ 2.4
L = 0.2126 * r + 0.7152 * g + 0.0722 * b

Le seuil de 0,179 ne doit pas être modifié car il est lié aux directives du W3C. Si vous ne trouvez pas les résultats à votre goût, essayez la formule plus simple ci-dessus.


2
Tks Mark. J'ai essayé quelques changements: calculé rouge vert et bleu par le premier chiffre seulement (moins précis mais c'est le chiffre avec plus de poids) et au lieu de 186 utilisé 9. fonctionne un peu mieux pour moi, surtout avec les verts.
DJPB du

2
Cette formule est fausse, par beaucoup. Pour prendre un exemple, il donne au jaune #D6B508une valeur de 171, d'où un contraste blanc. Le contraste devrait cependant être noir (confirmé ici: webaim.org/resources/contrastchecker )
McGarnagle

1
@MarkRansom oui, à mes yeux, le noir est beaucoup mieux avec ce jaune. Comme je travaille avec un ensemble limité de couleurs, j'ai pu simplement déplacer la coupure de 186 à une valeur inférieure.
McGarnagle

3
Voici une autre démo comparant les deux formules données dans cette réponse. En utilisant le sélecteur de couleurs (dans Firefox ou Chrome récent), vous pouvez comparer le contraste avec n'importe quelle couleur.
chetstone

3
Après avoir joué un peu avec la démo de @ chetstone, je pense que la formule simple me convient le mieux, sauf que je ne suis pas d'accord avec la recommandation de seuil. Sur la base des résultats du vert pur, j'ai essayé de régler le seuil à 149 et cela fonctionne beaucoup mieux à mon avis. J'ai fait un violon vraiment stupide pour le démontrer ; vous pouvez essayer de changer le seuil en haut en arrière pour voir la suggestion d'origine.
Miral

30

Je ne prends aucun crédit pour ce code car ce n'est pas le mien, mais je le laisse ici pour que les autres le trouvent rapidement à l'avenir:

Basé sur la réponse de Mark Ransoms, voici un extrait de code pour la version simple:

function pickTextColorBasedOnBgColorSimple(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  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
  return (((r * 0.299) + (g * 0.587) + (b * 0.114)) > 186) ?
    darkColor : lightColor;
}

et voici l'extrait de code pour la version avancée:

function pickTextColorBasedOnBgColorAdvanced(bgColor, lightColor, darkColor) {
  var color = (bgColor.charAt(0) === '#') ? bgColor.substring(1, 7) : bgColor;
  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) => {
    if (col <= 0.03928) {
      return col / 12.92;
    }
    return Math.pow((col + 0.055) / 1.055, 2.4);
  });
  var L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
  return (L > 0.179) ? darkColor : lightColor;
}

Pour les utiliser, appelez simplement:

var color = '#EEACAE' // this can be any color
pickTextColorBasedOnBgColorSimple(color, '#FFFFFF', '#000000');

Merci aussi Alxet chetstone.


1
J'ai utilisé la fonction simple, et dépouillé vers le bas un peu plus: les 2 paramètres abandonné derniers et retitré est tout simplement isDark(bgColor)Mon utilisation est alors juste'color': isDark(color)?'white':'black'
diynevala

1
A fonctionné à merveille pour moi. Merci beaucoup! Je l'ai fait en php mais en convertissant simplement la syntaxe et les fonctions.
CezarBastos

19

Que diriez-vous de cela (code JavaScript)?

/**
 * 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';
}

1
Cela fonctionne la plupart du temps mais il y a des cas comme i.imgur.com/3pOUDe5.jpg qui ont l'air bizarre, la couleur de fond est en fait RVB (6, 247, 241);
madprops

J'apprécie que c'est un moyen relativement bon marché d'effectuer un calcul de contraste dans les cas où vous n'avez pas déjà converti la couleur en valeurs RVB (pas que ce soit trop difficile, juste des étapes mathématiques supplémentaires)
frumbert

12

En plus des solutions arithmétiques, il est également possible d'utiliser un réseau neuronal AI. L'avantage est que vous pouvez l'adapter à vos propres goûts et besoins (c'est-à-dire que le texte blanc cassé sur les rouges saturés brillants a l'air bien et est tout aussi lisible que le noir).

Voici une démo Javascript soignée qui illustre le concept. Vous pouvez également générer votre propre formule JS directement dans la démo.

https://harthur.github.io/brain/

Voici quelques graphiques qui m'ont aidé à me faire une idée du problème . Dans le premier graphique, la luminosité est une constante 128, tandis que la teinte et la saturation varient. Dans le deuxième graphique, la saturation est une constante 255, tandis que la teinte et la luminosité varient.

Dans le premier graphique, la luminosité est une constante 128, tandis que la teinte et la saturation varient:

La saturation est une constante 255, tandis que la teinte et la luminosité varient:


8

Voici ma solution en Java pour 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);

2
Est-ce vraiment «parfait»? Essayez un fond vert pur, # 00FF00.
Andreas Rejbrand

C'est vrai que ce n'est pas testé pour toutes les couleurs .... mais qui utiliserait un fond vert pur pour tout ce qui n'a pas été conçu pour agacer les utilisateurs?
mwieczorek

Les gens de @mwieczorek qui s'appuient sur du contenu généré par l'utilisateur ou des couleurs sélectionnées au hasard le font.
Marc Plano-Lesay

4

Sur la base de la réponse de @MarkRansom, j'ai créé un script PHP que vous pouvez trouver ici:

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';
}

3

Ceci est une version rapide de la réponse de Mark Ransom comme une extension de 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
} }

2

Ceci est juste un exemple qui changera la couleur d'une coche SVG lorsque vous cliquez sur un élément. Il définira la couleur de la coche sur noir ou blanc en fonction de la couleur d'arrière-plan de l'élément cliqué.

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


2

J'utilise cette fonction JavaScript pour convertir rgb/ rgbavers 'white'ou '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';
    }
}

Vous pouvez entrer n'importe lequel de ces formats et il sortira 'black'ou'white'

  • rgb(255,255,255)
  • rgba(255,255,255,0.1)
  • color:rgba(255,255,255,0.1)
  • 255,255,255,0.1

Essayez maintenant ceci avec un fond vert pur: # 00FF00.
Andreas Rejbrand

Merci pour ça! Traduit en swift et utilisé dans mon application ios!
Lucas P.

2

La réponse détaillée de Mark fonctionne très bien. Voici une implémentation en 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';
}

Vous pouvez ensuite appeler cette fonction lum([111, 22, 255])pour obtenir du blanc ou du noir.


1

Je n'ai jamais rien fait de tel, mais qu'en est-il de l'écriture d'une fonction pour vérifier les valeurs de chacune des couleurs par rapport à la couleur médiane de Hex 7F (FF / 2). Si deux des trois couleurs sont supérieures à 7F, alors vous travaillez avec une couleur plus foncée.


1

Sur la base de différentes entrées du lien Rendre l'avant-plan noir ou blanc en fonction de l'arrière - plan et de ce fil, j'ai créé une classe d'extension pour Couleur qui vous donne les couleurs de contraste requises.

Le code va comme ci-dessous:

 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);
    }
}

1

Si comme moi vous cherchiez une version RGBA qui prend en compte l'alpha, en voici une qui fonctionne vraiment bien pour avoir un contraste élevé.

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";
}

1

Il s'agit d'une version R de la réponse de Mark Ransom, utilisant uniquement la base R.

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"

0

@SoBiT, je regardais votre réponse, qui semble bonne, mais elle contient une petite erreur. Votre fonction hexToG et hextoB ont besoin d'une modification mineure. Le dernier nombre dans substr est la longueur de la chaîne, et dans ce cas, il devrait être "2", plutôt que 4 ou 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));
}

0

MOINS a une belle contrast()fonction qui a bien fonctionné pour moi, voir http://lesscss.org/functions/#color-operations-contrast

"Choisissez laquelle des deux couleurs offre le plus grand contraste avec une autre. Ceci est utile pour garantir qu'une couleur est lisible sur un arrière-plan, ce qui est également utile pour la conformité de l'accessibilité. Cette fonction fonctionne de la même manière que la fonction de contraste dans Compass for SASS. Conformément aux WCAG 2.0, les couleurs sont comparées en utilisant leur valeur luma corrigée gamma, et non leur légèreté. "

Exemple:

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%);
}

Production:

p {
    a: #000000 // black
    b: #ffffff // white
    c: #dddddd
    d: #000000 // black
    e: #ffffff // white
}

0

De l'hexagone au noir ou blanc:

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";
}

0

Code de version Objective-c pour iOS basé sur la réponse de Mark:

- (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;
}

0

Qu'en est-il des tests avec toutes les couleurs 24 bits?

Sachez que la méthode YIQ renverra un rapport de contraste minimum de 1,9: 1, qui ne passe pas les tests AA et AAA WCAG2.0, en supposant un seuil de 128.

Pour la méthode W3C, il renverra un rapport de contraste minimum de 4,58: 1, qui réussira les tests AA et AAA pour le grand texte et le test AA pour le petit texte, il ne passera pas le test AAA pour le petit texte pour toutes les couleurs.


0

Voici ma propre méthode que j'utilise et que je n'ai pas eu de problème jusqu'à présent 😄

const hexCode = value.charAt(0) === '#' 
                  ? value.substr(1, 6)
                  : value;

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);

contrastRatio >= 0.5
  ? 'black'
  : 'white';


0

Voici mon code pour Java Swing basé sur la réponse étonnante de Mark:

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;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.