27

I have a ProgressBar to which I want to assign a customColor color and based on the progress fade to another color. Using the method below I get a dark rainbow effect color including reds and dark brown and dark green. The start color will be a light blue one and the destination color a light green.

-(UIColor *) makeCustomColorFromProgressValue:(float) progress{


UIColor *color;

// startColor Color - lightBlue
float red = 0.53;
float green = 0.82;
float blue = 1;

//Destination Color - lightGreen
float finalRed = 0.53;
float finalGreen = 1;
float finalBlue = 0.82;

float newRed = 80;//finalRed *255;
float newGreen = (finalGreen *progress) *255;
float newBlue = (finalBlue *progress) *255;
color = Rgb2UIColor(newRed, newGreen, newBlue);



return color;
}
Aaron
  • 7,055
  • 2
  • 38
  • 53
snksnk
  • 1,566
  • 4
  • 21
  • 46

7 Answers7

29

You can do a "linear interpolation" between the colors:

CGFloat newRed   = (1.0 - progress) * red   + progress * finalRed;
CGFloat newGreen = (1.0 - progress) * green + progress * finalGreen;
CGFloat newBlue  = (1.0 - progress) * blue  + progress * finalBlue;
UIColor *color = [UIColor colorWithRed:newRed green:newGreen blue:newBlue alpha:1.0];

This gives the initial color for progress == 0 and the final color for progress == 1.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I figured another way at the end.. float newGreen = (((finalGreen -green) *progress) +green) *255; I ll give you the up vote thought and thank you for the reply. – snksnk Apr 04 '14 at 16:41
  • @snksnk: You are welcome (note that your and my formula are actually the same). – Martin R Apr 04 '14 at 16:46
  • I have noticed that they get to the same outcome. I'll mark your answer as the correct one also because you deserve it:) For any future considerations here's the macro I use also.. #define Rgb2UIColor(r, g, b) [UIColor colorWithRed:((r) / 255.0) green:((g) / 255.0) blue:((b) / 255.0) alpha:1] – snksnk Apr 04 '14 at 16:52
25

Swift version converted from the code from Jonathan Ellis

extension UIColor {
    func interpolateRGBColorTo(_ end: UIColor, fraction: CGFloat) -> UIColor? {
        let f = min(max(0, fraction), 1)

        guard let c1 = self.cgColor.components, let c2 = end.cgColor.components else { return nil }

        let r: CGFloat = CGFloat(c1[0] + (c2[0] - c1[0]) * f)
        let g: CGFloat = CGFloat(c1[1] + (c2[1] - c1[1]) * f)
        let b: CGFloat = CGFloat(c1[2] + (c2[2] - c1[2]) * f)
        let a: CGFloat = CGFloat(c1[3] + (c2[3] - c1[3]) * f)

        return UIColor(red: r, green: g, blue: b, alpha: a)
    }
}

let color1 = UIColor(red: 0.035, green: 0.216, blue: 0.933, alpha: 1.00)
let color2 = UIColor(red: 0.933, green: 0.794, blue: 0.000, alpha: 1.00)

color1.interpolateRGBColorTo(color2, fraction:0.1)

UIColor Interpolation

Atika
  • 1,560
  • 18
  • 18
  • Line 2 should be `f = min(1, f)` I believe, no? – Mikkel Selsøe Aug 10 '17 at 10:39
  • 3
    This will crash with colors made like this `UIColor(white: 0.0, alpha: 0.4)` – Mihai Fratu Nov 28 '17 at 11:00
  • Create a RGB UIColor: `UIColor(red:1.0, green:1.0, blue:1.0, alpha: 0.4)` or use this version: https://stackoverflow.com/a/35853850/657988 – Atika Nov 28 '17 at 16:16
  • 6
    Be careful with this, as not every UIColor has 4 components. If the color was created like this: `UIColor(white: 101.0 / 255.0, alpha: 1.0)` then the color will only have 2 components and this will crash. – Vladimir Mar 16 '18 at 09:38
19

The swift version provided above doesn't work with the white color, here the solution:

struct ColorComponents {
    var r:CGFloat, g:CGFloat, b:CGFloat, a:CGFloat
}

extension UIColor {

    func getComponents() -> ColorComponents {
        if (cgColor.numberOfComponents == 2) {
          let cc = cgColor.components!
          return ColorComponents(r:cc[0], g:cc[0], b:cc[0], a:cc[1])
        }
        else {
          let cc = cgColor.components!
          return ColorComponents(r:cc[0], g:cc[1], b:cc[2], a:cc[3])
        }
    }

    func interpolateRGBColorTo(end: UIColor, fraction: CGFloat) -> UIColor {
        var f = max(0, fraction)
        f = min(1, fraction)

        let c1 = self.getComponents()
        let c2 = end.getComponents()

        let r = c1.r + (c2.r - c1.r) * f
        let g = c1.g + (c2.g - c1.g) * f
        let b = c1.b + (c2.b - c1.b) * f
        let a = c1.a + (c2.a - c1.a) * f

        return UIColor.init(red: r, green: g, blue: b, alpha: a)
    }

}

let color1 = UIColor.whiteColor()
let color2 = UIColor(red: 0.933, green: 0.794, blue: 0.000, alpha: 1.00)

color1.interpolateRGBColorTo(color2, fraction:0.1)
color1.interpolateRGBColorTo(color2, fraction:0.2)
color1.interpolateRGBColorTo(color2, fraction:0.3)

❤︎ Playground

Hashem Aboonajmi
  • 13,077
  • 8
  • 66
  • 75
mt81
  • 3,288
  • 1
  • 26
  • 35
15

Here is a category for UIColor that can be used to linearly interpolate between two UIColors in either RGB or HSV:

@implementation UIColor (Interpolate)

+ (UIColor *)interpolateRGBColorFrom:(UIColor *)start to:(UIColor *)end withFraction:(float)f {

    f = MAX(0, f);
    f = MIN(1, f);

    const CGFloat *c1 = CGColorGetComponents(start.CGColor);
    const CGFloat *c2 = CGColorGetComponents(end.CGColor);

    CGFloat r = c1[0] + (c2[0] - c1[0]) * f;
    CGFloat g = c1[1] + (c2[1] - c1[1]) * f;
    CGFloat b = c1[2] + (c2[2] - c1[2]) * f;
    CGFloat a = c1[3] + (c2[3] - c1[3]) * f;

    return [UIColor colorWithRed:r green:g blue:b alpha:a];
}

+ (UIColor *)interpolateHSVColorFrom:(UIColor *)start to:(UIColor *)end withFraction:(float)f {

    f = MAX(0, f);
    f = MIN(1, f);

    CGFloat h1,s1,v1,a1;
    [start getHue:&h1 saturation:&s1 brightness:&v1 alpha:&a1];

    CGFloat h2,s2,v2,a2;
    [end getHue:&h2 saturation:&s2 brightness:&v2 alpha:&a2];

    CGFloat h = h1 + (h2 - h1) * f;
    CGFloat s = s1 + (s2 - s1) * f;
    CGFloat v = v1 + (v2 - v1) * f;
    CGFloat a = a1 + (a2 - a1) * f;

    return [UIColor colorWithHue:h saturation:s brightness:v alpha:a];
}

@end
Jonathan Ellis
  • 5,221
  • 2
  • 36
  • 53
  • A great solution, thanks! But will RGB interpolation method work with colors that was initially created using HSV model? Since we get access to internal storage, there can be values in HSV format... Am I wrong with this assumption? – Cemen Aug 12 '15 at 10:58
  • Yes, you should be able to interpolate HSV using RGB-created UIColors, and interpolate RGB using HSV-created UIColors. – Jonathan Ellis Aug 12 '15 at 11:10
9

Another port to swift, as an extension to UIColor. This time both interpolation functions.

extension UIColor {
    func interpolateRGBColorTo(end:UIColor, fraction:CGFloat) -> UIColor {
        var f = max(0, fraction)
        f = min(1, fraction)
        let c1 = CGColorGetComponents(self.CGColor)
        let c2 = CGColorGetComponents(end.CGColor)
        let r: CGFloat = CGFloat(c1[0] + (c2[0] - c1[0]) * f)
        let g: CGFloat = CGFloat(c1[1] + (c2[1] - c1[1]) * f)
        let b: CGFloat = CGFloat(c1[2] + (c2[2] - c1[2]) * f)
        let a: CGFloat = CGFloat(c1[3] + (c2[3] - c1[3]) * f)
        return UIColor.init(red:r, green:g, blue:b, alpha:a)
    }
    

    func interpolateHSVColorFrom(end: UIColor, fraction: CGFloat) -> UIColor {
        var f = max(0, fraction)
        f = min(1, fraction)
        var h1: CGFloat = 0, s1: CGFloat = 0, b1: CGFloat = 0, a1: CGFloat = 0
        self.getHue(&h1, saturation: &s1, brightness: &b1, alpha: &a1)
        var h2: CGFloat = 0, s2: CGFloat = 0, b2: CGFloat = 0, a2: CGFloat = 0
        end.getHue(&h2, saturation: &s2, brightness: &b2, alpha: &a2)
        let h = h1 + (h2 - h1) * f
        let s = s1 + (s2 - s1) * f
        let b = b1 + (b2 - b1) * f
        let a = a1 + (a2 - a1) * f
        return UIColor(hue: h, saturation: s, brightness: b, alpha: a)
    }
}

EDIT: fixed error found by @protuberian, thanks.

TODO: Linear interpolation on colors will not work, a more curved interpolation is in place .

elcuco
  • 8,948
  • 9
  • 47
  • 69
2

Here's a Swift 3 version that uses a convenience initializer and adds a function to return an array of intermediate colors.

extension UIColor {
convenience init?(interpolatedFrom fromColor: UIColor, to toColor: UIColor, byFraction fraction: CGFloat) {
    guard fromColor.cgColor.numberOfComponents >= 4 && toColor.cgColor.numberOfComponents >= 3 else {
        print("Color interpolation requires both the to and from color to be provided with components for red, green, blue, and alpha.")
        return nil
    }

    var fraction = max(0, fraction)
    fraction = min(1, fraction)

    guard let fromComponents = fromColor.cgColor.components, let toComponents = toColor.cgColor.components else {
        print("Unable to extract components from colors provided for interpolation.")
        return nil
    }

    let red: CGFloat = CGFloat(fromComponents[0] + (toComponents[0] - fromComponents[0]) * fraction)
    let green: CGFloat = CGFloat(fromComponents[1] + (toComponents[1] - fromComponents[1]) * fraction)
    let blue: CGFloat = CGFloat(fromComponents[2] + (toComponents[2] - fromComponents[2]) * fraction)
    let alpha: CGFloat = CGFloat(fromComponents[3] + (toComponents[3] - fromComponents[3]) * fraction)
    self.init(red:red, green:green, blue:blue, alpha:alpha)
}

class func interpolateColors(from fromColor: UIColor, to toColor: UIColor, interpolations: Int) -> [UIColor] {
    guard interpolations > 2 else { return [fromColor, toColor] }

    let increment = CGFloat(1) / CGFloat(interpolations - 1)
    var result = [UIColor]()
    for i in 0..<interpolations {
        let fraction = CGFloat(i) * increment
        guard let color = UIColor(interpolatedFrom: fromColor, to: toColor, byFraction: fraction) else {
            print("Unable to create an interpolated color for fraction \(fraction). Will use gray instead.")
            result.append(.gray)
            continue
        }
        result.append(color)
    }

    return result
}
}

Usage:

let color1 = UIColor(red: 0.137, green: 0.157, blue: 0.196, alpha: 1)
let color2 = UIColor(red: 0.455, green: 0.475, blue: 0.525, alpha: 1)
let interpolatedColors = UIColor.interpolateColors(from: color1, to: color2, interpolations: 5)

Output:

[
r 0.137 g 0.157 b 0.196 a 1.0,
r 0.216 g 0.236 b 0.278 a 1.0,
r 0.296 g 0.316 b 0.361 a 1.0, 
r 0.375 g 0.396 b 0.443 a 1.0,
r 0.455 g 0.475 b 0.525 a 1.0
]
Ben Packard
  • 26,102
  • 25
  • 102
  • 183
2

SwiftUI

extension Color {
    
    var components: (r: Double, g: Double, b: Double, o: Double)? {
        let uiColor: UIColor
        
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var o: CGFloat = 0
        
        if self.description.contains("NamedColor") {
            let lowerBound = self.description.range(of: "name: \"")!.upperBound
            let upperBound = self.description.range(of: "\", bundle")!.lowerBound
            let assetsName = String(self.description[lowerBound..<upperBound])
            
            uiColor = UIColor(named: assetsName)!
        } else {
            uiColor = UIColor(self)
        }

        guard uiColor.getRed(&r, green: &g, blue: &b, alpha: &o) else { return nil }
        
        return (Double(r), Double(g), Double(b), Double(o))
    }
    
    
    func interpolateTo(color: Color, fraction: Double) -> Color {
        let s = self.components!
        let t = color.components!
        
        let r: Double = s.r + (t.r - s.r) * fraction
        let g: Double = s.g + (t.g - s.g) * fraction
        let b: Double = s.b + (t.b - s.b) * fraction
        let o: Double = s.o + (t.o - s.o) * fraction
        
        return Color(red: r, green: g, blue: b, opacity: o)
    }
}

This would be a working example for SwiftUI with Color instead of UIColor. Note that I have considered assets color with light and dark appearance. A detailed explanation of why we have to use UIColor(named:) initializer instead can be found here.

Kai Zheng
  • 6,640
  • 7
  • 43
  • 66