Jak wybrać kolor czcionki w kolorze białym lub czarnym w zależności od koloru tła?


218

Chcę pokazać kilka zdjęć takich jak ten przykładalternatywny tekst

Kolor wypełnienia jest określany przez pole w bazie danych z kolorem szesnastkowym (np .: ClassX -> Color: # 66FFFF). Teraz chcę pokazać dane nad wypełnieniem wybranym kolorem (jak na powyższym obrazku), ale muszę wiedzieć, czy kolor jest ciemny, czy jasny, więc wiem, czy słowa powinny być białe lub czarne. Czy jest jakiś sposób? tks


Odpowiedzi:


346

Opierając się na mojej odpowiedzi na podobne pytanie .

Musisz podzielić kod szesnastkowy na 3 części, aby uzyskać indywidualne intensywności czerwony, zielony i niebieski. Każda 2 cyfra kodu reprezentuje wartość w zapisie szesnastkowym (podstawa 16). Nie będę tutaj wchodził w szczegóły konwersji, można je łatwo sprawdzić.

Po uzyskaniu intensywności dla poszczególnych kolorów możesz określić ogólną intensywność koloru i wybrać odpowiedni tekst.

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

Próg 186 jest oparty na teorii, ale można go dostosować do smaku. W oparciu o komentarze poniżej próg 150 może działać lepiej dla Ciebie.


Edycja: Powyższe jest proste i działa dość dobrze i wydaje się mieć dobrą akceptację tutaj w StackOverflow. Jednak jeden z poniższych komentarzy pokazuje, że w niektórych okolicznościach może prowadzić do niezgodności z wytycznymi W3C. Otrzymuję zmodyfikowaną formę, która zawsze wybiera najwyższy kontrast w oparciu o wytyczne. Jeśli nie musisz przestrzegać reguł W3C, trzymałbym się prostszej formuły powyżej.

Wzór podany dla kontrastu w Zaleceniach W3C jest taki (L1 + 0.05) / (L2 + 0.05), w którym L1luminancja jest najjaśniejszym kolorem i L2jest luminancją najciemniejszego w skali od 0,0 do 1,0. Luminancja czerni wynosi 0,0, a biel 1,0, więc zastąpienie tych wartości pozwala określić tę o najwyższym kontraście. Jeśli kontrast czerni jest większy niż kontrast bieli, użyj czerni, w przeciwnym razie użyj bieli. Biorąc pod uwagę luminancję koloru, który testujesz, gdy Ltest staje się:

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

Upraszcza to algebraicznie do:

if L > sqrt(1.05 * 0.05) - 0.05

Lub w przybliżeniu:

if L > 0.179 use #000000 else use #ffffff

Pozostało tylko obliczyć L. Ta formuła jest również podana w wytycznych i wygląda na to, że konwersja z sRGB na liniowy RGB następuje zgodnie z zaleceniem ITU-R BT.709 dla luminancji.

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

Próg 0,179 nie powinien zostać zmieniony, ponieważ jest powiązany z wytycznymi W3C. Jeśli wyniki Ci się nie podobają, wypróbuj powyższą prostszą formułę.


2
Tks Mark. Wypróbowałem kilka zmian: obliczone na czerwono, zielono i niebiesko tylko na pierwszej cyfrze (mniej dokładna, ale jest to cyfra o większej wadze) i zamiast 186 zastosowałem 9. działa trochę lepiej dla mnie, szczególnie z zieleniami.
DJPB

2
Ta formuła jest bardzo błędna. Na przykład daje żółtej #D6B508wartość 171, stąd biały kontrast. Kontrast powinien być jednak czarny (potwierdzony tutaj: webaim.org/resources/contrastchecker )
McGarnagle

1
@ MarkRansom tak, moim oczom czerń wygląda znacznie lepiej z tym żółtym. Ponieważ pracuję z ograniczonym zestawem kolorów, mogłem po prostu przesunąć granicę odcięcia z 186 do niższej wartości.
McGarnagle,

3
Oto kolejne demo porównujące dwie formuły podane w tej odpowiedzi. Za pomocą próbnika kolorów (w najnowszym Firefoksie lub Chrome) możesz sprawdzić kontrast z dowolnym kolorem.
chetstone

3
Po odrobinie gry z demo @ chetstone, myślę, że prosta formuła działa najlepiej dla mnie, z tym wyjątkiem, że nie zgadzam się z zaleceniem progu. Opierając się na wynikach czystej zieleni, próbowałem ustawić próg na 149, co moim zdaniem działa o wiele lepiej. Zrobiłem naprawdę głupie skrzypce, żeby to zademonstrować ; możesz spróbować zmienić próg u góry z tyłu, aby zobaczyć oryginalną sugestię.
Miral,

30

Nie przypisuję sobie tego kodu, ponieważ nie jest on mój, ale zostawiam go tutaj, aby inni mogli szybko znaleźć w przyszłości:

Na podstawie odpowiedzi Mark Ransoms, oto fragment kodu dla prostej wersji:

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

a oto fragment kodu dla wersji zaawansowanej:

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

Aby z nich skorzystać, wystarczy zadzwonić:

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

Również dzięki Alxi chetstone.


1
Użyłem prostej funkcji i usunąłem ją trochę bardziej: upuściłem 2 ostatnie parametry i przemianowałem to po prostu isDark(bgColor)Moje użycie jest wtedy'color': isDark(color)?'white':'black'
diynevala

1
Dla mnie zadziałało jak urok. Dziękuję Ci bardzo! Zrobił to w php, choć po prostu konwertował składnię i funkcje.
CezarBastos

18

Co powiesz na to (kod 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
Działa to przez większość czasu, ale są przypadki takie jak i.imgur.com/3pOUDe5.jpg, które wyglądają dziwnie, kolor tła jest w rzeczywistości rgb (6, 247, 241);
madprops

Rozumiem, że jest to stosunkowo tani sposób na wykonanie obliczenia kontrastu w przypadkach, w których kolor nie został jeszcze przekonwertowany na wartości rgb (nie jest to zbyt trudne, tylko dodatkowe kroki matematyczne)
frumbert

12

Oprócz rozwiązań arytmetycznych możliwe jest również użycie sieci neuronowej AI. Zaletą jest to, że możesz dostosować go do własnych upodobań i potrzeb (tj. Białawy tekst na jasnych nasyconych czerwieniach wygląda dobrze i jest tak samo czytelny jak czarny).

Oto fajne demo Javascript ilustrujące tę koncepcję. Możesz również wygenerować własną formułę JS bezpośrednio w wersji demonstracyjnej.

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

Poniżej znajdują się wykresy, które pomogły mi rozwiązać problem . Na pierwszym wykresie jasność wynosi stałą 128, a odcień i nasycenie są różne. Na drugim wykresie nasycenie wynosi stałą 255, a odcień i jasność różnią się.

Na pierwszym wykresie jasność wynosi stałą 128, a odcień i nasycenie różnią się:

Nasycenie jest stałą 255, a odcień i jasność różnią się:


8

Oto moje rozwiązanie w Javie dla Androida:

// 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
Czy to naprawdę „idealne”? Wypróbuj czyste zielone tło, # 00FF00.
Andreas Rejbrand

To prawda, że ​​nie jest to testowane dla wszystkich kolorów ... ale kto użyłby czystego zielonego tła do wszystkiego, co nie zostało zaprojektowane, aby drażnić użytkowników?
mwieczorek

@mwieczorek robią to ludzie, którzy polegają na treściach generowanych przez użytkowników lub losowo wybranych kolorach.
Marc Plano-Lesay,

4

Na podstawie odpowiedzi @MarkRansom stworzyłem skrypt PHP, który można znaleźć tutaj:

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

To jest szybka wersja odpowiedzi Marka Ransoma jako rozszerzenie 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

To tylko przykład, który zmieni kolor zaznaczenia SVG po kliknięciu elementu. Ustawi kolor zaznaczenia na czarny lub biały na podstawie koloru tła klikniętego elementu.

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

Używam tej funkcji JavaScript do konwersji rgb/ rgbado 'white'lub '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';
    }
}

Możesz wprowadzić dowolny z tych formatów i wyświetli on 'black'lub'white'

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

Teraz spróbuj tego z czystym zielonym tłem: # 00FF00.
Andreas Rejbrand

Dziękuję Ci za to! Przetłumaczyłem na szybki i użyłem go w mojej aplikacji na iOS!
Lucas P.

2

Szczegółowa odpowiedź Marka działa świetnie. Oto implementacja w 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';
}

Następnie można wywołać tę funkcję, lum([111, 22, 255])aby uzyskać biały lub czarny.


1

Nigdy nie robiłem czegoś takiego, ale co z pisaniem funkcji sprawdzającej wartości każdego z kolorów w stosunku do mediany koloru Hex 7F (FF / 2). Jeśli dwa z trzech kolorów są większe niż 7F, oznacza to, że pracujesz z ciemniejszym kolorem.


1

Na podstawie różnych danych wejściowych z linku Zmień kolor pierwszego planu na czarny lub biały w zależności od tła i tego wątku, stworzyłem klasę rozszerzenia dla Koloru, która zapewnia wymagane kolory kontrastu.

Kod wygląda następująco:

 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

Jeśli podobnie jak ja szukałeś wersji RGBA, która uwzględnia alfa, oto ta, która działa naprawdę dobrze, aby mieć wysoki kontrast.

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

To jest wersja R odpowiedzi Marka Ransoma, wykorzystująca tylko podstawową 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, patrzyłem na twoją odpowiedź, która wygląda dobrze, ale jest w tym mały błąd. Twoja funkcja hexToG i hextoB wymagają drobnej edycji. Ostatnią liczbą w substr jest długość łańcucha, dlatego w tym przypadku powinna ona wynosić „2”, a nie 4 lub 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

MNIEJ ma fajną contrast()funkcję, która działała dla mnie ładnie, patrz http://lesscss.org/functions/#color-operations-contrast

„Wybierz, który z dwóch kolorów zapewnia największy kontrast z innym. Jest to przydatne, aby zapewnić czytelność koloru na tle, co jest również przydatne dla zapewnienia zgodności z dostępnością. Ta funkcja działa tak samo, jak funkcja kontrastu w Kompasie dla SASS. Zgodnie z WCAG 2.0 kolory są porównywane przy użyciu wartości luma z korekcją gamma, a nie ich lekkości. ”

Przykład:

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

Wynik:

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

0

Od szesnastkowego do czarnego lub białego:

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

Kod wersji celu c dla systemu iOS na podstawie odpowiedzi Marka:

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

Co powiesz na testowanie ze wszystkimi 24-bitowymi kolorami?

Należy pamiętać, że metoda YIQ zwróci minimalny współczynnik kontrastu 1,9: 1, który nie przejdzie testów AA i AAA WCAG2.0, przy założeniu progu 128.

W przypadku metody W3C zwróci minimalny współczynnik kontrastu 4,58: 1, który przejdzie testy AA i AAA dla dużego tekstu, a test AA dla małego tekstu, nie przejdzie testu AAA dla małego tekstu dla każdego koloru.


0

Oto moja własna metoda, z której korzystałem i jak dotąd nie miałem problemu 😄

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

Oto mój kod dla Java Swing oparty na niesamowitej odpowiedzi Marka:

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.