กำหนดสีตัวอักษรตามสีพื้นหลัง


248

ให้ระบบ (เว็บไซต์เป็นต้น) ที่ช่วยให้ผู้ใช้ปรับแต่งสีพื้นหลังสำหรับบางส่วน แต่ไม่ใช่สีตัวอักษร (เพื่อให้จำนวนของตัวเลือกให้น้อยที่สุด) มีวิธีการทางโปรแกรมตรวจสอบว่า "แสง" หรือ " จำเป็นต้องใช้สีแบบอักษรเข้มหรือไม่

ฉันแน่ใจว่ามีอัลกอริธึม แต่ฉันไม่รู้เกี่ยวกับสีความส่องสว่างและอื่น ๆ มากพอที่จะเข้าใจได้ด้วยตัวเอง

คำตอบ:


447

ฉันพบปัญหาที่คล้ายกัน ฉันต้องหาวิธีที่ดีในการเลือกสีตัวอักษรตัดกันเพื่อแสดงป้ายข้อความบน colorscales / heatmaps มันต้องเป็นวิธีการที่เป็นสากลและสีที่สร้างขึ้นจะต้องเป็น "รูปลักษณ์ที่ดี" ซึ่งหมายความว่าสีเสริมที่สร้างง่ายนั้นไม่ใช่วิธีแก้ปัญหาที่ดี - บางครั้งมันก็สร้างสีที่แปลกและเข้มข้นมากซึ่งยากต่อการดู

หลังจากการทดสอบเป็นเวลานานและพยายามแก้ไขปัญหานี้ฉันพบว่าทางออกที่ดีที่สุดคือการเลือกแบบอักษรสีขาวสำหรับสี "มืด" และตัวอักษรสีดำสำหรับสี "สว่าง"

นี่คือตัวอย่างของฟังก์ชั่นที่ฉันใช้ใน 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);
}

นี่คือการทดสอบสำหรับ colorcales ต่าง ๆ มากมาย (สายรุ้งสีเทาความร้อนน้ำแข็งและอื่น ๆ อีกมากมาย) และเป็นวิธีการ "สากล" เดียวที่ฉันค้นพบ

แก้ไข
เปลี่ยนสูตรการนับaเป็น "perceptive luminance" - ดูดีกว่าจริง ๆ ! ดำเนินการแล้วในซอฟต์แวร์ของฉันดูดี

แก้ไข 2 @WebSeed ให้ตัวอย่างการทำงานที่ยอดเยี่ยมของอัลกอริทึมนี้: http://codepen.io/WebSeed/full/pvgqEq/


14
มันอาจจะไม่สำคัญ แต่คุณอาจต้องการฟังก์ชั่นที่ดีกว่าในการคำนวณความสว่างstackoverflow.com/questions/596216/...
จอชลี

1
ดูเหมือนว่ามันจะสมบูรณ์แบบ
โจเซฟ Daigle

สิ่งที่ฉันต้องการในวันนี้
Chris W

น้ำหนักการส่องสว่างที่รับรู้ของคุณมาจากไหน?
Mat

จากคำตอบนี้: stackoverflow.com/questions/596216/…
Gacek

23

ในกรณีที่มีคนต้องการคำตอบที่สั้นกว่าอาจจะง่ายกว่าที่จะเข้าใจคำตอบของ 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;
}

หมายเหตุ:ฉันลบการกลับกันของค่าluma (เพื่อให้สีสดใสมีค่าสูงกว่าสิ่งที่ดูเป็นธรรมชาติสำหรับฉันและเป็นวิธีการคำนวณ 'เริ่มต้น'

ฉันใช้ค่าคงที่เดียวกับ GaceK จากที่นี่เพราะมันใช้งานได้ดีสำหรับฉัน

(คุณสามารถใช้สิ่งนี้เป็นวิธีการขยายโดยใช้ลายเซ็นต่อไปนี้:

public static Color ContrastColor(this Color iColor)

จากนั้นคุณสามารถโทรได้ผ่านforegroundColor = background.ContrastColor())


11

ขอบคุณ@Gacek นี่เป็นเวอร์ชั่นสำหรับ 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);
}

และรุ่นที่ปรับปรุง (สั้นกว่า):

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

มันจะสั้นและง่ายต่อการอ่านมากขึ้นถ้าคุณต้องการใช้การเปลี่ยนแปลงของ @Marcus Mangelsdorf (กำจัด1 - ...ส่วนและเปลี่ยนชื่อaเป็นluminance
Ridcully

การใช้อันแรกคุณสามารถจับอัลฟ่าได้เช่นกัน
Brill Pappin

9

การติดตั้งคำตอบของ 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)
}

อย่างรวดเร็วเนื่องจาก r / g / b เป็น CGFloats คุณไม่ต้องการ "/ 255" เพื่อคำนวณความส่องสว่าง: let luminance = 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

ขอบคุณสำหรับโพสต์นี้

สำหรับผู้ที่อาจสนใจนี่คือตัวอย่างของฟังก์ชั่นใน 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;

มีข้อผิดพลาดเล็กน้อยในบรรทัดที่ 4 จากบรรทัดสุดท้าย ผลลัพธ์: = clBlack ไม่ควรมีเครื่องหมายเซมิโคลอนหลังจาก;
Andy k

5

นี่คือคำตอบที่เป็นประโยชน์ ขอบคุณสำหรับมัน!

ฉันต้องการแชร์เวอร์ชัน 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 );

}

ตอนนี้หาวิธีใช้อัลกอริทึมในการสร้างสีโฮเวอร์โดยอัตโนมัติสำหรับลิงก์เมนู ส่วนหัวที่อ่อนจะได้โฮเวอร์มืดกว่าและในทางกลับกัน


3

การนำ 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;
}

ทั้งหมดที่ฉันทำคือคำนำหน้าด้วย 'คงที่' ขอบคุณ!
Pete Alvin

2

Ugly Python ถ้าคุณไม่อยากเขียน :)

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

ฉันมีปัญหาเดียวกัน แต่ฉันมีการพัฒนาในPHP ฉันใช้โซลูชันของ @ Garek และฉันยังใช้คำตอบนี้: แปลงสีฐานสิบหกเป็นค่า RGB ใน PHPเพื่อแปลงรหัสสี HEX เป็น RGB

ดังนั้นฉันจึงแบ่งปัน

ฉันต้องการใช้ฟังก์ชั่นนี้ด้วยสีพื้นหลัง HEX ที่กำหนด แต่ไม่เริ่มจาก '#' เสมอไป

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

ส่วนขยายในฐานะ 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

การดำเนินการสำหรับวัตถุประสงค์ -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
คุณควรเพิ่มคำอธิบายที่นี่เพื่อให้ผู้ใช้รายอื่นรู้ว่าเกิดอะไรขึ้นในโค้ดของคุณ
Michael

1

iOS Swift 3.0 (ส่วนขยาย 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
ฟังก์ชั่นนี้ใช้งานได้ตามที่คาดหวัง แต่ต้องระวังด้วยผ้าสำลีและการหล่อแบบพิเศษ
RichAppz

1
เยี่ยมมากลองดูตัวอย่างของฉันสำหรับ Swift 4 ด้านล่างคุณจะเห็นการลดรหัส
RichAppz

1

ตัวอย่าง 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)
    }

}

อัปเดต - พบว่า0.6เป็นเตียงทดสอบที่ดีกว่าสำหรับการสืบค้น


นี่เป็นแนวโน้มที่จะล้มเหลวในหลาย ๆ สถานการณ์เนื่องจากถือว่ามีพื้นที่สี RGB จำนวนองค์ประกอบCGColor.componentsแตกต่างกันไปขึ้นอยู่กับพื้นที่สี: ตัวอย่างเช่นUIColor.whiteเมื่อร่าย CGColor มีเพียงสอง: [1.0, 1.0]แสดงสีเทา (ที่สีขาวเต็ม) ด้วยอัลฟาเต็ม วิธีที่ดีกว่าในการแยกองค์ประกอบ RGB ของ UIColor คือUIColor.getRed(_ red:, green:, blue:, alpha:)
Scott Matthewman

1

หมายเหตุมีขั้นตอนวิธีสำหรับการนี้ในห้องสมุดปิด Googleที่อ้างอิงคำแนะนำ W3C: http://www.w3.org/TR/AERT#color-contrast อย่างไรก็ตามใน API นี้คุณให้รายการสีที่แนะนำเป็นจุดเริ่มต้น

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

หากคุณจัดการช่องว่างของสีสำหรับเอฟเฟกต์ภาพโดยทั่วไปการทำงานใน HSL (Hue, Saturation และ Lightness) จะง่ายกว่า RGB การเคลื่อนย้ายสีใน RGB เพื่อให้เอฟเฟกต์ที่เป็นธรรมชาตินั้นมีความยากในเชิงแนวคิดในขณะที่การแปลงเป็น HSL จัดการที่นั่นจากนั้นการแปลงกลับออกมาอีกครั้งนั้นเป็นแนวคิดที่ใช้งานง่ายกว่าและให้ผลลัพธ์ที่ดีกว่า

Wikipedia มีการแนะนำ HSL ที่ดีและ HSV ที่เกี่ยวข้องอย่างใกล้ชิด และมีรหัสฟรีทั่วเน็ตเพื่อทำการแปลง (เช่นนี่คือการใช้จาวาสคริปต์ )

การเปลี่ยนแปลงที่แม่นยำที่คุณใช้นั้นเป็นเรื่องของรสนิยม แต่โดยส่วนตัวแล้วฉันคิดว่าการย้อนกลับส่วนประกอบของ Hue and Lightness จะทำให้เกิดความคมชัดสูงในการประมาณค่าสีครั้งแรก แต่คุณสามารถหาเอฟเฟกต์เพิ่มเติม


1
ใช่ แต่ให้พิจารณาด้วยว่าสายตามนุษย์สามารถมองเห็นสีเขียวได้เด่นกว่าสีอื่น ๆ และสีฟ้าก็น้อยลง (ซึ่งเป็นสาเหตุที่สีฟ้าได้รับบิตสีน้อยลงในรูปแบบภาพ)
wchargin

1
จริง หากเรากำลังจะย้ายไปที่ HSL เราอาจจะทำการกระโดดอย่างเต็มรูปแบบไปยัง YUV และคำนึงถึงการรับรู้ของมนุษย์
David Bradbury

0

คุณสามารถมีข้อความฮิวบนพื้นหลังสีใดก็ได้และตรวจสอบให้แน่ใจว่ามันเป็นข้อความที่อ่านง่าย ฉันทำมันตลอดเวลา มีสูตรสำหรับสิ่งนี้ใน Javascript บนข้อความที่อ่านได้ในสี - STW * ดังที่กล่าวไว้ในลิงค์นั้นสูตรนั้นมีรูปแบบที่หลากหลายในการคำนวณการปรับค่าผกผัน - แกมม่า เมนูทางด้านขวาของลิงค์นั้นและหน้าเว็บที่เกี่ยวข้องนั้นใช้สีที่สร้างขึ้นแบบสุ่มสำหรับข้อความและพื้นหลังซึ่งอ่านได้ง่าย ใช่แน่นอนสามารถทำได้ไม่มีปัญหา


0

รูปแบบ Android ที่จับอัลฟาเช่นกัน

(ขอบคุณ @ 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

baseคำตอบของ @ Gacek รุ่น R เพื่อรับluminance(คุณสามารถใช้เกณฑ์ของคุณเองได้อย่างง่ายดาย)

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

การใช้งาน:

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

0

จากคำตอบของ Gacekและหลังจากการวิเคราะห์ตัวอย่างของ @ WebSeedด้วยส่วนขยายเบราว์เซอร์WAVEฉันได้สร้างเวอร์ชันต่อไปนี้ที่เลือกข้อความสีดำหรือสีขาวตามอัตราส่วนความคมชัด (ตามที่กำหนดไว้ในแนวทางการเข้าถึงเนื้อหาเว็บของ W3C (WCAG) 2.1 ) แทนความสว่าง

นี่คือรหัส (ใน 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';
};

ตัวอย่างการทำงานอาจพบได้ใน codepen ของทางแยกของ @ WebSeed ซึ่งสร้างข้อผิดพลาดที่ไม่มีความคมชัดต่ำใน WAVE

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.