112

I need to determine whether a selected UIColor (picked by the user) is dark or bright, so I can change the color of a line of text that sits on top of that color, for better readability.

Here's an example in Flash/Actionscript (with demo): http://web.archive.org/web/20100102024448/http://theflashblog.com/?p=173

Any thoughts?

Cheers, Andre

UPDATE

Thanks to everyone's suggestions, here's the working code:

- (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");
    }
}

Thanks once again :)

TheNeil
  • 1,985
  • 2
  • 17
  • 36
Andre
  • 4,237
  • 7
  • 29
  • 37

14 Answers14

79

W3C has the following: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

If you're only doing black or white text, use the color brightness calculation above. If it is below 125, use white text. If it is 125 or above, use black text.

edit 1: bias towards black text. :)

edit 2: The formula to use is ((Red value * 299) + (Green value * 587) + (Blue value * 114)) / 1000.

Jasarien
  • 57,071
  • 27
  • 154
  • 186
Erik Nedwidek
  • 5,896
  • 1
  • 23
  • 23
  • do you know how to get the red , green and blue values of a color? – Andre Mar 24 '10 at 16:56
  • You can use `NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);` to get an array of the colour components, including the alpha. The docs don't specify what order they're in but I assume it would be red, green, blue, alpha. Also, from the docs, "the size of the array is one more than the number of components of the color space for the color." - it doesn't say why... – Jasarien Mar 24 '10 at 17:07
  • That's odd...using: NSArray *components = (NSArray *) CGColorGetComponents(newColor.CGColor); NSLog(@"my color components %f %f %f %f", components[0], components[1], components[2], components[3]); just to see if i can get the values, it seems only the 0 index changes, the others will remain the same, regardless of what color I pick. Any idea why? – Andre Mar 24 '10 at 17:21
  • Got it. You can't use NSArray. Use this instead: const CGFloat *components = CGColorGetComponents(newColor.CGColor); Also, the order is: red is components[0]; green is components[1]; blue is components[2]; alpha is components[3]; – Andre Mar 24 '10 at 17:24
  • Ah, my bad. I thought it returned a CFArrayRef, not a C array. Sorry! – Jasarien Mar 24 '10 at 17:30
46

Here is a Swift (3) extension to perform this check.

This extension works with greyscale colors. However, if you are creating all your colors with the RGB initializer and not using the built in colors such as UIColor.black and UIColor.white, then possibly you can remove the additional checks.

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

Tests:

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

Note: Updated to Swift 3 12/7/18

josh-fuggle
  • 2,666
  • 2
  • 23
  • 24
  • 6
    Works Great. With Swift 2.0 - I got a compiler error for the brightness calculation: "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions". I fixed it by adding type: "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 Nov 15 '15 at 12:46
  • 1
    I had the same issue with Swift 2.1. I solved this issue by simply creating variables for the components instead of accessing them with `components[0]`. Like so: `let componentColorX: CGFloat = components[1]` – petritz Nov 28 '15 at 07:38
  • 1
    Xcode tells me the brightness = "Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions" – daihovey Mar 24 '16 at 04:47
  • daidai, just simplify the expression. The division at the end is unnecessary. We don't need to work in the range 0 - 1. Don't divide by 1000, then just check if the value is greater than 500 instead. – aronspring Mar 30 '16 at 10:41
37

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
}
Community
  • 1
  • 1
neoneye
  • 44,507
  • 23
  • 155
  • 143
  • For me this one is the best. You can even set range of white in the check to suit your needs and is super simple and native. Thanks. – Andreas777 May 24 '17 at 07:59
  • ((rgb.red * 299) + (rgb.green * 587) + (rgb.blue * 114)) / 1000 is more accurate, e.g. for color #7473E8, getWhite(&white, alpha: nil) is 0.49656128883361816, but this formula is 0.5044464148879051, so the formula way is better than getWhite. – TonnyTao Mar 02 '21 at 20:53
34

Using Erik Nedwidek's answer, I came up with that little snippet of code for easy inclusion.

- (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];
}
Zachary Orr
  • 1,578
  • 1
  • 14
  • 25
Remy Vanherweghem
  • 3,724
  • 1
  • 27
  • 43
  • I used this code, worked perfectly, but dont know when I set [UIColor blackColor], it returns darknessScore = 149.685. Can you explain why this is happening ? – DivineDesert Jun 20 '14 at 20:02
  • 4
    This code won't work with non-RGB colors such as `UIColor whiteColor, grayColor, blackColor`. – rmaddy Nov 21 '14 at 16:14
  • @rmaddy why is that? or why aren't whiteColor, grayColor and blackColor RGB colors? – mattsven Apr 21 '15 at 13:34
  • @mattsven Because those colors are gray scale colors and they only have one color component. – rmaddy Apr 21 '15 at 15:24
  • 1
    @rmaddy How can I programmatically tell the difference between colors in the RGB color space vs. grayscale? – mattsven Apr 21 '15 at 18:02
  • 1
    @maddy Nevermind! Thanks for the help - http://stackoverflow.com/questions/1099569/how-to-convert-colors-from-one-color-space-to-another – mattsven Apr 21 '15 at 18:17
10

My solution to this problem in a category (drawn from other answers here). Also works with grayscale colors, which at the time of writing none of the other answers do.

@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
mattsven
  • 19,239
  • 8
  • 62
  • 102
9

Swift 4 Version

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)
    }
}
Kaiyuan Xu
  • 770
  • 7
  • 12
  • 1
    That probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation. – Skrew Aug 06 '18 at 10:03
5

Simpler Swift 3 extension:

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
    }
}
Sunkas
  • 8,816
  • 5
  • 55
  • 94
  • 2
    That probably won't work for default system colors like UIColor.white as it'll have only 2 components, it's not RGB representation. – Skrew Aug 06 '18 at 10:03
  • True! You could convert the color to hex-string and then back to UIColor to account for that. – Sunkas Aug 13 '18 at 08:53
4

For me using only CGColorGetComponents didn't worked, I get 2 components for UIColors like white. So I have to check the color spaceModel first. This is what I came up with that ended up being the swift version of @mattsven's answer.

Color space taken from here: https://stackoverflow.com/a/16981916/4905076

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
    }
Lucho
  • 949
  • 13
  • 20
3

If you prefer the block version:

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;
};
kakilangit
  • 1,260
  • 1
  • 11
  • 11
2

For everything that's not grayish, the RGB inverse of a color is usually highly contrasted with it. The demo just inverts the color and desaturates it (converts it to a gray).

But generating a nice soothing combination of colors is quite complicated. Look at :

http://particletree.com/notebook/calculating-color-contrast-for-legible-text/

rep_movsd
  • 6,277
  • 4
  • 26
  • 31
  • 1
    If only there was an iPhone version of that :D – Andre Mar 24 '10 at 16:41
  • So if I got the RGB values of a color (which I don't know how to do) and created a new color with the inverse of those values, that should work on a very basic level? – Andre Mar 24 '10 at 16:46
2

Following method is find color is light or dark in Swift language based on white in color.

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
}

Following method is find color is light or dark in Swift using color components.

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
}
abhi
  • 507
  • 6
  • 9
2

UIColor has the following method to convert to HSB color space:

- (BOOL)getHue:(CGFloat *)hue saturation:(CGFloat *)saturation brightness:(CGFloat *)brightness alpha:(CGFloat *)alpha;
Cuddy
  • 46
  • 3
1
- (BOOL)isColorLight:(UIColor*)color
{
    CGFloat white = 0;
    [color getWhite:&white alpha:nil];
    return (white >= .85);
}

Added Swift 5 version:

var white: CGFloat = 0.0
color.getWhite(&white, alpha: nil)
return white >= .85 // Don't use white background
Laszlo
  • 2,708
  • 2
  • 24
  • 33
Mike Glukhov
  • 1,493
  • 16
  • 15
  • 1
    This only works if the `UIColor` is a grayscale color. A random RGB color won't work with this code. – rmaddy Nov 21 '14 at 16:12
0

If you want to find the brightness of the color, here is some pseudo code:

public float GetBrightness(int red, int blue, int green)
{
    float num = red / 255f;
    float num2 = blue / 255f;
    float num3 = green / 255f;
    float num4 = num;
    float num5 = num;
    if (num2 > num4)
        num4 = num2;
    if (num3 > num4)
        num4 = num3;
    if (num2 < num5)
        num5 = num2;
    if (num3 < num5)
        num5 = num3;
    return ((num4 + num5) / 2f);
}

If it is > 0.5 it is bright, and otherwise dark.

Bryan Denny
  • 26,451
  • 32
  • 103
  • 124