Determina il colore del carattere in base al colore di sfondo


248

Dato un sistema (ad esempio un sito Web) che consente a un utente di personalizzare il colore di sfondo per alcune sezioni ma non il colore del carattere (per mantenere il numero di opzioni al minimo), esiste un modo per determinare a livello di codice se una "luce" o " scuro "il colore del carattere è necessario?

Sono sicuro che esiste un algoritmo, ma non ne so abbastanza di colori, luminosità, ecc. Per capirlo da solo.

Risposte:


447

Ho riscontrato un problema simile. Ho dovuto trovare un buon metodo per selezionare il colore del carattere contrastante per visualizzare le etichette di testo su scale di colori / mappe di calore. Doveva essere un metodo universale e il colore generato doveva essere "bello", il che significa che la semplice generazione di colori complementari non era una buona soluzione - a volte generava colori strani, molto intensi che erano difficili da guardare e leggere.

Dopo lunghe ore di test e tentando di risolvere questo problema, ho scoperto che la soluzione migliore è selezionare il carattere bianco per i colori "scuri" e il carattere nero per i colori "luminosi".

Ecco un esempio della funzione che sto usando 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);
}

Questo è stato testato per diverse scale di colori (arcobaleno, scala di grigi, calore, ghiaccio e molti altri) ed è l'unico metodo "universale" che ho scoperto.

Modifica
Modificata la formula del conteggioa in "luminanza percettiva" - sembra davvero migliore! Già implementato nel mio software, sembra fantastico.

Modifica 2 @WebSeed ha fornito un ottimo esempio funzionante di questo algoritmo: http://codepen.io/WebSeed/full/pvgqEq/


14
Probabilmente non è importante, ma potresti desiderare una funzione migliore per calcolare la luminosità stackoverflow.com/questions/596216/…
Josh Lee,

1
Sembra che sarà perfetto.
Joseph Daigle,

Esattamente quello di cui avevo bisogno oggi.
Chris W,

Da dove vengono le ponderazioni della luminanza percettiva?
Mat


23

Nel caso qualcuno volesse una versione più breve, forse più facile da capire, della risposta di 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;
}

Nota: ho rimosso l'inversione del valore di luminanza (per rendere i colori brillanti un valore più elevato, ciò che mi sembra più naturale ed è anche il metodo di calcolo "predefinito".

Ho usato le stesse costanti di GaceK da qui poiché hanno funzionato alla grande per me.

(Puoi anche implementarlo come metodo di estensione usando la seguente firma:

public static Color ContrastColor(this Color iColor)

Puoi quindi chiamarlo via foregroundColor = background.ContrastColor().)


11

Grazie @Gacek . Ecco una versione per 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);
}

E una versione migliorata (più corta):

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

Sarebbe ancora più breve e più facile da leggere, se applichi le modifiche a @Marcus Mangelsdorf (elimini la 1 - ...parte e rinomini ainluminance
Ridcully

Utilizzando il primo, è possibile acquisire anche l'alfa.
Brill Pappin,

9

La mia rapida implementazione della risposta di 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)
}

In breve tempo, poiché r / g / b sono CGFloats, non è necessario "/ 255" per calcolare la luminanza: lasciare luminanza = 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;
};

6

Grazie per questo post

Per chiunque possa essere interessato, ecco un esempio di quella funzione 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;

C'è un piccolo errore nel 4 dall'ultima riga. Risultato: = clBlack non dovrebbe avere un punto e virgola dopo di esso;
Andy k,

5

Questa è una risposta così utile. Grazie per questo!

Vorrei condividere una versione 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 );

}

Ora sto scoprendo come utilizzare l'algoritmo per creare automaticamente i colori al passaggio del mouse per i collegamenti ai menu. Le intestazioni chiare diventano più scure e viceversa.


3

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

Tutto quello che ho fatto è stato il prefisso con "statico". Grazie!
Pete Alvin,

2

Ugly Python se non hai voglia di scriverlo :)

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

2

Ho avuto lo stesso problema ma ho dovuto svilupparlo in PHP . Ho usato la soluzione di @ Garek e ho anche usato questa risposta: Converti i colori esadecimali in valori RGB in PHP per convertire il codice colore HEX in RGB.

Quindi lo condivido.

Volevo usare questa funzione con un dato colore HEX di sfondo, ma non sempre a partire da '#'.

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

1

Come estensione 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
}

1

Un'implementazione per obiettivo-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];
}

2
Dovresti aggiungere una descrizione qui per far sapere agli altri cosa sta succedendo nel tuo codice
Michael,

1

iOS Swift 3.0 (estensione 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
}

1
La funzione sta funzionando come previsto, ma fai attenzione ai filamenti e ai lanci di forza
RichAppz,

1
Ottimo, guarda il mio esempio per Swift 4 qui sotto, puoi vedere la riduzione del codice
RichAppz

1

Swift 4 Esempio:

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

}

AGGIORNAMENTO : è stato riscontrato che 0.6era un banco di prova migliore per la query


Questo in realtà è molto probabile che fallisca in diverse situazioni, dato che assume uno spazio cromatico RGB. Il numero di elementi in CGColor.componentsvaria a seconda dello spazio colore: ad esempio, UIColor.whitequando trasmesso a un CGColor, ha solo due: che [1.0, 1.0]rappresenta un colore in scala di grigi (a tutto bianco) con un alfa completa. Un modo migliore per estrarre gli elementi RGB di un UIColor èUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman,

1

Nota che esiste un algoritmo per questo nella libreria di chiusura di Google che fa riferimento a una raccomandazione w3c: http://www.w3.org/TR/AERT#color-contrast . Tuttavia, in questa API viene fornito un elenco di colori suggeriti come punto di partenza.

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

0

Se stai manipolando gli spazi colore per effetti visivi, è generalmente più facile lavorare in HSL (Tonalità, Saturazione e Luminosità) rispetto a RGB. Spostare i colori in RGB per dare effetti naturalmente piacevoli tende ad essere concettualmente difficile, mentre la conversione in HSL, la manipolazione lì, quindi la riconversione di nuovo sono più intuitivi nel concetto e invariabilmente danno risultati più belli.

Wikipedia ha una buona introduzione a HSL e HSV strettamente correlati. E c'è del codice gratuito in rete per fare la conversione (ad esempio qui c'è un'implementazione javascript )

Quale trasformazione precisa usi è una questione di gusti, ma personalmente avrei pensato di invertire i componenti Tonalità e Luminosità per generare un buon colore ad alto contrasto come prima approssimazione, ma puoi facilmente ottenere effetti più sottili.


1
Sì, ma considera anche che l'occhio umano può vedere il verde in modo molto più dominante rispetto ad altri colori e il blu in meno (motivo per cui il blu ottiene meno frammenti di colore nei formati di immagine).
mercoledì

1
Infatti. Se ci trasferiremo in HSL, potremmo anche fare il salto completo in YUV e prendere in considerazione la percezione umana.
David Bradbury,

0

Puoi avere qualsiasi testo di tonalità su qualsiasi sfondo di tonalità e assicurarti che sia leggibile. Lo faccio tutto il tempo. C'è una formula per questo in Javascript sul testo leggibile a colori - STW * Come dice quel link, la formula è una variazione del calcolo di regolazione gamma inversa, sebbene un IMHO un po 'più gestibile. I menu sul lato destro di quel link e le pagine associate usano colori generati casualmente per testo e sfondo, sempre leggibili. Quindi sì, chiaramente può essere fatto, nessun problema.


0

Una variante Android che cattura anche l'alfa.

(grazie @ 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);
}

0

baseOttieni la versione R della risposta di @ Gacek luminance(puoi applicare facilmente la tua soglia)

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

Uso:

luminance(c('black', 'white', '#236FAB', 'darkred', '#01F11F'))
# [1] 0.0000000 1.0000000 0.3730039 0.1629843 0.5698039

0

Sulla base della risposta di Gacek e dopo aver analizzato l'esempio di @ WebSeed con l' estensione del browser WAVE , ho creato la seguente versione che sceglie il testo in bianco o nero in base al rapporto di contrasto (come definito nelle Linee guida sull'accessibilità dei contenuti Web del W3C (WCAG) 2.1 ) , invece di luminanza.

Questo è il codice (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';
};

Un esempio funzionante può essere trovato nel mio fork del codice di @ WebSeed, che produce zero errori a basso contrasto in WAVE.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.