keegan3d
keegan3d

Reputation: 11285

Lightening a UIColor

I ran into a situation yesterday where I needed to lighten a UIColor so I created a category and added a lighten method. I thought it would be straight forward to just multiply the value by each component of the color but my greens started turning yellow so I knew it had to be more complicated.

The solution I came up with was to convert from sRGB to Linear, multiply the color, and then convert back. This seems to work but I'm not sure if it's "correct". I couldn't find anything in the docs that stated that UIColor was in sRGB space. I'm also no color scientist so I only have a rudimentary knowledge of the math involved.

Anyway here is my code, I'm asking for some peer review and to see if anyone has a better understanding of modifying UIColors.

CGFloat sRGB2Linear(CGFloat x){
    CGFloat a = 0.055;
    if(x <= 0.04045){
        return x * (1.0 / 12.92);
    }else{
        return pow((x + a) * (1.0 / (1 + a)), 2.4);
    }
}

CGFloat linear2sRGB(CGFloat x){
    CGFloat a = 0.055;
    if(x <= 0.0031308){
        return x * 12.92;
    }else{
        return (1 + a) * pow(x, 1 / 2.4) - a;
    }
}

- (UIColor *)lighten:(CGFloat)value{
    const CGFloat *components = CGColorGetComponents([self CGColor]);
    CGFloat newR = (sRGB2Linear(components[0])+1)*value;
    CGFloat newG = (sRGB2Linear(components[1])+1)*value;
    CGFloat newB = (sRGB2Linear(components[2])+1)*value;
    newR = MAX(0, MIN(1, linear2sRGB(newR)));
    newG = MAX(0, MIN(1, linear2sRGB(newG)));
    newB = MAX(0, MIN(1, linear2sRGB(newB)));
    return [UIColor colorWithRed:newR green:newG blue:newB alpha:components[3]];
}

Upvotes: 9

Views: 4164

Answers (7)

turingtested
turingtested

Reputation: 7154

Mixing with white is the only way I've found that doesn't distort the original color, like in this Swift extension:

extension UIColor {

    @objc func lighten(_ alpha: CGFloat) -> UIColor {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        guard self.getRed(&r, green: &g, blue: &b, alpha: &a) else { return self }
        return UIColor(red:r + (1.0-r) * alpha, green:g + (1.0-g) * alpha, blue:b + (1.0-b) * alpha, alpha: a)
    }
}

Upvotes: 1

Jakub Truhl&#225;ř
Jakub Truhl&#225;ř

Reputation: 20710

- (UIColor *)changeBrightnessToPercentage:(CGFloat)percentage {
    CGFloat hue, saturation, brightness, alpha;
    if ([self getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) {
        brightness += (percentage - 1.0);
        brightness = MAX(MIN(brightness, 1.0), 0.0);
        return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
    }

    CGFloat white;
    if ([self getWhite:&white alpha:&alpha]) {
        white += (percentage - 1.0);
        white = MAX(MIN(white, 1.0), 0.0);
        return [UIColor colorWithWhite:white alpha:alpha];
    }

    return nil;
}

==1.0 is the provided color, >1.0 is lighter one and <1.0 is darker one.

Upvotes: 0

GilroyKilroy
GilroyKilroy

Reputation: 894

Kof's answer in Swift:

extension UIColor {
    public func changeBrightness(b: CGFloat, saturation s: CGFloat = 1.0, hue h: CGFloat = 1.0) -> UIColor? {
        var hue : CGFloat = 0.0
        var saturation : CGFloat = 0.0
        var brightness : CGFloat = 0.0
        var alpha : CGFloat = 0.0

        if self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) {
            brightness *= b
            brightness = max(min(brightness, 1.0), 0.0)
            saturation *= s
            saturation = max(min(saturation, 1.0), 0.0)
            hue *= h
            hue = max(min(hue, 1.0), 0.0)

            return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
        }

        var white: CGFloat = 0.0
        if self.getWhite(&white, alpha: &alpha) {
            white += (b - 1.0)
            white = max(min(b, 1.0), 0.0)

            return UIColor(white: white, alpha: alpha)
        }

        return nil
    }
}

Upvotes: 2

marcinno
marcinno

Reputation: 11

try

- (UIColor *)mixColorWithWhiteAndAlpha:(CGFloat)alpha {
    CGFloat r, g, b, a;
    if ([self getRed:&r green:&g blue:&b alpha:&a])
        return [UIColor colorWithRed:r + (1 -r) * alpha
                               green:g + (1 -g) * alpha
                                blue:b + (1 -b) * alpha
                               alpha:a];
    return nil;
}

it should return mixed white color with your color

so you can set alpha to 0.2 and I think it will be ok

Upvotes: 1

Kof
Kof

Reputation: 25283

I've improved ank's method to allow modification of saturation and hue as well.

Input is treated as percentage, so b of 1.0 equals to 100% (no change). To weaken brightness by 30%, set b to 0.7.

+ (UIColor*)changeColor:(UIColor*)color brightness:(CGFloat)b saturation:(CGFloat)s hue:(CGFloat)h
{

    CGFloat hue, saturation, brightness, alpha;
    if ([color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) {
        brightness *= b;
        brightness = MAX(MIN(brightness, 1.0), 0.0);
        saturation *= s;
        saturation = MAX(MIN(saturation, 1.0), 0.0);
        hue *= h;
        hue = MAX(MIN(hue, 1.0), 0.0);
        return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
    }

    CGFloat white;
    if ([color getWhite:&white alpha:&alpha]) {
        white += (b-1.0);
        white = MAX(MIN(b, 1.0), 0.0);
        return [UIColor colorWithWhite:white alpha:alpha];
    }

    return nil;
}

Upvotes: 0

Andres Kievsky
Andres Kievsky

Reputation: 3491

This is my iOS5+ solution to make relative brightness changes easily (lighten and darken all-in-one):

+ (UIColor*)changeBrightness:(UIColor*)color amount:(CGFloat)amount
{

    CGFloat hue, saturation, brightness, alpha;
    if ([color getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) {
        brightness += (amount-1.0);
        brightness = MAX(MIN(brightness, 1.0), 0.0);
        return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:alpha];
    }

    CGFloat white;
    if ([color getWhite:&white alpha:&alpha]) {
        white += (amount-1.0);
        white = MAX(MIN(white, 1.0), 0.0);
        return [UIColor colorWithWhite:white alpha:alpha];
    }

    return nil;
}

It's invoked like so:

[self changeBrightness:someUiColor amount:1.1]

Using 1.1 it will increase the brightness by 10%; 0.9 will decrease the brightness by 10%. Note that the 10% is relative to pure white (i.e., 10% is always a .1 increase in brightness.) This is the expected behavior if you want a percentage increase to lighten or darken the colors consistently regardless of their initial brightness.

Upvotes: 24

Eiko
Eiko

Reputation: 25632

You can work with hue, saturation, and brightness.

Get the old values using

- (BOOL)getHue:(CGFloat *)hue saturation:(CGFloat *)saturation brightness:(CGFloat *)brightness alpha:(CGFloat *)alpha

then adjust the brightness, and construct a new color:

- (UIColor *)initWithHue:(CGFloat)hue saturation:(CGFloat)saturation brightness:(CGFloat)brightness alpha:(CGFloat)alpha

Keep in mind that you need to pass pointers to the first function.

Upvotes: 4

Related Questions