Xác định màu phông chữ dựa trên màu nền


248

Đưa ra một hệ thống (ví dụ: một trang web) cho phép người dùng tùy chỉnh màu nền cho một số phần nhưng không phải màu phông chữ (để giữ số lượng tùy chọn ở mức tối thiểu), có cách nào để xác định theo cách lập trình nếu "ánh sáng" hoặc " màu chữ tối là cần thiết?

Tôi chắc chắn có một số thuật toán, nhưng tôi không biết đủ về màu sắc, độ sáng, v.v ... để tự mình tìm ra nó.

Câu trả lời:


447

Tôi gặp phải vấn đề tương tự. Tôi đã phải tìm một phương pháp tốt để chọn màu phông chữ tương phản để hiển thị nhãn văn bản trên bảng màu / bản đồ nhiệt. Nó phải là phương pháp phổ quát và màu sắc được tạo ra phải là "ưa nhìn", điều đó có nghĩa là màu bổ sung tạo ra đơn giản không phải là giải pháp tốt - đôi khi nó tạo ra màu sắc lạ, rất nặng, khó xem và đọc.

Sau nhiều giờ thử nghiệm và cố gắng giải quyết vấn đề này, tôi phát hiện ra rằng giải pháp tốt nhất là chọn phông chữ màu trắng cho màu "tối" và phông màu đen cho màu "sáng".

Đây là một ví dụ về chức năng tôi đang sử dụng trong 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);
}

Điều này đã được thử nghiệm cho nhiều màu sắc khác nhau (cầu vồng, thang độ xám, nhiệt, băng và nhiều màu khác) và là phương pháp "phổ quát" duy nhất tôi tìm ra.

Chỉnh sửa
Thay đổi công thức đếm athành "độ chói nhận thức" - nó thực sự trông tốt hơn! Đã thực hiện nó trong phần mềm của tôi, trông rất tuyệt.

Chỉnh sửa 2 @WebSeed cung cấp một ví dụ hoạt động tuyệt vời của thuật toán này: http://codepen.io/WebSeed/full/pvgqEq/


14
Điều này có thể không quan trọng, nhưng bạn có thể muốn một chức năng tốt hơn để tính toán stackoverflow.com/questions/596216/ mẹo
Josh Lee

1
Điều này có vẻ như nó sẽ hoàn hảo.
Joseph Daigle

Chính xác những gì tôi cần ngày hôm nay.
Chris W

Trường hợp trọng lượng Luminance nhận thức của bạn đến từ đâu?
Mat

Từ câu trả lời này: stackoverflow.com/questions/596216/
Kẻ

23

Chỉ trong trường hợp ai đó muốn một phiên bản ngắn hơn, có thể dễ hiểu hơn về câu trả lời của 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;
}

Lưu ý: Tôi đã loại bỏ nghịch đảo của giá trị luma (để làm cho màu sáng có giá trị cao hơn, đối với tôi có vẻ tự nhiên hơn và cũng là phương pháp tính toán 'mặc định'.

Tôi đã sử dụng các hằng số tương tự như GaceK từ đây vì chúng hoạt động rất tốt cho tôi.

(Bạn cũng có thể thực hiện điều này như một Phương thức mở rộng bằng chữ ký sau:

public static Color ContrastColor(this Color iColor)

Sau đó bạn có thể gọi nó qua foregroundColor = background.ContrastColor().)


11

Cảm ơn bạn @Gacek . Đây là phiên bản dành cho 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);
}

Và một phiên bản cải tiến (ngắn hơn):

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

Nó thậm chí còn ngắn hơn và dễ đọc hơn, nếu bạn áp dụng các thay đổi của @Marcus Mangelsdorf (loại bỏ 1 - ...phần đó và đổi tên athànhluminance
Ridcully

Sử dụng cái đầu tiên, bạn cũng có thể chụp alpha.
Brill Pappin

9

Việc triển khai Swift của tôi về câu trả lời của 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)
}

Trong swift, vì r / g / b là CGFloats, bạn không cần "/ 255" để tính độ chói: hãy để độ chói = 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

Cảm ơn cho bài viết này.

Đối với bất cứ ai có thể quan tâm, đây là một ví dụ về chức năng đó trong 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ó một lỗi nhỏ ở dòng thứ 4 từ dòng cuối cùng. Kết quả: = clBlack không nên có dấu chấm phẩy sau nó;
Andy k

5

Đây là một câu trả lời hữu ích. Cảm ơn cho nó!

Tôi muốn chia sẻ phiên bản 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 );

}

Bây giờ hãy tìm ra cách sử dụng thuật toán để tự động tạo màu di chuột cho các liên kết menu. Tiêu đề ánh sáng có được một bay lượn tối hơn, và ngược lại.


3

Thực hiện rung

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

Tất cả những gì tôi làm là tiền tố với 'static.' Cảm ơn!
Pete Alvin

2

Python xấu xí nếu bạn không cảm thấy muốn viết nó :)

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

Tôi đã có cùng một vấn đề nhưng tôi đã phải phát triển nó trong PHP . Tôi đã sử dụng giải pháp của @ Garek và tôi cũng đã sử dụng câu trả lời này: Chuyển đổi màu hex thành giá trị RGB trong PHP để chuyển đổi mã màu HEX thành RGB.

Vì vậy, tôi đang chia sẻ nó.

Tôi muốn sử dụng chức năng này với màu Nền HEX đã cho, nhưng không phải lúc nào cũng bắt đầu từ '#'.

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

Là tiện ích mở rộng của 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

Một triển khai cho mục tiêu-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
Bạn nên thêm một số mô tả ở đây để cho người dùng khác biết những gì đang diễn ra trong mã của bạn
Michael

1

iOS Swift 3.0 (tiện ích mở rộng 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
Chức năng đang hoạt động như mong đợi nhưng hãy cẩn thận với việc đúc và ép lực
RichAppz

1
Tuyệt vời, hãy xem ví dụ của tôi về Swift 4 bên dưới, bạn có thể thấy việc giảm mã
RichAppz

1

Swift 4 Ví dụ:

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

}

CẬP NHẬT - Tìm thấy đó 0.6là một giường thử nghiệm tốt hơn cho truy vấn


Điều này thực sự có khả năng thất bại trong một số tình huống, vì nó giả sử một không gian màu RGB. Số lượng phần tử CGColor.componentsthay đổi tùy thuộc vào không gian màu: ví dụ: UIColor.whitekhi được chuyển sang CGColor, chỉ có hai: [1.0, 1.0]đại diện cho màu thang độ xám (hoàn toàn trắng) với màu alpha đầy đủ. Một phương tiện tốt hơn để trích xuất các phần tử RGB của UIColor làUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman

1

Lưu ý rằng có một thuật toán cho điều này trong thư viện đóng cửa google tham chiếu đề xuất w3c: http://www.w3.org/TR/AERT#color-contrast . Tuy nhiên, trong API này, bạn cung cấp một danh sách các màu được đề xuất làm điểm bắt đầu.

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

Nếu bạn đang thao tác các không gian màu để tạo hiệu ứng hình ảnh, nó thường hoạt động dễ dàng hơn trong HSL (Hue, Saturation và Lightness) so với RGB. Di chuyển màu sắc trong RGB để tạo hiệu ứng đẹp mắt tự nhiên có xu hướng khá khó khăn về mặt khái niệm, trong khi chuyển đổi thành HSL, thao tác ở đó, sau đó chuyển đổi trở lại là khái niệm trực quan hơn và luôn mang lại kết quả tốt hơn.

Wikipedia có phần giới thiệu tốt về HSL và HSV liên quan chặt chẽ. Và có mã miễn phí trên mạng để thực hiện chuyển đổi (ví dụ ở đây là triển khai javascript )

Biến đổi chính xác mà bạn sử dụng là vấn đề sở thích, nhưng cá nhân tôi nghĩ rằng việc đảo ngược các thành phần Hue và Lightness chắc chắn sẽ tạo ra màu tương phản cao tốt như một xấp xỉ đầu tiên, nhưng bạn có thể dễ dàng tạo ra các hiệu ứng tinh tế hơn.


1
Có, nhưng cũng nên xem xét rằng mắt người có thể nhìn thấy màu xanh lá cây chiếm ưu thế hơn nhiều so với các màu khác và màu xanh lam ít hơn (đó là lý do tại sao màu xanh lam có ít bit màu hơn trong các định dạng hình ảnh).
wchargein

1
Thật. Nếu chúng ta sẽ chuyển sang HSL, chúng ta cũng có thể thực hiện bước nhảy hoàn toàn đến YUV và tính đến nhận thức của con người.
David Bradbury

0

Bạn có thể có bất kỳ văn bản màu nào trên bất kỳ nền màu nào và đảm bảo rằng nó dễ đọc. Tôi làm nó suốt. Có một công thức cho điều này trong Javascript trên Văn bản có thể đọc được bằng màu - STW * Như đã nói trên liên kết đó, công thức là một biến thể của phép tính điều chỉnh gamma nghịch đảo, mặc dù IMHO dễ quản lý hơn một chút. Các menu ở phía bên phải của liên kết đó và các trang được liên kết của nó sử dụng các màu được tạo ngẫu nhiên cho văn bản và nền, luôn dễ đọc. Vì vậy, có, rõ ràng nó có thể được thực hiện, không có vấn đề.


0

Một biến thể Android cũng nắm bắt được alpha.

(cảm ơn @ 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

basePhiên bản R của câu trả lời của @ Gacek để nhận luminance(bạn có thể áp dụng ngưỡng của riêng mình một cách dễ dàng)

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

Sử dụng:

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

0

Dựa trên câu trả lời của Gacek và sau khi phân tích ví dụ của @ WebSeed với tiện ích mở rộng trình duyệt WAVE , tôi đã đưa ra phiên bản sau đây chọn văn bản đen hoặc trắng dựa trên tỷ lệ tương phản (như được định nghĩa trong Nguyên tắc truy cập nội dung web của W3C (WCAG) 2.1 ) , thay vì độ chói.

Đây là mã (trong 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';
};

Một ví dụ hoạt động có thể được tìm thấy trong ngã ba mã của @ WebSeed, tạo ra các lỗi tương phản thấp trong WAVE.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.