Blend UIColors in Swift

13.6k views Asked by At

I have two SKSpriteNode and their colors are defined like this:

colorNode[0].color = UIColor(red: 255, green: 0, blue: 0, alpha: 1)
colorNode[1].color = UIColor(red: 0, green: 255, blue: 0, alpha: 1)

and I want to have a third SKSpriteNode colorized with a blend of the two first ones, the result should be like this :

colorNode[2].color = UIColor(red: 255, green: 255, blue: 0, alpha: 1)

but is there a way to addition two UIColors ? Like this :

colorNode[2].color = colorNode[0].color + colorNode[1].color
6

There are 6 answers

5
vacawama On BEST ANSWER

How about something like this:

func addColor(_ color1: UIColor, with color2: UIColor) -> UIColor {
    var (r1, g1, b1, a1) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
    var (r2, g2, b2, a2) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))

    color1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
    color2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

    // add the components, but don't let them go above 1.0
    return UIColor(red: min(r1 + r2, 1), green: min(g1 + g2, 1), blue: min(b1 + b2, 1), alpha: (a1 + a2) / 2)
}

func multiplyColor(_ color: UIColor, by multiplier: CGFloat) -> UIColor {
    var (r, g, b, a) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))
    color.getRed(&r, green: &g, blue: &b, alpha: &a)
    return UIColor(red: r * multiplier, green: g * multiplier, blue: b * multiplier, alpha: a)
}

Define operators to add colors and multiply a color by a Double:

func +(color1: UIColor, color2: UIColor) -> UIColor {
    return addColor(color1, with: color2)
}

func *(color: UIColor, multiplier: Double) -> UIColor {
    return multiplyColor(color, by: CGFloat(multiplier))
}

Then you can blend colors like this:

// Make orange with 50% red and 50% yellow    
let orange = .red * 0.5 + .yellow * 0.5

// Make light gray with 25% black and 75% white
let lightGray = .black * 0.25 + .white * 0.75

// Make sky blue by lightening a combination of 25% blue and 75% cyan
let skyBlue = (.blue * 0.25 + .cyan * 0.75) * 0.25 + .white * 0.75

// Make dark red by combining 50% red and 50% black
let darkRed = .red * 0.50 + .black * 0.50

// Make purple from 60% blue and 40% red
let purple = (.blue * 0.60 + .red * 0.40)

// Then make lavender from 25% purple and 75% white
let lavender = purple * 0.25 + .white * 0.75
5
Federico Zanetello On

My version (it blends as many colors as you'd like), Swift 4+:

func blend(colors: [UIColor]) -> UIColor {
    let numberOfColors = CGFloat(colors.count)
    var (red, green, blue, alpha) = (CGFloat(0), CGFloat(0), CGFloat(0), CGFloat(0))

    let componentsSum = colors.reduce((red: CGFloat(0), green: CGFloat(0), blue: CGFloat(0), alpha: CGFloat())) { temp, color in
        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
        return (temp.red+red, temp.green + green, temp.blue + blue, temp.alpha+alpha)
    }
    return UIColor(red: componentsSum.red / numberOfColors,
                       green: componentsSum.green / numberOfColors,
                       blue: componentsSum.blue / numberOfColors,
                       alpha: componentsSum.alpha / numberOfColors)
}
0
Mojtaba Hosseini On

All Platforms

✅ Both UIKit & AppKit: 'aka' UIColor & NSColor

With this method, you can mix colors in any platform with any color.

public extension NativeColor {
    func mix(with target: NativeColor, amount: CGFloat) -> Self {
        var r1: CGFloat = 0, g1: CGFloat = 0, b1: CGFloat = 0, a1: CGFloat = 0
        var r2: CGFloat = 0, g2: CGFloat = 0, b2: CGFloat = 0, a2: CGFloat = 0

        getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
        target.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

        return Self(
            red: r1 * (1.0 - amount) + r2 * amount,
            green: g1 * (1.0 - amount) + g2 * amount,
            blue: b1 * (1.0 - amount) + b2 * amount,
            alpha: a1
        )
    }
}

Don't forget to define what is the NativeColor in each environment:

#if canImport(UIKit)
public typealias NativeColor = UIColor
#elseif canImport(AppKit)
public typealias NativeColor = NSColor
#endif

SwiftUI: Color - iOS 14 / macOS 10.16

⚠️ Requires previous extension

public extension Color {
    func mix(with target: Color, amount: CGFloat) -> Color {
        Color(NativeColor(self).mix(with: NativeColor(target), amount: amount))
    }
}
5
Bersaelor On

Swift3 version, as extension with some more guards and fine grained adjustment:

extension UIColor {
    static func blend(color1: UIColor, intensity1: CGFloat = 0.5, color2: UIColor, intensity2: CGFloat = 0.5) -> UIColor {
        let total = intensity1 + intensity2
        let l1 = intensity1/total
        let l2 = intensity2/total
        guard l1 > 0 else { return color2}
        guard l2 > 0 else { return color1}
        var (r1, g1, b1, a1): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)
        var (r2, g2, b2, a2): (CGFloat, CGFloat, CGFloat, CGFloat) = (0, 0, 0, 0)

        color1.getRed(&r1, green: &g1, blue: &b1, alpha: &a1)
        color2.getRed(&r2, green: &g2, blue: &b2, alpha: &a2)

        return UIColor(red: l1*r1 + l2*r2, green: l1*g1 + l2*g2, blue: l1*b1 + l2*b2, alpha: l1*a1 + l2*a2)
    }
}
0
blwinters On

For my project I needed to simulate a dynamic theme color with partial opacity over a white background, without any actual transparency. I was able to achieve this with the following UIColor extension (based on this answer):

static func simulatingAlpha(_ alpha: CGFloat, for color1: UIColor, over color2: UIColor) -> UIColor {

    let whiteComponents: [CGFloat] = [1.0, 1.0, 1.0, 1.0] //UIColor.white.cgColor.components only returns [1.0, 1.0]

    var rgba1: [CGFloat] = whiteComponents //set a valid default
    var rgba2: [CGFloat] = whiteComponents

    if let components = color1.cgColor.components, components.count > 2 {
        rgba1 = components
    }

    if let components = color2.cgColor.components, components.count > 2  {
        rgba2 = components
    }

    let r1: CGFloat = rgba1[0]
    let g1: CGFloat = rgba1[1]
    let b1: CGFloat = rgba1[2]

    let r2: CGFloat = rgba2[0]
    let g2: CGFloat = rgba2[1]
    let b2: CGFloat = rgba2[2]

    let r3 = ((1 - alpha) * r2) + (r1 * alpha)
    let g3 = ((1 - alpha) * g2) + (g1 * alpha)
    let b3 = ((1 - alpha) * b2) + (b1 * alpha)

    print("Simulated RGB: \(Int(r3 * 255)), \(Int(g3 * 255)), \(Int(b3 * 255))")

    let newComponents: [CGFloat] = [r3, g3, b3, 1.0]
    let space = CGColorSpace(name:CGColorSpace.sRGB)!
    guard let cgColor3 = CGColor(colorSpace: space, components: newComponents) else {
        print("Failed to create new CGColor in default color space")
        return color1
    }

    return UIColor(cgColor: cgColor3)
}
1
OrangeEvan On

The code has achieved all the layer blend for photoshop.If is useful for you.Don't forget to give me a start. Have fun~

GitHub:https://github.com/Orange-W/PhotoshopBending

import Foundation
import UIKit

extension UIColor {
    // MARK: - 常用叠图
    // Alpha Blending 前景色叠图
    func blendAlpha(coverColor: UIColor) -> UIColor {
        let c1 = coverColor.rgbaTuple()
        let c2 = self.rgbaTuple()

        let c1r = CGFloat(c1.r)
        let c1g = CGFloat(c1.g)
        let c1b = CGFloat(c1.b)

        let c2r = CGFloat(c2.r)
        let c2g = CGFloat(c2.g)
        let c2b = CGFloat(c2.b)

        // 前景色叠图公式
        let r = c1r * c1.a + c2r  * (1 - c1.a)
        let g = c1g * c1.a + c2g  * (1 - c1.a)
        let b = c1b * c1.a + c2b  * (1 - c1.a)

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


    // MARK: - 去亮度型
    /// Darken 变暗  B<=A: C=B; B>=A: C=A
    func blendDarken(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 <= $1) ? $0 : $1 }
    }

    /// Multiply 正片叠底 C = A*B
    func blendMultiply(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return $0 * $1 }
    }

    /// Color Burn 颜色加深 C=1-(1-B)/A
    func blendColorBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $0) / $1 }
    }

    /// Linear Burn 线性加深 C=A+B-1
    func blendLinearBurn(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($1 + $0) - 1.0 }
    }

    // MARK: - 去暗型
    /// Lighten 变亮   B>=A: C=B; B<=A: C=A
    func blendLighten(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return ($0 >= $1) ? $0 : $1 }
    }

    /// Screen 滤色 C=1-(1-A)*(1-B), 也可以写成 1-C=(1-A)*(1-B)
    func blendScreen(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return 1 - (1 - $1) * (1 - $0) }
    }

    /// Color Dodge 颜色减淡 C=B/(1-A)
    func blendColorDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 >= 1.0 { return $1 }
            else { return min(1.0, $0 / (1 - $1)) }
        }
    }

    /// Linear Dodge 线性减淡 C=A+B
    func blendLinearDodge(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return min(1, $1 + $0) }
    }


    // MARK: - 溶合型
    /// Overlay 叠加 B<=0.5: C=2*A*B; B>0.5: C=1-2*(1-A)*(1-B)
    func blendOverlay(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
        }
    }

    /// Soft Light 柔光 A<=0.5: C=(2*A-1)*(B-B*B)+B; A>0.5: C=(2*A-1)*(sqrt(B)-B)+B
    func blendSoftLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return (2 * $1 - 1) * ($0 - $0 * $0) + $0 }
            else { return (2 * $1 - 1)*( sqrt($0) - $0) + $0 }
        }
    }

    /// Hard Light 强光 A<=0.5: C=2*A*B; A>0.5: C=1-2*(1-A)*(1-B)
    func blendHardLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return 2 * $1 * $0 }
            else { return 1 - 2 * (1 - $1) * (1 - $0) }
        }
    }

    /// Vivid Light 亮光 A<=0.5: C=1-(1-B)/(2*A); A>0.5: C=B/(2*(1-A))
    func blendVividLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 0.5 { return self.fitIn((1 - (1 - $0) / (2 * $1)), ceil: 1.0) }
            else { return self.fitIn($0 / (2 * (1 - $1)), ceil: 1.0) }
        }
    }

    /// Linear Light 线性光 C=B+2*A-1
    func blendLinearLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { return self.fitIn($0 + 2 * $1 - 1, ceil: 1.0) }
    }

    /// Pin Light 点光
    /// B<2*A-1:     C=2*A-1
    /// 2*A-1<B<2*A: C=B
    /// B>2*A:       C=2*A
    func blendPinLight(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 <= 2 * $1 - 1 { return 2 * $1 - 1 }
            else if (2 * $1 - 1 < $0) && ($0 < 2 * $1) { return $0}
            else { return 2 * $1 }
        }
    }

    /// Hard Mix 实色混合A<1-B: C=0; A>1-B: C=1
    func blendHardMix(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $1 <= 1 - $0 { return 0 }
            else { return 1 }
        }
    }

    // MARK: - 色差型
    /// Difference 差值 C=|A-B|
    func blendDifference(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { fabs($1 - $0) }
    }

    /// Exclusion 排除 C = A+B-2*A*B
    func blendExclusion(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 + $0 - 2 * $1 * $0  }
    }

    /// 减去 C=A-B
    func blendMinus(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) { $1 - $0 }
    }

    /// 划分 C=A/B
    func blendDivision(coverColor: UIColor,alpha: CGFloat = 1.0) -> UIColor {
        return blendProcedure(coverColor: coverColor, alpha: alpha) {
            if $0 == 0{
                return 1.0
            }else {
                return self.fitIn($1 / $0, ceil: 1.0)
            }
        }
    }

    // MARK: 处理函数
    func blendProcedure(
        coverColor: UIColor,
        alpha: CGFloat,
        procedureBlock: ((_ baseValue: CGFloat,_ topValue: CGFloat) -> CGFloat)?
        ) -> UIColor {
        let baseCompoment = self.rgbaTuple()
        let topCompoment = coverColor.rgbaTuple()

        // 该层透明度
        let mixAlpha = alpha * topCompoment.a + (1.0 - alpha) * baseCompoment.a

        // RGB 值
        let mixR = procedureBlock?(
            baseCompoment.r / 255.0,
            topCompoment.r / 255.0)
            ?? (baseCompoment.r) / 255.0

        let mixG = procedureBlock?(
            baseCompoment.g / 255.0,
            topCompoment.g / 255.0)
            ?? (baseCompoment.g) / 255.0

        let mixB = procedureBlock?(
            baseCompoment.b / 255.0,
            topCompoment.b / 255.0)
            ?? baseCompoment.b / 255.0


        return UIColor.init(red:   fitIn(mixR),
                            green: fitIn(mixG),
                            blue:  fitIn(mixB),
                            alpha: mixAlpha)
    }

    // 防止越界
    func fitIn(_ value: CGFloat, ceil: CGFloat = 255) -> CGFloat { return max(min(value,ceil),0) }
    func fitIn(_ value: Double, ceil: CGFloat = 255) -> CGFloat { return fitIn(CGFloat(value), ceil: ceil) }

    // 返回 RBGA
    func rgbaTuple() -> (r: CGFloat, g: CGFloat, b: CGFloat,a: CGFloat) {
        var r: CGFloat = 0
        var g: CGFloat = 0
        var b: CGFloat = 0
        var a: CGFloat = 0
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        r = r * 255
        g = g * 255
        b = b * 255

        return ((r),(g),(b),a)
    }
}