Tentukan warna font berdasarkan warna latar belakang

248

Diberikan suatu sistem (situs web misalnya) yang memungkinkan pengguna menyesuaikan warna latar belakang untuk beberapa bagian tetapi bukan warna font (untuk menjaga jumlah opsi seminimal mungkin), apakah ada cara untuk menentukan secara terprogram apakah "menyala" atau " warna font "gelap perlu?

Saya yakin ada beberapa algoritma, tapi saya tidak cukup tahu tentang warna, luminositas, dll untuk mencari tahu sendiri.

Joseph Daigle
sumber

Jawaban:

447

Saya mengalami masalah serupa. Saya harus menemukan metode yang baik untuk memilih warna font yang kontras untuk menampilkan label teks pada colorcales / heatmaps. Itu harus metode universal dan warna yang dihasilkan harus "tampan", yang berarti bahwa menghasilkan warna komplementer sederhana bukanlah solusi yang baik - kadang-kadang menghasilkan warna aneh, sangat intensif yang sulit untuk dilihat dan dibaca.

Setelah berjam-jam pengujian dan mencoba menyelesaikan masalah ini, saya menemukan bahwa solusi terbaik adalah memilih font putih untuk warna "gelap", dan font hitam untuk warna "cerah".

Berikut ini contoh fungsi yang saya gunakan di 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);
}

Ini diuji untuk berbagai macam skala warna (pelangi, skala abu-abu, panas, es, dan banyak lainnya) dan merupakan satu-satunya metode "universal" yang saya temukan.

Sunting
Mengubah rumus penghitungan amenjadi "luminansi perseptif" - benar-benar terlihat lebih baik! Sudah menerapkannya di perangkat lunak saya, terlihat hebat.

Sunting 2 @WebSeed memberikan contoh yang bagus untuk algoritma ini: http://codepen.io/WebSeed/full/pvgqEq/

Gacek
sumber
14
Ini mungkin tidak penting, tetapi Anda mungkin ingin fungsi yang lebih baik untuk menghitung kecerahan stackoverflow.com/questions/596216/...
Josh Lee
1
Sepertinya ini akan sempurna.
Joseph Daigle
Persis seperti yang saya butuhkan hari ini.
Chris W
Dari mana bobot Luminance perseptif Anda berasal?
Mat
Dari jawaban ini: stackoverflow.com/questions/596216/…
Gacek
23

Untuk berjaga-jaga seandainya seseorang menginginkan versi jawaban GaceK yang lebih pendek, mungkin lebih mudah dimengerti :

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

Catatan: Saya menghapus inversi dari nilai luma (untuk membuat warna-warna cerah memiliki nilai lebih tinggi, yang tampaknya lebih alami bagi saya dan juga merupakan metode perhitungan 'default'.

Saya menggunakan konstanta yang sama dengan GaceK dari sini karena mereka bekerja bagus untuk saya.

(Anda juga dapat menerapkan ini sebagai Metode Ekstensi menggunakan tanda tangan berikut:

public static Color ContrastColor(this Color iColor)

Anda kemudian dapat memanggilnya via foregroundColor = background.ContrastColor().)

Marcus Mangelsdorf
sumber
11

Terima kasih @Gacek . Ini versi untuk 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);
}

Dan versi yang lebih baik (lebih pendek):

@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
sumber
Akan lebih pendek dan lebih mudah dibaca, jika Anda akan menerapkan perubahan dari @Marcus Mangelsdorf (singkirkan 1 - ...bagian itu dan ganti nama amenjadiluminance
Ridcully
Menggunakan yang pertama, Anda bisa menangkap alpha juga.
Brill Pappin
9

Implementasi Swift saya atas jawaban 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
sumber
Dengan cepat, karena r / g / b adalah CGFloats, Anda tidak memerlukan "/ 255" untuk menghitung luminance: biarkan luminance = 1 - ((0,299 * r) + (0,587 * g) + (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
sumber
6

Terima kasih untuk posting ini.

Bagi siapa pun yang tertarik, berikut adalah contoh fungsi itu di 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.
sumber
Ada kesalahan kecil pada baris ke-4 dari baris terakhir. Hasil: = clBlack tidak boleh memiliki titik koma setelahnya;
Andy k
5

Ini adalah jawaban yang sangat membantu. Terimakasih untuk ini!

Saya ingin membagikan versi 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 );

}

Sekarang mencari tahu bagaimana menggunakan algoritma untuk secara otomatis membuat warna hover untuk tautan menu. Header cahaya mendapatkan hover yang lebih gelap, dan sebaliknya.

ricotheque
sumber
3

Implementasi 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
sumber
Yang saya lakukan adalah awalan dengan 'statis'. Terima kasih!
Pete Alvin
2

Python Jelek jika Anda tidak ingin menulisnya :)

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

Saya memiliki masalah yang sama tetapi saya harus mengembangkannya di PHP . Saya menggunakan solusi @ Garek dan saya juga menggunakan jawaban ini: Konversi warna hex ke nilai RGB di PHP untuk mengkonversi kode warna HEX ke RGB.

Jadi saya membagikannya.

Saya ingin menggunakan fungsi ini dengan warna Background HEX yang diberikan, tetapi tidak selalu dimulai dari '#'.

//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
sumber
1

Sebagai ekstensi 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
sumber
1

Implementasi untuk tujuan-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
sumber
2
Anda harus menambahkan deskripsi di sini untuk memberi tahu pengguna lain apa yang terjadi dalam kode Anda
Michael
1

iOS Swift 3.0 (ekstensi 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
sumber
1
Fungsi ini melakukan seperti yang diharapkan tetapi hati-hati dengan linting dan memaksa casting
RichAppz
1
Hebat, lihat contoh saya untuk Swift 4 di bawah ini, Anda dapat melihat pengurangan kode
RichAppz
1

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

}

PEMBARUAN - Ditemukan bahwa 0.6itu adalah test bed yang lebih baik untuk kueri

RichAppz
sumber
Ini sebenarnya sangat mungkin gagal dalam beberapa situasi, karena mengasumsikan ruang warna RGB. Jumlah elemen dalam CGColor.componentsbervariasi tergantung pada ruang warna: misalnya, UIColor.whiteketika dilemparkan ke CGColor, hanya memiliki dua: [1.0, 1.0]mewakili warna skala abu-abu (putih sepenuhnya) dengan alfa penuh. Cara yang lebih baik untuk mengekstraksi elemen RGB dari UIColor adalahUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman
1

Perhatikan ada algoritma untuk ini di pustaka penutupan google yang mereferensikan rekomendasi w3c: http://www.w3.org/TR/AERT#color-contrast . Namun, dalam API ini Anda memberikan daftar warna yang disarankan sebagai titik awal.

/**
 * 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
sumber
0

Jika Anda memanipulasi ruang warna untuk efek visual, umumnya lebih mudah bekerja di HSL (Hue, Saturation, dan Lightness) daripada RGB. Memindahkan warna dalam RGB untuk memberikan efek yang menyenangkan secara alami cenderung sangat sulit secara konseptual, sedangkan mengubah menjadi HSL, memanipulasi di sana, kemudian mengonversi kembali keluar lebih intuitif dalam konsep dan selalu memberikan hasil yang terlihat lebih baik.

Wikipedia memiliki pengantar yang baik untuk HSL dan HSV yang terkait erat. Dan ada kode gratis di sekitar net untuk melakukan konversi (misalnya di sini adalah implementasi javascript )

Apa transformasi tepat yang Anda gunakan adalah masalah selera, tetapi secara pribadi saya akan berpikir membalik komponen Hue dan Lightness pasti akan menghasilkan warna kontras tinggi yang baik sebagai perkiraan pertama, tetapi Anda dapat dengan mudah menggunakan efek yang lebih halus.

Cruachan
sumber
1
Ya, tetapi pertimbangkan juga bahwa mata manusia dapat melihat hijau jauh lebih dominan daripada warna lain, dan biru kurang begitu (itulah sebabnya biru mendapat sedikit bit warna dalam format gambar).
wchargin
1
Memang. Jika kita akan pindah ke HSL, kita juga bisa melakukan lompatan penuh ke YUV dan mempertimbangkan persepsi manusia.
David Bradbury
0

Anda dapat memiliki teks rona pada latar belakang rona apa pun dan memastikan bahwa teks itu dapat dibaca. Saya selalu melakukannya. Ada rumus untuk ini dalam Javascript pada Teks yang Dapat Dibaca dalam Warna - STW * Seperti yang dikatakan pada tautan itu, rumus adalah variasi pada perhitungan penyesuaian invers-gamma, meskipun IMHO sedikit lebih mudah dikelola. Menu di sisi kanan tautan itu dan halaman terkait menggunakan warna yang dihasilkan secara acak untuk teks dan latar belakang, selalu dapat dibaca. Jadi ya, jelas itu bisa dilakukan, tidak masalah.

Dave Collier
sumber
0

Variasi Android yang menangkap alfa juga.

(terima kasih @ 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
sumber
0

baseVersi R dari jawaban @ Gacek untuk mendapatkan luminance(Anda dapat menerapkan ambang Anda sendiri dengan mudah)

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

Pemakaian:

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

Berdasarkan jawaban Gacek , dan setelah menganalisis contoh @ WebSeed dengan ekstensi browser WAVE , saya membuat versi berikut ini yang memilih teks hitam atau putih berdasarkan rasio kontras (seperti yang didefinisikan dalam Pedoman Aksesibilitas Konten Web (WCAG) W3C dalam Web Content Accessibility Guidelines (WCAG) 2.1 ) , bukannya pencahayaan.

Ini adalah kode (dalam 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';
};

Contoh yang berfungsi dapat ditemukan di fork saya dari @ WebSeed's codepen, yang menghasilkan nol kesalahan kontras rendah di WAVE.

Seirios
sumber