Проверить, светлый или темный UIColor?

107

Мне нужно определить, является ли выбранный 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.
Гленн Хоуз,

Ответы:

75

W3C имеет следующее: http://www.w3.org/WAI/ER/WD-AERT/#color-contrast

Если вы делаете только черный или белый текст, используйте расчет яркости цвета выше. Если ниже 125, используйте белый текст. Если 125 или больше, используйте черный текст.

редактировать 1: предвзятое отношение к черному тексту. :)

редактировать 2: Используемая формула: ((Красное значение * 299) + (Зеленое значение * 587) + (Синее значение * 114)) / 1000.

Эрик Недвидидек
источник
вы знаете, как получить значения красного, зеленого и синего цвета?
Андре
Вы можете использовать NSArray *components = (NSArray *)CGColorGetComponents([UIColor CGColor]);для получения массива компонентов цвета, включая альфа-канал. В документации не указано, в каком порядке они находятся, но я предполагаю, что это будет красный, зеленый, синий, альфа. Кроме того, из документации, «размер массива на единицу больше, чем количество компонентов цветового пространства для цвета». - не сказано, почему ...
Jasarien
Странно ... используя: NSArray * components = (NSArray *) CGColorGetComponents (newColor.CGColor); NSLog (@ "мои компоненты цвета% f% f% f% f", компоненты [0], компоненты [1], компоненты [2], компоненты [3]); просто чтобы посмотреть, могу ли я получить значения, кажется, изменяется только индекс 0, остальные останутся такими же, независимо от того, какой цвет я выберу. Есть идеи, почему?
Андре
Понял. Вы не можете использовать NSArray. Используйте вместо этого: const CGFloat * components = CGColorGetComponents (newColor.CGColor); Также порядок следующий: красный - это компоненты [0]; зеленый - компоненты [1]; синий - компоненты [2]; альфа - компоненты [3];
Андре
Ах, моя беда. Я думал, что он вернул CFArrayRef, а не массив C. Сожалею!
Jasarien,
44

Вот расширение Swift (3) для выполнения этой проверки.

Это расширение работает с оттенками серого. Однако, если вы создаете все свои цвета с помощью инициализатора RGB и не используете встроенные цвета, такие как UIColor.blackи UIColor.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 07.12.18

Джош-фугл
источник
6
Прекрасно работает. В Swift 2.0 я получил ошибку компилятора при расчете яркости: «Выражение было слишком сложным, чтобы его можно было решить за разумное время; подумайте о том, чтобы разбить выражение на отдельные подвыражения». Я исправил это, добавив тип: «let яркость = (((компоненты [0] * 299.0) как CGFloat) + ((компоненты [1] * 587.0) как CGFloat) + ((компоненты [2] * 114.0)» как CGFloat ) / (1000.0 как CGFloat) "
GK100
1
У меня была такая же проблема со Swift 2.1. Я решил эту проблему, просто создав переменные для компонентов вместо того, чтобы обращаться к ним с помощью components[0]. Вроде так:let componentColorX: CGFloat = components[1]
petritz
1
Xcode сообщает мне яркость = «Выражение было слишком сложным, чтобы его можно было решить за разумное время; подумайте о том, чтобы разбить выражение на отдельные
подвыражения
daidai, просто упрости выражение. Деление в конце не нужно. Нам не нужно работать в диапазоне от 0 до 1. Не делите на 1000, а просто проверьте, не превышает ли значение 500.
aronspring
32

Используя ответ Эрика Недвидида, я придумал этот небольшой фрагмент кода для легкого включения.

- (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], он возвращает DarkScore = 149.685. Вы можете объяснить, почему это происходит?
DivineDesert,
3
Этот код не будет работать с цветами, отличными от RGB, такими как UIColor whiteColor, grayColor, blackColor.
rmaddy
@rmaddy, почему это так? или почему нет цветов whiteColor, grayColor и blackColor RGB?
mattsven
1
@rmaddy Как я могу программно отличить цвета в цветовом пространстве RGB от оттенков серого?
mattsven
1
@maddy Nevermind! Спасибо за помощь - stackoverflow.com/questions/1099569/…
mattsven
32

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
9

Версия 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, поскольку у него будет только 2 компонента, это не представление RGB.
Skrew
7

Мое решение этой проблемы в категории (взято из других ответов здесь). Также работает с оттенками серого, чего на момент написания нет ни в одном другом ответе.

@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
источник
хорошая обработка цветов не RGB - работает как шарм.
hatunike
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;
};
какилангит
источник
2

Для всего, что не является серым, инверсия цвета RGB обычно сильно контрастирует с ним. Демо просто инвертирует цвет и обесцвечивает его (преобразует его в серый).

Но создать приятную успокаивающую комбинацию цветов довольно сложно. Смотреть на :

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

rep_movsd
источник
1
Если бы только была версия этого для iPhone: D
Андре
Итак, если я получил значения RGB цвета (что я не знаю, как это сделать) и создал новый цвет с инверсией этих значений, это должно работать на самом базовом уровне?
Андре
2

Следующий метод - найти светлый или темный цвет на языке 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
}
абхи
источник
2

Для меня использование только CGColorGetComponents не сработало, я получил 2 компонента для UIColors, таких как белый. Поэтому сначала мне нужно проверить модель цветового пространства. Это то, что я придумал, что в итоге стало быстрой версией ответа @mattsven.

Цветовое пространство взято отсюда: 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
    }
Лучо
источник
2

UIColor имеет следующий метод преобразования в цветовое пространство HSB :

- (BOOL)getHue:(CGFloat *)hue saturation:(CGFloat *)saturation brightness:(CGFloat *)brightness alpha:(CGFloat *)alpha;
Кадди
источник
1
- (BOOL)isColorLight:(UIColor*)color
{
    CGFloat white = 0;
    [color getWhite:&white alpha:nil];
    return (white >= .85);
}

Добавленная Swift 5версия:

var white: CGFloat = 0.0
color.getWhite(&white, alpha: nil)
return white >= .85 // Don't use white background
Майк Глухов
источник
1
Это работает, только если UIColorэто цвет в оттенках серого. Случайный цвет RGB не будет работать с этим кодом.
rmaddy
0

Если вы хотите узнать яркость цвета, вот какой-то псевдокод:

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

Если он> 0,5, он яркий, в противном случае - темный.

Брайан Денни
источник
2
Вы не можете одинаково взвесить каждый из цветов. Наши глаза имеют предубеждение против синего и в меньшей степени красного.
Эрик Недвидайд,
@ErikNedwidek: Достаточно справедливо, вопрос просто задавал о яркости цвета :)
Брайан Денни,