Określ kolor czcionki na podstawie koloru tła

248

Biorąc pod uwagę system (na przykład stronę internetową), który pozwala użytkownikowi dostosować kolor tła dla niektórych sekcji, ale nie kolor czcionki (aby ograniczyć liczbę opcji do minimum), istnieje sposób, aby programowo ustalić, czy „jasne” czy „ potrzebny jest ciemny kolor czcionki?

Jestem pewien, że istnieje jakiś algorytm, ale nie wiem wystarczająco dużo o kolorach, jasności itp., Aby samemu go wymyślić.

Joseph Daigle
źródło

Odpowiedzi:

447

Napotkałem podobny problem. Musiałem znaleźć dobrą metodę wyboru kontrastowego koloru czcionki, aby wyświetlać etykiety tekstowe na skalach kolorów / mapach cieplnych. Musiała to być metoda uniwersalna, a generowany kolor musiał być „dobrze wyglądający”, co oznacza, że ​​proste generowanie komplementarnego koloru nie było dobrym rozwiązaniem - czasami generowało dziwne, bardzo intensywne kolory, które były trudne do oglądania i czytania.

Po długich godzinach testowania i próbach rozwiązania tego problemu odkryłem, że najlepszym rozwiązaniem jest wybranie białej czcionki dla „ciemnych” kolorów i czarnej czcionki dla „jasnych” kolorów.

Oto przykład funkcji używam w 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);
}

Zostało to przetestowane na wiele różnych skal kolorów (tęcza, skala szarości, ciepło, lód i wiele innych) i jest to jedyna „uniwersalna” metoda, jaką odkryłem.

Edytuj
Zmieniono formułę liczeniaa na „spostrzegawczą luminancję” - naprawdę wygląda lepiej! Już zaimplementowane w moim oprogramowaniu, wygląda świetnie.

Edycja 2 @WebSeed stanowi świetny działający przykład tego algorytmu: http://codepen.io/WebSeed/full/pvgqEq/

Gacek
źródło
14
Prawdopodobnie nie jest to ważne, ale możesz chcieć lepszej funkcji do obliczania jasności stackoverflow.com/questions/596216/…
Josh Lee
1
Wygląda na to, że będzie idealnie.
Joseph Daigle,
Właśnie tego potrzebowałem dzisiaj.
Chris W
Skąd pochodzą twoje spostrzegawcze współczynniki luminancji?
Mat.
Z tej odpowiedzi: stackoverflow.com/questions/596216/…
Gacek
23

Na wypadek, gdyby ktoś chciał krótszą, być może łatwiejszą do zrozumienia wersję odpowiedzi 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;
}

Uwaga: usunąłem inwersję lumy wartości (aby jasne kolory miały wyższą wartość, co wydaje mi się bardziej naturalne i jest również „domyślną” metodą obliczania).

Użyłem tych samych stałych co GaceK stąd, ponieważ działały dla mnie świetnie.

(Można to również zaimplementować jako metodę rozszerzenia przy użyciu następującego podpisu:

public static Color ContrastColor(this Color iColor)

Możesz wtedy zadzwonić przez foregroundColor = background.ContrastColor().)

Marcus Mangelsdorf
źródło
11

Dziękuję @Gacek . Oto wersja dla Androida:

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

I ulepszona (krótsza) wersja:

@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
źródło
Byłoby jeszcze krótsze i łatwiejsze do odczytania, gdybyś zastosował zmiany @Marcus Mangelsdorf (pozbyć się 1 - ...części i zmienić nazwę analuminance
Ridcully,
Za pomocą pierwszego możesz również przechwycić alfa.
Brill Pappin
9

Moja szybka implementacja odpowiedzi Gacka:

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
źródło
Szybko, ponieważ r / g / b to CGFloats, nie potrzebujesz "/ 255" do obliczenia luminancji: niech luminancja = 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
źródło
6

Dzięki za ten post.

Dla każdego, kto może być zainteresowany, oto przykład tej funkcji w 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.
źródło
W czwartym wierszu jest mały błąd. Wynik: = clBlack nie powinien mieć za sobą średnika;
Andy k
5

To taka pomocna odpowiedź. Dzięki za to!

Chciałbym udostępnić wersję 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 );

}

Teraz zastanawiam się, jak użyć algorytmu do automatycznego tworzenia kolorów aktywacji dla łączy menu. Jasne nagłówki mają ciemniejszy ruch i na odwrót.

ricotheque
źródło
3

Implementacja trzepotania

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
źródło
Wszystko, co zrobiłem, to przedrostek „static”. Dzięki!
Pete Alvin,
2

Brzydki Python, jeśli nie masz ochoty go pisać :)

'''
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
źródło
2

Miałem ten sam problem, ale musiałem go rozwinąć w PHP . Użyłem rozwiązania @ Garek i użyłem również tej odpowiedzi: Konwertuj kolor szesnastkowy na wartości RGB w PHP, aby przekonwertować kod koloru HEX na RGB.

Więc dzielę się tym.

Chciałem użyć tej funkcji z danym kolorem HEX tła, ale nie zawsze zaczynając od „#”.

//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
źródło
1

Jako rozszerzenie 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
źródło
1

Implementacja dla celu-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
źródło
2
Powinieneś tutaj dodać opis, aby inni użytkownicy wiedzieli, co dzieje się w twoim kodzie
Michael
1

iOS Swift 3.0 (rozszerzenie 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
źródło
1
Funkcja działa zgodnie z oczekiwaniami, ale należy zachować ostrożność przy odlewaniu i wymuszaniu
rzutów
1
Świetnie, sprawdź mój przykład dla Swift 4 poniżej, możesz zobaczyć redukcję kodu
RichAppz
1

Przykład 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)
    }

}

AKTUALIZACJA - Stwierdzono, że 0.6to lepsze łóżko testowe dla zapytania

RichAppz
źródło
Jest to całkiem prawdopodobne, że zawiedzie w kilku sytuacjach, ponieważ zakłada przestrzeń kolorów RGB. Liczba elementów CGColor.componentsróżni się w zależności od przestrzeni kolorów: na przykład UIColor.whitepo rzutowaniu na CGColor ma tylko dwa: [1.0, 1.0]reprezentuje kolor szarości (w pełni biały) z pełną alfą. Lepszym sposobem na wydobycie elementów RGB z UIColor jestUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman
1

Zauważ, że istnieje algorytm do tego w bibliotece zamknięcia google, który odwołuje się do zalecenia w3c: http://www.w3.org/TR/AERT#color-contrast . Jednak w tym interfejsie API podajesz listę sugerowanych kolorów jako punkt wyjścia.

/**
 * 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]);
};
Paweł
źródło
0

Jeśli manipulujesz przestrzenią kolorów w celu uzyskania efektu wizualnego, generalnie łatwiej jest pracować w HSL (Barwa, Nasycenie i Jasność) niż RGB. Przesuwanie kolorów w RGB w celu uzyskania naturalnie przyjemnych efektów jest zazwyczaj dość trudne koncepcyjnie, podczas gdy konwersja do HSL, manipulowanie tam, a następnie konwersja z powrotem jest bardziej intuicyjna i niezmiennie daje lepsze wyniki.

Wikipedia ma dobre wprowadzenie do HSL i blisko spokrewnionego HSV. W sieci jest darmowy kod do konwersji (na przykład tutaj jest implementacja javascript) )

Ta precyzyjna transformacja jest kwestią gustu, ale osobiście pomyślałbym, że odwrócenie komponentów Barwa i Lekkość z pewnością wygeneruje dobry kontrast o wysokim kontraście jako pierwszym przybliżeniu, ale łatwo można uzyskać bardziej subtelne efekty.

Cruachan
źródło
1
Tak, ale weź również pod uwagę, że ludzkie oko widzi zieleń o wiele bardziej dominująco niż inne kolory, a niebieski mniej (dlatego niebieski traci mniej kolorów w formatach graficznych).
wchargin
1
W rzeczy samej. Jeśli zamierzamy przejść do HSL, równie dobrze możemy przejść do YUV i wziąć pod uwagę ludzką percepcję.
David Bradbury,
0

Możesz mieć dowolny tekst odcienia na dowolnym tle odcienia i upewnić się, że jest czytelny. Ciągle to robię. Istnieje formuła na to w JavaScript w czytelnym tekście w kolorze - STW * Jak napisano w tym linku, formuła jest odmianą obliczenia korekcji odwrotnej gamma, choć nieco łatwiejszą do zarządzania IMHO. Menu po prawej stronie tego linku i powiązanych z nim stron używają losowo generowanych kolorów tekstu i tła, zawsze czytelnych. Więc tak, oczywiście można to zrobić, nie ma problemu.

Dave Collier
źródło
0

Odmiana Androida, która przechwytuje również alfa.

(dzięki @ 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
źródło
0

baseWersja R odpowiedzi @ Gacek, aby uzyskać luminance(możesz łatwo zastosować własny próg)

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

Stosowanie:

luminance(c('black', 'white', '#236FAB', 'darkred', '#01F11F'))
# [1] 0.0000000 1.0000000 0.3730039 0.1629843 0.5698039
MichaelChirico
źródło
0

W oparciu o odpowiedź Gacka i po przeanalizowaniu przykładu @ WebSeed z rozszerzeniem przeglądarki WAVE , opracowałem następującą wersję, która wybiera czarny lub biały tekst na podstawie współczynnika kontrastu (zgodnie z definicją w wytycznych WAG dotyczących dostępności treści internetowych (WCAG) 2.1 ) , zamiast luminancji.

To jest kod (w 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';
};

Działający przykład można znaleźć w moim rozwidleniu codepen @ WebSeed, który powoduje zero błędów niskiego kontrastu w WAVE.

Seirios
źródło