检查UIColor是暗色还是亮色?

125
我需要确定用户选择的UIColor(由用户选择)是暗色还是亮色,以便我可以更改文本行的颜色,使其更易读。有关Flash / Actionscript的示例,请参见以下链接(附有演示):http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173 你有什么想法吗?谢谢!
- (void) updateColor:(UIColor *) newColor
{
    const CGFloat *componentColors = CGColorGetComponents(newColor.CGColor);

    CGFloat colorBrightness = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
    if (colorBrightness < 0.5)
    {
        NSLog(@"my color is dark");
    }
    else
    {
        NSLog(@"my color is light");
    }
}

再次感谢你 :)


貌似有些颜色没有三个组件,比如UIColor.black。 - Glenn Howes
14个回答

80
W3C提供以下信息:http://www.w3.org/WAI/ER/WD-AERT/#color-contrast 如果文本只有黑色或白色,请使用上面的颜色亮度计算。如果结果小于125,则使用白色文本,如果是125或更高,则使用黑色文本。
编辑1:偏向使用黑色文本。 :)
编辑2:需要使用的公式为 ((红色值 * 299) + (绿色值 * 587) + (蓝色值 * 114)) / 1000。

你知道如何获取颜色的红、绿和蓝值吗? - Andre
你可以使用NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);来获取颜色组件的数组,包括alpha值。文档没有指定它们的顺序,但我认为应该是红、绿、蓝、alpha。此外,从文档中可以看到,“数组的大小比颜色空间的组件数量多一个”——它没有说明原因... - Jasarien
很奇怪...使用以下代码: NSArray *components = (NSArray *) CGColorGetComponents(newColor.CGColor); NSLog(@"my color components %f %f %f %f", components[0], components[1], components[2], components[3]); 只是为了查看值,似乎只有0索引会改变,其他的将保持不变,无论我选择什么颜色。 有任何想法为什么? - Andre
明白了。你不能使用NSArray,应该使用以下代码: const CGFloat *components = CGColorGetComponents(newColor.CGColor); 另外,顺序是:红色是components[0]; 绿色是components[1]; 蓝色是components[2]; 透明度是components[3]; - Andre
啊,我的错。我以为它返回的是CFArrayRef,而不是C数组。抱歉! - Jasarien

61

以下是一个用于进行此检查的Swift(3)扩展。

此扩展适用于灰度颜色。但是,如果您使用RGB初始化程序创建所有颜色而不是使用内置颜色(例如UIColor.blackUIColor.white),则可能可以省略其他检查。

extension UIColor {

    // Check if the color is light or dark, as defined by the injected lightness threshold.
    // Some people report that 0.7 is best. I suggest to find out for yourself.
    // A nil value is returned if the lightness couldn't be determined.
    func isLight(threshold: Float = 0.5) -> Bool? {
        let originalCGColor = self.cgColor

        // Now we need to convert it to the RGB colorspace. UIColor.white / UIColor.black are greyscale and not RGB.
        // If you don't do this then you will crash when accessing components index 2 below when evaluating greyscale colors.
        let RGBCGColor = originalCGColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil)
        guard let components = RGBCGColor?.components else {
            return nil
        }
        guard components.count >= 3 else {
            return nil
        }

        let brightness = Float(((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000)
        return (brightness > threshold)
    }
}

测试:

func testItWorks() {
    XCTAssertTrue(UIColor.yellow.isLight()!, "Yellow is LIGHT")
    XCTAssertFalse(UIColor.black.isLight()!, "Black is DARK")
    XCTAssertTrue(UIColor.white.isLight()!, "White is LIGHT")
    XCTAssertFalse(UIColor.red.isLight()!, "Red is DARK")
}

注意:已更新至 Swift 3,日期为 12/7/18


6
很好用。使用Swift 2.0时,我在亮度计算中遇到一个编译器错误:“表达式太复杂,无法在合理的时间内求解;请考虑将表达式拆分为不同的子表达式。”我通过添加类型修饰符进行了修复: "let brightness = (((components[0] * 299.0) as CGFloat) + ((components[1] * 587.0) as CGFloat) + ((components[2] * 114.0)) as CGFloat) / (1000.0 as CGFloat)"。 - GK100
1
我在Swift 2.1中遇到了同样的问题。我通过简单地为组件创建变量而不是使用components [0]来访问它们来解决了这个问题。像这样:let componentColorX: CGFloat = components [1] - petritz
1
Xcode 告诉我亮度 = "表达式过于复杂,无法在合理的时间内解决;请考虑将表达式分解为不同的子表达式"。 - daihovey
daidai,简化一下表达式即可。结尾处的除法是不必要的。我们不需要在0-1范围内工作。不要除以1000,只需检查该值是否大于500即可。 - aronspring
考虑到这个返回一个可选的 Bool,有哪些情况会导致失败并返回 nil?@josh-fuggle - Andrea Mario Lufino

48

Swift3

extension UIColor {
    var isLight: Bool {
        var white: CGFloat = 0
        getWhite(&white, alpha: nil)
        return white > 0.5
    }
}

// Usage
if color.isLight {
    label.textColor = UIColor.black
} else {
    label.textColor = UIColor.white
}

对我来说,这个是最好的。你甚至可以设置白色范围以适应你的需求,而且非常简单和本地化。谢谢。 - Andreas777
1
((rgb.red * 299) + (rgb.green * 587) + (rgb.blue * 114)) / 1000 更加准确,例如对于颜色 #7473E8,使用 getWhite(&white, alpha: nil) 得到的值为 0.49656128883361816,但是使用这个公式得到的值为 0.5044464148879051,因此公式更好用。 - TonnyTao

34
使用Erik Nedwidek的答案,我编写了这个小片段的代码以便更容易地引入它。
- (UIColor *)readableForegroundColorForBackgroundColor:(UIColor*)backgroundColor {
    size_t count = CGColorGetNumberOfComponents(backgroundColor.CGColor);
    const CGFloat *componentColors = CGColorGetComponents(backgroundColor.CGColor);

    CGFloat darknessScore = 0;
    if (count == 2) {
        darknessScore = (((componentColors[0]*255) * 299) + ((componentColors[0]*255) * 587) + ((componentColors[0]*255) * 114)) / 1000;
    } else if (count == 4) {
        darknessScore = (((componentColors[0]*255) * 299) + ((componentColors[1]*255) * 587) + ((componentColors[2]*255) * 114)) / 1000;
    }

    if (darknessScore >= 125) {
        return [UIColor blackColor];
    }

    return [UIColor whiteColor];
}

我使用了这段代码,完美地运行了,但是当我设置 [UIColor blackColor] 时,它返回 darknessScore = 149.685。你能解释一下为什么会发生这种情况吗? - DivineDesert
4
这段代码不能处理非RGB颜色,例如 UIColor whiteColor, grayColor, blackColor - rmaddy
@rmaddy为什么呢?或者为什么whiteColor、grayColor和blackColor不是RGB颜色? - mattsven
@mattsven 因为这些颜色是灰度颜色,只有一个颜色分量。 - rmaddy
1
@rmaddy 我如何在编程中区分RGB颜色空间和灰度? - mattsven
1
@maddy 不用在意!感谢你的帮助 - https://dev59.com/rnNA5IYBdhLWcg3wEpkU - mattsven

11

Swift 4 版本

extension UIColor {
    func isLight() -> Bool {
        guard let components = cgColor.components, components.count > 2 else {return false}
        let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000
        return (brightness > 0.5)
    }
}

1
可能对于默认系统颜色(例如UIColor.white)不起作用,因为它只有两个组件,而不是RGB表示方式。 - Skrew

11

这个问题的我的解决方案属于某个类别(来自其他答案)。同时适用于灰度颜色,在撰写本文时,其他回答均不支持此功能。

@interface UIColor (Ext)

    - (BOOL) colorIsLight;

@end

@implementation UIColor (Ext)

    - (BOOL) colorIsLight {
        CGFloat colorBrightness = 0;

        CGColorSpaceRef colorSpace = CGColorGetColorSpace(self.CGColor);
        CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);

        if(colorSpaceModel == kCGColorSpaceModelRGB){
            const CGFloat *componentColors = CGColorGetComponents(self.CGColor);

            colorBrightness = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
        } else {
            [self getWhite:&colorBrightness alpha:0];
        }

        return (colorBrightness >= .5f);
    }

@end

非 RGB 颜色的处理很好 - 运行得很顺畅。 - hatunike

5
对我来说只使用CGColorGetComponents不起作用,对于像白色这样的UIColor,我只得到2个分量。所以我必须先检查颜色空间模型。 这就是我想出来的最终结果,它成为@mattsven答案的swift版本。
颜色空间从这里获取:https://dev59.com/rnNA5IYBdhLWcg3wEpkU#16981916
extension UIColor {
    func isLight() -> Bool {
        if let colorSpace = self.cgColor.colorSpace {
            if colorSpace.model == .rgb {
                guard let components = cgColor.components, components.count > 2 else {return false}

                let brightness = ((components[0] * 299) + (components[1] * 587) + (components[2] * 114)) / 1000

                return (brightness > 0.5)
            }
            else {
                var white : CGFloat = 0.0

                self.getWhite(&white, alpha: nil)

                return white >= 0.5
            }
        }

        return false
    }

5

更简单的Swift 3扩展:

extension UIColor {
    func isLight() -> Bool {
        guard let components = cgColor.components else { return false }
        let redBrightness = components[0] * 299
        let greenBrightness = components[1] * 587
        let blueBrightness = components[2] * 114
        let brightness = (redBrightness + greenBrightness + blueBrightness) / 1000
        return brightness > 0.5
    }
}

2
这可能对默认系统颜色(如UIColor.white)不起作用,因为它只有2个组件,而不是RGB表示。 - Skrew
当然可以!您可以将颜色转换为十六进制字符串,然后再转换回UIColor以解决这个问题。 - Sunkas

3

如果您更喜欢块状版本:

BOOL (^isDark)(UIColor *) = ^(UIColor *color){
    const CGFloat *component = CGColorGetComponents(color.CGColor);
    CGFloat brightness = ((component[0] * 299) + (component[1] * 587) + (component[2] * 114)) / 1000;

    if (brightness < 0.75)
        return  YES;
    return NO;
};

3

以下方法是基于颜色中的白色,使用Swift语言查找颜色是浅色还是深色。

func isLightColor(color: UIColor) -> Bool 
{
   var white: CGFloat = 0.0
   color.getWhite(&white, alpha: nil)

   var isLight = false

   if white >= 0.5
   {
       isLight = true
       NSLog("color is light: %f", white)
   }
   else
   {
      NSLog("Color is dark: %f", white)
   }

   return isLight
}

以下方法是使用颜色组件在Swift中查找颜色是浅色还是深色。

func isLightColor(color: UIColor) -> Bool 
{
     var isLight = false

     var componentColors = CGColorGetComponents(color.CGColor)

     var colorBrightness: CGFloat = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
     if (colorBrightness >= 0.5)
     {
        isLight = true
        NSLog("my color is light")
     }
     else
     {
        NSLog("my color is dark")
     }  
     return isLight
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接