Déterminer la couleur de police en fonction de la couleur d'arrière-plan

248

Étant donné un système (un site Web par exemple) qui permet à un utilisateur de personnaliser la couleur d'arrière-plan d'une section mais pas la couleur de la police (pour limiter le nombre d'options au minimum), existe-t-il un moyen de déterminer par programme si une "lumière" ou " une couleur de police "sombre" est nécessaire?

Je suis sûr qu'il existe un algorithme, mais je ne connais pas suffisamment les couleurs, la luminosité, etc. pour le découvrir par moi-même.

Joseph Daigle
la source

Réponses:

447

J'ai rencontré un problème similaire. J'ai dû trouver une bonne méthode pour sélectionner la couleur de police contrastée pour afficher les étiquettes de texte sur les échelles de couleurs / cartes thermiques. Il devait s'agir d'une méthode universelle et la couleur générée devait être «belle», ce qui signifie qu'une simple génération de couleur complémentaire n'était pas une bonne solution - parfois, elle générait des couleurs étranges et très intenses qui étaient difficiles à regarder et à lire.

Après de longues heures de test et d'essayer de résoudre ce problème, j'ai découvert que la meilleure solution était de sélectionner la police blanche pour les couleurs "foncées" et la police noire pour les couleurs "lumineuses".

Voici un exemple de fonction que j'utilise en 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);
}

Cela a été testé pour de nombreuses échelles de couleurs différentes (arc-en-ciel, niveaux de gris, chaleur, glace et bien d'autres) et c'est la seule méthode "universelle" que j'ai découverte.

Modifier
la formule de comptagea en "luminance perceptive" - ​​ça a vraiment l'air mieux! Déjà implémenté dans mon logiciel, il a fière allure.

Edit 2 @WebSeed a fourni un excellent exemple de travail de cet algorithme: http://codepen.io/WebSeed/full/pvgqEq/

Gacek
la source
14
Ce n'est probablement pas important, mais vous voudrez peut-être une meilleure fonction pour calculer la luminosité stackoverflow.com/questions/596216/…
Josh Lee
1
On dirait que ce sera parfait.
Joseph Daigle
Exactement ce dont j'avais besoin aujourd'hui.
Chris W
D'où viennent vos pondérations de luminance perceptive?
Mat
De cette réponse: stackoverflow.com/questions/596216/…
Gacek
23

Juste au cas où quelqu'un voudrait une version plus courte, peut-être plus facile à comprendre de la réponse de GaceK :

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

Remarque: j'ai supprimé l'inversion de la lumière valeur de (pour que les couleurs vives aient une valeur plus élevée, ce qui me semble plus naturel et c'est aussi la méthode de calcul `` par défaut '').

J'ai utilisé les mêmes constantes que GaceK à partir d' ici car elles fonctionnaient très bien pour moi.

(Vous pouvez également l'implémenter en tant que méthode d'extension à l'aide de la signature suivante:

public static Color ContrastColor(this Color iColor)

Vous pouvez ensuite l'appeler via foregroundColor = background.ContrastColor().)

Marcus Mangelsdorf
la source
11

Merci @Gacek . Voici une version pour 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);
}

Et une version améliorée (plus courte):

@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;
}
Thomas Vos
la source
Il serait encore plus court et plus facile à lire, si vous appliquez les modifications de @Marcus Mangelsdorf (se débarrasser de la 1 - ...partie et renommage aàluminance
Ridculle
En utilisant le premier, vous pouvez également capturer l'alpha.
Brill Pappin
9

Ma mise en œuvre rapide de la réponse de Gacek:

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)
}
Vito Royeca
la source
En rapide, puisque r / g / b sont des CGFloats, vous n'avez pas besoin du "/ 255" pour calculer la luminance: laissez la luminance = 1 - ((0,299 * r) + (0,587 * g) + (0,114 * b))
SuperDuperTango
8

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;
};
matfin
la source
6

Merci pour ce post.

Pour ceux qui pourraient être intéressés, voici un exemple de cette fonction dans 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;
Richard D.
la source
Il y a une petite erreur dans la 4e de la dernière ligne. Résultat: = clBlack ne doit pas avoir de point-virgule après lui;
Andy k
5

C'est une réponse tellement utile. Merci pour cela!

Je voudrais partager une version SCSS:

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

}

Maintenant, découvrez comment utiliser l'algorithme pour créer automatiquement des couleurs de survol pour les liens de menu. Les en-têtes clairs obtiennent un survol plus sombre, et vice-versa.

ricotheque
la source
3

Implémentation Flutter

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;
}
Mamnarock
la source
Je n'ai fait que préfixer «statique». Merci!
Pete Alvin
2

Ugly Python si vous n'avez pas envie de l'écrire :)

'''
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'
Joseph Coco
la source
2

J'ai eu le même problème mais j'ai dû le développer en PHP . J'ai utilisé la solution de @ Garek et j'ai également utilisé cette réponse: convertir la couleur hexadécimale en valeurs RVB en PHP pour convertir le code couleur HEX en RVB.

Je le partage donc.

Je voulais utiliser cette fonction avec une couleur de fond HEX donnée, mais pas toujours à partir de '#'.

//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;
}
GregV
la source
1

En tant qu'extension Kotlin / Android:

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
}
Gabriel
la source
1

Une implémentation pour objectif-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];
}
Andreas Lytter
la source
2
Vous devez ajouter une description ici pour informer les autres utilisateurs de ce qui se passe dans votre code
Michael
1

iOS Swift 3.0 (extension UIColor):

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
}
Josh O'Connor
la source
1
La fonction fonctionne comme prévu, mais soyez prudent avec les peluches et les moulages
forcés
1
Super, consultez mon exemple pour Swift 4 ci-dessous, vous pouvez voir la réduction de code
RichAppz
1

Exemple de Swift 4:

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

}

MISE À JOUR - Trouvé qui 0.6était un meilleur banc d'essai pour la requête

RichAppz
la source
Il est en fait très probable que cela échoue dans plusieurs situations, car cela suppose un espace colorimétrique RVB. Le nombre d'éléments dans CGColor.componentsvarie en fonction de l'espace colorimétrique: par exemple, UIColor.whitelorsqu'il est converti en CGColor, il n'en a que deux: [1.0, 1.0]représentant une couleur en niveaux de gris (entièrement blanche) avec un alpha complet. Un meilleur moyen d'extraire les éléments RVB d'un UIColor estUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman
1

Notez qu'il existe un algorithme pour cela dans la bibliothèque de fermeture de Google qui fait référence à une recommandation w3c: http://www.w3.org/TR/AERT#color-contrast . Cependant, dans cette API, vous fournissez une liste de couleurs suggérées comme point de départ.

/**
 * 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]);
};
Paul
la source
0

Si vous manipulez des espaces colorimétriques pour obtenir un effet visuel, il est généralement plus facile de travailler en HSL (teinte, saturation et luminosité) qu'en RVB. Déplacer des couleurs en RVB pour donner des effets naturellement agréables a tendance à être assez difficile sur le plan conceptuel, alors que la conversion en HSL, la manipulation, puis la conversion à nouveau est plus intuitive dans le concept et donne invariablement de meilleurs résultats.

Wikipedia a une bonne introduction à HSL et au HSV étroitement lié. Et il y a du code gratuit sur le net pour faire la conversion (par exemple, voici une implémentation javascript )

La transformation précise que vous utilisez est une question de goût, mais personnellement, j'aurais pensé qu'inverser les composants Teinte et Luminosité serait certain de générer une bonne couleur à contraste élevé en première approximation, mais vous pouvez facilement opter pour des effets plus subtils.

Cruachan
la source
1
Oui, mais considérez également que l'œil humain peut voir le vert de manière beaucoup plus dominante que les autres couleurs, et le bleu moins (c'est pourquoi le bleu obtient moins de bits de couleur dans les formats d'image).
wchargin
1
En effet. Si nous allons passer à HSL, nous pouvons aussi bien faire le saut complet vers YUV et prendre en compte la perception humaine.
David Bradbury
0

Vous pouvez avoir n'importe quel texte de teinte sur n'importe quel arrière-plan de teinte et vous assurer qu'il est lisible. Je le fais tout le temps. Il y a une formule pour cela en Javascript sur du texte lisible en couleur - STW * Comme il est dit sur ce lien, la formule est une variation du calcul d'ajustement du gamma inverse, bien qu'un peu plus gérable à mon humble avis. Les menus sur le côté droit de ce lien et ses pages associées utilisent des couleurs générées aléatoirement pour le texte et l'arrière-plan, toujours lisibles. Alors oui, c'est clairement possible, pas de problème.

Dave Collier
la source
0

Une variation Android qui capture également l'alpha.

(merci @ 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 /programming/1855884/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);
}
Brill Pappin
la source
0

baseVersion R de la réponse de @ Gacek à obtenir luminance(vous pouvez facilement appliquer votre propre seuil)

# 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
MichaelChirico
la source
0

Sur la base de la réponse de Gacek et après avoir analysé l'exemple de @ WebSeed avec l' extension de navigateur WAVE , j'ai trouvé la version suivante qui choisit le texte en noir ou blanc en fonction du rapport de contraste (tel que défini dans les directives d'accessibilité du contenu Web (WCAG) 2.1 du W3C ) , au lieu de la luminance.

Voici le code (en 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';
};

Un exemple de travail peut être trouvé dans ma fourchette de codepen @ WebSeed, qui ne produit aucune erreur de faible contraste dans WAVE.

Seirios
la source